• 🏆 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!

Bug in WC3 rawcode handling

Status
Not open for further replies.
Level 21
Joined
Mar 27, 2012
Messages
3,232
Apparently small and large letters mean the same thing in some cases, but not in others.
In those pictures you can see the object editor data for 2 abilities. Notice how their icons are not same in object editor, but are ingame.
Also notice how the tooltip of the ability has changed ingame, but description hasn't.
Additionally, their rawcodes differ only in the capitalization of one letter.


RawcodeBug1.jpg
RawcodeBug2.jpg
RawcodeBug3.jpg


What I believe is going on in the background is that whenever an object's data is read the game stores the fact somewhere. Later when the same object's data is read again it simply takes the stored value. The bug comes from this rereading part being case insensitive.
In this case the tooltip is from Web because the tooltip of Web got loaded first(I have a trigger that assigns abilities to triggers based on tooltips). However, the description only got loaded when I picked the hero, so this is how the combination happened.

There might be similar bugs elsewhere, with implications to how save/load systems work.

EDIT: I just realized that the tooltips were right at least once, otherwise my system would have been unable to find either of the abilities. Also, Inner Fire somehow worked properly even though it should have the same bug.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
It's not practical for me to strip down my map for that.
I discovered this so soon only because I use ability names to assign rawcodes to triggers. This is not only for triggering their effects, but also for being able to define what abilities a hero has solely through triggers.
The method that I use is as follows:

1. I have external object files. Those files are loaded into the game through a wrapper script on top of ObjectMerger. Among other things this wrapper automatically grants rawcodes to most abilities. I can recreate most of the abilities in the map by simply resaving and it only takes a few seconds.
2. At the start of the game I have a 0-second timer that checks the names of all objects in a large range(A001 to AZZ1, with the A and 1 not changing). All abilities that have a name ending with (Q) are added to a list.
When the checking is done another 0-second timer starts that takes the names of all abilities that have requested their abilities to be found and finds which abilities in the list have the matching names. Those abilities are matched with their appropriate triggers.
Triggers whose abilities did not get matched throw an error and get skipped. This is why I instantly knew when something was wrong.

The rest of the process is so far irrelevant, so I'll home down on what I discovered.
I debugged the matching stage and discovered that the abilities that throw errors have been found, but that they get assigned somewhere else. Upon closer inspection I saw a pattern. The first ability in each of those combinations broke when the second was added:
A0A1 and A0a1
A0B1 and A0b1
A0C1 and A0c1
(I don't have abilities beyond A0c1 atm, but I'm sure the pattern would continue)

Yet, the other 37 abilities did not bug. I tried adding and removing abilities to create an offset and the pattern remained.
Eventually I tracked it down to GetObjectName producing wrong names. In the above pairs, the second ones overwrote the names of the first ones.
I fixed it by making caching names in the detection stage and using the cached names for matching instead of getting them again through GetObjectName. This made all abilities match properly.
Ingame I tried the heroes that have the problematic abilities to see if any problems persisted. A0B1 and A0C1 were okay, but A0A1 took the icon and tooltip from A0a1(seen in first post). I figured that the problem might just be with this pair. As an attempt at a solution I removed 'a' from the code alphabet so that A0a1 would never be used as a rawcode.
... This caused A0b1 to bug, so I reverted the change.

It's important to understand what order the detection and matching works in.
Detection:
A011
A021
A031
...
A0X1
A0Y1
A0Z1
A0a1
A0b1
A0c1

My code alphabet:
123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Matching works in the opposite direction. Why is this important? Because it's always the non-capitalized letters that overwrite the capitalized.
Remember that the first read always worked right, so the problem comes from subsequent reads. This might mean that the misread bug can be avoided by having a wrapper for GetObjectName, which stores values in a hashtable and only uses GetObjectName when a hashtable entry doesn't exist.
It can also (additionally) mean that introducing additional instances of the icon bug fixes previous ones. (Possibly refreshes flushes whatever cache causes the bug?)
Or it could mean that only the lowest used non-capitalized letter overwrites its capital counterpart.

