library Units initializer ini import Table import Players import DummyUnitStack native UnitAlive takes unit u returns boolean struct LightningBallDat unit u effect fx thistype prev = null thistype next = null end struct BeastLightningDat unit u integer ticksUntilNextCheck integer balls real phase = 0. LightningBallDat ballHead = null integer typ thistype prev = null thistype next = null end public struct Units private static constant string LIGHTNING_BALL_FX = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl" private static constant real CLOCK_PERIOD = 1. / 30. private static constant real LIGHTNING_CHECK_PERIOD = 5. private static constant real BALL_OFF = 64. private static constant real PHASE_ANGULAR_VELOCITY = bj_PI / 8. * CLOCK_PERIOD private static constant real EFFECT_SCALE = .5 private static constant real EFFECT_HEIGHT = 60. private static constant integer BLINK_PASSIVE_ID = 'A00E' private static constant integer BLINK_ID = 'A001' private static constant integer HEMISECT_PASSIVE_ID = 'A00H' private static constant integer HEMISECT_ID = 'A00G' private static constant integer CONCAT_PASSIVE_ID = 'A00J' private static constant integer CONCAT_ID = 'A00K' static constant integer HUNTER_ID = 'H00L' static constant integer BEAST_ID = 'H00M' static constant integer LESSER_BEAST_ID = 'H001' static constant integer HUNTER_PROJECTILE_ID = 'h000' static constant integer ARTIFACT_ID = 'I000' static constant integer GAS_ACTION = 'I003' static constant integer INITIAL_BALLS = 0 static constant integer MAX_BALLS = 5 static constant integer MAX_BALLS_LESSER = 3 private static group grp = CreateGroup() private static timer clock = CreateTimer() private static BeastLightningDat beastHead = null private static Table fromBeast = new Table() static unit array hero static unit array subHero static Table thunderCd = new Table() // cooldown for AI static Table thunderClock = new Table() // clock for max duration static Table beastState = new Table() static Table sitePref = new Table() // A/B preference for Hunter AI static item artifact static item artifact2 private static method pushBall takes unit u returns nothing string fxString = LIGHTNING_BALL_FX BeastLightningDat ua = fromBeast.loadInt(u.getHandleId()) castTo BeastLightningDat LightningBallDat lbd = new LightningBallDat LightningBallDat swap lbd.u = DummyUnitStack.get() SetUnitFlyHeight(lbd.u, EFFECT_HEIGHT, 0.) SetUnitScale(lbd.u, EFFECT_SCALE, EFFECT_SCALE, EFFECT_SCALE) // risky removal of effect if Players.locl != GetOwningPlayer(u) then fxString = "" end lbd.fx = AddSpecialEffectTarget(fxString, lbd.u, "origin") ua.balls++ if ua.balls == 1 then // beast received its first ball UnitRemoveAbility(ua.u, BLINK_PASSIVE_ID) UnitAddAbility( ua.u, BLINK_ID) // beast received its third ball elseif ua.balls == 3 and ua.typ == BEAST_ID then UnitRemoveAbility(ua.u, HEMISECT_PASSIVE_ID) UnitAddAbility( ua.u, HEMISECT_ID) elseif ua.balls == 3 and ua.typ == LESSER_BEAST_ID then UnitRemoveAbility(ua.u, CONCAT_PASSIVE_ID) UnitAddAbility( ua.u, CONCAT_ID) end if (ua.ballHead castTo integer) == 0 then ua.ballHead = lbd else swap = ua.ballHead swap.prev = lbd lbd.next = swap ua.ballHead = lbd end end static method countBalls takes unit u returns integer BeastLightningDat ua if fromBeast.hasInt(u.getHandleId()) then ua = fromBeast.loadInt(u.getHandleId()) castTo BeastLightningDat return ua.balls end debug BJDebugMsg("Units.countBalls: Tried to count balls on unindexed unit") return -1 end static method dischargeBall takes unit u returns nothing BeastLightningDat ua = fromBeast.loadInt(u.getHandleId()) castTo BeastLightningDat LightningBallDat lbd = ua.ballHead LightningBallDat swap DestroyEffect(lbd.fx) DummyUnitStack.release(lbd.u) swap = lbd.next destroy lbd ua.ballHead = swap ua.balls-- // beast lost its third ball if ua.balls == 2 and ua.typ == BEAST_ID then UnitRemoveAbility(ua.u, HEMISECT_ID) UnitAddAbility( ua.u, HEMISECT_PASSIVE_ID) elseif ua.balls == 2 and ua.typ == LESSER_BEAST_ID then UnitRemoveAbility(ua.u, CONCAT_ID) UnitAddAbility( ua.u, CONCAT_PASSIVE_ID) end if (swap castTo int) == 0 then // beast no longer has any lightning balls UnitRemoveAbility(ua.u, BLINK_ID) UnitAddAbility( ua.u, BLINK_PASSIVE_ID) else swap.prev = null end end private static method beastLightningTick takes nothing returns nothing BeastLightningDat ua = beastHead BeastLightningDat uaSwap LightningBallDat lbd LightningBallDat swap integer index real facing loop exitwhen ua == (null) ua.ticksUntilNextCheck-- facing = GetUnitFacing(ua.u) * bj_DEGTORAD ua.phase = ua.phase + PHASE_ANGULAR_VELOCITY if ua.ticksUntilNextCheck <= 0 then // push lightning ball if applicable if ua.balls < MAX_BALLS and ua.typ == BEAST_ID then pushBall(ua.u) elseif ua.balls < MAX_BALLS_LESSER and ua.typ == LESSER_BEAST_ID then pushBall(ua.u) end ua.ticksUntilNextCheck = R2I(LIGHTNING_CHECK_PERIOD / CLOCK_PERIOD) end // update all ball locations index = 0 lbd = ua.ballHead loop exitwhen index >= ua.balls SetUnitX(lbd.u, GetUnitX(ua.u) + BALL_OFF* Cos(facing + ua.phase + index * 2. * bj_PI / ua.balls)) SetUnitY(lbd.u, GetUnitY(ua.u) + BALL_OFF* Sin(facing + ua.phase + index * 2. * bj_PI / ua.balls)) lbd = lbd.next index++ end // check for destruct if not (UnitAlive(ua.u) and GetUnitTypeId(ua.u) > 0) then // destroy all balls lbd = ua.ballHead loop exitwhen lbd == null swap = lbd.next DestroyEffect(lbd.fx) DummyUnitStack.release(lbd.u) destroy lbd lbd = swap end // destroy self, re-linking the list if ua.prev != null then uaSwap = ua.prev uaSwap.next = ua.next else beastHead = ua.next if ua.next == null then PauseTimer(clock) end end if ua.next != null then uaSwap = ua.next uaSwap.prev = ua.prev end destroy ua end ua = ua.next end end static method initializeBeast takes unit u returns nothing BeastLightningDat ua = new BeastLightningDat BeastLightningDat swap integer index = 0 integer id = GetUnitTypeId(u) ua.typ = id ua.u = u ua.ticksUntilNextCheck = R2I(LIGHTNING_CHECK_PERIOD / CLOCK_PERIOD) ua.balls = 0 loop exitwhen index >= INITIAL_BALLS pushBall(u) index++ end fromBeast.saveInt(u.getHandleId(), ua castTo int) // push dat if beastHead == null then beastHead = ua TimerStart(clock, CLOCK_PERIOD, true, function beastLightningTick) else swap = beastHead swap.prev = ua ua.next = swap beastHead = ua end end static method initializeBeastEx takes unit u, integer balls returns nothing BeastLightningDat ua = new BeastLightningDat BeastLightningDat swap integer index = 0 integer id = GetUnitTypeId(u) ua.typ = id ua.u = u ua.ticksUntilNextCheck = R2I(LIGHTNING_CHECK_PERIOD / CLOCK_PERIOD) ua.balls = 0 fromBeast.saveInt(u.getHandleId(), ua castTo int) // push dat if beastHead == null then beastHead = ua TimerStart(clock, CLOCK_PERIOD, true, function beastLightningTick) else swap = beastHead swap.prev = ua ua.next = swap beastHead = ua end loop exitwhen index >= balls pushBall(u) index++ end end static method clearAll takes nothing returns nothing unit iter integer index = 0 // enumerate by player to catch decayed heroes loop exitwhen index > Players.LAST_BEAST GroupEnumUnitsOfPlayer(grp, Players.fromId[index], null) loop iter = FirstOfGroup(grp) exitwhen iter == null GroupRemoveUnit(grp, iter) RemoveUnit(iter) end index++ end end private static method filterItems takes nothing returns nothing item f = GetEnumItem() if GetItemTypeId(f) != ARTIFACT_ID then RemoveItem(f) end end static method clearExtraItems takes nothing returns nothing EnumItemsInRect(bj_mapInitialPlayableArea, null, function filterItems) end static method clearNonHunters takes nothing returns nothing unit iter integer index = 0 // enumerate by player to catch decayed heroes loop exitwhen index > Players.LAST_BEAST GroupEnumUnitsOfPlayer(grp, Players.fromId[index], null) loop iter = FirstOfGroup(grp) exitwhen iter == null GroupRemoveUnit(grp, iter) if GetUnitTypeId(iter) != HUNTER_ID or not UnitAlive(iter) then RemoveUnit(iter) end end index++ end end static method endRoundVfx takes nothing returns nothing unit iter // pause existing units GroupEnumUnitsInRect(grp, bj_mapInitialPlayableArea, null) loop iter = FirstOfGroup(grp) exitwhen iter == null if GetPlayerId(GetOwningPlayer(iter)) <= Players.LAST_BEAST then PauseUnit(iter, true) SetUnitInvulnerable(iter, true) end GroupRemoveUnit(grp, iter) end end end function ini takes nothing returns nothing integer index = 0 // reset subHero array loop exitwhen index >= Players.COUNT Units.subHero[index] = null index++ end end endlibrary