library AI import TerrainData import Players import Units import Statistics native UnitAlive takes unit u returns boolean constant integer STATE_AGGRESSIVE = 0 constant integer STATE_RETREAT = 1 constant integer BEAST_ID = 'h002' constant integer THUNDER_CD = 10 player tempPlayer unit tempUnit group grp = CreateGroup() unit hunterLeader = null function hunterElection takes nothing returns nothing integer index = 0 loop exitwhen index >= Players.FIRST_BEAST if UnitAlive(Units.hero[index]) and not Players.aiControlled[index] then hunterLeader = Units.hero[index] return end index++ end index = 0 loop exitwhen index >= Players.FIRST_BEAST if UnitAlive(Units.hero[index]) then hunterLeader = Units.hero[index] return end index++ end end function randomizeSitePreferences takes nothing returns nothing integer index = 0 loop exitwhen index >= Players.FIRST_BEAST Units.sitePref.saveInt(Units.hero[index].getHandleId(), GetRandomInt(0, 1)) index++ end end function proceedObjective takes nothing returns nothing if UnitHasItem(tempUnit, Units.artifact) then // Head home. IssuePointOrder(tempUnit, "move", TerrainData.RUNE_RETURN_X, TerrainData.RUNE_RETURN_Y) else // Pick an artifact based on objective. item artifact int objective = Units.sitePref.loadInt(Units.hero[tempUnit.getOwner().getId()].getHandleId()) if objective == 0 then artifact = Units.artifact else artifact = Units.artifact2 end // Head to artifact. if IsUnitInRangeXY(tempUnit, GetItemX(artifact), GetItemY(artifact), 200.) then IssueTargetOrder(tempUnit, "smart", artifact) else IssuePointOrder(tempUnit, "move", artifact.getX(), artifact.getY()) end end end function parseHunter takes nothing returns nothing unit iter real d = 1200. unit selected = null player owner = GetOwningPlayer(tempUnit) real hunterX real hunterY real tempD real ang if not UnitAlive(hunterLeader) then hunterElection() end hunterX = GetUnitX(tempUnit) hunterY = GetUnitY(tempUnit) // Cache closest enemy if one is in range. GroupEnumUnitsInRange(grp, GetUnitX(tempUnit), GetUnitY(tempUnit), d, null) loop iter = FirstOfGroup(grp) exitwhen iter == null if UnitAlive(iter) and IsUnitInRange(iter, tempUnit, d) and IsUnitEnemy(iter, owner) and IsUnitVisible(iter, owner) then selected = iter d = TerrainData.distanceBetweenXY(GetUnitX(iter), hunterX, GetUnitY(iter), hunterY) end GroupRemoveUnit(grp, iter) end // Fight if in range. if selected != null then if IsUnitInRange(tempUnit, selected, 900.) then tempUnit.issuePointOrder("channel", vec2(GetUnitX(selected), GetUnitY(selected))) else IssuePointOrder(tempUnit, "move", GetUnitX(selected), GetUnitY(selected)) end else // Proceed; no enemy is available. if tempUnit == hunterLeader then // I am the leader. // Check how far away buddies are. GroupEnumUnitsInRange(grp, GetUnitX(tempUnit), GetUnitY(tempUnit), 1400., null) d = 0. loop iter = FirstOfGroup(grp) exitwhen iter == null tempD = TerrainData.distanceBetweenXY(GetUnitX(iter), hunterX, GetUnitY(iter), hunterY) if UnitAlive(iter) and iter.getOwner().getId() < Players.FIRST_BEAST and tempD > d then d = tempD end GroupRemoveUnit(grp, iter) end if d > 1000. then // Buddies are a bit slow, wait. IssueImmediateOrder(tempUnit, "stop") SetUnitFacingTimed(tempUnit, GetUnitFacing(tempUnit) - 90., 1.25) else // Proceed depending on objective. ExecuteFunc("proceedObjective") end else // I am supportive. ang = TerrainData.radiansBetweenXY(hunterX, GetUnitX(hunterLeader), hunterY, GetUnitY(hunterLeader)) + bj_PI IssuePointOrder(tempUnit, "move", GetUnitX(hunterLeader) + 200. * Cos(ang), GetUnitY(hunterLeader) + 200. * Sin(ang)) end end end function parseBeast takes nothing returns nothing unit iter real d = 99999999. unit selected = null real safeX real safeY real ang GroupEnumUnitsInRect(grp, bj_mapInitialPlayableArea, null) loop iter = FirstOfGroup(grp) exitwhen iter == null if IsUnitEnemy(iter, tempPlayer) and UnitAlive(iter) and IsUnitInRangeXY(iter, GetUnitX(tempUnit), GetUnitY(tempUnit), d) and GetUnitAbilityLevel(iter, 'Binv') < 1 then selected = iter d = TerrainData.distanceBetweenXY(GetUnitX(iter), GetUnitX(tempUnit), GetUnitY(iter), GetUnitY(tempUnit)) end GroupRemoveUnit(grp, iter) end if Units.beastState.loadInt(tempUnit.getHandleId()) == STATE_AGGRESSIVE then // Aggressive. // Change state if low. if GetWidgetLife(tempUnit) < GetUnitState(tempUnit, UNIT_STATE_MAX_LIFE) * .5 then Units.beastState.saveInt(tempUnit.getHandleId(), STATE_RETREAT) end // Become passive if fighting without lightning. if Units.countBalls(tempUnit) < 2 and IsUnitInRange(tempUnit, selected, 1200.) then Units.beastState.saveInt(tempUnit.getHandleId(), STATE_RETREAT) end // Go fight. if selected != null and GetUnitCurrentOrder(tempUnit) != OrderId("attack") then // Try lightning. if IsUnitInRange(tempUnit, selected, 140.) and (Statistics.secondsElapsed - Units.thunderCd.loadInt(tempUnit.getHandleId()) > THUNDER_CD) then IssueImmediateOrder(tempUnit, "berserk") Units.thunderCd.saveInt(tempUnit.getHandleId(), Statistics.secondsElapsed) else IssueTargetOrder(tempUnit, "attack", selected) end end else // Retreat. // Change state if high. if GetWidgetLife(tempUnit) > GetUnitState(tempUnit, UNIT_STATE_MAX_LIFE) * .8 then Units.beastState.saveInt(tempUnit.getHandleId(), STATE_AGGRESSIVE) end // Run away. if GetItemX(Units.artifact) > 0. and GetItemY(Units.artifact) < 0. then // Defend base area. safeX = 3350. safeY = -3700. else // Defend castle area. safeX = -2150. safeY = 3180. end if selected != null and IsUnitInRange(tempUnit, selected, 1200.) then ang = TerrainData.radiansBetweenXY(GetUnitX(selected), safeX, GetUnitY(selected), safeY) IssuePointOrder(tempUnit, "move", GetUnitX(selected) + 1400. * Cos(ang), GetUnitY(selected) + 1400. * Sin(ang)) else SetUnitFacingTimed(tempUnit, GetUnitFacing(tempUnit) + 90., 1.) end end end function parsePlayer takes nothing returns nothing unit hero = Units.hero[GetPlayerId(tempPlayer)] tempUnit = hero if not UnitAlive(hero) then return end if GetUnitTypeId(hero) == BEAST_ID then ExecuteFunc("parseBeast") else ExecuteFunc("parseHunter") end end public struct AI static constant real CLOCK_PERIOD = 1./2. static player array players static integer count = -1 static timer clock = CreateTimer() static method periodic takes nothing returns nothing integer index = 0 loop exitwhen index > count tempPlayer = players[index] ExecuteFunc("parsePlayer") index++ end end static method resetState takes nothing returns nothing integer index = Players.FIRST_BEAST // Elect new hunter leader. hunterElection() randomizeSitePreferences() loop exitwhen index > Players.LAST_BEAST // Reset beast states to aggressive. Units.beastState.saveInt(Units.hero[index].getHandleId(), STATE_AGGRESSIVE) // Reset thunder cooldown. Units.thunderCd.saveInt(Units.hero[index].getHandleId(), 0) index++ end end static method addPlayer takes player p returns nothing count++ players[count] = p if count == 0 then TimerStart(clock, CLOCK_PERIOD, true, function periodic) end end end endlibrary