At this point I have circumvented each bug by removing the non-capital letters from my code alphabet(over 1k rawcodes is still way more than I need, considering I only use 40 so far). However, I could not produce an easy to use testmap even if I readded the missing part of the alphabet, because the map is coded from the ground up to take full use of my prewritten libraries. I'd need to figure out all the missing requirements and share them as well, in addition to giving 2 scripts, the object files and instructions on where to put everything.

If not being able to save the map isn't a problem, then I can readd the non-capital letters to the code alphabets and recompile the map, but I won't strip it down.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
This is not surprising to me.
As Leandrop found out by analyzing the game dll letter captions are actually *used* by the game hardcoded in their effect. In this example, a capital letter rawcode for a unit indicates a hero unit, whereas a lowercaps unit indicates a normal unit.
And, yes, they actually used a switch-case to detect the capital letter rawcode with every single letter of the alphabet entered by hand. This is how blizzard wrote code for their games back then, folks. ;)
To be honest, I'm always amazed how WC3 is relatively bugfree with that horrible spaghetti code they produced...

Basicly, what this tells us, is, that Blizzard never intended for rawcodes to be case sensitive in the first place, simply because it could mess up these cases in which a certain naming convention triggers a hardcoded behaviour (like the hero unit stuff).

Also remember that custom rawcode definition was not part of the vanilla wc3 editor. You were bound to whatever the editor selected for you back then.


On a side note: this is for a system that allows for custom dynamic hotkeys? GG then, creative approach! It's unfortunate that it requires all abilities to be LUA generated for it to work (then again, you would probably do that anyway if you had to create 12 variants per ability anyway).
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
On a side note: this is for a system that allows for custom dynamic hotkeys? GG then, creative approach! It's unfortunate that it requires all abilities to be LUA generated for it to work (then again, you would probably do that anyway if you had to create 12 variants per ability anyway).

If I used a different code alphabet(letters + some special signs, maybe) I could avoid collisions with the usual rawcodes. Heck, I could even just reverse the alphabet and I'd only get collisions when it's inevitable, because the way I do it is quite similar to how the normal editor does it.
Also, detecting abilities is surprisingly cheap. Atm my system takes about 1-2 seconds to scan the whole range from 00 to ZZ to zz. Adding a letter does increase the time a lot, but this range itself already gives 3844 abilities. I tell apart slots with the last letter, so I never need to detect them separately.
This also means I get very few collisions with blizzard stuff - they didn't use numbers all that much in rawcodes.
If I wanted I could detect any kind of ability whatsoever. I just chose not to, to save processing time. 1-2 seconds lag at the start of the game is not very noticable, but 4-5 might be.

Generating abilities by lua is actually easier than normal once it's set up, especially with this project(it's that same map where I make hero versions of normal units). I avoid the deleting-ability-crashes-editor bug by just resaving the map and restarting the editor.

I'm currently working on a new version of the generation script that would be godlike compared to what I currently use. I might even publish it once it's done, since it's really so much better than anything when making interrelated objects(or objects that are generated based on some system).
 
Level 9
Joined
Jul 30, 2012
Messages
156
Basicly, what this tells us, is, that Blizzard never intended for rawcodes to be case sensitive in the first place.

Rawcodes were supposed to be case sensitive. What is presented here is indeed a bug, that I also found some time ago.

The bug is related to how the game handles command buttons. Whenever an ability is loaded by the first time, the game creates a CommandButton object. Future instances of that ability will use the same object. This is why abilities that are loaded in the map first will take priority in the command card, as I reported [post=2696338]here[/post]

The problem is, these objects are cached into a table using some kind of hashing algorithm, and this algorithm is case-insensitive (which is not a surprise, considering that StringHash is also case-insensitive). What happens is that, when A0a1 is loaded into the map, it will create a new CommandButton object, and that object will override A0A1 in the CommandButton table, causing both abilities to appear with the same name and icon.

