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

Text Tags Leaking?

Status
Not open for further replies.
Hey Hive, I have a problem with text tags in a spell I'm working on. It's basically Ogre Magi's Multicast from DotA, but more complex. Anyhow, I've gotten everything to work, except for one thing: it appears the text tags are not being removed properly. I've deduced this because in the beginning the text tags display properly; however after some time the text tags only appear for a brief moment, and then they don't even show up; only occasionally(i.e when it's the only text tag in the game). Based on this I can only conclude that I have reached the text tag limit. But this perplexes me because I'm sure I've properly removed my text tags: I set their lifespan and disabled their permanence; I've even nulled the local variable I've set it too. This is really puzzling, I've never had a problem with text tags leaking before.:vw_wtf:

Anyways here's the trigger(I only deal with text tags in the "Actions" function, I just posted the entire thing in case something else in the code is conflicting and causing errors):

JASS:
scope AetherealMastery initializer OnInit
    //Set up global variables.
    globals
        private constant integer SPELL_ID = 'A0EY'
        private constant integer SPELL_ID1 = 'A0EW'
        private constant integer SPELL_ID2 = 'A0ES'
        private constant integer SPELL_ID3 = 'A0EX'
        private integer CASTS
        private group GROUP = CreateGroup()
        private unit CASTER
        private unit array RANDOMU
        private integer RANDOMI = 0
    endglobals
    //Set up the struct that will make the spell MUI.
    private struct MC
        unit u
        integer cast
        integer spell_id
        integer lvl
        real x
        real y
        unit target
        method destroy takes nothing returns nothing
            set this.u = null
            set this.target = null
            call this.deallocate()
        endmethod
    endstruct
    //Filter all nearby enemies, assign them to unit array to be able to pick a random unit later.
    private function FilterGroup takes nothing returns boolean
    
        if UnitAlive(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(CASTER)) and IsUnitVisible(GetFilterUnit(), GetOwningPlayer(CASTER)) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) then
            set RANDOMI = RANDOMI + 1
            set RANDOMU[RANDOMI] = GetFilterUnit()
        endif
    
        return false
    endfunction
    //Filter all nearby allies, assign them to unit array to be able to pick a random unit later.
    private function FilterGroup2 takes nothing returns boolean
    
        if UnitAlive(GetFilterUnit()) and IsUnitAlly(GetFilterUnit(), GetOwningPlayer(CASTER)) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) then
            set RANDOMI = RANDOMI + 1
            set RANDOMU[RANDOMI] = GetFilterUnit()
        endif
    
        return false
    endfunction
    //This function is confusing but I think u can ignore it; I only work with text tags in the next function.
    private function Handler takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local MC data = GetTimerData(t)
        local unit d
        local unit targ

        //If all instances are finished, clean up the instance, else create a dummy to cast another instance of the spell:    
        if data.cast <= 0 then
            call data.destroy()
            call ReleaseTimer(t)
        else
            set data.cast = data.cast - 1
            //If spell is Aether Flare, create dummy at target location so it will always be in range, regardless if caster moves. (No projectile, so don't need to create it at caster's location)
            if data.spell_id != SPELL_ID3 then
                set d = CreateUnit(GetOwningPlayer(data.u), 'h007', GetUnitX(data.u), GetUnitY(data.u), 0.)
            else
                set d = CreateUnit(GetOwningPlayer(data.u), 'h007', data.x, data.y, 0.)
            endif
            call UnitApplyTimedLife(d, 'BTLF', 1.)
            //If spell is Aether Bolt, give dummy a version of the spell with a global cast range, so the dummy can still cast the spell even if the caster moves.
            if data.spell_id != SPELL_ID1 then
                call UnitAddAbility(d, data.spell_id)
                call SetUnitAbilityLevel(d, data.spell_id, data.lvl)
            else
                call UnitAddAbility(d, 'A0F9')
                call SetUnitAbilityLevel(d, 'A0F9', data.lvl)
            endif
    
            call SetUnitState(data.u, UNIT_STATE_MANA, GetUnitState(data.u, UNIT_STATE_MANA) - (GetUnitState(data.u, UNIT_STATE_MANA) * .1))
            //Determines what spell we should be casting.
            if data.spell_id == SPELL_ID1 then
                //If target is dead we try to find another one in range.
                if not UnitAlive(data.target) or data.target == null then
                    set CASTER = data.u
                    set RANDOMI = 0
                    //Adds all nearby enemies to unit group
                    call GroupEnumUnitsInRange(GROUP, GetUnitX(data.u), GetUnitY(data.u), 800., Filter(function FilterGroup))
                    if RANDOMI > 0 then
                        //Sets target to random unit from array we set before.
                        set targ = RANDOMU[GetRandomInt(1, RANDOMI)]
                        call IssueTargetOrder(d, "thunderbolt", targ)
                        set RANDOMI = 0
                    endif
                else
                    call IssueTargetOrder(d, "thunderbolt", data.target)
                endif
            elseif data.spell_id == SPELL_ID2 then
                //Decides if target already has max attack speed; if it does, find another ally to cast it on.
                if AETHER_BUFF_LVL[GetUnitUserData(data.target)] >= 20 or not UnitAlive(data.target) or data.target == null then
                    set CASTER = data.u
                    set RANDOMI = 0
                    //Adds all nearby allies to unit group.
                    call GroupEnumUnitsInRange(GROUP, GetUnitX(data.u), GetUnitY(data.u), 900., Filter(function FilterGroup2))
                    if RANDOMI > 0 then
                        //Sets target to random unit from array we set before.
                        set targ = RANDOMU[GetRandomInt(1, RANDOMI)]
                        call IssueTargetOrder(d, "cripple", targ)
                        set RANDOMI = 0
                    endif
                else
                    call IssueTargetOrder(d, "cripple", data.target)
                endif
            elseif data.spell_id == SPELL_ID3 then
                call IssuePointOrder(d, "impale", data.x, data.y)
            endif
    
            call DestroyEffect(AddSpecialEffect("war3mapImported\\Stomp.mdx", GetUnitX(data.u), GetUnitY(data.u)))
            
            //Restarts timer so we can cast another instance, if any are left. Else it will auto clean up the trigger.
            call SetTimerData(t, data)
            call TimerStart(t, .3, false, function Handler)
        endif
    
        set targ = null
        set d = null
        set t = null
    endfunction
    //This is where I create the text tag and set up the timer. I thought I cleaned up the text tag pretty thoroughly, but apparently not...
    private function Actions takes nothing returns nothing
        local timer t = NewTimer()
        local MC data = MC.create()
        local texttag tt = CreateTextTag()
        local string text = "|cFF1C88E2M|r|cFF1A91E4u|r|cFF199AE6l|r|cFF17A3E7t|r|cFF16ACE9i|r|cFF14B5EBc|r|cFF12BEECa|r|cFF11C6EEs|r|cFF0FCFF0t|r|cFF0ED8F2 |r|cFF0CE1F4x|r" + "|cFF0BEAF5" + I2S(CASTS + 1) + "|r" + "|cFF09F3F7!|r"
        set data.u = GetTriggerUnit()
        set data.cast = CASTS
        set data.spell_id = GetSpellAbilityId()
        set data.lvl = GetUnitAbilityLevel(data.u, data.spell_id)
    
        call SetTextTagText(tt, text, .024)
        call SetTextTagPos(tt, GetUnitX(data.u), GetUnitY(data.u), 0.0)
        call SetTextTagVelocity(tt, 0.0, 0.04)
        call SetTextTagPermanent(tt, false)
        call SetTextTagFadepoint(tt, 2.0)
        call SetTextTagLifespan(tt, 2.0)
        call SetTextTagVisibility(tt, true)
    
        if data.spell_id == SPELL_ID1 or data.spell_id == SPELL_ID2 then
            set data.target = GetSpellTargetUnit()
        else
            set data.x = GetSpellTargetX()
            set data.y = GetSpellTargetY()
        endif
    
        call SetTimerData(t, data)
        call TimerStart(t, .3, false, function Handler)
    
        set tt = null
        set t = null
        set text = null
    endfunction

    private function Conditions takes nothing returns boolean
        local integer chance
        local integer lvl = GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID)
        if (GetSpellAbilityId() == SPELL_ID1 or GetSpellAbilityId() == SPELL_ID2 or GetSpellAbilityId() == SPELL_ID3) and lvl >= 1 then
            //Here we determine how many additional spell casts will happen, if any.
            set chance = GetRandomInt(1, 100)
            if chance <= lvl then
                set CASTS = 4
                call Actions()
                return false
            elseif chance <= lvl * 2 then
                set CASTS = 3
                call Actions()
                return false
            elseif chance <= lvl * 3 then
                set CASTS = 2
                call Actions()
                return false
            elseif chance <= lvl * 4 then
                set CASTS = 1
                call Actions()
                return false
            endif
        endif
        return false
    endfunction
    
    private function OnInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function Conditions))
    endfunction

