• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Fingolfin's Attachment Tester

We were having a discussion in the Reviewer's Reserve forum regarding ways of making the model testing process more streamlined, and i got the idea to make a system which allows you to test attachments on units using simple text commands. I was quite happy with the result and i figured it might be useful to other people!

These are the commands that are available to you in the map:

  • -create [name] Creates a unit by name at the center of your screen. Example: "-create Ghoul".
  • -kill Kills all selected units.
  • -play [name] Plays the specified animation on all selected units. Example: "-play Death". You can queue multiple animations by using "then", as such: "-play attack then Death then Dissipate".
  • -play [value] Plays an animation by index on all selected units. Indices range from 0 up to the max animation count of the model.
  • -tag [name] Toggles animation tag on all selected units. Example: "-tag Alternate".
  • -attach [path] to [name] Attaches a model with path [path] to attachment point [name] of all selected units. You can choose to omit the ".mdx" ending of the path as this will then be added automatically. It is also possible to specify multiple attachment points by separating them with "and". Example: "-attach axe to hand left and hand right"
  • -remove [name] Removes all attachments from attachment point [name] of all selected units. Just like -attach, this can use "and" to add more points. Example: "-remove hand left and hand right".
  • -clear Clears all attachments from selected units.
  • -path Allows you to specify a path which will be automatically added to all "-attach" commands you make. Example: "-path Abilities\Spells\Human\"
  • -owner [0-11] Changes the owner of all selected units to player [value], where value is a number in the range of 0-11. You can use this in conjunction with "-create" to stage battles.
  • -alpha [0-100] Sets the transparency of all selected units to a percentage between 0-100.
  • -time [0-24] Sets the current time of day.
  • -define [name] as [command] Lets you create a macro command. More info below.
  • -undefine [name] Removes a created macro command.
  • -l Repeats the last used valid command.
  • -help Displays a list of all commands.

The way you use this is that you simply import the models you want to test into the map and run it. Then, you can spawn in whatever units you want to test them on and do "-attach myAttachmentModel to overhead", or wherever you want to try them! It's also useful for finding out the animation indices of a model. Check out the images for more detailed examples. I hope you enjoy!

The following extra models are available in the test map:
  • axe.mdx
  • torch.mdx
  • piratehat.mdx
  • centaur_axe.mdx
  • buckler.mdx
  • helmet.mdx
Of course you can also use any of the models that are already in the game! Adding to this, there are a few macros you can use to access some useful ingame attachments without the hassle of memorizing their paths, here is a list of them:

  • #brilliance (brilliance aura)
  • #bloodlust
  • #ensnare
  • #spikes (spiked barricades)
  • #innerfire
  • #magicsentry
  • #frostarmor
  • #sleep
  • #fire (small building fire)
To use these, simply type "-attach #bloodlust to hand left and hand right", or whichever attachment points you prefer! Macros will auto-complete, so for maximum laziness you can even type "-attach #blo" to hand left" and it will understand what you want!

Finally, to minimize the amount of typing, you can create your own test suites using the "-define" command. If you type -"define test as -attach #bloodlust to hand left and hand right", then you only have to type "test" to execute this. "Overwriting" a command just adds more lines to it, so if you'd continue to type "-define test as -attach #brilliance to origin", then typing "test" will do both these things, in the order they were defined! Typing "-undefine test" removes the definition.

UPDATE:

The system will now auto-complete ALL commands, not just macros. This means that for maximum laziness, you can type "-at #br to origin", which will be interpreted as "-attach #brilliance to origin". Note that attachment point names and words like "to", "and" cannot be shortened. There are now also some pre-defined definitions such as "morph", which essentially does "tag alternate" and "play morph". You can find these built-in definitions in the "Configuration" trigger and add your own if you want.
Previews
Contents

Attachment Test Map (Map)

Reviews
KILLCIDE
An extremely useful map for our model reviewers and moderators. It can also be useful for people who just want to take models through a "stress test" :D The map can only get better given users should be giving the creator tips to improve its usage. I...
-play ""
-index x
->
-play ""
-play x

^seems more intuitive.