The rule is, the last loaded ability will always override the first. The tooltip is not affected because tooltips are retrieved directly from the object data, which is case-sensitive as it's supposed to be. But the name and icon of the ability are cached in the CommandButton.
 
Level 9
Joined
Jul 30, 2012
Messages
156
Ah I see. I have never tested with GetObjectName, so I wasn't sure. What I did was create 4 abilities: AA00, AB00, Aa00 and Ab00. I added AA00 and AB00 to Paladin, and the others to Archmage.

If my map creates a Paladin before an Archmage, AA00 and AB00 have their icons overriden by the lowercase ones. And if Archmage is created before Paladin, the opposite happens. I don't know how GetObjectName interacts with that, or why you had only one ability bugged. Try adding them all to units in game and tell us what happens.

EDIT: I am now testing with GetObjectName and it appears that it does not load the ability at all. I have not found any way to produce the bug only with GetObjectName, and it seems to never return the wrong name even after the ability icon got bugged. Are you sure you're not missing something? Maybe it's because you have too many abilities. Try testing only this issue with a new blank map. The GetObjectName bug might be a different bug from the CommandButton bug that I talked about.

EDIT: Now I am getting interesting results:
JASS:
BJDebugMsg(GetObjectName('AA00'));
BJDebugMsg(GetObjectName('Aa00'));
BJDebugMsg(GetObjectName('AB00'));
BJDebugMsg(GetObjectName('Ab00'));
Obviously, when calling this for the first time, the results are correct. When calling for the second time, this happens:
Code:
Divine Shield
Divine Shield
Resurrection
Resurrection

We can see that the names of 'AA00' and 'AB00' (Holy Light and Devotion Aura) were overriden by 'Aa00' and 'Ab00' (Divine Shield and Resurrection)
After that, I add 'AA00' (Holy Light) to a unit in the map.

Result:
Code:
Holy Light
Holy Light
Resurrection
Resurrection

Then, after that, I add 'Aa00' (Divine shield) to a unit for the first time. When I do that, the icon of Holy Light gets transformed into Divine Shield. And the result is, again:

Code:
Divine Shield
Divine Shield
Resurrection
Resurrection

My conclusion is, that adding the ability to a unit for the first time causes it to be loaded in the map again, despite the fact that GetObjectName has already loaded it. And the opposite is also true, calling GetObjectName with a rawcode for the first time causes the ability to be reloaded, even if it was already added to a unit.

So the rule is still valid: the last loaded ability will override the CommandButton, and the value returned by GetObjectName. But an ability will always be loaded twice: when it's first added to a unit, and when GetObjectName is called for the first time.
 
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
This means that the icon should be normal when first being given to a unit, but that's not how it seems to be for me. It seems that the overriding always happens the same way - a overrides A.

If a unit with 'A0A1' is created before you call GetObjectName('A0a1'), then the GetObjectName call will cause 'A0a1' to be loaded, overriding 'A0A1'.

You're probably calling your code after units in the map are created. And since your alphabet is 123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz, the lowercase letters come later.

Instead of adding abilities with object editor, try to do UnitAddAbility(someUnit, 'A0A1') after all calls to GetObjectName. And make sure no other unit has 'A0A1'.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
If a unit with 'A0A1' is created before you call GetObjectName('A0a1'), then the GetObjectName call will cause 'A0a1' to be loaded, overriding 'A0A1'.

You're probably calling your code after units in the map are created. And since your alphabet is 123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz, the lowercase letters come later.

Instead of adding abilities with object editor, try to do UnitAddAbility(someUnit, 'A0A1') after all calls to GetObjectName. And make sure no other unit has 'A0A1'.

I don't use object editor for almost anything. Hero units have some dummy abilities based on critical strike, but that's it. Those dummies use completely different rawcodes(AHL1 to AHL4).
As such, there are no units that start with the generated hero abilities and all calls to GetObjectName were indeed before those abilities were given to units.
 