endscope


Hopefully someone can help me with this, because I'm really stumped. Thanks!
 
Last edited:
Level 12
Joined
Oct 16, 2010
Messages
680
you forgot to deallocate in destroy method, just sayin

and why do u set fadepoint to it if its the same as lifespan?

EDIT:
local MC data = MC.create()
local MC data = GetTimerData(t)
this is the way you passing data to func ,but
how do u know it will return the correct integer?Oo
I can't follow your logic here

probably you left out dealloc for a reason and you are counting on that the indexing of the timers will be the same as the structs?
 
Actually you also don't necessarily have to null the local texttags.

I don't see mistakes in your texttag creation. But I don't understand one thing. Why have you seperated actions and conditions?

You manually call Actions() by yourself in the condition part, even it's not really related to the triggering trigger.

So I wonder how responses like GetTriggerUnit() actually work for you. It's just a guess, but anyway, also would also be more porper strucutre. Could you just move the "Actions" part into the condition function, so it's in the same function.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
IcemanBo is right, just return true after setting the CASTS variable to the desired value to execute the trigger action.

@IcemanBo: Action() is still within the trigger's event execution thread, it will still hold a valid value, maybe LOL.

visualization:
Code:
event {
  TriggerUnit // Variables localized for the event
  condition{
    action{
    }
  }
}
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
@IcemanBo: the Action() was called within the Condition() thread function