The printed help texts is too short, and they disappear not at same time which looks not good.
I would just make it to last very long by alone, like at least one minute.
Maybe you can even put all the command just into a perma multiboard.

The "-alpha" command changes RGB to FFFFFF, always.
If "alpha" exists, why not also allow RGB changes?
And it might be that in object editor the default values is not 255 for R/G/B.
New commands for RGB would help in each case.

The default constants for attachment points are really useful.
I can't really imagine someone to write out full paths into the command.
I believe if important ones are shared by such constants it will be fine, and I just trust you as modeler, that you added good ones.

Maybe a scale command is useful.

I think clicks can make such things a lot of easier, something like play next/prev animation, maybe alpha, RGB +/-, scaling, or just some kind of click interface. Spellbooks, casters, trackables, I don't know. But it maybe would be more handy.
 
All right, made a new update:

  • Help message will now show longer and disappear consistently.
  • Path macros will now auto-complete, so it is enough to just type "#blo" for "#bloodlust" or "#br" for "#brilliance", etc.
  • Merged "-index" with "-play", it will now recognize whether you are typing a letter or a number.
  • Added "helmet.mdx" model to the standard imports.

As for the rest - frankly, i don't think that scale or color is particularly useful for moderating. I'm also not a huge fan of click interfaces, though i guess people have different preferences. I appreciate your feedback though!

EDIT: Also note that "-alpha" takes a percentage value 0-100. Not sure where hex values come into the picture.
 
[...]

ok, sounds all good. The autocomplete sounds very neat.

