• Check out the results of the Techtree Contest #19!
  • Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

[JASS] SOLVED

Hello Hive,

I have a spell called "past image" which has 2 parts. Part 1 is that it allocates a dummy unit and creates a text tag showing the caster's life and mana at the time of creation every time it casts. This is accomplished via Passive ability with cooldown - Best method! using an ability based on Exhume Corpse (IMAGEAID in the script). Part 2 which isn't the issue but I am mentioning for completeness is that the caster can actively cast CASTAID to set their stats to the closest image of the target point (since you cannot target units that have locust). Part 3 which also works as intended is that if the unit takes fatal damage they will assume their most recent image's stats instead of dying. Currently the duration normal time for the image creation is 15 seconds and the cooldown is 90 seconds. The duartion - normal field is set to 15 so that even if the caster gets 100% cooldown reduction it won't spam the screen with images.

The issue is that if the caster is currently stunned e.g. by storm bolt when the 15 second interval is up the image timer will tick a far fewer times then it should and then skip to deallocating without recycling the image. See the comments in the OnPeriod method for more info. The cast event will trigger after the unit is no longer stunned which is the expected behavior.

I can provide a test map if people want to play around with this code.

Systems used:

JASS:
scope PastImage

    globals
        private integer CASTAID = 'A000'
        private integer IMAGEAID = 'A001'
        private real    IMAGETICKS  = 240
        private real    PERIOD  = 1.
    private real     DAMAGECOOLDOWNFACTOR = 1.5
    private real array imagelife
    private real array imagemana
    private texttag array imageui
    private unit mostrecentimage
    endglobals

    private function RecycleImage takes unit u returns nothing //Clean the data attached to an image and recycle the dummy
    call DestroyTextTag(imageui[GetUnitUserData(u)])
    set imageui[GetUnitUserData(u)] = null
    call BlzSetUnitSkin(u,'dumi')
    call SetUnitTimeScale(u,1.0)
    call SetUnitVertexColor(u, 255, 255, 255, 255)
    call RecycleDummy(u)
    endfunction

    private function AssumeImage takes unit c, unit t returns nothing //Have the unit c assume the stats of the image t
    call SetUnitX(c,GetUnitX(t))
    call SetUnitY(c,GetUnitY(t))
    call SetUnitFacing(c,GetUnitFacing(t))
    call SetUnitState(c,UNIT_STATE_LIFE,RMaxBJ(imagelife[GetUnitUserData(t)],GetUnitState(c,UNIT_STATE_LIFE)))
    call SetUnitState(c,UNIT_STATE_MANA,RMaxBJ(imagemana[GetUnitUserData(t)],GetUnitState(c,UNIT_STATE_MANA)))
    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl",GetUnitX(t),GetUnitY(t)))
    call RecycleImage(t)
    endfunction

    private function onReplaceCast takes nothing returns nothing //Find the closest image to the target point and assume it
    local unit caster=GetTriggerUnit()
    local real x=GetSpellTargetX()
    local real y=GetSpellTargetY()
    local real distance = 99999.
    local group g=CreateGroup()
    local unit target
    local unit temp

    call GroupEnumUnitsOfPlayer(g,GetOwningPlayer(caster),null)
    loop
            set temp=FirstOfGroup(g)
            exitwhen temp == null
            if imageui[GetUnitUserData(temp)]!=null and DistanceBetweenPointsReal(GetUnitX(temp) , GetUnitY(temp) , x , y) < distance then
            set target=temp
            set distance=DistanceBetweenPointsReal(GetUnitX(target) , GetUnitY(target) , x , y)
            endif
            call GroupRemoveUnit(g, temp)
        endloop
    call AssumeImage(caster,target) //I realize need to add a check that the image exists but that's not what I'm asking in this thread
    call DestroyGroup(g)
    set caster=null
    set g=null
    set target=null
    set temp=null
    endfunction

    private function OnLethalConditions takes nothing returns boolean //Is the damage lethal and is the assume ability ready?
    return GetUnitAbilityLevel(udg_DamageEventTarget,CASTAID) > 0 and BlzGetUnitAbilityCooldownRemaining(udg_DamageEventTarget,CASTAID) <= 0. and udg_DamageEventAmount >= GetUnitState(udg_DamageEventTarget,UNIT_STATE_LIFE) then
    endfunction

    private function OnLethalActions takes nothing returns nothing //Assume the most recent image
    local integer lvl=GetUnitAbilityLevel(udg_DamageEventTarget,CASTAID)
    set udg_DamageEventAmount = 0.
    call UnitRemoveBuffsExBJ(bj_BUFF_POLARITY_NEGATIVE, bj_BUFF_RESIST_EITHER, udg_DamageEventTarget, false, false)

        call UnitRemoveBuffsExBJ(bj_BUFF_POLARITY_NEGATIVE, bj_BUFF_RESIST_BOTH, udg_DamageEventTarget, false, false)

    call UnitRemoveAbility(udg_DamageEventTarget,'BHbn')
    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl",GetUnitX(udg_DamageEventTarget),GetUnitY(udg_DamageEventTarget)))
        call CalculateAbilityCooldown(udg_DamageEventTarget,CASTAID,lvl,BlzGetAbilityCooldown(CASTAID,lvl-1))
    call BlzStartUnitAbilityCooldown(udg_DamageEventTarget,CASTAID,BlzGetUnitAbilityCooldown(udg_DamageEventTarget,CASTAID,lvl -1) * DAMAGECOOLDOWNFACTOR)
    call AssumeImage(udg_DamageEventTarget,mostrecentimage)
    endfunction

    private struct ImageStruct

        timer   timer
    unit    pastimage
    integer ticks
    player owner

        static method onPeriod takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
        if imageui[GetUnitUserData(pastimage)]==null then //Supposed to run if an image is already recycled to clean the struct (Can happen from an image timing out or from assuming an image's stats and position) but will erroneously run before recycle image if the unit is stunned when it attempts to make the image
                call ReleaseTimer(timer)
                call deallocate()
                set timer  = null
                set pastimage  = null
        elseif ticks > IMAGETICKS then //Image has timed out. This should always run 2nd but doesn't run at all in the glitch scenario.
        call RecycleImage(pastimage)
        else //Image still exists This should always run 1st at least once
        set ticks = ticks + 1
        if GetLocalPlayer() == owner then

            call PingMinimapEx(GetUnitX(pastimage),GetUnitY(pastimage), 1.0, 0, 255, 255, false)

        endif
        endif
        endmethod

        static method onImageCast takes nothing returns nothing
        local unit caster=GetTriggerUnit()
            local thistype this = thistype.create()
        set pastimage = GetRecycledDummy(GetUnitX(caster),GetUnitY(caster),0.,GetUnitFacing(caster))
        set owner = GetOwningPlayer(caster)
        call SetUnitOwner(pastimage,owner,true)
        set mostrecentimage = pastimage
        call BlzSetUnitSkin(pastimage,BlzGetUnitSkin(caster))
        call SetUnitVertexColor(pastimage, 255, 100, 255, 100)
        call SetUnitTimeScale(pastimage,0.)
        call SetUnitPropWindow(pastimage, 0.)
        set imagelife[GetUnitUserData(pastimage)]=GetUnitState(caster,UNIT_STATE_LIFE)
        set imagemana[GetUnitUserData(pastimage)]=GetUnitState(caster,UNIT_STATE_MANA)
        set imageui[GetUnitUserData(pastimage)] = CreateTextTag()
        call SetTextTagText(imageui[GetUnitUserData(pastimage)],I2S(R2I(imagelife[GetUnitUserData(pastimage)])) + " | " + I2S(R2I(imagemana[GetUnitUserData(pastimage)])),0.025)
        call SetTextTagPosUnit(imageui[GetUnitUserData(pastimage)], pastimage, 0)
        call SetTextTagColor(imageui[GetUnitUserData(pastimage)],0,255,0,255)
        call SetTextTagVisibility(imageui[GetUnitUserData(pastimage)],GetLocalPlayer()==owner or IsPlayerAlly(GetLocalPlayer(),owner))
        set ticks = 0
            set timer  = NewTimerEx(this)
            call TimerStart(timer, PERIOD, true, function thistype.onPeriod)
        set caster=null
        endmethod

        static method onInit takes nothing returns nothing
        local trigger t=CreateTrigger()
            call RegisterSpellEffectEvent(IMAGEAID, function thistype.onImageCast)
            call RegisterSpellEffectEvent(CASTAID, function onReplaceCast)
        call TriggerRegisterVariableEvent(t, "udg_DamageModifierEvent", EQUAL, 1.00)
        call TriggerAddCondition(t,Condition(function OnLethalConditions))
        call TriggerAddAction(t,function OnLethalActions)
        set t=null
        endmethod
    endstruct
endscope
 
Back
Top