- Joined
- May 24, 2016
- Messages
- 358
The ability is based on Pocket Factory. After casting, a Factory (nfac) is created (this is by defauth). The code waits for the projectile travel time, then finds the spawned Factory and stores its handle.
A periodic timer (0.03 s) is then started. Every tick it:
The issue: the desync does not occur when the lightning is created for the first time. It only happens after the lightning has been destroyed and then recreated.
Reproduction steps:
The first AddLightningEx() always works correctly. The desync appears to be related specifically to the DestroyLightning() → AddLightningEx() reconnection cycle, or to code executed during that transition.
A periodic timer (0.03 s) is then started. Every tick it:
- checks whether both the caster and the Factory are still alive;
- if the caster is within the Factory's range, it creates (or updates) a lightning effect between the Factory and the caster;
- if the caster leaves the range, it destroys the lightning;
- if the caster enters the range again, it recreates the lightning;
- while the lightning is active, it periodically restores mana to the caster;
- when either the caster or the Factory dies, it destroys the lightning, visual effects, sound, flushes the hashtable, and stops the timer.
The issue: the desync does not occur when the lightning is created for the first time. It only happens after the lightning has been destroyed and then recreated.
Reproduction steps:
- Cast the ability and create the Factory.
- The lightning is created and works correctly.
- Move outside the Factory's range so the lightning is destroyed.
- Move back into range so the lightning is recreated.
- The game desyncs immediately after the lightning is recreated.
The first AddLightningEx() always works correctly. The desync appears to be related specifically to the DestroyLightning() → AddLightningEx() reconnection cycle, or to code executed during that transition.
JASS:
//Spell registration. Nothing strange here
// L_point, L_point2 are global locs that are declared before
if spellid == 'ANsy' then
set x = GetSpellTargetX()
set y = GetSpellTargetY()
set cx = GetUnitX(c)
set cy = GetUnitY(c)
set dx = x - cx
set dy = y - cy
set L_point = GetUnitLoc(c)
set L_point2 = Location(x,y)
set dz = GetLocationZ(L_point2) - GetLocationZ(L_point)
set dist = SquareRoot(dx*dx + dy*dy + dz*dz)
call RemoveLocation(L_point)
call RemoveLocation(L_point2)
set t = CreateTimer()
set idT = GetHandleId(t)
call SaveUnitHandle(Hash,idT,0,c)
call SaveReal(Hash,idT,1,x)
call SaveReal(Hash,idT,2,y)
call SaveInteger(Hash,idT,7171,GetUnitAbilityLevel(c,'ANsy'))
call TimerStart(t,dist/1000.+0.5,false,function MechaGenerator_L)
JASS:
function MechaGenerator_Destroy takes timer t returns nothing
local integer idT = GetHandleId(t)
local unit gen = LoadUnitHandle(Hash,idT,3)
local lightning l = LoadLightningHandle(Hash,idT,66)
local effect ef = LoadEffectHandle(Hash,idT,67)
local sound s = LoadSoundHandle(Hash,idT,73)
if gen != null and not IsUnitDead(gen) then
call KillUnit(gen)
endif
if l != null then
call DestroyLightning(l)
endif
if ef != null then
call DestroyEffect(ef)
endif
if s != null then
call StopSound(s,true,false)
call KillSoundWhenDone(s)
endif
call FlushChildHashtable(Hash,idT)
call DestroyTimer(t)
set gen = null
set l = null
set ef = null
set s = null
set t = null
endfunction
function MechaGenerator_FindGenerator takes unit caster, real x, real y, integer lvl returns unit
local group g = CreateGroup()
local unit u
local unit result = null
call GroupEnumUnitsInRange(g, x, y, 600., null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if GetUnitTypeId(u) == 'nfac' and GetOwningPlayer(u) == GetOwningPlayer(caster) and not IsUnitDead(u) then
if lvl > 1 then
call UnitAddAbility(u,'A04C')
if lvl == 2 then
call SetUnitAbilityLevel(u,'A04C',3)
else
call SetUnitAbilityLevel(u,'A04C',5)
endif
call SetUnitScale(u,1.2,1.2,1.2)
call UnitRemoveAbility(u,'A04C')
endif
set result = u
exitwhen true
endif
endloop
call DestroyGroup(g)
set g = null
set u = null
return result
endfunction
function MechaGenerator_L2 takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer idT = GetHandleId(t)
local unit c = LoadUnitHandle(Hash,idT,0)
local unit gen = LoadUnitHandle(Hash,idT,3)
local lightning l = LoadLightningHandle(Hash,idT,66)
local effect ef = LoadEffectHandle(Hash,idT,67)
local sound s = LoadSoundHandle(Hash,idT,73)
local real x = LoadReal(Hash,idT,1)
local real y = LoadReal(Hash,idT,2)
local real cx
local real cy
local real gx
local real gy
local real dist
local real maxDistance = 600.
local integer lvl = LoadInteger(Hash,idT,7171)
local real manaRestore = 0.6
// local location L_point
//local location L_point2
if lvl < 1 then
set lvl = 1
endif
if lvl == 1 then
set maxDistance = 650.
set manaRestore = 0.6
elseif lvl == 2 then
set maxDistance = 850.
set manaRestore = 0.85
else
set maxDistance = 1050.
set manaRestore = 1.1
endif
// Кастер умер
if c == null or IsUnitDead(c) then
call MechaGenerator_Destroy(t)
return
endif
// Первый тик — ищем генератор
if gen == null then
set gen = MechaGenerator_FindGenerator(c,x,y,lvl)
if gen == null then
call MechaGenerator_Destroy(t)
return
endif
call SaveUnitHandle(Hash,idT,3,gen)
// Запускаем периодический режим
call TimerStart(t,0.03,true,function MechaGenerator_L2)
endif
// Генератор умер
if IsUnitDead(gen) then
call MechaGenerator_Destroy(t)
return
endif
set cx = GetUnitX(c)
set cy = GetUnitY(c)
set gx = GetUnitX(gen)
set gy = GetUnitY(gen)
set dist = SquareRoot((gx-cx)*(gx-cx)+(gy-cy)*(gy-cy))
// Кастер вышел из радиуса действия генератора
if dist > maxDistance then
if l != null then
call DestroyLightning(l)
call SaveLightningHandle(Hash,idT,66,null)
if ef != null then
call DestroyEffect(ef)
call SaveEffectHandle(Hash,idT,67,null)
endif
if s != null then
call StopSound(s,true,false)
call KillSoundWhenDone(s)
call SaveSoundHandle(Hash,idT,73,null)
endif
endif
else
// Создаем молнию первый раз
if l == null then
set L_point = GetUnitLoc(c)
set L_point2 = GetUnitLoc(gen)
set l = AddLightningEx("CLPB",true,GetLocationX(L_point),GetLocationY(L_point),GetLocationZ(L_point)+50.,GetLocationX(L_point2),GetLocationY(L_point2),GetLocationZ(L_point2)+50.)
call RemoveLocation(L_point)
call RemoveLocation(L_point2)
call SetLightningColor(l,100./255.,60./255.,255./255.,1.)
call SaveLightningHandle(Hash,idT,66,l)
call DestroyEffect(AddSpecialEffectTarget("war3mapImported\\GearChangeBlue.mdx",c,"chest"))
set ef = AddSpecialEffectTarget("war3mapImported\\RefreshingSurge.mdx",c,"chest")
call SaveEffectHandle(Hash,idT,67,ef)
set s = CreateSound("Abilities\\Spells\\Human\\CloudOfFog\\CloudOfFogLoop1.wav",true,true,true,10,10,"DefaultEAXON")
call SetSoundVolume(s,60)
call AttachSoundToUnit(s,c)
call StartSound(s)
call SaveSoundHandle(Hash,idT,73,s)
else
// Просто двигаем существующую молнию
set L_point = GetUnitLoc(c)
set L_point2 = GetUnitLoc(gen)
call MoveLightningEx(l,true,GetLocationX(L_point),GetLocationY(L_point),GetLocationZ(L_point)+50.,GetLocationX(L_point2),GetLocationY(L_point2),GetLocationZ(L_point2)+50.)
call RemoveLocation(L_point)
call RemoveLocation(L_point2)
endif
// Восполняем ману
call SetUnitState(c,UNIT_STATE_MANA,GetUnitState(c,UNIT_STATE_MANA)+manaRestore)
endif
set c = null
set gen = null
set l = null
set ef = null
set s = null
set L_point = null
set L_point2 = null
set t = null
endfunction