Level 9
Joined
Jul 30, 2012
Messages
156
Without looking at your code it's difficult to tell what's happening. You said that in the first step your code calls GetObjectName for all codes from A001 to Azz1. That is the time where the abilities get loaded, and since lowercase letters come later in the alphabet, they override the uppercase ones.

But if, after that, you add 'A0A1' to a unit for the first time, the ability should be loaded again, overriding the lowercase ability, and therefore giving the correct icon. At least this is what happens in my machine.

If for some reason your machine doesn't reload the uppercase ability when you add it to the unit, then it will indeed cause the effect you're seeing, as the lowercase ability has been loaded after. But this should happen for all abilities, not just one.

Could you post the triggers of your map here? Maybe we can find what's going on.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
I'll just pack things up at upload them. If something is missing, ask.

Instructions:
*The attached map doesn't have generated objects. You'll have to follow the rest of the instructions for them to appear.
*Unpack the archive in your jngp folder.
*Place the map wherever you want.
*Open the map in editor and navigate to Initilization>Objects.
*Change the 3 // lines to //!
*This activates the object generation script.
*Save.
*The map is now complete if I didn't forget something.

EDIT: All heroes are around taverns. I didn't remove the map's mechanics for this demonstration. You can see the list of abilities of every hero in Heroes>Heroes.
 

Attachments

  • Rawcodebugtest.rar
    462.6 KB · Views: 37
Last edited:
Level 19
Joined
Dec 12, 2010
Messages
2,069
So the rule is still valid: the last loaded ability will override the CommandButton, and the value returned by GetObjectName. But an ability will always be loaded twice: when it's first added to a unit, and when GetObjectName is called for the first time.
So there's possibility to update ability's tooltip at least once without changing ID?
 
Level 9
Joined
Jul 30, 2012
Messages
156
So there's possibility to update ability's tooltip at least once without changing ID?

According to my tests, it's possible to do that at least twice: an ability will be "loaded" when its first added to an unit, and it will be loaded again in the first call to GetObjectName. So you could, at most, add the ability to a dummy to change tooltip, then call GetObjectName to restore the original tooltip, and then use GetObjectName again to change the tooltip once more.

Even then, I don't see that being much useful, as you can do the change only twice, and it's global, affecting all units in the map. Also I recommend that you do some tests on your own, as you see we have been testing with the World Editor, which stores ability data in .w3a files, but you will be editing SLKs directly, so it's possible that the behaviour would not be the same.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Probably. But since you still have to create two abilities for that, there is almost no advantage over just swapping the abilities, right?

cons of swapping - different ID (valuable for custom keys users) and introducing another conditions to game triggers in order to get correct spell/spells' level (regarding your comment about levels)

According to my tests, it's possible to do that at least twice: an ability will be "loaded" when its first added to an unit, and it will be loaded again in the first call to GetObjectName. So you could, at most, add the ability to a dummy to change tooltip, then call GetObjectName to restore the original tooltip, and then use GetObjectName again to change the tooltip once more.

Even then, I don't see that being much useful, as you can do the change only twice, and it's global, affecting all units in the map. Also I recommend that you do some tests on your own, as you see we have been testing with the World Editor, which stores ability data in .w3a files, but you will be editing SLKs directly, so it's possible that the behaviour would not be the same.

it's not that useful, but, for instance, it allows to adjust cooldown description for hero's abilities at least twice (on picking up octarine core & dropping). its not perfect, but better than nothing
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
=== some tests===
Well i didn't succeed with swapping anything. I used SD's shadow poison sub-ability "release poison" A1S9.
At first I've added a1S9 key to strings with diff art & description, as well as debug command with getobjectname('a1S9')
result - nothing
then I made it reversed, placing a1S9 strings data above original and calling for name of A1S9 instead. nothing again.

if it's w3a-only feature it turns this feature into useless since no big map can work without widgetizing
 
Level 9
Joined
Jul 30, 2012
Messages
156
=== some tests===
Well i didn't succeed with swapping anything. I used SD's shadow poison sub-ability "release poison" A1S9.
At first I've added a1S9 key to strings with diff art & description, as well as debug command with getobjectname('a1S9')
result - nothing
then I made it reversed, placing a1S9 strings data above original and calling for name of A1S9 instead. nothing again.

