- Joined
- Oct 9, 2019
- Messages
- 345
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:
www.hiveworkshop.com
www.hiveworkshop.com
www.hiveworkshop.com
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:
GUI Unit Event (now with a state-of-the-art Lua version)
Unit Event for GUI gives you access to all kinds of events which normal GUI events can't do: Function Lua vJass Subject unit of the event UnitEvent_unit UDexUnits[UDex] Event index (for use within an array). UnitEvent_index UDex Data attachment to unit Set UnitEvent_setKey = unit Set...
Dummy Recycler v1.25
library DummyRecycler /* // DummyRecycler v1.25 // by Flux // // A system that recycles dummy units while considering their facing angle. // It can be used as attachment dummies for visual effects or as dummy caster. // // Why is recycling a...
[Snippet] SpellEffectEvent
A handy approach to using spell effect responses. You can assign an event to fire for only a specific ability code, or when any ability is cast. API is intuitive and avoids creating tons and tons of handles to get the job done. This kind of thing has similar approaches by other people but this...
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
