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

[vJASS] fun stuff with images

Status
Not open for further replies.
Level 17
Joined
Apr 27, 2008
Messages
2,455
After i've heard about a way to get shadow of units playing with images by Mag, i've made exhaustive tests about it :

EDIT : Read the other posts, you will lileky mess with with the internal stack if you destroy such images, but you can make them invisible.
Also i think it's good to be aware about this bug, i mean it could eventually ease a debug if you have forgotten to null an image variable and use it later.

JASS:
scope ShadowTest initializer init

    function GetFutureNextImage takes nothing returns image
        local image im = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
        call DestroyImage(im)
        return im
    endfunction
    
    function CreateUnitAndGetShadow takes nothing returns image
        local image i = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 ) // the last argument (integer) can't be 0
        // the image has to be created, else its id is -1, we have to use a valid string argument for the image
        // note that textags have a lame convention, id == 0 means both the last valid texttag created and an invalid one (limit of 100 texttags reached)
        call BJDebugMsg(I2S(GetHandleId(i))) // no handle id leak, woot, images are so special, or the handle id leak is fixed with locals ?!
        // i'm wondering if it's the same with texttags and also with other pseudo handles (sounds ?!)
        call DestroyImage(i)
        // we need to create a valid image to get the next id handle, we destroy it to recycle it
        call CreateUnit(Player(0), 'hpea', 0, 0, 270)
        // the shadow will use the recycled id : i
        return i
    endfunction
    
    private function init takes nothing returns nothing
        local image shadow = CreateUnitAndGetShadow()
        call DestroyImage(shadow)
        set shadow = CreateUnitAndGetShadow()
        call DestroyImage(shadow)
        set shadow = GetFutureNextImage()
        call CreateUnit(Player(0),'hfoo',0,0,0)
        call DestroyImage(shadow)
    endfunction
    
endscope

Coz of the results i've also made other tests.

JASS:
scope LocalUnitHandleIdLeak initializer init

    globals
        private integer I = 0
    endglobals
    
    private function Test takes nothing returns nothing
        local unit u = CreateUnit(Player(0), 'hpea', 0, 0, 270)
        set I = I+1
        if I == 10 then
            call PauseTimer(GetExpiredTimer())
        endif
        call BJDebugMsg("unit handle id == "+I2S(GetHandleId(u))) // local handle id leak is not fixed
        call RemoveUnit(u)
    endfunction
    
    private function init takes nothing returns nothing
        call TimerStart(CreateTimer(),0.1,true,function Test)
    endfunction
    
endscope

JASS:
scope LocalTexttagHandleIdLeak initializer init

    globals
        private integer I = 0
    endglobals
    
    private function Test takes nothing returns nothing
        local texttag x = CreateTextTag()
        set I = I+1
        if I == 10 then
            call PauseTimer(GetExpiredTimer())
        endif
        call BJDebugMsg("texttag handle id == "+I2S(GetHandleId(x))) // same as image, no leak
        call DestroyTextTag(x)
    endfunction
    
    private function init takes nothing returns nothing
        call TimerStart(CreateTimer(),0.1,true,function Test)
    endfunction
    
endscope

JASS:
scope TexttagTest initializer init

    globals
        private texttag Text = null
    endglobals
    
    private function Test takes nothing returns nothing
        //call SetTextTagVisibility(Text,false)
        call DestroyTextTag(Text)
        call BJDebugMsg(I2S(GetHandleId(Text)))
    endfunction
    
    private function Act takes nothing returns nothing
        call TimerStart(CreateTimer(),0.2,false,function Test)
    endfunction
    
    private function init takes nothing returns nothing
        local trigger trig = CreateTrigger()
        local integer i = 0
        local unit u = CreateUnit(Player(0),'Hpal',0,0,0)
        call TriggerRegisterUnitEvent(trig,u,EVENT_UNIT_DAMAGED)
        call TriggerAddAction(trig,function Act)
        loop
        exitwhen i == 95
        set i = i+1
            call CreateTextTagLocBJ( I2S(i), Location(0,i*30-2000), 0, 10, 100, 100, 100, 0 ) // yeah horrible and leaks, quite a copy/paste of a GUI action :p
        endloop
        set Text = CreateTextTag()
        call DestroyTextTag(Text)
        call BJDebugMsg(I2S(GetHandleId(Text)))
    endfunction
    