if it's w3a-only feature it turns this feature into useless since no big map can work without widgetizing

Did you edit only the strings file? You need to actually create this ability in the AbilityData SLK
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Did you edit only the strings file? You need to actually create this ability in the AbilityData SLK

just tried, added it to slk data
Code:
[A1S9]
name="Release Poison"
tip="R|cffffcc00e|rlease Poison"
ubertip="Releases the shadow poison from affected units, causing the bonus damage to occur immediately."
hotkey=E
art=ReplaceableTextures\CommandButtons\BTNReleasePoison
buttonpos=2,1
[a1S9]
art=ReplaceableTextures\CommandButtons\BTNSnowball2
tip="Lich"
ubertip="Releases the shadow poison from affected units, causing the bonus damage to occur immediately."
name="alt"

C;Y3745;X1;K"a1S9"
C;X2;K"ANcl"
C;X11;K1
C;X19;K1
C;X25;K1
C;X28;K"acolyteharvest"

call BJDebugMsg(GetObjectName('a1S9'))
it always shows "release poison".
I tried to give a1S9 as defaul ability for fountain (loaded at first initializing wave), still the same. Used debug msg before picking hero with A1S9 - again, no effect.

/conspiracy theory
Maybe raw txts data being interpreted somehow differently rather than packed data? Normally game loads slk+txts, then go to w3a data, updating empty fields if related data is found inside archieve. It will close WC3 in case if data found in w3a but slk didn't contain ID/correct levels amount to put this data. Basically it may be another instance of objects creation.
 
Level 9
Joined
Jul 30, 2012
Messages
156
just tried, added it to slk data
Code:
[A1S9]
name="Release Poison"
tip="R|cffffcc00e|rlease Poison"
ubertip="Releases the shadow poison from affected units, causing the bonus damage to occur immediately."
hotkey=E
art=ReplaceableTextures\CommandButtons\BTNReleasePoison
buttonpos=2,1
[a1S9]
art=ReplaceableTextures\CommandButtons\BTNSnowball2
tip="Lich"
ubertip="Releases the shadow poison from affected units, causing the bonus damage to occur immediately."
name="alt"

C;Y3745;X1;K"a1S9"
C;X2;K"ANcl"
C;X11;K1
C;X19;K1
C;X25;K1
C;X28;K"acolyteharvest"

call BJDebugMsg(GetObjectName('a1S9'))
it always shows "release poison".
I tried to give a1S9 as defaul ability for fountain (loaded at first initializing wave), still the same. Used debug msg before picking hero with A1S9 - again, no effect.

/conspiracy theory
Maybe raw txts data being interpreted somehow differently rather than packed data? Normally game loads slk+txts, then go to w3a data, updating empty fields if related data is found inside archieve. It will close WC3 in case if data found in w3a but slk didn't contain ID/correct levels amount to put this data. Basically it may be another instance of objects creation.

Just as a last test, try adding both abilities, A1S9 and a1S9, to the same unit at the same time. If they appear with different icons, then I really have to review my theory.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
both abilities appears the same.
which one described higher in .txt takes priority (normal ability (A1S9) for me). same icons, same descriptions, nothing changes when GetObjectName being used.

Just random note: I saw usage of auras with buff = same ID with lowercase first letter
Basically aura A300 has a300 buff, which is appears in txts as:
[A300]
name=aura's name
tip=blabla
ubertip=uberblabla
bufftip=actually aura tip
buffubertip=actually aura ubertip
buffart=aura art

If it would be a different object ID as you assumed, they won't be parsed, but it's correct declaration - I don't have to declare another ID and able to put stuff directly here
 
Level 9
Joined
Jul 30, 2012
Messages
156
both abilities appears the same.
which one described higher in .txt takes priority (normal ability (A1S9) for me). same icons, same descriptions, nothing changes when GetObjectName being used.