Yes maybe clicking is personal preference. I mean we will probably anyways use it, as you made it as helping resource, so it's nothing I or someone else would wanna enfore to you as modeler.
If other modelers also like more the command style, then I guess it's fine-- but can we maybe @tag some model guys, just to get opinions.
@PROXY @The_Silent @JesusHipster
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
An extremely useful map for our model reviewers and moderators. It can also be useful for people who just want to take models through a "stress test" :D The map can only get better given users should be giving the creator tips to improve its usage. I hope @Ralle can integrate this into the website (;

Needs Fixed

  • Given the nature of this system, and what it will be used for, I can't see anyone using this map for anymore than 5 minutes. With that in mind, I'm making it an exception that if everything works as intended, it will be approved. I will still drop suggestions to improve code

Suggestions

  • Instead of having two seperate loops to add events to the trigger, you can just have one loop and add the events simultaneously
  • To improve readability of OnChatMessage, I would seperate the different parsed commands into functions instead of the long if block
  • Personally, FirstOfGroup() > ForGroup(). Allows you to recycle groups a lot easier
  • Given that local integer d = StringLength(" to ") and StringLength(" then " ) is a constant, you can just store it into a global variable. You could also put the number directly in the arithmetic operators
  • I see that you null string message when passed as a parameter in ParseAttachmentPoints. Strings aren't agents, so there's really no need to null them. If you do pass an agent as a parameter, there's also no need to null them
  • Not using thistype.deallocate() will easily make the struct reach the max 8190 indexes. You should make sure to use this in accordance with thistype.allocate() :p
  • effect model should be nulled onDestroy

Status

Approved
 
@KILLCIDE Thanks a lot for your review! Here are my comments:

  • Instead of having two seperate loops to add events to the trigger, you can just have one loop and add the events simultaneously
  • To improve readability of OnChatMessage, I would seperate the different parsed commands into functions instead of the long if block
  • I see that you null string message when passed as a parameter in ParseAttachmentPoints. Strings aren't agents, so there's really no need to null them. If you do pass an agent as a parameter, there's also no need to null them

I will fix these! Thank you for the advice on agents in particular, i always thought that handles leaked if you returned them.

  • Personally, FirstOfGroup() > ForGroup(). Allows you to recycle groups a lot easier
  • Given that local integer d = StringLength(" to ") and StringLength(" then " ) is a constant, you can just store it into a global variable. You could also put the number directly in the arithmetic operators

Performance is not really an issue in this map and i find that my solution increases readability somewhat.

  • Not using thistype.deallocate() will easily make the struct reach the max 8190 indexes. You should make sure to use this in accordance with thistype.allocate() :p

thistype.deallocate() is called automatically when you destroy the instance, which i do both when the unit dies and when you call "-clear". There are no struct leaks.

  • effect model should be nulled onDestroy

This makes no difference since the value is set in the constructor and thus will never retain the old value. Since struct members are arrays there can also not be any leaks.

By the way, do you or @IcemanBo know if there are any good ways for saving strings between map sessions? Like, if you would save a string while you are playing and load it the next time you play. I guess that GameCache probably needs you to save/load the game, and the preload method requires you to enable local files, right?
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
i find that my solution increases readability somewhat.
I guess it's just preference then. I personally find FirstOfGroup() to be easier.

This makes no difference since the value is set in the constructor and thus will never retain the old value. Since struct members are arrays there can also not be any leaks.
Given you recycle indexes, you may encounter a situation where you never use all values in your recycle stack. This doesn't pose an issue for people who use dynamic indexing since they always use the first X indexes. I guess the argument can also go for dynamic indexing, so I take it as one of those "should I still use my emergency brake if I'm parked on flat ground?" If this were a normal spell/system, I wouldn't let it slide (;

I guess that GameCache probably needs you to save/load the game, and the preload method requires you to enable local files, right?
Yep!
 
Given you recycle indexes, you may encounter a situation where you never use all values in your recycle stack. This doesn't pose an issue for people who use dynamic indexing since they always use the first X indexes. I guess the argument can also go for dynamic indexing, so I take it as one of those "should I still use my emergency brake if I'm parked on flat ground?" If this were a normal spell/system, I wouldn't let it slide (;

I still don't understand the issue here. You are not allowed to call "allocate" outside of the struct so you have to use "create". Inside create, all member variables are given a new value so the old value is never retained. Whether the handle is null or not makes no difference since struct members are arrays, and array values are always kept in memory whether you null them or not.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
You are not allowed to call "allocate" outside of the struct so you have to use "create".
Huh? I never suggested for you to use thistype.allocate() outside of the struct.

Inside create, all member variables are given a new value so the old value is never retained.
Again, that only happens if you use up all values in your recycle stack.

Whether the handle is null or not makes no difference since struct members are arrays, and array values are always kept in memory whether you null them or not.
The internals of how WC3 work are beyond my scope of knowledge. As far as I know, global agents can still have a reference leak if not nulled. I don't want to misinform you, so I will quote PurgeandFire:

There is a global reference count and a local reference count. When an object is destroyed and going to be handled by wc3's memory management, it first checks if the local reference count is > 0. If it is > 0, then it won't recycle the handle ID. If the local reference count is 0, it checks if the global reference count is > 0. If it is > 0, then it will still have a small amount of memory (most likely just the reference count and miscellaneous memory) left over. But this is on such a monumentally low level, that it is generally regarded as useless to fix. I think I remember creating several billion of these reference leaks, and the memory use still wasn't capped or anything. So for practical purposes, (type 2) is optional to fix. (it wasn't discovered until late in wc3 modding, partially because it was such a minuscule leak).

As for (type 1), it leaks more memory than (type 2), and it does annoying things such as making handle counters not function properly. This leak is still minor, but in the past it was decided that the leak was significant and needed to be dealt with.

Unfortunately, Blizzard (and its royalties) are a bit weird in coding their garbage collectors, so it leaks differently depending on whether the references are still global or local.
 
Array structure is a reserved chain of memory that is reserved to serve the data type.
This sized memory chain inside RAM will be staticly used for the array, yes, that's right.
But this nature for the array itself isn't directly related to the dynamic objects that can be created.
The agent type object itslelf can't recylce it's handle id when there is still something pointing on it.
So it's no matter if it's object reference is stored into the arrays's static memory chain or not, if we don't don't null it (or overwrite it).
Yes, the reference may be overwritten on some next allocations, but it's technicaly not ensured it will happen. But on the other side, we want to ensure
that all agent references are nulled when ever we are about to destroy our object, so we just always null them when they fall out of our usage inside destroy function.
 
Top