EDIT: I'm speaking about the thread scope, not the function scope bro.

EDIT 2: thread is misleading, let's call it the event thread scope.
 
@IceManBo
I separated Actions from Conditions for readability reasons. Since I called Actions() from the Conditions trigger, functions such as GetTriggerUnit() will still work. It is a little more inefficient, since we are executing one more function call than necessary, but it's so negligible that I'd rather have the readability.

@Lender
You're right, I forgot to deallocate. Fixed, thanks!

However, my text tags still don't seem to be destroying properly... I still hit the limit and am only able to use one floating text the entire game.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
Try an I2S(GetHandleId(tt)) debug message
EDIT: To check if the texttag is indeed not recycled.
 
Ok, I'll try it using debug messages when I get off work in a few hours.

EDIT: Ok, so this is weird... I tested it and the text tags do indeed recycle properly at first. I got Handle IDs 93-80(I have around 7 permanent text tags in my map), slowly going back up to 93 just fine. But... after I used the multicast for a while the text tags suddenly break and jump to handle id 0... which is what I'm assuming is the last available handle id, and after that, it doesn't go up...

EDIT: It's not my trigger that's leaking text tags, but standard wc3 text tags! I made a unit attack another unit with 100% evasion, after he attacked(and missed) a lot, I tried using my multicast ability, and the Handle Id went down and never went up. SO.... that means standard wc3 text tags don't destroy themselves??? I mean, I see no way I could manipulate standard wc3 text tags to make them leak? (Well I did change the miss text, but that doesn't explain how gold bounty text tags don't recover either...)
 
Last edited:
Level 12
Joined
Oct 16, 2010
Messages
680
EDIT: It's not my trigger that's leaking text tags, but standard wc3 text tags! I made a unit attack another unit with 100% evasion, after he attacked(and missed) a lot, I tried using my multicast ability, and the Handle Id went down and never went up. SO.... that means standard wc3 text tags don't destroy themselves??? I mean, I see no way I could manipulate standard wc3 text tags to make them leak? (Well I did change the miss text, but that doesn't explain how gold bounty text tags don't recover either...)

not shure of it:/ make a stress test like 10-15 units with 100% evasion and 10-15 units attacking them fast. If it breaks after a while then wc3 texttags ARE messed up.
if this is the situation i prefer to remove every chance from your map to miss or crit and using a DDS create your own versions(if u need them)

by the way havn't you tried removing that gigantic color code?:D not sure how wc3 texttags can handle a beast like thatxD
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Make two freaking constant string variables and label them "PREFIX" and "SUFFIX". Set them to your color codes!
--> set text = PREFIX + I2S(CAST + 1) + SUFFIX

Also: call Actions(CAST) instead of set CAST = number and call Actions()

Interesting supposition that standart texttags may leak ( I think it's not true ). Can you prove it?
 
Well that doesn't explain why my code works fine until Wc3 text tags start displaying... My code's text tag handle IDs prove that they are getting properly recycled. But after wc3 starts displaying its text tags, my code's text tag handle IDs start getting lower, until I'm only left with handle ID 0...Unless I somehow broke wc3 text tags to mess up somehow? Is that even possible? o_O
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
Is that the only one using a texttag? Try disabling other texttag using scripts you have to make sure.
 
Status
Not open for further replies.
Top