Just random note: I saw usage of auras with buff = same ID with lowercase first letter
Basically aura A300 has a300 buff, which is appears in txts as:
[A300]
name=aura's name
tip=blabla
ubertip=uberblabla
bufftip=actually aura tip
buffubertip=actually aura ubertip
buffart=aura art

If it would be a different object ID as you assumed, they won't be parsed, but it's correct declaration - I don't have to declare another ID and able to put stuff directly here

Alright so now we know that the conflicts are not exclusive to .w3a files.

So, which ability takes priority? Is it determined by the order of loading, or the order you put them on the .txts? Try swapping the order in which they are added to the unit, add A1S9 before a1S9, and then test the opposite. For me the last ability added to the unit is the one that overrides, regardless of which comes first in the file. But since you're using SLKs, this could be different.

EDIT: Just found an interesting fact - apparently abilities from the .w3a files will not collide with those from the SLKs. I made a custom ability with code 'Ahtb', which should collide with the 'AHtb' already present in the Warcraft MPQ, but it doesn't - they appear with different icons and don't collide.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
should be uniquness of w3a handling mechanic.
txt's being parsed from the bottom to the top, values override each other in case of any conflict. From my experience I can say it's true for any kind of manipulation - no matter if a or A ability enters map first, they both will share the same properties.

(or from the top to the bottom, locking value after it's being defined, but I can't say which one. highest value will always take priority, anyway)

Code:
[a1s9]
art=ReplaceableTextures\CommandButtons\BTNPhoenix2
[a1S9]
art=ReplaceableTextures\CommandButtons\BTNSnowball2
tip="Lich"
ubertip="Releases the shadow poison from affected units, causing the bonus damage to occur immediately."
name="alt"
[A1S9]
name="Release Poison"
//tip="R|cffffcc00e|rlease Poison"
ubertip="Releases the shadow poison from affected units, causing the bonus damage to occur immediately."
hotkey=E
art=ReplaceableTextures\CommandButtons\BTNReleasePoison
buttonpos=2,1
so final result will be icon from a1s9 (cause it defined last), tip from a1S9 (since it broken at bottom one) and rest from bottom as well.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
w3a takes enormous amount of time at loading stage. literally few (4-10 at least) seconds! while tables being parsed & ready in 1-2 seconds
Still many people (including me) optimize but don't widgetize. The reason for that are: 1) it slightly increases map size, 2) it breaks compatibility with non-widgetized maps

So, saying that something is useless because it doesn't work on widgetized maps is a fallacy. Widgetizing is not an assumed standard procedure (unlike optimizing).
 
Level 9
Joined
Jul 30, 2012
Messages
156
Still many people (including me) optimize but don't widgetize. The reason for that are: 1) it slightly increases map size, 2) it breaks compatibility with non-widgetized maps

So, saying that something is useless because it doesn't work on widgetized maps is a fallacy. Widgetizing is not an assumed standard procedure (unlike optimizing).

But even on regular maps, the usability of this "feature" is still very small, if any at all. This thread was discussing a bug in first place.
 
Level 12
Joined
Mar 13, 2012
Messages
1,121
Additionally mind that this probably is screwed over when loading a saved game, as I don't think this information is saved.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Additionally mind that this probably is screwed over when loading a saved game, as I don't think this information is saved.
That makes a lot of sense, yes.

But even on regular maps, the usability of this "feature" is still very small, if any at all. This thread was discussing a bug in first place.
It certainly wouldn't be the first bug that the WC3 community turned into something useful.

Just my personal collection of "good" bugs:
Command button stacking
Negative health/mana bug
Units with hidden hero stats
Passive transformations
All the neat little locust exploits
Resetting ability cooldowns inside spellbooks
Setting the icon position inside spellbooks
... and of course the infamous spellbook orderstring bug
The preload bug
And last but not least, the mother of all beneficial WC3 bugs (before it was fixed), the one and only return bug
 
Status
Not open for further replies.
Top