endscope

Conclusions :

The local handle id leak is still not fixed :/
But it makes sense that texttags and images are not concerned by this bug, because it seems that it can safely be used locally (created/destroyed...) within a GetLocalPlayer block.
At very least it's true with texttags, i haven't tested with images.

I've not sucesfully got the texttag displayed by abilities using the same trick as images.
I've tested it with the orc blademaster critical strike.
I suspect these texttags don't use the same stack/queue/whatever, as the ones created with triggers.
Again, that makes sense, since it's not because you've reached the hardcoded limit of 100 texttags with triggers, that these texttags are not displayed.

Also sounds don't seem to be pseudo handles according to their high handle id.

I've included a test map.

Oh, btw if you will never use images (also remove shadows of units on unit creation).
You even don't need to create/destroy image, just set your image variable default value to null, create the unit, get the shadow and destroy it : it's the "null" variable image.
But i don't recommend it, it's enough fair to create/destroy a new valid image.
 

Attachments

  • Shadow test.w3x
    18.9 KB · Views: 105
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
In short :

It's a bug exploit.
Shadows of units are actually images.
Even if the return bug is dead we still can get them using a trick, images handles are immediatly recycled when an image is destroyed.
(You only need to read the first snippet)
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Hmm, if i remember correctly (need to be confirmed, would you be kind enough ?, i can't test it now ^^) :
If you don't create any unit (included preplaced), nor images and just use the scope LocalTexttagHandleIdLeak in the first post, the id displayed by the debug message is always 0, meaning that at least UI images don't share the same data structure :/

EDIT : Tested, my guess was right.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
I've tried to create an image and display its id during the cinematic mode on (after a TSA just to be sure).
Its id was equal to the previous created image (and still not destroyed) + 1, instead of +2.
Which means, cinematic mode don't create an image.
 
I'm making some tests too and it seems that unit's shadows are not the only images created. So far, I identified 4 images :
- The unit shadow is the first created and it's created right after the unit (only if the unit has a shadow).
- The building shadow is created then (only if the unit has a building shadow). It won't display on non-buildings units.
- The preselection circle is created (locally?) when the player drag-selects the unit or when he puts his mouse over it.
- The selection circle is created when the player selects the unit.

In most cases, the preselection comes before the selection but you can use the function SelectUnit (true + false works) to immediatly create the selection circle (the preselection isn't created yet with it).

The bug you found about preselection occurs when you destroy the shadow before the preselection is created : when the preselection circle is created, it takes the Id of the destroyed shadow (it won't happen in real maps, only in test maps where few units are created) and it doesn't hide for some reason. Preselection circles are normally internally hidden but not using the ShowImage function, so that line won't affect it (as well as the other unit related images, btw). However, you can still destroy it.

An overview about unit related image manipulation, because if they share the same type, they are not handled the same way :
SetImageColor :
for shadows, only alpha argument seems to work,
for preselection circle, all the arguments work but they are often reinitialized (when the mouse moves for drag-preselection and when it hides/shows for normal preselection).
same thing for selection but it is reinitialized only when it hides/shows.
ShowImage :
never works.
DestroyImage :
works at first but mess up with the stack so you can get the bug you know and also wrong positioned images.

I don't know what renders are.

There are also few other things to know : the selection circle is used when you target the unit (it flashes at confirmation) and selection/preselection circles are the same when a unit changes owner (it only changes the color).

Those examples work if the only relevant events during the wait are preselection and selection of the unit u.
Units are supposed to have both a shadow and a building shadow. If not, the stack is simply translated.
JASS:
function UnitImages1 takes nothing returns nothing
    local unit u
    local image shadow
    local image buildingshadow
    local image preselection
    local image selection
    set shadow = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    set buildingshadow = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    set preselection = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    set selection = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    call DestroyImage( shadow )
    call DestroyImage( buildingshadow )
    call DestroyImage( preselection )
    call DestroyImage( selection )
    set u=CreateUnit( Player(0), 'hfoo', 0, 0, bj_UNIT_FACING )
    // shadow is properly assigned to unit's shadow.
    // buildingshadow is properly assigned to building's shadow.
    call TriggerSleepAction( 4 ) // give time to preselect and select the unit.
    // preselection is properly assigned to unit's preselection circle.
    // selection is properly assigned to unit's selection circle.
    call DestroyImage( shadow )
    call DestroyImage( buildingshadow )
    call DestroyImage( preselection )
    call DestroyImage( selection )
endfunction

function UnitImages2 takes nothing returns nothing
    local unit u
    local image shadow
    local image buildingshadow
    local image preselection
    local image selection
    set shadow = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    set buildingshadow = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    set selection = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    set preselection = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    call DestroyImage( shadow )
    call DestroyImage( buildingshadow )
    call DestroyImage( selection )
    call DestroyImage( preselection )
    set u=CreateUnit( Player(0), 'hfoo', 0, 0, bj_UNIT_FACING )
    // shadow is properly assigned to unit's shadow.
    // buildingshadow is properly assigned to building's shadow.
    call SelectUnit( u, true )
    // selection is properly assigned to unit's selection circle.
    call SelectUnit( u, false )
    call TriggerSleepAction( 4 ) // give time to preselect the unit.
    // preselection is properly assigned to unit's preselection circle.
    call DestroyImage( shadow )
    call DestroyImage( buildingshadow )
    call DestroyImage( selection )
    call DestroyImage( preselection )
endfunction
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Well, that's a bug exploit, i didn't expected a clean usage of it :p
Also if you are willing to do more tests, if i remember correctly, an image variable which is equal to null actually refers to the first image created, which is a really unexpected behavior ... (i mean could you confirm it plz ?)

The same test with a texttag wouldn't hurt too :p (except that it would be the last texttag (100) instead of the first one like images)
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
While i'm agree you shoudn't use a null variable, if we follow the general behavior of jass native functions dealing with handles argument, null == nothing.
It's something to have in mind, that's not so obvious.

Hmm, wait :

local texttag txt = Create... // it's the hundredth texttag

if txt == null // it will be considered as true ?!, if it is then it's incredibly lame, we have no way to know when we have reached the limit, except with some global integer usage.
 
When the 100th text tag is reached, CreateTextTag will always return null (until another text tag is destroyed). That means the 101th call will return the pointer to the same text tag than the 100th call. I guess it answers your question :p.

However, you can't use "null" as a valid argument for text tags functions until the 100th text tag is created because CreateTextTag not only returns an id but also configure the memory so it can store text tag datas.

I made more tests :
- there is apparently no collision between a text tag and an image that share the same Id.
- SetImageRender and SetImageRenderAlways don't seem to work on unit's images.

Curiously (for me), destroying an unit's image seems to have irreversible consequences :
JASS:
    local unit u
    local image shadow
    set shadow = CreateImage("ReplaceableTextures\\Splats\\AuraRune9b.blp",0,0,0,0,0,0,0,0,0,1 )
    call DestroyImage( shadow )
    set u=CreateUnit( Player(0), 'hfoo', 0, 0, bj_UNIT_FACING )
    call DestroyImage( shadow )
    call RemoveUnit(CreateUnit( Player(0), 'hfoo', 0, 0, bj_UNIT_FACING ))
    // Here, the unit creates an image with the same id than "shadow"
    // and removing the unit destroys that image.
    // So, the id of shadow is available again for a new image (after a 0.00 second wait actually)
    // But if that image is a (pre)selection circle, it bugs
    // even if the Id was recycled using the internal shadow images destroyer (via RemoveUnit).

Also, disabling shadows in the options doesn't hide the bugged (pre)selection circles (it could have "absorb" the displaying rules of the shadow image but it's not the case).

Still a long way if we want to fully understand that bug... I'm sure 95% of people are lost since a long time ^^.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
there is apparently no collision between a text tag and an image that share the same Id.

I had never thought about a such scenario, that would be totally crazy if there was a collision with 2 different types.
Jass is jass, but still :p

// So, the id of shadow is available again for a new image (after a 0.00 second wait actually)

Not immediatly ?!
Oh, wait it's just because RemoveUnit is not applied while the current thread is not closed.
I have never tested if it's delayed like a Timer(0), but i'm sure it's not applied until the thread is open (ofc using a TSA will open a new thread).
And in fact if there are several consecutive threads which will fire, like several triggers with the same unit event, if you remove the unit in the first trigger, it will still not be removed when the other triggers will fire.
So yeah maybe Blizzard choiced to delay the unit (and probably other handles types) removing.
 
Status
Not open for further replies.
Top