//--------------------------------------------------------------------------------------------------
// Storm Tactical Hero AI
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Copies of constants to avoid include, must match original definitions
//--------------------------------------------------------------------------------------------------

const string c_Storm_AB_Attack = "attack";
const string c_Storm_AB_Move = "move";

//--------------------------------------------------------------------------------------------------
// Other constants 
//--------------------------------------------------------------------------------------------------

const string c_AbathurUltimateEvolutionControlBehavior    = "AbathurUltimateEvolutionControlBehavior";

unit[libCore_gv_bALMaxPlayers] abathurSymbioteUnit;
unit[libCore_gv_bALMaxPlayers] abathurUltimateEvolutionUnit;

//--------------------------------------------------------------------------------------------------
//  Tactical Helper Functions
//--------------------------------------------------------------------------------------------------
order StormHeroAICreateOrder (int player, string abilLink, int abilIndex) {
    abilcmd cmd = AbilityCommand(abilLink, abilIndex);
    order ord;
    if (cmd == c_nullCmd) {
        return null;
    }
    ord = Order(cmd);
    OrderSetPlayer(ord, player);
    return ord;
}

//---------------------------------------------------------------------------------------------
bool HeroIssueOrder (int player, unit aiUnit, order spellOrd, order attackOrd, int addToFront, unit targetUnit, point pos) {
    // Make AI miss skillshots
    point fudgedTargetPos;
    if (pos != null) {
        fudgedTargetPos = libAIAI_gf_HeroAIFudgeTargetPosition(player, pos);
    }

    // Set the target of the spell order
    if (fudgedTargetPos != null) {
        OrderSetTargetPoint(spellOrd, fudgedTargetPos);
        if (!UnitOrderIsValid(aiUnit, spellOrd)) {
            //TriggerDebugOutput(1, StringToText("HeroIssueOrder Invalid #1?"), true);
            return false;
        }
    } 
    else if (targetUnit != null) {
        OrderSetTargetUnit(spellOrd, targetUnit);
        if (!UnitOrderIsValid(aiUnit, spellOrd)) {
            //TriggerDebugOutput(1, StringToText("HeroIssueOrder Invalid #2?"), true);
            return false;
        }
    }
    
    // Set the target of the attack order
    if (attackOrd != null) {
        if (targetUnit != null) {
            OrderSetTargetUnit(attackOrd, targetUnit);
        }
        else if (fudgedTargetPos != null) {
            OrderSetTargetPoint(attackOrd, fudgedTargetPos);
        }

        if (UnitOrderIsValid(aiUnit, attackOrd)) {
            UnitIssueOrder(aiUnit, attackOrd, c_orderQueueReplace);
            addToFront = c_orderQueueAddToFront;
        }
        else  {
            //TriggerDebugOutput(1, StringToText("HeroIssueOrder Invalid #3?"), true);
        }
    }
     
    // Cast the ability
    libAIAI_gf_HeroAIUpdateNextSpellCheckTime(player);
    UnitIssueOrder(aiUnit, spellOrd, addToFront);
    return true;
}

//---------------------------------------------------------------------------------------------
point MovePoint (point p, fixed dist, fixed angle) {
    return Point(PointGetX(p) + dist * Cos(angle), PointGetY(p) + dist * Sin(angle));
}

//---------------------------------------------------------------------------------------------
point TacticalNormalize (point vec) {
    fixed length;
    
    length = SquareRoot(PointGetX(vec) * PointGetX(vec) + PointGetY(vec) * PointGetY(vec));
    
    return Point(PointGetX(vec) / length, PointGetY(vec) / length);
}

//---------------------------------------------------------------------------------------------
fixed GetAngleToTarget (unit hero, unit target) {
    point heroPos;
    point targetPos;
    point direction;
    point directionVec;
    fixed directionAngle;
    
    heroPos = UnitGetPosition(hero);
    targetPos = UnitGetPosition(target);
    direction = Point(PointGetX(targetPos) - PointGetX(heroPos), PointGetY(targetPos) - PointGetY(heroPos));
    directionVec = TacticalNormalize(direction);
    directionAngle = ATan2(PointGetY(directionVec), PointGetX(directionVec));

    return directionAngle;
}

//---------------------------------------------------------------------------------------------
bool LineSegmentCollide(point p1, point p2, point t, fixed r) {
    // Test if point t is inside a line segment from p1 to p2 that extends r in both directions normal to the segment axis (squared off capsule)
    fixed diffx;
    fixed diffy;
    fixed length;
    point axis;
    point line;
    fixed projMag;
    point orth;
    fixed orthMagSquare;

    // Get the unit line segment axis
    diffx =  PointGetX(p2) - PointGetX(p1);
    diffy = PointGetY(p2) - PointGetY(p1);
    length = SquareRoot(diffx * diffx + diffy * diffy);
    
    if (length == 0.0) {
        return false;
    }
    
    axis = Point(diffx / length, diffy / length);

    // Project point we are testing onto the axis 
    line = Point(PointGetX(t) - PointGetX(p1), PointGetY(t) - PointGetY(p1));
    projMag = PointGetX(axis) * PointGetX(line) + PointGetY(axis) * PointGetY(line);  
     
    // The projection of the point onto the line segment axis is not on the line segment,
    // so it does not collide with the line segment
    if (projMag < 0.0 || projMag > length) {
        return false;
    }
    
    // Check if it is within the radius of the line
    orth = Point(PointGetX(line) - PointGetX(axis) * projMag, PointGetY(line) - PointGetY(axis) * projMag);
    orthMagSquare = PointGetX(orth) * PointGetX(orth) + PointGetY(orth) * PointGetY(orth);
    
    // The test point is further than r away from the line segment, so it does not collide with it
    if (orthMagSquare > r * r) {
        return false;
    }
    
    return true;
}

//---------------------------------------------------------------------------------------------
bool HaveBeenAttackedRecently (unit aiUnit) {
    if (UnitTestState(aiUnit, c_unitStateHallucination)) {
        return AILastAttack(aiUnit) >= (AIGetTime() - 3.0);
    }
    else {
        return libAIAI_gf_HeroAIHeroHasBeenAttackedRecently(UnitGetOwner(aiUnit), 3.0);    
    }
}

//---------------------------------------------------------------------------------------------
unitgroup UnitGroupFilterAttribute (unitgroup group, int attribute) {
    unitfilter filter = UnitFilter(
        (1 << attribute),                                                                                              
        0,
        0,
        (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32))
    );
    
    return UnitGroupFilter(null, c_playerAny, group, filter, 0);
}
//---------------------------------------------------------------------------------------------
unitgroup UnitGroupFilterHeroes (unitgroup group) {
    return UnitGroupFilterAttribute(group, c_targetFilterHeroic);
}

//---------------------------------------------------------------------------------------------
unitgroup UnitGroupFilterMinions (int player, unitgroup group) {
    unitgroup result = UnitGroupEmpty();
    
    // Lane Minions
    UnitGroupAddUnitGroup(result, UnitGroupFilterAttribute(group, c_targetFilterBiological));   
    
    // Camp Minions
    if (libAIAI_gf_HeroAIShouldTacticalAITargetCreeps(player)) {
        UnitGroupAddUnitGroup(result, UnitGroupFilterAttribute(group, c_targetFilterLight));        
    }
    
    return result;
}

//---------------------------------------------------------------------------------------------
unitgroup UnitGroupFilterHeroOrMinion (int player, unitgroup group) {
    unitgroup result = UnitGroupEmpty();
    
    UnitGroupAddUnitGroup(result, UnitGroupFilterHeroes(group));
    UnitGroupAddUnitGroup(result, UnitGroupFilterMinions(player, group));

    return result;
}

//---------------------------------------------------------------------------------------------
unitgroup UnitGroupFilterTowers (unitgroup group) {
    return UnitGroupFilterAttribute(group, c_targetFilterArmored);
}

//---------------------------------------------------------------------------------------------
const int c_towerNearbyDist = 12;
unit GetNearbyTower (unit aiUnit, unitgroup group) {
    unit unitToCheck;
    int groupCount = UnitGroupCount(group, c_unitCountAll);
    
    while (groupCount > 0) {
        unitToCheck = UnitGroupUnit(group,groupCount);
        groupCount = groupCount - 1;
        if (UnitTypeTestAttribute(UnitGetType(unitToCheck), c_unitAttributeArmored) == true) {
            if (PointsInRange (UnitGetPosition(aiUnit), UnitGetPosition(unitToCheck), c_towerNearbyDist) ) {
                return unitToCheck;
            }
        }
    }

    return null;
}

//---------------------------------------------------------------------------------------------
bool NearbyTower (unit aiUnit, unitgroup group) {
    unit nearbyTower = GetNearbyTower(aiUnit, group);

    if (UnitIsValid(nearbyTower)) {
        return true;
    }

    return false;
}

//---------------------------------------------------------------------------------------------
unit FindClosestWeakUnit (unitgroup testGroup, unitgroup scanGroup, unit aiUnit, fixed minDist, fixed maxDist, fixed maxHealthPercent, fixed maxHealth, fixed minMissingHealth, bool clearShot) {
    point unitPosition;
    int scanGroupCount;
    int unitCount;
    unit nearestUnit;
    fixed unitHealth;
    fixed nearestHeroDistance;
    
    point inFrontofUnit;
    region inFrontRegion;
    unitgroup unitsInTheWayGroup;
    unitgroup minionsInTheWayGroup;
    unit unitToCheck;
    point unitToCheckPosition;
    fixed unitToCheckDistance;
    
    int clearShotCount;
    unit clearShotUnitToCheck;
    bool blocked;

    unit resultUnit;
    fixed resultDistance = 999.0;

    unitPosition = UnitGetPosition(aiUnit);        
    scanGroupCount = UnitGroupCount(scanGroup, c_unitCountAll); 
    unitCount = UnitGroupCount(testGroup, c_unitCountAll);
    
    // check all the units in the area
    while (unitCount > 0) {
        unitToCheck = UnitGroupUnit(testGroup,unitCount);
        unitCount = unitCount - 1;
        
        // make sure unit is not invalid
        if (!UnitIsValid(unitToCheck)) {
            continue;
        }
        
        // make sure it is under the maximum health percent constraint
        if (UnitGetPropertyFixed(unitToCheck, c_unitPropLifePercent, c_unitPropCurrent) > maxHealthPercent) {
            continue;
        }
        
        unitHealth = UnitGetPropertyFixed(unitToCheck, c_unitPropLife, c_unitPropCurrent);
        
        // make sure it is under the maximum health constraint if specified
        if (maxHealth > 0 && unitHealth > maxHealth) {
            continue;
        }   
        
        // make sure it is under the minimum missing health constraint if specified
        if (minMissingHealth > 0 && UnitGetPropertyFixed(unitToCheck, c_unitPropLifeMax, c_unitPropCurrent) - unitHealth < minMissingHealth) {
            continue;
        }  
        unitToCheckPosition = UnitGetPosition(unitToCheck);
        unitToCheckDistance = DistanceBetweenPoints(unitPosition, unitToCheckPosition);
    
        // make sure it is within the distance constraint
        if (unitToCheckDistance > maxDist || unitToCheckDistance < minDist) {
            continue;
        }
        
        // make sure it there is a clear shot between the units if requested
        // replace with something better later
        if (clearShot && libAIAI_gv_aIHeroLineUpClearShotForSkillShots[libAIAI_gv_aIHeroes[UnitGetOwner(aiUnit)].lv_difficulty]) {
            
            // check if any of the units in the scan group intersect the line segment connecting the two units with a width of 1.0
            clearShotCount = scanGroupCount;
            blocked = false;
            // check all the units in the area
            while (clearShotCount > 0) {
                clearShotUnitToCheck = UnitGroupUnit(scanGroup,clearShotCount);
                clearShotCount = clearShotCount - 1;
                
                if (unitToCheck == clearShotUnitToCheck) { 
                    continue;
                }
                
                if (LineSegmentCollide(unitPosition, unitToCheckPosition, UnitGetPosition(clearShotUnitToCheck), 1.5)) {
                    blocked = true;
                    break;
                }
            }
        
            // there is something in the way, can't target this unit
            if (blocked) {
                continue;
            }
            
        }
        
        // make sure it is closer than the best found so far
        if (unitToCheckDistance > resultDistance) {
            continue;
        }

        resultUnit = unitToCheck;
        resultDistance = unitToCheckDistance;
    }
    
    return resultUnit;
}

//---------------------------------------------------------------------------------------------
unitgroup FilterBadTargets (unitgroup scanGroup, int player) {
    int unitCount;
    unit unitToCheck;
    fixed healthPercent;

    // Avoid targetting weak human heroes
    if (!libAIAI_gv_aIHeroCastAbilitiesOnWeakHumanHeroes[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        unitCount = UnitGroupCount(scanGroup, c_unitCountAll);    
        while (unitCount > 0) {
            unitToCheck = UnitGroupUnit(scanGroup, unitCount);
            unitCount = unitCount - 1;
            
            if (UnitTypeTestAttribute(UnitGetType(unitToCheck), c_unitAttributeHeroic) == false) {
                continue;
            }
            
            if (libAIAI_gf_HeroAIIsPlayerActive(UnitGetOwner(unitToCheck), libAIAI_gv_heroAIHumanPlayerType) == false) {
                continue;
            }
        
            healthPercent =  UnitGetPropertyFixed(unitToCheck, c_unitPropLifePercent, c_unitPropCurrent);
            if (healthPercent > 25.0) {
                continue;
            }

            UnitGroupRemove(scanGroup, unitToCheck);
        }
    }
    
    return scanGroup;
}

//---------------------------------------------------------------------------------------------
unit FindClosestWeakHero (unitgroup scanGroup, unit aiUnit, fixed minDist, fixed maxDist, fixed maxHealthPercent, fixed maxHealth, fixed minMissingHealth, bool clearShot) {
    unitgroup targets = FilterBadTargets(UnitGroupFilterHeroes(scanGroup), UnitGetOwner(aiUnit));
    return FindClosestWeakUnit (targets, scanGroup, aiUnit, minDist, maxDist, maxHealthPercent, maxHealth, minMissingHealth, clearShot);
}

//---------------------------------------------------------------------------------------------
unit FindClosestWeakMinion (unitgroup scanGroup, unit aiUnit, fixed minDist, fixed maxDist, fixed maxHealthPercent, fixed maxHealth, fixed minMissingHealth, bool clearShot) {
    return FindClosestWeakUnit (UnitGroupFilterMinions(UnitGetOwner(aiUnit), scanGroup), scanGroup, aiUnit, minDist, maxDist, maxHealthPercent, maxHealth, minMissingHealth, clearShot);
}

//---------------------------------------------------------------------------------------------
unit FindClosestWeakHeroOrMinion (unitgroup scanGroup, unit aiUnit, fixed minDist, fixed maxDist, fixed maxHealthPercent, fixed maxHealth, fixed minMissingHealth, bool clearShot) {
    unitgroup targets = FilterBadTargets(UnitGroupFilterHeroOrMinion(UnitGetOwner(aiUnit), scanGroup), UnitGetOwner(aiUnit));
    return FindClosestWeakUnit (targets, scanGroup, aiUnit, minDist, maxDist, maxHealthPercent, maxHealth, minMissingHealth, clearShot);
}

//---------------------------------------------------------------------------------------------
unit FindClosestWeakTower (unitgroup scanGroup, unit aiUnit, fixed minDist, fixed maxDist, fixed maxHealthPercent, fixed maxHealth, fixed minMissingHealth, bool clearShot) {
    return FindClosestWeakUnit (UnitGroupFilterTowers(scanGroup), scanGroup, aiUnit, minDist, maxDist, maxHealthPercent, maxHealth, minMissingHealth, clearShot);
}

//---------------------------------------------------------------------------------------------
unit FindStrongestNearbyAllyHero (int player, unit aiUnit, fixed maxDist, fixed minHealthPercent, bool excludeSelf) {
    // chosen by hitpoints, but at least a certain health percent 
    region r;
    unitgroup nearbyAllies;
    int allyCount;    
    unit unitToCheck;
    fixed unitHealth;
    fixed unitHealthPercent;
    unit resultUnit;
    fixed resultHealth = -1.0;

    // find nearby allied heroes
    r = RegionCircle(UnitGetPosition(aiUnit), maxDist);
    nearbyAllies = UnitGroupAlliance(player, c_unitAllianceAlly, r, null, c_noMaxCount);
    nearbyAllies = UnitGroupFilterHeroes(nearbyAllies);

    allyCount = UnitGroupCount(nearbyAllies, c_unitCountAll);

    // check all the ally heroes in the area
    while (allyCount > 0) {
        unitToCheck = UnitGroupUnit(nearbyAllies,allyCount);
        allyCount = allyCount - 1;

        if (excludeSelf && aiUnit == unitToCheck) {
            continue;
        }
        
        unitHealthPercent = UnitGetPropertyFixed(unitToCheck, c_unitPropLifePercent, c_unitPropCurrent);

        if (unitHealthPercent < minHealthPercent) {
            continue;
        }

        unitHealth = UnitGetPropertyFixed(unitToCheck, c_unitPropLife, c_unitPropCurrent);

        if (unitHealth > resultHealth) {
            resultUnit = unitToCheck;
            resultHealth = unitHealth;
        }
    }
    
    return resultUnit;
}

//---------------------------------------------------------------------------------------------
unit FindWeakestNearbyAllyHero (int player, unit aiUnit, fixed maxDist, fixed maxHealthPercent, fixed minMissingHealth, bool excludeSelf) {
    region r;
    unitgroup nearbyAllies;
    int allyCount;    
    unit unitToCheck;
    fixed unitHealthPercent;
    unit resultUnit;
    fixed resultHealthPercent = maxHealthPercent;

    // find nearby allied heroes
    r = RegionCircle(UnitGetPosition(aiUnit), maxDist);
    nearbyAllies = UnitGroupAlliance(player, c_unitAllianceAlly, r, null, c_noMaxCount);
    nearbyAllies = UnitGroupFilterHeroes(nearbyAllies);

    allyCount = UnitGroupCount(nearbyAllies, c_unitCountAll);

    // check all the ally heroes in the area
    while (allyCount > 0) {
        unitToCheck = UnitGroupUnit(nearbyAllies,allyCount);
        allyCount = allyCount - 1;

        if (excludeSelf && aiUnit == unitToCheck) {
            continue;
        }
        
        unitHealthPercent = UnitGetPropertyFixed(unitToCheck, c_unitPropLifePercent, c_unitPropCurrent);

        if (unitHealthPercent < resultHealthPercent) {
            resultUnit = unitToCheck;
            resultHealthPercent = unitHealthPercent;
        }
    }
    
    return resultUnit;
}

//---------------------------------------------------------------------------------------------
unit FindBestHealTarget (int player, unit aiUnit, fixed maxDist) {
    region r;
    unitgroup nearbyAllies;
    int allyCount;     
    unit unitToCheck;
    bool unitIsAI;
    fixed unitHealthPercent;
    unit bestUnit;
    bool bestUnitIsAI;
    fixed bestUnitHealthPercent;        
    
    // find nearby allied heroes
    r = RegionCircle(UnitGetPosition(aiUnit), maxDist);
    nearbyAllies = UnitGroupAlliance(player, c_unitAllianceAlly, r, null, c_noMaxCount);
    nearbyAllies = UnitGroupFilterHeroes(nearbyAllies);

    allyCount = UnitGroupCount(nearbyAllies, c_unitCountAll);

    // check all the ally heroes in the area
    while (allyCount > 0) {
        unitToCheck = UnitGroupUnit(nearbyAllies,allyCount);
        allyCount = allyCount - 1;
        
        unitHealthPercent = UnitGetPropertyFixed(unitToCheck, c_unitPropLifePercent, c_unitPropCurrent);

        if (unitHealthPercent < 50.0 || (HaveBeenAttackedRecently(unitToCheck) && unitHealthPercent < 70.0)) {
            unitIsAI = libAIAI_gf_HeroAIIsPlayerActive(UnitGetOwner(unitToCheck), libAIAI_gv_heroAIComputerPlayerType);
                        
            // Check if this is better than our current best target
            if (UnitIsValid(bestUnit)) {
                // Prioritize human heroes over AI
                if (!unitIsAI && bestUnitIsAI) {
                    if (unitHealthPercent > bestUnitHealthPercent + libAIAI_gv_aIHeroPrioritizeHealingHumanAlliesThreshold[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
                        continue;
                    }  
                }
                else {
                    if (unitHealthPercent > bestUnitHealthPercent) {
                        continue;
                    }
                }
            }
                        
            bestUnit = unitToCheck;
            bestUnitIsAI = unitIsAI;
            bestUnitHealthPercent = unitHealthPercent;
        }
    }
    
    return bestUnit;
}

//---------------------------------------------------------------------------------------------
unit FindWeakestNearbyAllyTowers (int player, unit aiUnit, fixed maxDist, fixed maxHealthPercent, fixed minMissingHealth) {
    region r;
    unitgroup nearbyAllies;
    int allyCount;    
    unit unitToCheck;
    fixed unitHealthPercent;
    unit resultUnit;
    fixed resultHealthPercent = maxHealthPercent;

    // find nearby allied towers
    r = RegionCircle(UnitGetPosition(aiUnit), maxDist);
    nearbyAllies = UnitGroupAlliance(player, c_unitAllianceAlly, r, null, c_noMaxCount);
    nearbyAllies = UnitGroupFilterTowers(nearbyAllies);

    allyCount = UnitGroupCount(nearbyAllies, c_unitCountAll);

    // check all the ally towers in the area
    while (allyCount > 0) {
        unitToCheck = UnitGroupUnit(nearbyAllies,allyCount);
        allyCount = allyCount - 1;
        
        unitHealthPercent = UnitGetPropertyFixed(unitToCheck, c_unitPropLifePercent, c_unitPropCurrent);

        if (unitHealthPercent < resultHealthPercent) {
            resultUnit = unitToCheck;
            resultHealthPercent = unitHealthPercent;
        }
    }
    
    return resultUnit;
}

//---------------------------------------------------------------------------------------------
unitgroup AllyUnitsInArea (int player, point center, fixed radius) {
    region r;
    
    r = RegionCircle(center, radius);
    return UnitGroupAlliance(player, c_unitAllianceAlly, r, null, c_noMaxCount);
}

//---------------------------------------------------------------------------------------------
unitgroup EnemyUnitsInArea (unitgroup scanGroup, point center, fixed radius) {
    region r;
    
    r = RegionCircle(center, radius);
    return UnitGroupFilterRegion(scanGroup, r, 0);
}

//---------------------------------------------------------------------------------------------
unit FindClosestAllyMinion (int player, point center, fixed maxDist) {
    unitgroup nearbyAllies;
    unitgroup nearbyAllyMinions;
    int allyMinionCount;    

    unit unitToCheck;
    point unitToCheckPosition;
    fixed unitToCheckDistance;

    unit resultUnit = null;
    fixed resultDistance = 999.0;
    
    nearbyAllies = AllyUnitsInArea(player, center, maxDist);
    nearbyAllyMinions = UnitGroupFilterMinions(player, nearbyAllies);
    allyMinionCount = UnitGroupCount(nearbyAllyMinions, c_unitCountAll);

    // check all the ally minions in the area
    while (allyMinionCount > 0) {
        unitToCheck = UnitGroupUnit(nearbyAllyMinions, allyMinionCount);
        allyMinionCount = allyMinionCount - 1;

        unitToCheckPosition = UnitGetPosition(unitToCheck);
        unitToCheckDistance = DistanceBetweenPoints(center, unitToCheckPosition);
    
        if (unitToCheckDistance < resultDistance) {
            resultUnit = unitToCheck;
            resultDistance = unitToCheckDistance;
        }
    }
    
    return resultUnit;
}

//---------------------------------------------------------------------------------------------
int CountHeroesInGroup (unitgroup group) {
    unitgroup enemyHeroes;   
    enemyHeroes = UnitGroupFilterHeroes(group);
    return UnitGroupCount(enemyHeroes, c_unitCountAll);  
}

//---------------------------------------------------------------------------------------------
int CountMinionsInGroup (int player, unitgroup group) {
    unitgroup enemyHeroes;    
    enemyHeroes = UnitGroupFilterMinions(player, group);   
    return UnitGroupCount(enemyHeroes, c_unitCountAll);  
}

//---------------------------------------------------------------------------------------------
int CountEnemiesInArea (int player, unitgroup scanGroup, point center, fixed radius, int heroValue, int minionValue) {
    unitgroup enemies;
    int heroes;
    int minions;
    
    enemies = EnemyUnitsInArea(scanGroup, center, radius);
    heroes = CountHeroesInGroup(enemies);
    minions = CountMinionsInGroup(player, enemies);
    
    return heroValue * heroes + minionValue * minions;
}

//---------------------------------------------------------------------------------------------
int CountAlliesInArea (int player, point center, fixed radius, int heroValue, int minionValue) {
    unitgroup allies;
    int heroes;
    int minions;
    
    allies = AllyUnitsInArea(player, center, radius);
    heroes = CountHeroesInGroup(allies);
    minions = CountMinionsInGroup(player, allies);

    return heroValue * heroes + minionValue * minions;
}

//---------------------------------------------------------------------------------------------
bool TowardFarSideOfTheMap (int player, unit aiUnit) {
    point currentPos = UnitGetPosition(aiUnit);

    if (libCore_gf_TestLocationClosertoFactionSide(currentPos, libGame_gv_players[player].lv_faction, 0.8)) {
        return false;
    }
    return true;
}

//---------------------------------------------------------------------------------------------
const string c_AB_UseItem = "StormInventory";
const int c_ABIdx_UseItemInstant = 3;
const int c_ABIdx_UseItemTarget = 4;
const int c_ABIdx_SellItem = 6;

//---------------------------------------------------------------------------------------------
bool UseDefensiveItem (int player, unit aiUnit, unitgroup scanGroup) {
    unit invItem;
    string itemType;
    int itemIdx = 1;
    int towerIdx = 0;
    int tryIdx = 0;
    fixed curHealth;
    fixed targetHealth;
    point pos;
    unit target;
    order ord;
    order ordTarget;
    int i;

    if (UnitTestState(aiUnit, c_unitStateHallucination)) {
        return false;
    }
    
    ord = StormHeroAICreateOrder(player, c_AB_UseItem, c_ABIdx_UseItemInstant);
    ordTarget = StormHeroAICreateOrder(player, c_AB_UseItem, c_ABIdx_UseItemTarget);
    
    while (itemIdx <= libCore_gv_bALPlayerActiveInventorySlots) {
        invItem = UnitInventoryItem(aiUnit, itemIdx);        
        itemIdx = itemIdx + 1;

        if (!UnitIsValid(invItem)) {
            continue;
        }

        OrderSetTargetItem(ord, invItem);
        OrderSetTargetItem(ordTarget, invItem);
        if (!UnitOrderIsValid(aiUnit, ord) && !UnitOrderIsValid(aiUnit, ordTarget)) {
            continue;
        }

        itemType = UnitGetType(invItem);           
        
        if (itemType == "TalentFirstAid") {
            // Self heal
            if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 50.0) {
                UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
                return true;
            }
        }
        else if (itemType == "TalentBucketIceBlock") {
            // Become invulnerable if you become very low health during a team fight
            if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 20.0 && CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 9.0, 1, 0) >= 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 9.0, 1, 0) >= 2) {
                UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
            }        
        }
        else if (itemType == "TalentStoneskin" || itemType == "TalentBucketStormShield") {
            // Place a temporary shield on self
            if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 30.0) {
                UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
                return true;
            }
        }
        else if (itemType == "TalentBucketSprint") {
            // Temporarily increase movement speed
            if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
                UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
                return true;
            }
        }
        else if (itemType == "TalentBerserk") {
            // Temporarily increase movement and attack speed
            if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
                UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
                return true;
            }
        }
        else if (itemType == "TalentBloodForBlood") {
            // Steals percentage of enemies life
            if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > 60.0) {
                continue;
            }
           
            target = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, 100.0, -1, -1, false);
            if (!UnitIsValid(target)) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, target, null);
        }
        else if (itemType == "TalentBucketCalldownMule") {
            // Calls down a MULE to repair an ally tower
            target = FindWeakestNearbyAllyTowers(player, aiUnit, 12.0, 70.0, -1.0);
            if (!UnitIsValid(target)) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, target, null);            
        }
        else if (itemType == "FlashoftheStorms") {
            // Blink to target position
            
            if (!HaveBeenAttackedRecently(aiUnit) || UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
                continue;
            }   
            
            pos = libAIAI_gf_HeroAIGetRetreatPosition(UnitGetOwner(aiUnit), 9.0); 

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueReplace, null, pos);            
        }
        else if (itemType == "TalentBucketHealingWard") {
            // Place a healing ward
            target = FindWeakestNearbyAllyHero(player, aiUnit, 7.0, 50.0, -1.0, false);
            if (!UnitIsValid(target)) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueReplace, null, UnitGetPosition(target));            
        }
        else if (itemType == "ProtectiveShield") {
            // Places a temporary shield on an ally
            curHealth = UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent);

            // Find a weak ally, otherwise try to cast on self
            target = FindWeakestNearbyAllyHero(player, aiUnit, 7.0, curHealth - 10.0, -1.0, false);
            if (!UnitIsValid(target)) {
                target = aiUnit;
            }

            targetHealth = UnitGetPropertyFixed(target, c_unitPropLifePercent, c_unitPropCurrent);
            if (!HaveBeenAttackedRecently(target) && targetHealth > 70.0) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, target, null);
        }      
    }

    return false;
}

//---------------------------------------------------------------------------------------------
bool UseOffensiveItem (int player, unit aiUnit, unitgroup scanGroup) {
    unit invItem;
    string itemType;
    int itemIdx = 1;
    order ord;
    order ordTarget;
    order attackOrd;
    unit target;
    point pos;
    fixed energy;
    bool nearEnemyTower;
    int abilityIndex;
    int abilitiesOnCooldown;
    
    if (UnitTestState(aiUnit, c_unitStateHallucination)) {
        return false;
    }
    
    ord = StormHeroAICreateOrder(player, c_AB_UseItem, c_ABIdx_UseItemInstant);
    ordTarget = StormHeroAICreateOrder(player, c_AB_UseItem, c_ABIdx_UseItemTarget);
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    while (itemIdx <= libCore_gv_bALPlayerActiveInventorySlots) {
        invItem = UnitInventoryItem(aiUnit, itemIdx);        
        itemIdx = itemIdx + 1;

        if (!UnitIsValid(invItem)) {
            continue;
        }

        OrderSetTargetItem(ord, invItem);
        OrderSetTargetItem(ordTarget, invItem);
        if (!UnitOrderIsValid(aiUnit, ord) && !UnitOrderIsValid(aiUnit, ordTarget)) {
            continue;
        }

        itemType = UnitGetType(invItem);  

        if (itemType == "TalentOverdrive") {
            // Temporarily buff abilities if we are in a team fight
            if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
                continue;
            }
            
            if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2) {
                continue;
            }        

            UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
            return true;
        }
        else if (itemType == "SearingAttacks") {
            // Temporarily buff auto-attacks if we are in a team fight
            if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
                continue;
            }
            
            if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2) {
                continue;
            }        

            UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
            return true;
        }
        else if (itemType == "TalentRewind") {
            // Reduces the cooldown of the QWE abilities
            if (UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent) < 25.0) {
                continue;
            }

            abilitiesOnCooldown = 0;
            abilityIndex = 1;
            while (abilityIndex < 4) {
                if (libAIAI_gf_HeroAIIsAbilityOnCooldown(player, abilityIndex)) {
                    abilitiesOnCooldown = abilitiesOnCooldown + 1;
                }

                abilityIndex = abilityIndex + 1;
            }

            if (abilitiesOnCooldown < 2) {
                continue;
            } 

            UnitIssueOrder(aiUnit, ord, c_orderQueueAddToFront);
            return true;
        }
        else if (itemType == "TalentBucketShrinkRay") {
            // Reduce an enemy heroes damage if we are in a team fight
            if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2) {
                continue;
            }        

            target = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.5, 100.0, -1, -1, false);
            if (!UnitIsValid(target)) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, target, null);
        }
        else if (itemType == "TalentBucketPromote") {
            // Buff a ally minion
            target = FindClosestAllyMinion(UnitGetOwner(aiUnit), UnitGetPosition(aiUnit), 7.0);
            if (!UnitIsValid(target)) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, target, null);            
        }
        else if (itemType == "AbathurSwarmNest") {
            // Summons three locusts at target position
            pos = UnitGetPosition(aiUnit);
            
            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, null, pos);            
        }
        else if (itemType == "Envenom") {
            // Put a DoT on enemy hero
            target = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 5.0, 50.0, -1, -1, false);
            if (!UnitIsValid(target)) {
                continue;
            }

            return HeroIssueOrder (player, aiUnit, ordTarget, null, c_orderQueueAddToFront, target, null);
        }
    }

    return false;
}

//---------------------------------------------------------------------------------------------
bool RevealCloakedEnemies (int player, unit aiUnit, unitgroup scanGroup) {
    if (UnitTestState(aiUnit, c_unitStateHallucination)) {
        return false;
    }
    else {
        return libAIAI_gf_HeroAIRandomlyCastAbilityOnCloakedEnemy(player);
    }
}

//---------------------------------------------------------------------------------------------
bool HeroSkipTactical (int player, unit aiUnit) {
    // Check if this is a valid AI controlled hero
    if (libAIAI_gf_HeroAIIsAIEnabledForPlayer(player) == false) {
        return true;
    }

    // Special Hero
    if (UnitTestState(aiUnit, c_unitStateHallucination)) { 
        // Abathur coppied hero
        if (UnitHasBehavior(aiUnit, c_AbathurUltimateEvolutionControlBehavior)) {
            abathurUltimateEvolutionUnit[player - 1] = aiUnit;
            return false;
        }
        
        return true;
    }
    // Regular hero
    else {
        // No tactical if the AI in the triggers doesn't want us to
        if (libAIAI_gf_HeroAIShouldSkipTactical(player) == true) {
            return true;
        }
        
        // Only ignore spell casting delay for Defensive spells when there is a human ally nearby
        if (libAIAI_gf_HeroAIHumanAllyNearby(player) == false && libAIAI_gv_aIHeroes[player].lv_nextOffensiveSpellCheckTime > TimerGetElapsed(libGame_gv_gameTimer)) {
            return true;
        }
        
        if ((UnitOrder(aiUnit, 0) != null)) {
            // No tactical while hearthing
            if (OrderGetAbilityCommand(UnitOrder(aiUnit, 0)) == AbilityCommand("PortBackToBase", 0)) {
                return true;  
            }

            // No tactical while capturing a map mechanic
            if (OrderGetAbilityCommand(UnitOrder(aiUnit, 0)) == AbilityCommand("CaptureMacGuffin", 0)) {
                return true;  
            }
        }
        
        // No tactical driving vehicle
        if (UnitHasBehavior(aiUnit, "UsingVehicle") == true) {
            return true;
        }
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool HeroSkipOffensiveTactical (int player, unit aiUnit) {
    // No tactical if we have already used a spell recently
    if (libAIAI_gv_aIHeroes[player].lv_nextOffensiveSpellCheckTime > TimerGetElapsed(libGame_gv_gameTimer)) {
        return true;
    }

    if ((UnitOrder(aiUnit, 0) != null)) {
        // No tactical while mounting
        if (OrderGetAbilityCommand(UnitOrder(aiUnit, 0)) == AbilityCommand("Mount", 0)) {
            return true;  
        }
    }
    
    // No tactical while mounted
    if (UnitHasBehavior(aiUnit, "Mounted") == true) {
        return true; 
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
// Muradin Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_MuradinStormBolt     = "MuradinStormBolt";
const string c_AB_MuradinThunderClap   = "MuradinThunderclap";
const string c_AB_MuradinDwarfToss     = "MuradinDwarfToss";
const string c_AB_MuradinAvatar        = "MuradinAvatar";
const string c_AB_MuradinHaymaker      = "MuradinHaymaker";
const string c_AB_Stoneform          = "Stoneform";

//---------------------------------------------------------------------------------------------
bool MuradinAvatar (int player, unit aiUnit, unitgroup scanGroup) {
    // Gives Muradin addtional health for a period of time    
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_MuradinAvatar, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // If our health is low and we've been damaged recently use Avatar to try to save us
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > 50.0) {
        return false;
    }

    if (!HaveBeenAttackedRecently(aiUnit)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool MuradinStormBolt (int player, unit aiUnit, unitgroup scanGroup) {
    // Skill-shot that stuns & damages the target
    order spellOrd;
    order attackOrd;
    unit targetHero;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_MuradinStormBolt, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Use on a weak enemy hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.5, healthReq, -1, -1, true);    
    if (!UnitIsValid(targetHero)) {        
        // If we have nearly full energy, we're allowed to target minions
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetHero = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 6.5, 100.0, -1, -1, false);
        if (!UnitIsValid(targetHero)) {
            return false;
        }
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
bool MuradinDwarfToss (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Medium-Range Jump that damages enemies near the landing location

    // Two ways we might cast it:
    //      If we're in danger jump away to survive
    //      If there is a clump of enemies who are safe to attack, jump on top of them
    order spellOrd;
    point retreatPos;
    point center;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_MuradinDwarfToss, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Jump towards your spawn for safety if you are low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        // Find position that is 6 units from current position towards the base
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 6); 

        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }
    
    // Don't be offensive if we're near a tower
    if (nearEnemyTower) {
        return false;
    }

    // Don't jump into a battle with low health, you are under attack or it is not safe
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) {
        return false;
    }

    if (HaveBeenAttackedRecently(aiUnit)) {
        return false;
    }

    if (!libAIAI_gf_HeroAIIsSafe(player)) {
        return false;
    }

    // Find a cluster of enemies
    center = UnitGroupCenterOfGroup(scanGroup);
    if (CountEnemiesInArea(player, scanGroup, center, 2.0, 1, 1) < 4) {
        return false;
    }  
    
    // Jump on the cluster of enemies if in range
    if (DistanceBetweenPoints(UnitGetPosition(aiUnit), center) > 5.0) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, center);
}

//---------------------------------------------------------------------------------------------
bool MuradinThunderClap (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE that damages & slows enemies. Moves with Muradin.
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_MuradinThunderClap, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast if there is a weak hero
    nearbyHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 2.0, 40.0, -1, -1, false);   
    if (UnitIsValid(nearbyHero)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }

    // Cast if there are many enemies in the area
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 2;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }

    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 3.0, 1, 1) < minEnemyEval) {
        return false;
    }  

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool MuradinHaymaker (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages and knocks back target enemy
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_MuradinHaymaker, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
     // Use if there is a weak enemy nearby
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 3.0, 40.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool Stoneform (int player, unit aiUnit, unitgroup scanGroup) {
    // Cast heal over time on self
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_Stoneform, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > 50.0) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkMuradin (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (Stoneform(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && MuradinAvatar(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit, scanGroup);

    if (MuradinDwarfToss(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (MuradinStormBolt(player, aiUnit, scanGroup)) {
        return;
    }

    if (MuradinThunderClap(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && MuradinHaymaker(player, aiUnit, scanGroup)) {
        return;
    }

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }
    
    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Stitches Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_StitchesHook              = "StitchesHook";
const string c_AB_StitchesSlam              = "StitchesSlam";
const string c_AB_StitchesDevour            = "StitchesDevour";
const string c_AB_StitchesPutridBile        = "StitchesPutridBile";
const string c_AB_StitchesGorge             = "StitchesGorge";

//---------------------------------------------------------------------------------------------
bool StitchesDevour (int player, unit aiUnit, unitgroup scanGroup) {
    // Single target spell that damages enemy and heals Stitches
    order spellOrd;
    unit targetEnemy;    
       
    spellOrd = StormHeroAICreateOrder(player, c_AB_StitchesDevour, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Use on a nearby enemy if low health to heal
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > 80.0) {
        return false;
    }
    
    targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 5.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool StitchesHook (int player, unit aiUnit, unitgroup scanGroup) {
    // skill-shot that hooks the target to Stitches
    order spellOrd;
    order attackOrd;
    unit targetHero;    
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_StitchesHook, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
     
    // grab nearest enemy if they are low health
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 3.0, 8.0, 60.0, -1, -1, true);
    if (!UnitIsValid(targetHero)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
bool StitchesSlam (int player, unit aiUnit, unitgroup scanGroup) {
    // Cone damage spell in front of Stitches
    order spellOrd;
    unit targetHero;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    point inFrontofHero;
    int minEnemyEval;

    spellOrd = StormHeroAICreateOrder(player, c_AB_StitchesSlam, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
  
    // Cast on a weak enemy hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 4.0, 40.0, -1, -1, false);   
    if (UnitIsValid(targetHero)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetHero));
    }

    // Cast on a group of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 2;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 8;
    }

    inFrontofHero = MovePoint(UnitGetPosition(aiUnit), 2.5, UnitGetFacing(aiUnit));
    if (CountEnemiesInArea(player, scanGroup, inFrontofHero, 2.5, 2, 1) < minEnemyEval) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, inFrontofHero);
}

//---------------------------------------------------------------------------------------------
bool StitchesPutridBile (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE that damages & slows enemies. Moves with Stitches.
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval = 0;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_StitchesPutridBile, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast to slow enemies chasing you while retreating
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
    
    // Cast if there are many enemies around
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }

    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 3, 1, 1) < minEnemyEval) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool StitchesGorge (int player, unit aiUnit, unitgroup scanGroup) {
    // Eats an enemy and spits them out a few seconds later
    order spellOrd;
    unit targetHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_StitchesGorge, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Only use during a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2) {
        return false;
    }

    // Eat the cloeset enemy hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 4.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkStitches (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    if (StitchesDevour(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (StitchesHook(player, aiUnit, scanGroup)) {
        return; 
    }

    if (StitchesSlam(player, aiUnit, scanGroup)) {
        return;
    }  

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit, scanGroup);

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }
  
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && StitchesPutridBile(player, aiUnit, scanGroup)) {
        return;
    }   

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && StitchesGorge(player, aiUnit, scanGroup)) {
        return;
    }   

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}

//---------------------------------------------------------------------------------------------
// Nova Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_NovaSnipeStorm         = "NovaSnipeStorm";
const string c_AB_NovaPinningShot        = "NovaPinningShot";
const string c_AB_NovaHoloDecoy          = "NovaHoloDecoy";
const string c_AB_NovaTripleTap          = "NovaTripleTap";
const string c_AB_NovePrecisionStrike    = "NovaPrecisionStrike";

//---------------------------------------------------------------------------------------------
bool NovaSnipeStorm (int player, unit aiUnit, unitgroup scanGroup) {
    // Skill-shot, high damage single target
    order spellOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_NovaSnipeStorm, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Shoot any nearby enemy if there is a clear shot
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 10.0, healthReq, -1, -1, true);
    if (!UnitIsValid(targetEnemy)) {
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 10.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool NovaPinningShot (int player, unit aiUnit, unitgroup scanGroup) {
    // Damage and slow enemy target  
    order spellOrd;
    order attackOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
  
    spellOrd = StormHeroAICreateOrder(player, c_AB_NovaPinningShot, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false; 
    }
    
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Use if there is a weak enemy nearby and attack them
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }
  
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool NovaHoloDecoy (int player, unit aiUnit, unitgroup scanGroup) {
    // Creates a copy of Nova at nearby position to fool enemies
    order ord;

    ord = StormHeroAICreateOrder(player, c_AB_NovaHoloDecoy, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Use when low health to run away safely
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, UnitGetPosition(aiUnit));
}

//---------------------------------------------------------------------------------------------
bool NovaTripleTap (int player, unit aiUnit, unitgroup scanGroup) {
    // Shoots three shots at target enemy after a short delay
    order spellOrd;
    order attackOrd;
    unit targetHero;
 
    spellOrd = StormHeroAICreateOrder(player, c_AB_NovaTripleTap, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Use if there is a weak enemy nearby and attack them
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 12.0, 60.0, -1, -1, true);
    if (!UnitIsValid(targetHero)) {
        return false;
    }
  
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, null); 
}

//---------------------------------------------------------------------------------------------
bool NovaPrecisionStrike (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals aoe damage/slows enemies and has unlimited range
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_NovePrecisionStrike, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
     
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 500.0, 70.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    // Only cast if it will hit many enemies
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetHero), 5.0, 4, 1) < 8) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkNova (int player, unit aiUnit, unitgroup scanGroup) {    
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (NovaHoloDecoy(player, aiUnit, scanGroup)) {
        return;
    }

    if (NovaPinningShot(player, aiUnit, scanGroup)) {
        return;
    }
       
    if (NovaSnipeStorm(player, aiUnit, scanGroup)) {
        return;    
    }
 
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {   
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && NovaTripleTap(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && NovaPrecisionStrike(player, aiUnit, scanGroup)) {
        return;
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}

//---------------------------------------------------------------------------------------------
// Nova HoloClone Tactical
//---------------------------------------------------------------------------------------------
const string c_HoloDecoyNoMastery1Disabler      = "HoloDecoyNoMastery1Disabler";

//---------------------------------------------------------------------------------------------
bool NovaHoloCloneSnipe (int player, unit aiUnit, unitgroup scanGroup) {
    // Skill-shot, high damage single target
    order spellOrd;
    unitgroup enemyGroup;
    unitfilter filter;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_NovaSnipeStorm, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    enemyGroup = UnitGroupFilterAttribute(scanGroup, c_targetFilterHeroic);  // first consider heroes
    targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
    if (!UnitIsValid(targetEnemy)) {        
        filter = UnitFilter(
            (1 << c_unitAttributeArmored),                   // next towers
            0,
            0,
            (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32)) | (1 << (c_targetFilterPassive - 32)) | (1 << (c_targetFilterBenign - 32))
        );
        enemyGroup = UnitGroupFilter(null, c_playerAny, scanGroup, filter, 0);
        targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
        if (!UnitIsValid(targetEnemy)) {
            filter = UnitFilter(
                (1 << c_targetFilterLight),                  // next jungle creeps                                                                                          
                0,
                0,
                (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32)) | (1 << (c_targetFilterPassive - 32)) | (1 << (c_targetFilterBenign - 32))
            );
            enemyGroup = UnitGroupFilter(null, c_playerAny, scanGroup, filter, 0);
            targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
            if (!UnitIsValid(targetEnemy)) {
                filter = UnitFilter(
                    (1 << c_targetFilterBiological),         // last minions                                                                                    
                    0,
                    0,
                    (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32)) | (1 << (c_targetFilterPassive - 32)) | (1 << (c_targetFilterBenign - 32))
                );
                enemyGroup = UnitGroupFilter(null, c_playerAny, scanGroup, filter, 0);
                targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
                if (!UnitIsValid(targetEnemy)) {
                    return false;
                }
            }
        }
    }
        
    OrderSetTargetPoint(spellOrd, UnitGetPosition(targetEnemy));
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    UnitIssueOrder(aiUnit, spellOrd, c_orderQueueReplace);
    return true;
}

//---------------------------------------------------------------------------------------------
bool NovaHoloClonePinningShot (int player, unit aiUnit, unitgroup scanGroup) {
    // Damage and slow enemy target  
    order spellOrd;
    unitgroup enemyGroup;
    unitfilter filter;
    unit targetEnemy;
  
    spellOrd = StormHeroAICreateOrder(player, c_AB_NovaPinningShot, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    enemyGroup = UnitGroupFilterAttribute(scanGroup, c_targetFilterHeroic);  // first consider heroes
    targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
    if (!UnitIsValid(targetEnemy)) {
        filter = UnitFilter(
            (1 << c_unitAttributeArmored),                   // next towers
            0,
            0,
            (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32)) | (1 << (c_targetFilterPassive - 32)) | (1 << (c_targetFilterBenign - 32))
        );
        enemyGroup = UnitGroupFilter(null, c_playerAny, scanGroup, filter, 0);
        targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
        if (!UnitIsValid(targetEnemy)) {
            filter = UnitFilter(
                (1 << c_targetFilterLight),                  // next jungle creeps                                                                                          
                0,
                0,
                (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32)) | (1 << (c_targetFilterPassive - 32)) | (1 << (c_targetFilterBenign - 32))
            );
            enemyGroup = UnitGroupFilter(null, c_playerAny, scanGroup, filter, 0);
            targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
            if (!UnitIsValid(targetEnemy)) {
                filter = UnitFilter(
                    (1 << c_targetFilterBiological),         // last minions                                                                                    
                    0,
                    0,
                    (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32)) | (1 << (c_targetFilterInvulnerable - 32)) | (1 << (c_targetFilterPassive - 32)) | (1 << (c_targetFilterBenign - 32))
                );
                enemyGroup = UnitGroupFilter(null, c_playerAny, scanGroup, filter, 0);
                targetEnemy = UnitGroupClosestToPoint(enemyGroup, UnitGetPosition(aiUnit));
                if (!UnitIsValid(targetEnemy)) {
                    return false;
                }
            }
        }
    }
        
    OrderSetTargetUnit(spellOrd, targetEnemy);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    UnitIssueOrder(aiUnit, spellOrd, c_orderQueueReplace);
    return true;
}

//---------------------------------------------------------------------------------------------
void AIThinkNovaHoloClone (int player, unit aiUnit, unitgroup scanGroup) {    
    // Only cast abilities if Nova has the HoloDecoy talent
    if (UnitHasBehavior(aiUnit, c_HoloDecoyNoMastery1Disabler) == true) {
        return;
    }
   
    if (NovaHoloCloneSnipe(player, aiUnit, scanGroup)) {
        return;
    }

    if (NovaHoloClonePinningShot(player, aiUnit, scanGroup)) {
        return;
    }   
}

//---------------------------------------------------------------------------------------------
// Kerrigan Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_KerriganRavage                = "KerriganRavage";
const string c_AB_KerriganKerriganCrushingGrip  = "KerriganCrushingGrip";
const string c_AB_KerriganPrimalGrasp           = "KerriganPrimalGrasp";
const string c_AB_KerriganSummonUltralisk       = "KerriganSummonUltralisk";
const string c_AB_KerriganMaelstrom             = "KerriganMaelstrom";

//---------------------------------------------------------------------------------------------
bool KerriganRavage (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Moves leaps to target dealing damage to it
    order spellOrd;
    order attackOrd;
    point retreatPos;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_KerriganRavage, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Don't jump into battle if near a tower or have low health
    if (nearEnemyTower) {
        return false;
    }
	
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 4.5, 50.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool KerriganCrushingGrip (int player, unit aiUnit, unitgroup scanGroup) {
	// Damages and stuns all enemies in target area
    order spellOrd;
    unit targetHero;
    point targetPos;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_KerriganKerriganCrushingGrip, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Shoot any nearby hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, healthReq, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetPos = UnitGroupCenterOfGroup(scanGroup);
        if (CountEnemiesInArea(player, scanGroup, targetPos, 2.0, 0, 1) < 3) {
            return false;
        }
    }
    else {
        targetPos = UnitGetPosition(targetHero);
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
bool KerriganPrimalGrasp (int player, unit aiUnit, unitgroup scanGroup) {
    // Pulls in all enemies in front of Kerrigan
    order spellOrd;
    order attackOrd;
    unit targetHero;
    point targetPos;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed minEnemyEval;
    unitgroup nearbyEnemies;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_KerriganPrimalGrasp, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Prefer casting on heroes
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 5.0, 60.0, -1, -1, false);
    if (UnitIsValid(targetHero)) {
        attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
        if (!UnitOrderIsValid(aiUnit, attackOrd)) {
            return false;
        }

        return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
    }

    // Cast if there is a large number of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 2;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 7;
    }

    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 5.0);
    targetPos = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, nearbyEnemies, targetPos, 3.0, 3, 1) < minEnemyEval) {
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
bool KerriganSummonUltralisk (int player, unit aiUnit, unitgroup scanGroup) {
    // Summons a ultralisk to attack the target
    order spellOrd;
    order attackOrd;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_KerriganSummonUltralisk, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    // Find a target hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 9.0, 60.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool KerriganMaelstrom (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE that damages enemies and reduces damage on kerrigan. Moves with Kerrigan.

    // Two uses:
    //     If there are at least X nearby enemies
    //     Kerrigan is hurt and needs to try to live
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_KerriganMaelstrom, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // We're low health and under attack, cast this to reduce damage to try to live
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
    
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }
    
    // Only cast if there are many enemies in range
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 3.0, 2, 1) < minEnemyEval) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}


//---------------------------------------------------------------------------------------------
void AIThinkKerrigan (int player, unit aiUnit, unitgroup scanGroup) {    
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && KerriganMaelstrom(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup); 
    
    if (KerriganRavage(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (KerriganPrimalGrasp(player, aiUnit, scanGroup)) {
        return;
    }

    if (KerriganCrushingGrip(player, aiUnit, scanGroup)) {
        return;
    }    

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && KerriganSummonUltralisk(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}

//---------------------------------------------------------------------------------------------
// Zeratul Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_ZeratulCleave             = "ZeratulCleave";
const string c_AB_ZeratulSustainedAnomaly    = "ZeratulSustainedAnomaly";
const string c_AB_ZeratulBlinkStorm         = "ZeratulBlinkStorm";
const string c_AB_ZeratulVoidPrison         = "ZeratulVoidPrison";
const string c_AB_ZeratulShadowAssault      = "ZeratulShadowAssault";

//---------------------------------------------------------------------------------------------
bool ZeratulCleave (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE cleave, damages nearby enemies
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_ZeratulCleave, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Cast if there is a weak hero
    nearbyHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 3.0, 50.0, -1, -1, false);   
    if (UnitIsValid(nearbyHero)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
    
    // Cast if there is a large number of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 2;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 3;
    }
    else {
        minEnemyEval = 6;
    }

    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 3.0, 3, 1) < minEnemyEval) {
        return false;
    }  

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool ZeratulSustainedAnomaly (int player, unit aiUnit, unitgroup scanGroup) {
    // Skill-shot bomb that sticks to a target, after a delay it damages & slows the target
    order spellOrd;
    order attackOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_ZeratulSustainedAnomaly, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, true);
    if (!UnitIsValid(targetEnemy)) {  
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool ZeratulBlinkStorm (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Teleports zeratul a short distance and cloaks him for a brief period

    // Two uses:
    //   Run away to live
    //   Catch a fleeing enemy hero
    unit targetEnemy;
    point retreatPos;
    order spellOrd;
    order attackOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_ZeratulBlinkStorm, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Retreat if you have low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        // Blink back to retreat to safety
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 8);
       
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }

    // Don't jump into battle if near a tower or have low health
    if (nearEnemyTower) {
        return false;
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 2.0, 8.0, 50.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool ZeratulVoidPrison (int player, unit aiUnit, unitgroup scanGroup) {
    // creates a bubble that stuns all units inside, but also makes them invulnerable
    order spellOrd;
    order attackOrd;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_ZeratulVoidPrison, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    // find a weak enemy who is getting away
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 4.0, 8.0, 40.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool ZeratulShadowAssault (int player, unit aiUnit, unitgroup scanGroup) {
    // Temporary cloak
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_ZeratulShadowAssault, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Use when low health to run away safely
    if (!HaveBeenAttackedRecently(aiUnit) || UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkZeratul (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    
    if (ZeratulBlinkStorm(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && ZeratulShadowAssault(player, aiUnit, scanGroup)) {
        return;
    }

    if (ZeratulCleave(player, aiUnit, scanGroup)) {
        return;
    }

    if (ZeratulSustainedAnomaly(player, aiUnit, scanGroup)) {
        return;
    }

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && ZeratulVoidPrison(player, aiUnit, scanGroup)) {
            return;
        }
        
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Sgt Hammer Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_SgtHammerSiegeMode             = "SgtHammerSiegeMode";
const string c_AB_SgtHammerTankMode              = "SgtHammerTankMode";
const string c_AB_SgtHammerConcussiveBlast       = "SgtHammerConcussiveBlast";
const string c_AB_SgtHammerMineField             = "SgtHammerMineField";
const string c_AB_SgtHammerNapalmStrike          = "SgtHammerNapalmStrike";
const string c_AB_SgtHammerBluntForceGun         = "SgtHammerBluntForceGun";

//---------------------------------------------------------------------------------------------
bool SgtHammerSiegeMode (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // If Sgt Hammer is in tank mode, check if we should convert him to siege mode
    order spellOrd; 
    fixed range;

    // Don't be too offensive if we're near a tower
    if (nearEnemyTower) {
        return false;
    }

    spellOrd = StormHeroAICreateOrder(player, c_AB_SgtHammerSiegeMode, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // If we've been attacked recently or aren't near full health, then don't siege
    if (HaveBeenAttackedRecently(aiUnit)) {
        return false;
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 80.0) {
        return false;
    }
    
    if (!libAIAI_gf_HeroAIIsSafe(player)) {
        return false;
    }

    range = libAIAI_gf_AIGetActiveWeaponRange(aiUnit);
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), range, 3, 1) < 5) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);
 }

//---------------------------------------------------------------------------------------------
bool SgtHammerTankMode (int player, unit aiUnit, unitgroup scanGroup) {
    // If Sgt Hammer is in siege mode, check if we should convert him to tank mode
    order spellOrd;
    bool leaveSiege = false;
    fixed range;

    spellOrd = StormHeroAICreateOrder(player, c_AB_SgtHammerTankMode, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // If we're low health, leave seige mode
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 70.0) {
        leaveSiege = true;
    }
    // If it's not safe anymore, leave seige mode
    else if (!libAIAI_gf_HeroAIIsSafe(player)) {
        leaveSiege = true;
    }
    else {
        range = libAIAI_gf_AIGetActiveWeaponRange(aiUnit);
        if (range < 8) {
            range = 8;
        }

        // Leave siege if there is nothing left to attack
        if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), range, 1, 1) == 0) {
            leaveSiege = true;
        }
    }

    if (!leaveSiege) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);
}

//---------------------------------------------------------------------------------------------
bool SgtHammerConcussiveBlast (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // pushes back all enemies in a cone and deals damage
    order ord; 
    unit targetEnemy;
    fixed directionAngle;    
    point inFrontOfHero;
        
    // Don't be offensive if we're near a tower
    if (nearEnemyTower) {
        return false;
    }

    ord = StormHeroAICreateOrder(player, c_AB_SgtHammerConcussiveBlast, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) { 
        return false;
    }
    
    // Only push away units if they are attacking you
    if (!HaveBeenAttackedRecently(aiUnit)) {  
        return false;
    }

    // find a target enemy
    targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 6.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }

    // check how many enemies are in that direction
    directionAngle = GetAngleToTarget(aiUnit, targetEnemy);
    inFrontOfHero = MovePoint(UnitGetPosition(aiUnit), 3, directionAngle);
    
    if (CountEnemiesInArea(player, scanGroup, inFrontOfHero, 3.5, 4, 1) < 4) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, inFrontOfHero);
 }

//---------------------------------------------------------------------------------------------
bool SgtHammerMineField (int player, unit aiUnit, unitgroup scanGroup) {
    // Drops mines at a nearby location
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    unitgroup nearbyEnemies;
    unitgroup nearbyEnemyHeroes;
    unit targetEnemy;
    point center;
    int minEnemyEval;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_SgtHammerMineField, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 5.5, 60.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }
   
    // Center on heroes are any is nearby
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 5.5);
    nearbyEnemyHeroes = UnitGroupFilterHeroes(nearbyEnemies);
    
    if (UnitGroupCount(nearbyEnemyHeroes, c_unitCountAll) > 0) {
        center = UnitGroupCenterOfGroup(nearbyEnemyHeroes);
    }
    // Target minions instead
    else {
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 5.5, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        
        center = UnitGetPosition(targetEnemy);
    }

    // Cast if there is a large number of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 2;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }
    
    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < minEnemyEval) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
 }
 
 //---------------------------------------------------------------------------------------------
bool SgtHammerNapalmStrike (int player, unit aiUnit, unitgroup scanGroup) {
    // Attack that damages all enemies in an area
    order ord;
    unitgroup largeScanGroup;
    unit targetEnemy;
    fixed range;
    
    ord = StormHeroAICreateOrder(player, c_AB_SgtHammerNapalmStrike, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    range = UnitAbilityEffectInstantGetRange(aiUnit, c_AB_SgtHammerNapalmStrike);
    
    // This skill's range is so large we have to scan a new larger scanGroup
    largeScanGroup = UnitGroupSearch(null, player, UnitGetPosition(aiUnit), range, UnitFilter((1 << c_targetFilterVisible) | (1 << c_targetFilterEnemy), 0, 0, 0), c_noMaxCount);

    // Find a target hero
    targetEnemy = FindClosestWeakHero(largeScanGroup, aiUnit, 0.0, range, 100.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {  
        // If no heroes, try to find a target minion    
        targetEnemy = FindClosestWeakMinion(largeScanGroup, aiUnit, 0.0, range, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }
    
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetEnemy), 3.0, 3, 1) < 5) {
        return false;
    }
 
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
 }

//---------------------------------------------------------------------------------------------
bool SgtHammerBluntForceGun (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to large area in front of hero
    order spellOrd;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_SgtHammerBluntForceGun, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    center = UnitGroupCenterOfGroup(scanGroup);

    if (CountEnemiesInArea(player, scanGroup, center, 4.0, 3, 1) < 6) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
void AIThinkSgtHammer (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (libAIAI_gf_HeroAIIsAIEnabledForPlayer(player) == false) {
        return;
    }
    
    if (SgtHammerTankMode(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (SgtHammerSiegeMode(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (SgtHammerConcussiveBlast(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }
    
    if (SgtHammerMineField(player, aiUnit, scanGroup)) {
        return;
    }   

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && SgtHammerNapalmStrike(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && SgtHammerBluntForceGun(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Uther Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_UtherHolyLight                 = "UtherHolyLight";
const string c_AB_UtherHolyShock                 = "UtherHolyShock";
const string c_AB_UtherHolyRadiance              = "UtherHolyRadiance";
const string c_AB_UtherHammerOfJustice           = "UtherHammerOfJustice";
const string c_AB_UtherDivineIntervention        = "UtherDivineIntervention";
const string c_AB_UtherDivineStorm               = "UtherDivineStorm";

//---------------------------------------------------------------------------------------------
bool UtherHolyLight (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals a single target
    order ord;
    unit targetUnit;
    fixed targetHealth;
    
    ord = StormHeroAICreateOrder(player, c_AB_UtherHolyLight, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weak nearby ally to heal
    targetUnit = FindBestHealTarget(player, aiUnit, 8.0);
    if (!UnitIsValid(targetUnit)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null);
}

//---------------------------------------------------------------------------------------------
bool UtherHolyShock (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals a single target, or deal damage to an enemy
    order ord;
    unit targetUnit;
    fixed targetHealth;
    
    ord = StormHeroAICreateOrder(player, c_AB_UtherHolyShock, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weak nearby ally to heal
    targetUnit = FindBestHealTarget(player, aiUnit, 8.0);
    if (UnitIsValid(targetUnit)) {
        if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null)) {
            return true;
        }
    }
    
    // Cast on a weak enemy hero
    targetUnit = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 20.0, -1, -1, false);   
    if (UnitIsValid(targetUnit)) {
        return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null);
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool UtherDivineIntervention (int player, unit aiUnit, unitgroup scanGroup) {
    // Target becomes invulnerable and gains a movement buff, use to save friendly heros
    order ord;
    unit targetUnit;
  
    ord = StormHeroAICreateOrder(player, c_AB_UtherDivineIntervention, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Find weakest nearby ally hero
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 11.0, 30.0, -1.0, false);
    if (!UnitIsValid(targetUnit)) {  
        return false;
    }
    
    // Don't use if he is already in safety
    if (!HaveBeenAttackedRecently(targetUnit)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null);
}

//---------------------------------------------------------------------------------------------
bool UtherHolyRadiance (int player, unit aiUnit, unitgroup scanGroup) {
    // line heal friend / damage enemies spell
    order ord;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
    unit targetEnemy;

    ord = StormHeroAICreateOrder(player, c_AB_UtherHolyRadiance, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
 
    // Use to heal ally
    targetEnemy = FindWeakestNearbyAllyHero(player, aiUnit, 7.0, 50.0, -1, true);
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetEnemy, null);
    }
    
    // Use on damage enemy
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 7.0, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {  
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 7.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        
        // Only cast if it will hit multiple enemies
        if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetEnemy), 2.0, 2, 1) < 2) {
            return false;
        }
    }
  
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool UtherHammerOfJustice (int player, unit aiUnit, unitgroup scanGroup) {
    // Very short range stun + damage spell
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_UtherHammerOfJustice, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
          
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 4.0, 60.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool UtherDivineStorm (int player, unit aiUnit, unitgroup scanGroup) {
    // Buff that makes hero damage/slow nearby enemies and increases health regen
   
    // Two uses:
    //      When getting attacked while retreating
    //      When engaging a team fight
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_UtherDivineStorm, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
     
    // Use if you are retreating
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2) {
        return false;
    }

    // Attack nearest hero after casting
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 7.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    // Deliberately setting the attackOrd target here and passing in "null, null" for the target since divine storm itself has no target
    OrderSetTargetUnit(attackOrd, targetHero);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkUther (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && UtherDivineIntervention(player, aiUnit, scanGroup)) {
        return;
    }

    if (UtherHolyLight(player, aiUnit, scanGroup)) {
        return;
    }

    if (UtherHolyShock(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    if (UtherHammerOfJustice(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {      
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }
    
    if (UtherHolyRadiance(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && UtherDivineStorm(player, aiUnit, scanGroup)) {
        return;
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Tassadar Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_TassadarPlasmaShield      = "TassadarPlasmaShield";
const string c_AB_TassadarVoidShift             = "TassadarVoidShift";
const string c_AB_TassadarTimeWarp              = "TassadarTimeWarp";
const string c_AB_TassadarTimeWarpRelic         = "TassadarTimeWarpRelic";
const string c_AB_TassadarForceWall             = "TassadarForceWall";
const string c_AB_TassadarTwilightMessiah       = "TassadarTwilightMessiah";

//---------------------------------------------------------------------------------------------
bool TassadarPlasmaShield (int player, unit aiUnit, unitgroup scanGroup) {
    // Shields a single target for 10-15 seconds
    order ord;
    fixed curHealth;
    unit targetUnit;
    fixed targetHealth;
  
    ord = StormHeroAICreateOrder(player, c_AB_TassadarPlasmaShield, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // If low health shield ourself first
    curHealth = UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent);
    if (HaveBeenAttackedRecently(aiUnit) && curHealth < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, aiUnit, null)) {
            return true;
        }
    }

    // Find weakest nearby ally hero
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, curHealth - 10.0, -1.0, false);

    if (UnitIsValid(targetUnit)) {
        targetHealth = UnitGetPropertyFixed(targetUnit, c_unitPropLifePercent, c_unitPropCurrent);
        if (HaveBeenAttackedRecently(targetUnit) && targetHealth < 70.0) {
            if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null)) {
                return true;
            }
        }
    }

    // Else shield ourself
    if (HaveBeenAttackedRecently(aiUnit) && curHealth < 70.0) {
        if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, aiUnit, null)) {
            return true;
        }
    }

    return false;
}

//---------------------------------------------------------------------------------------------
bool TassadarVoidShift (int player, unit aiUnit, unitgroup scanGroup) {
    // Become invisible/invincible and gain movement speed
    order ord;

    ord = StormHeroAICreateOrder(player, c_AB_TassadarVoidShift, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Use when low health to run away safely
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return false;
    }
  
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool TassadarTimeWarpGeneric (order spellOrd, int player, unit aiUnit, unitgroup scanGroup) {
    // Use same logic for both the base skill and the relic varient
    unitgroup nearbyEnemies;
    unitgroup nearbyEnemyHeroes;
    unit targetEnemy;
    point center;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
        
    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 10.0, 40.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }
    
    // Center on heroes if there are any nearby
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 7.0);
    nearbyEnemyHeroes = UnitGroupFilterHeroes(nearbyEnemies);

    if (UnitGroupCount(nearbyEnemyHeroes, c_unitCountAll) > 0) {
        center = UnitGroupCenterOfGroup(nearbyEnemyHeroes);
    }
    // Target minions instead
    else {
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 10.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        
        center = UnitGetPosition(targetEnemy);
    }
    
    // Cast if there is a large number of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 3;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 7;
    }
    else {
        minEnemyEval = 9;
    }
    
    // Only use on large group of enemies   
    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < minEnemyEval) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool TassadarTimeWarp (int player, unit aiUnit, unitgroup scanGroup) {
    // Place a bubble that slows and damages enemies
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TassadarTimeWarp , 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    return TassadarTimeWarpGeneric(spellOrd, player, aiUnit, scanGroup);
}

//---------------------------------------------------------------------------------------------
bool TassadarTimeWarpRelic (int player, unit aiUnit, unitgroup scanGroup) {
    // Place a bubble that slows and damages enemies. 
    // Growns in size over time and pull enemies toward the center upon ending.
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TassadarTimeWarpRelic , 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    return TassadarTimeWarpGeneric(spellOrd, player, aiUnit, scanGroup);
}

//---------------------------------------------------------------------------------------------
bool TassadarForceWall (int player, unit aiUnit, unitgroup scanGroup) {
    // Creates a "sentry" wall that can block an enemy hero from running away
    order spellOrd;
    order attackOrd;
    unit targetHero;
    fixed directionAngle;
    point targetPoint;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TassadarForceWall, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
     
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 2.0, 6.0, 60.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) {
        return false;
    }

    // Get a point on the other side of the enemy hero
    directionAngle = GetAngleToTarget(aiUnit, targetHero);
    targetPoint = MovePoint(UnitGetPosition(targetHero), 1.5, directionAngle);
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, targetPoint);
}

//---------------------------------------------------------------------------------------------
bool TassadarTwilightMessiah (int player, unit aiUnit, unitgroup scanGroup) {
    // Gives tassadar a shield and buffs his attack damage
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TassadarTwilightMessiah, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }    
    
    // Use if you are retreating
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
    
    // Activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 8.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 8.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkTassadar (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (TassadarPlasmaShield(player, aiUnit, scanGroup)) {
        return;
    }    

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (TassadarVoidShift(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TassadarTwilightMessiah(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TassadarForceWall(player, aiUnit, scanGroup)) {
        return;
    }    
    
    if (TassadarTimeWarpRelic(player, aiUnit, scanGroup)) {
        return;
    }

    if (TassadarTimeWarp(player, aiUnit, scanGroup)) {
        return;
    }
    
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Arthas Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_ArthasFrostStrike             = "ArthasFrostStrike";
const string c_AB_ArthasFrostCharge             = "ArthasFrostCharge";
const string c_AB_ArthasHowlingBlast            = "ArthasHowlingBlast";
const string c_AB_ArthasDeathAndDecayTog        = "ArthasDeathAndDecayTog";
const string c_AB_ArthasDeathAndDecayCancel     = "ArthasDeathAndDecayCancel";
const string c_AB_ArthasArmyOfTheDead           = "ArthasArmyOfTheDead";
const string c_AB_ArthasArmyOfTheDeadSacrifice  = "ArthasArmyOfTheDeadSacrifice";
const string c_AB_ArthasSummonSindragosa        = "ArthasSummonSindragosa";

//---------------------------------------------------------------------------------------------
bool ArthasFrostStrike (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages and slows nearby target. If it kills the target, they will cast a frost nova
    order spellOrd;
    order attackOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
  
    spellOrd = StormHeroAICreateOrder(player, c_AB_ArthasFrostStrike, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false; 
    }
    
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Use if there is a weak enemy nearby and attack them
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 3.0, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        // If high mana, use it to last hit a minions
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 3.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }
  
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool ArthasFrostCharge (int player, unit aiUnit, unitgroup scanGroup) {
    // Dasjes tp target causing damage and slow. If it kills the target, they will cast a frost nova
    order spellOrd;
    order attackOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
  
    spellOrd = StormHeroAICreateOrder(player, c_AB_ArthasFrostCharge, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false; 
    }
    
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Use if there is a weak enemy nearby and attack them
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 5.5, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        // If high mana, use it to last hit a minions
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 5.5, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }
  
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool ArthasArmyOfTheDead (int player, unit aiUnit, unitgroup scanGroup) {
    // Summons a ghoul to help arthas fight    
    order ord;
    
    ord = StormHeroAICreateOrder(player, c_AB_ArthasArmyOfTheDead, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Always cast this is we can (if nothing else we can sack it for health)
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool ArthasArmyOfTheDeadSacrifice (int player, unit aiUnit, unitgroup scanGroup) {
    // Sacrifice the ghoul for a little bit of health
    order ord;

    ord = StormHeroAICreateOrder(player, c_AB_ArthasArmyOfTheDeadSacrifice, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // If we're above 50% health don't sack it yet
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > 50.0) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool ArthasHowlingBlast (int player, unit aiUnit, unitgroup scanGroup) {
    // Short range snare + minor damage, use it to catch fleeing heros
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_ArthasHowlingBlast, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
     
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 7.0, 50.0, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
bool ArthasDeathAndDecayTog (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE that damages nearby enemies. Moves with Arthas.
    order ord;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;

    ord = StormHeroAICreateOrder(player, c_AB_ArthasDeathAndDecayTog, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 2;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }
    
    // If there are not enough enemy units around the hero, do not cast
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 4.0, 2, 1) < minEnemyEval) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool ArthasDeathAndDecayCancel(int player, unit aiUnit, unitgroup scanGroup) {
    order ord;

    ord = StormHeroAICreateOrder(player, c_AB_ArthasDeathAndDecayCancel, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Turn off once there are no longer units around to damage
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 4.0, 1, 1) > 1) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool ArthasSummonSindragosa (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to large area in front of hero
    order spellOrd;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_ArthasSummonSindragosa, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    center = UnitGroupCenterOfGroup(scanGroup);

    if (CountEnemiesInArea(player, scanGroup, center, 6.0, 3, 1) < 6) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
void AIThinkArthas (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

     if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (ArthasDeathAndDecayCancel(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && ArthasArmyOfTheDeadSacrifice(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && ArthasArmyOfTheDead(player, aiUnit, scanGroup)) {
        return;
    } 
     
    if (ArthasFrostStrike(player, aiUnit, scanGroup)) {
        return;
    }   

    if (ArthasFrostCharge(player, aiUnit, scanGroup)) {
        return;
    }   
    
    if (ArthasDeathAndDecayTog(player, aiUnit, scanGroup)) {
        return;
    }

    if (ArthasHowlingBlast(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && ArthasSummonSindragosa(player, aiUnit, scanGroup)) {
        return;
    } 

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Tinker Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_TinkerRockItTurrett       = "TinkerRockItTurret";
const string c_AB_TinkerDethLazorCharged    = "TinkerDethLazorCharged";
const string c_AB_TinkerDethLazorCancel     = "DethLazorCancel";
const string c_AB_TinkerXplodiumBomb        = "TinkerXplodiumBomb";
const string c_AB_TinkerRoboGoblin          = "TinkerRoboGoblin";
const string c_AB_TinkerGravOBomb3000         = "TinkerGravOBomb3000";

const string c_DethLazorActiveBehavior      = "DethLazorActive";

//---------------------------------------------------------------------------------------------
bool TinkerDethLazorCharged (int player, unit aiUnit, unitgroup scanGroup) {
    // Shoots a lazer that damages everything in its path. The longer it is charged the larger the AOE.
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    unitgroup nearbyEnemies;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TinkerDethLazorCharged, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast if there is a large number of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 6;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 8;
    }
    else {
        minEnemyEval = 10;
    }
    
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 10.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < minEnemyEval) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//--------------------------------------------------------------------------------------------------
void AIChannelDethLazor (int player, unit aiUnit, unitgroup scanGroup) {
    order cancelOrd;
    bool cancel = false;
    unitgroup nearbyEnemies;
    point center;
    order turnOrd;

    cancelOrd = StormHeroAICreateOrder(player, c_AB_TinkerDethLazorCancel, 0);
    if (!UnitOrderIsValid(aiUnit, cancelOrd)) {
        return;
    }

    // Stop chanelling if in danger
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        cancel = true;
    }
    
    // Stop chanelling if some of the units you were originally targetting are leaving your range
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 10.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < 4) {
        cancel = true;
    }
   
    if (cancel) {
        // Don't use HeroIssueOrder for channeling
        UnitIssueOrder(aiUnit, cancelOrd, c_orderQueueReplace);   
    }
    else {
        turnOrd = StormHeroAICreateOrder(player, c_Storm_AB_Move, 4);
        if (!UnitOrderIsValid(aiUnit, turnOrd)) {
            return;
        }

        OrderSetTargetPoint(turnOrd, center);
        if (!UnitOrderIsValid(aiUnit, turnOrd)) {
            return;
        }   

        // Don't use HeroIssueOrder for channeling
        UnitIssueOrder(aiUnit, turnOrd, c_orderQueueReplace);
    }
}

//---------------------------------------------------------------------------------------------
bool TinkerRockItTurret (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Creates a turret that attacks enemies
    order ord;
    int enemyCount;

    // Don't be too offensive if we're near a tower (tower will just kill it)
    if (nearEnemyTower) {
        return false;
    }

    ord = StormHeroAICreateOrder(player, c_AB_TinkerRockItTurrett, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Always cast this if we have energy and there are enemies nearby
    if (UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent) < libAIAI_gv_aIHeroLowEnergyPercent) {
        return false;
    }

    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 7.0, 1, 1) < 2) {
        return false;
    }
  
    // Deliberately setting the ord point here and passing in "null, null" since we don't want to fudge this position   
    OrderSetTargetPoint(ord,UnitGetPosition(aiUnit));
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool TinkerXplodiumBomb (int player, unit aiUnit, unitgroup scanGroup) {
    // Places a bomb that damages and stuns nearby enemies
    order spellOrd;
    unit targetHero;
    point targetPos;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TinkerXplodiumBomb, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Shoot at a nearby hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, healthReq, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetPos = UnitGroupCenterOfGroup(scanGroup);
        if (CountEnemiesInArea(player, scanGroup, targetPos, 3.0, 0, 1) < 3) {
            return false;
        }
    }
    else {
        targetPos = UnitGetPosition(targetHero);
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
bool TinkerRoboGoblin (int player, unit aiUnit, unitgroup scanGroup) {
    // Can't be snared, knocks enemies out of the way, damage boost vs structures
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TinkerRoboGoblin, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Use speed boost to retreat faster
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
    
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool TinkerGravOBomb3000 (int player, unit aiUnit, unitgroup scanGroup) {
    // Place bomb that after a delay sucks in all nearby units and detonates
    order spellOrd;
    unit targetHero;
    point targetPos;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TinkerGravOBomb3000, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 7.0, 60.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    // Only cast if there are other nearby enemies for it to hit also
    targetPos = UnitGetPosition(targetHero);
    if (CountEnemiesInArea(player, scanGroup, targetPos, 3.0, 4, 1) < 8) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
void AIThinkTinker (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UnitHasBehavior(aiUnit, c_DethLazorActiveBehavior) == true) {
        AIChannelDethLazor(player, aiUnit, scanGroup);
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TinkerRoboGoblin(player, aiUnit, scanGroup)) {
        return;
    }
    
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (TinkerRockItTurret(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (TinkerXplodiumBomb(player, aiUnit, scanGroup)) {
        return;
    }

    if (TinkerDethLazorCharged(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TinkerGravOBomb3000(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {        
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}

//---------------------------------------------------------------------------------------------
// Witch Doctor Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_WitchDoctorCorpseSpiders      = "WitchDoctorCorpseSpiders";
const string c_AB_WitchDoctorZombiePrison       = "WitchDoctorZombiePrison";
const string c_AB_WitchDoctorPlagueToadsCone    = "WitchDoctorPlagueToadsCone";
const string c_AB_WitchDoctorGargantuan         = "WitchDoctorGargantuan";
const string c_AB_WitchDoctorRavenousSouls      = "WitchDoctorRavenousSouls";

unit[libCore_gv_bALMaxPlayers] witchDoctorRavenousSoulsUnit;

//---------------------------------------------------------------------------------------------
bool WitchDoctorCorpseSpiders (int player, unit aiUnit, unitgroup scanGroup) {
    // Spawns spiders at the target location that attack nearby enemies
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
    unit targetEnemy;
       
    spellOrd = StormHeroAICreateOrder(player, c_AB_WitchDoctorCorpseSpiders, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Find target hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {  
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool WitchDoctorZombiePrison (int player, unit aiUnit, unitgroup scanGroup) {
    // Surround the target with a circular wall of zombies
    order spellOrd;
    order attackOrd;    
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_WitchDoctorZombiePrison, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Trap an weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 3.0, 8.0, 50.0, -1, -1, false);    
    if (!UnitIsValid(targetEnemy)) {
        // Otherwise attack a large group of enemies
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 3.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }

        if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
            minEnemyEval = 4;
        }
        else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
            minEnemyEval = 6;
        }
        else {
            minEnemyEval = 8;
        }
         
        if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetEnemy), 4.5, 2, 1) < minEnemyEval) {
            return false;
        }  
    }

    // If low health just cast the prison, but don't turn to attack
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) <= libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool WitchDoctorPlagueToadsCone (int player, unit aiUnit, unitgroup scanGroup) {
    // Waves of damaging toads from the witch doctor toward the target point
    order ord;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;     
    unit targetEnemy;
    fixed directionAngle;    
    point inFrontOfHero;
    
    ord = StormHeroAICreateOrder(player, c_AB_WitchDoctorPlagueToadsCone, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
  
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 3;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }
 
    // Find a target enemy
    targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 7.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }

    // Don't cast if there are not many units in front of hero
    directionAngle = GetAngleToTarget(aiUnit, targetEnemy);
    inFrontOfHero = MovePoint(UnitGetPosition(aiUnit), 3.5, directionAngle);  
    
    if (CountEnemiesInArea(player, scanGroup, inFrontOfHero, 3.5, 2, 1) < minEnemyEval) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, inFrontOfHero);
}

//---------------------------------------------------------------------------------------------
bool WitchDoctorGargantuan (int player, unit aiUnit, unitgroup scanGroup) {
    // Summons gargantuan at target location
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_WitchDoctorGargantuan, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 60.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
bool WitchDoctorRavenousSouls (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Summons an AOE that you can move around while channeling
    order ord;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
    unit targetEnemy;

    // Don't be offensive if we're near a tower
    if (nearEnemyTower) {
        return false;
    }

    ord = StormHeroAICreateOrder(player, c_AB_WitchDoctorRavenousSouls, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Don't cast if it is not safe
    if (HaveBeenAttackedRecently(aiUnit)) {
        return false;
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 60.0) {
        return false;
    }
    
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Find a target that is a bit away, so you wont get attacked while channeling
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 5.0, 10.0, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {
        return false;    
    }
                
    // Don't cast if there is nothing for it's AOE to hit besides the target
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetEnemy), 5.0, 3, 1) < 6) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueReplace, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
void AIChannelWitchDoctorRavenousSouls (int player, unit aiUnit, unitgroup scanGroup) {
    // If WitchDoctor is getting attacked, cancel maelstorm
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        UnitKill(witchDoctorRavenousSoulsUnit[player - 1]);   
    }
}

//---------------------------------------------------------------------------------------------
void AIThinkWitchDoctor (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    // If WitchDoctor is channeling RavenousSouls check if he should cancel it
    if (UnitIsValid(witchDoctorRavenousSoulsUnit[player - 1])) {    
        AIChannelWitchDoctorRavenousSouls(player, aiUnit, scanGroup);
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (WitchDoctorCorpseSpiders(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && WitchDoctorGargantuan(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (WitchDoctorPlagueToadsCone(player, aiUnit, scanGroup)) {
        return;
    }
    
    // Don't be offensive if we're near a tower
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && WitchDoctorRavenousSouls(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (WitchDoctorZombiePrison(player, aiUnit, scanGroup)) {
        return;
    }

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {        
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}

//---------------------------------------------------------------------------------------------
// WitchDoctorRavenousSouls Tactical
//---------------------------------------------------------------------------------------------

void AIThinkWitchDoctorRavenousSouls (int player, unit aiUnit, unitgroup scanGroup) {
    unit targetEnemy;
    order ord;
    
    witchDoctorRavenousSoulsUnit[player - 1] = aiUnit;
    
    // Only control the ability if it is an AI player
    if (libAIAI_gf_HeroAIIsAIEnabledForPlayer(player) == false) {
        return;
    }
    
    // Find a target hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, 100.0, -1, -1, false);

    // If no heroes, try to find a target minion  
    if (!UnitIsValid(targetEnemy)) {  
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 6.0, 100.0, -1, -1, false);
    }
    
    // If can't find either, do nothing
    if (!UnitIsValid(targetEnemy)) {
        return;
    }
    
    ord = StormHeroAICreateOrder(player, c_Storm_AB_Move, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return;
    }
    
    OrderSetTargetUnit(ord,targetEnemy);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return;
    }  

    // Don't use HeroIssueOrder for locust swarm, it's not casting spells, it's just moving around
    UnitIssueOrder(aiUnit, ord, c_orderQueueReplace);
}


//---------------------------------------------------------------------------------------------
// L90ETC Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_L90ETCPowerSlide        = "L90ETCPowerslide";
const string c_AB_L90ETCFaceMelt          = "L90ETCFaceMelt";
const string c_AB_L90ETCGuitarSolo        = "L90ETCGuitarSolo";
const string c_AB_L90ETCStageDive         = "L90ETCStageDive";
const string c_AB_L90ETCMoshPit           = "L90ETCMoshPit";
const string c_AB_L90ETCMoshPitCancel     = "L90ETCMoshPitCancel";

//---------------------------------------------------------------------------------------------
bool L90ETCPowerSlide (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Charge forward damaging and stunning enemies in a line
    
    // two uses for now:
    //   run away to live
    //   catch a fleeing enemy hero
    unit targetEnemy;
    point retreatPos;
    order spellOrd;
    order attackOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_L90ETCPowerSlide, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // retreat if you have low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        // charge back to retreat to safety
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 8.0);
       
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }

    // don't jump into battle near a tower
    if (nearEnemyTower) {
        return false;
    }
    
    // don't jump into battle with low health
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) { 
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // find a target hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 8.0, 50.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool L90ETCFaceMelt (int player, unit aiUnit, unitgroup scanGroup) {
    // Pushes back and damages all nearby enemies
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_L90ETCFaceMelt, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast if there are many enemies in the area
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 3;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 4;
    }
    else {
        minEnemyEval = 6;
    }

    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 3.0, 2, 1) < minEnemyEval) {
        return false;
    }  

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool L90ETCGuitarSolo (int player, unit aiUnit, unitgroup scanGroup) {
    // Reens heath of all nearby heroes over next few seconds
    order ord;
    unit targetUnit; 
    
    ord = StormHeroAICreateOrder(player, c_AB_L90ETCGuitarSolo, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Use if there are any nearby heroes that could use healing including self
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 5.0, 70.0, -1, false);
    if (!UnitIsValid(targetUnit)) {  
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool L90ETCStageDive (int player, unit aiUnit, unitgroup scanGroup) {
    // Leap (teleport) to any location on the map. Landing on enemies dealing damage and stunning them.

    order ord;
    
    ord = StormHeroAICreateOrder(player, c_AB_L90ETCStageDive, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Flee battle to survive at low health.
    // This doesn't interact well with the retreat state, which immediately cancels the ability.
    //
    // if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
    //     return false;
    // }
    // return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueReplace, null, libGame_gv_players[player].lv_spawnPoint);
 
    // Currnently not used by AI
    return false;
}

//---------------------------------------------------------------------------------------------
bool L90ETCMoshPit (int player, unit aiUnit, unitgroup scanGroup) {
    // Cloaks all nearby allies
    order spellOrd;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_L90ETCMoshPit, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 5.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 7.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIChannelL90ETC (int player, unit aiUnit, unitgroup scanGroup) {
    order currentOrd = UnitOrder(aiUnit, 0);
    order ord;
    
    if (currentOrd == null) {
        return;
    }
    else if (OrderGetAbilityCommand(currentOrd) == AbilityCommand(c_AB_L90ETCMoshPit, 0)) {
        // Cancel Mosh Pit if we are getting killed
        ord = StormHeroAICreateOrder(player, c_AB_L90ETCMoshPitCancel, 0);
        if (!UnitOrderIsValid(aiUnit, ord)) {
            return;
        }

        if (!HaveBeenAttackedRecently(aiUnit) || UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
            return;
        }
        
        HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
    }
}

//---------------------------------------------------------------------------------------------
void AIThinkL90ETC (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;
    
    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && L90ETCStageDive(player, aiUnit, scanGroup)) {
        return;
    }

    if (L90ETCGuitarSolo(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (L90ETCPowerSlide(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (L90ETCFaceMelt(player, aiUnit, scanGroup)) {
            return;
        }

        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && L90ETCMoshPit(player, aiUnit, scanGroup)) {
            return;
        }
        
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}

//---------------------------------------------------------------------------------------------
// Diablo Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_DiabloShadowCharge                = "DiabloShadowCharge";
const string c_AB_DiabloFireStomp                   = "DiabloFireStomp";
const string c_AB_DiabloOverpower                   = "DiabloPound";
const string c_AB_DiabloLightningBreathTargeted     = "DiabloLightningBreathTargeted";
const string c_AB_DiabloApocalypse                  = "DiabloApocalypse";

//---------------------------------------------------------------------------------------------
bool DiabloFireStomp (int player, unit aiUnit, unitgroup scanGroup) {
    // Shoots fireballs radially in all direction
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_DiabloFireStomp, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast if there is a weak hero
    nearbyHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 4.0, 40.0, -1, -1, false);   
    if (UnitIsValid(nearbyHero)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }

    // Cast if there are many enemies in the area
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 3;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 6;
    }
    else {
        minEnemyEval = 9;
    }

    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 5.0, 3, 1) < minEnemyEval) {
        return false;
    }  

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool DiabloShadowCharge (int player, unit aiUnit, unitgroup scanGroup) {
    // Charge at an enemy pushing them back. Stuns them if it pushes them into a wall.
    unit targetEnemy;
    order spellOrd;
    order attackOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DiabloShadowCharge, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Don't jump into battle with low health
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 65.0) { 
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 2.0, 7.0, 40.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool DiabloOverpower (int player, unit aiUnit, unitgroup scanGroup) {
    // Flips the target behind Diablo and stuns them
    unit targetEnemy;
    order spellOrd;
    order attackOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DiabloOverpower, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
        
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 2.5, 40.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}


//---------------------------------------------------------------------------------------------
bool DiabloLightningBreathTargeted (int player, unit aiUnit, unitgroup scanGroup) {
    // Slow target and shoot multiple fireballs toward them
    order spellOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DiabloLightningBreathTargeted, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0; 
    }

    // Shoot any nearby enemy if there is a clear shot
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, healthReq, -1, -1, true);
    if (!UnitIsValid(targetEnemy)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool DiabloApocalypse (int player, unit aiUnit, unitgroup scanGroup) {
    // Create a fire under all enemy heroes revealing them and damaging them if they don't move out in time
    order ord;

    ord = StormHeroAICreateOrder(player, c_AB_DiabloApocalypse, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Ideas:
    // Use any time 3 heroes are within 5 of each other
    // Use any time an enemy hero is stunned & less than 60% health

    // Cast whenever cooldown ends
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkDiablo (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    if (DiabloShadowCharge(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (DiabloOverpower(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (DiabloFireStomp(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && DiabloApocalypse(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && DiabloLightningBreathTargeted(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {     
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Malfurion Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_MalfurionRegrowth         = "MalfurionRegrowth";
const string c_AB_MalfurionMoonfire         = "MalfurionMoonfire";
const string c_AB_MalfurionEntanglingRoots  = "MalfurionEntanglingRoots";
const string c_AB_MalfurionTranquility      = "MalfurionTranquility";
const string c_AB_MalfurionTwilightDream    = "MalfurionTwilightDream";
const string c_AB_MalfurionInnervate        = "MalfurionInnervate";

//---------------------------------------------------------------------------------------------
bool MalfurionRegrowth (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals a single target with a h.o.t. after
    order ord;
    unit targetUnit;
    fixed targetHealth;
  
    ord = StormHeroAICreateOrder(player, c_AB_MalfurionRegrowth, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Find weak nearby ally to heal
    targetUnit = FindBestHealTarget(player, aiUnit, 8.0);
    if (!UnitIsValid(targetUnit)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null);
}

//---------------------------------------------------------------------------------------------
bool MalfurionMoonfire (int player, unit aiUnit, unitgroup scanGroup) {
    // AOE damage spell with short cooldown
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    unitgroup nearbyEnemies;
    unitgroup nearbyEnemyHeroes;
    unit targetEnemy;
    point center;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_MalfurionMoonfire, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 40.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }
    
    // Save mana for other skills
    if (energy < libAIAI_gv_aIHeroLowEnergyPercent) {
        return false;
    }
    
    // Center on heroes if there are any nearby
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 8.0);
    nearbyEnemyHeroes = UnitGroupFilterHeroes(nearbyEnemies);

    if (UnitGroupCount(nearbyEnemyHeroes, c_unitCountAll) > 0) {
        center = UnitGroupCenterOfGroup(nearbyEnemyHeroes);
    }
    // Target minions instead
    else {
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        
        center = UnitGetPosition(targetEnemy);
    }
    
    // Only use on a large group of enemies   
    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < 4) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool MalfurionEntanglingRoots (int player, unit aiUnit, unitgroup scanGroup) {
     // AOE damage spell with short cooldown
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    unitgroup nearbyEnemies;
    unitgroup nearbyEnemyHeroes;
    unit targetEnemy;
    point center;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_MalfurionEntanglingRoots, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 40.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }
    
    // Save mana for other skills
    if (energy < libAIAI_gv_aIHeroLowEnergyPercent) {
        return false;
    }
    
    // Center on heroes if there are any nearby
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 8.0);
    nearbyEnemyHeroes = UnitGroupFilterHeroes(nearbyEnemies);

    if (UnitGroupCount(nearbyEnemyHeroes, c_unitCountAll) > 0) {
        center = UnitGroupCenterOfGroup(nearbyEnemyHeroes);
    }
    // Target minions instead
    else {
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        
        center = UnitGetPosition(targetEnemy);
    }
    
    // Use on large group of enemies   
    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < 6) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool MalfurionTranquility (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE heal of all nearby allies. Moves with Malfurion.
    region r;
    unitgroup nearbyAllies;
    unit targetUnit;
    fixed healAmount;
    order ord;
  
    ord = StormHeroAICreateOrder(player, c_AB_MalfurionTranquility, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weakest nearby ally hero
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 9.0, 35.0, 300, false);
    if (!UnitIsValid(targetUnit)) {  
        return false;
    }

    // Only activate if they are being attacked
    if (!HaveBeenAttackedRecently(targetUnit)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool MalfurionTwilightDream (int player, unit aiUnit, unitgroup scanGroup) {
    // Silence and slow all nearby enemies
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_MalfurionTwilightDream, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
  
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool MalfurionInnervate (int player, unit aiUnit, unitgroup scanGroup) {
    // Grants mana regen to ally
    order ord;
    unit allyHero;
  
    ord = StormHeroAICreateOrder(player, c_AB_MalfurionInnervate, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Cast on a random ally
    allyHero = FindWeakestNearbyAllyHero(player, aiUnit, 7.0, 100.0, -1.0, false);

    if (!UnitIsValid(allyHero)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, allyHero, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkMalfurion (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (MalfurionRegrowth(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && MalfurionTranquility(player, aiUnit, scanGroup)) {
        return;
    }

    if (MalfurionInnervate(player, aiUnit, scanGroup)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (MalfurionMoonfire(player, aiUnit, scanGroup)) {
        return;
    }
        
    if (MalfurionEntanglingRoots(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {    
        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && MalfurionTwilightDream(player, aiUnit, scanGroup)) {
            return;
        }

        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Falstad Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_FalstadHammerang          = "FalstadHammerang";
const string c_AB_FalstadThunderstorm       = "FalstadThunderstorm";
const string c_AB_FalstadBarrelRoll         = "FalstadBarrelRoll";
const string c_AB_FalstadShockAndAwe        = "FalstadShockAndAwe";
const string c_AB_FalstadAerialBlitzkrieg   = "FalstadAerialBlitzkrieg";

const string c_ThunderstormMarkedBehavior   = "ThunderstormMarkedDebuff";

//---------------------------------------------------------------------------------------------
bool FalstadHammerang (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages enemies in a line and slows them
    unit targetEnemy;
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);

    spellOrd = StormHeroAICreateOrder(player, c_AB_FalstadHammerang, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        // If we have a decent amount of energy, we're allowed to target minions
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        
        // Only if there are at least 2 other enemies near that minion        
        if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetEnemy), 3.0, 1, 1) < 3) {
            return false;
        }
    }  
         
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool FalstadThunderstorm (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages nearby enemy hit by passive effect of this ability
    order spellOrd;
    unitgroup nearbyEnemies;
    unitgroup nearbyEnemyHeroes;
    unit unitToCheck;
    int groupCount;

    spellOrd = StormHeroAICreateOrder(player, c_AB_FalstadThunderstorm, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 7.0);    
    nearbyEnemyHeroes = UnitGroupFilterHeroes(nearbyEnemies);
    
    groupCount = UnitGroupCount(nearbyEnemyHeroes, c_unitCountAll);   
    while (groupCount > 0) {
        unitToCheck = UnitGroupUnit(nearbyEnemyHeroes,groupCount);
        groupCount = groupCount - 1;

        if (UnitHasBehavior(unitToCheck, c_ThunderstormMarkedBehavior) == true) {
            return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
        }
    }
  
    return false;
}

//---------------------------------------------------------------------------------------------
bool FalstadBarrelRoll (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Dash toward target location and gain a small shield for a short duration

    // Two uses for now:
    //   Run away to live
    //   Catch a fleeing enemy hero
    unit targetEnemy;
    point retreatPos;
    order spellOrd;
    order attackOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_FalstadBarrelRoll, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Retreat if you have low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        // Dash back to retreat to safety
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 8);
       
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }

    // Don't jump into battle near a tower or low health
    if (nearEnemyTower) {
        return false;
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 2.0, 8.0, 50.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool FalstadShockAndAwe (int player, unit aiUnit, unitgroup scanGroup) {   
    // Damages all enemies in a wide beam after a short delay
    order ord;
    unitgroup enemyHeroes;
    unit targetEnemy;
    point center;

    ord = StormHeroAICreateOrder(player, c_AB_FalstadShockAndAwe, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // Don't use if retreating. It has a long cast time and can't be canceled.
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return false;
    }
    
    // Center on the heroes if one is nearby
    enemyHeroes = UnitGroupFilterHeroes(scanGroup);
    if (UnitGroupCount(enemyHeroes, c_unitCountAll) > 0) {
        center = UnitGroupCenterOfGroup(enemyHeroes);
    }
    // Target minions instead
    else {
        targetEnemy = FindClosestWeakHeroOrMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
        center = UnitGetPosition(targetEnemy);
    }

    // Only use on large group of enemies   
    if (CountEnemiesInArea(player, scanGroup, center, 3.0, 3, 1) < 6) {
        return false;
    }
 
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool FalstadAerialBlitzkrieg (int player, unit aiUnit, unitgroup scanGroup) {
    // Damage and stun all nearby heroes
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_FalstadAerialBlitzkrieg, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
  
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 9.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 9.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkFalstad (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (FalstadBarrelRoll(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (FalstadHammerang(player, aiUnit, scanGroup)) {
        return;
    }

    if (FalstadThunderstorm(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && FalstadAerialBlitzkrieg(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && FalstadShockAndAwe(player, aiUnit, scanGroup)) {
        return;
    }  
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Demon Hunter Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_DemonHunterHungeringArrow     = "DemonHunterHungeringArrow";
const string c_AB_DemonHunterMultishot          = "DemonHunterMultishot";
const string c_AB_DemonHunterVault              = "DemonHunterVault";
const string c_AB_DemonHunterRainOfVengeance    = "DemonHunterRainOfVengeance";
const string c_AB_DemonHunterStrafe             = "DemonHunterStrafe";

//---------------------------------------------------------------------------------------------
bool DemonHunterHungeringArrow (int player, unit aiUnit, unitgroup scanGroup) {
    // Skillshot that damages other nearby enemies
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DemonHunterHungeringArrow, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 50.0;
    }
    
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 7.0, healthReq, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {  
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroLowEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 7.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }
 
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool DemonHunterMultishot (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to all enemies in fron to hero
    order spellOrd;
    unitgroup nearbyEnemies;
    unit targetEnemy;
    point center;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    int minEnemyEval;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_DemonHunterMultishot, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, 40.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }
    
    // Cast if there is a large number of enemies
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        minEnemyEval = 3;
    }
    else if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        minEnemyEval = 6;
    }
    else {
        minEnemyEval = 8;
    }
    
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 8.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, nearbyEnemies, center, 3.0, 3, 1) < minEnemyEval) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool DemonHunterVault (int player, unit aiUnit, unitgroup scanGroup) {
    // Dash to target location
    point retreatPos;
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DemonHunterVault, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    if (!HaveBeenAttackedRecently(aiUnit) || UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return false;
    }

    // Jump towards your spawn for safety
    retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 7.0); 

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
}

//---------------------------------------------------------------------------------------------
bool DemonHunterRainOfVengeance (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to all enemies in fron to hero
    order spellOrd;
    unitgroup nearbyEnemies;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DemonHunterRainOfVengeance, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 7.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, nearbyEnemies, center, 3.0, 3, 1) < 6) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool DemonHunterStrafe (int player, unit aiUnit, unitgroup scanGroup) {
    // Shoots arrows at nearby enemies over next few seconds
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_DemonHunterStrafe, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    // Attack closest enemy after activating
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 5.0, 70.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    // Only use if there are multiple enemies in the area
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetHero), 6.0, 4, 1) < 8) {
        return false;
    }
 
    // Deliberately setting the attackOrd target here and passing in "null, null" for the target since strafe itself has no target
    OrderSetTargetUnit(attackOrd, targetHero);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkDemonHunter (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (DemonHunterVault(player, aiUnit, scanGroup)) {
        return;
    }

    if (DemonHunterHungeringArrow(player, aiUnit, scanGroup)) {
        return;
    }

    if (DemonHunterMultishot(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && DemonHunterRainOfVengeance(player, aiUnit, scanGroup)) {
        return;
    }
    
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && DemonHunterStrafe(player, aiUnit, scanGroup)) {
            return;
        }

        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Tychus Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_TychusGrenade                 = "TychusGrenade";
const string c_AB_TychusRunAndGun               = "TychusRunAndGun";
const string c_AB_TychusOverkillTargeted        = "TychusOverkillTargeted";
const string c_AB_TychusOdin                    = "TychusOdin";
const string c_AB_OdinAnnihilate                = "OdinAnnihilate";
const string c_AB_OdinRagnarokMissiles          = "OdinRagnarokMissiles";
const string c_AB_OdinNuclearBlast              = "OdinNuclearBlast";
const string c_AB_TychusDrakkenLaserDrill       = "TychusDrakkenLaserDrill";

//---------------------------------------------------------------------------------------------
bool TychusGrenade (int player, unit aiUnit, unitgroup scanGroup) {
    // Place bomb that detonates after a delay
    order spellOrd;
    unit targetHero;
    point targetPos;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TychusGrenade, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Shoot any nearby hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 7.0, healthReq, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetPos = UnitGroupCenterOfGroup(scanGroup);
        if (CountEnemiesInArea(player, scanGroup, targetPos, 2.0, 0, 1) < 3) {
            return false;
        }
    }
    else {
        targetPos = UnitGetPosition(targetHero);
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
bool TychusRunAndGun (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Dashes to target position and winds up his gun
    order spellOrd;
    order attackOrd;
    point retreatPos;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TychusRunAndGun, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Dash to safety if low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 5.0); 

        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }
    
    // Don't jump into battle if near a tower or have low health
    if (nearEnemyTower) {
        return false;
    }
	
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 50.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool TychusOverkillTargeted (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages all enemies in front of hero over next few seconds
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TychusOverkillTargeted, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
   
    // Don't use for now.
    // Causes AI to walk too far forward while laning due to attack move not being able to aquire
    //     a target while this ability is active
    return false;
   
    // attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    // if (!UnitOrderIsValid(aiUnit, attackOrd)) {
    //     return false;
    // }

    // targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 6.0, 100.0, -1, -1, false);
    // if (!UnitIsValid(targetHero)) { 
    //     return false;
    // } 

    // only activate if there are other nearby enemies for it to hit
    // if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetHero), 5.0, 3, 1) < 5) {
    //     return false;
    // }

    // return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool TychusOdin (int player, unit aiUnit, unitgroup scanGroup) {
    // Transform into odin
    order spellOrd;
    order attackOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TychusOdin, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
     
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 9.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 9.0, 1, 0) < 2) {
        return false;
    }

    // Attack nearest hero after transforming
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 9.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    // deliberately setting the attackOrd target here and passing in "null, null" for the target since odin itself has no target
    OrderSetTargetUnit(attackOrd, targetHero);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool OdinAnnihilate (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals AoE damage in a line
    order spellOrd;
    point center;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_OdinAnnihilate, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    center = UnitGroupCenterOfGroup(scanGroup);

    if (CountEnemiesInArea(player, scanGroup, center, 4.0, 2, 1) < 4) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool OdinRagnarokMissiles (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages all nearby enemies
    order spellOrd;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_OdinRagnarokMissiles, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast if there is a weak hero
    nearbyHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 30.0, -1, -1, false);   
    if (UnitIsValid(nearbyHero)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }

    // Cast if there are many enemies in the area
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 8.0, 2, 1) < 4) {
        return false;
    }  

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool OdinNuclearBlast (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals aoe damage to target location after a delay
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_OdinNuclearBlast, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
     
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 10.0, 100.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    // Only cast if it will hit many enemies
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetHero), 5.0, 4, 1) < 8) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool TychusDrakkenLaserDrill (int player, unit aiUnit, unitgroup scanGroup) {
    // Places a turret that shoots nearby enemies
    order spellOrd;
    unit targetHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TychusDrakkenLaserDrill, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }    
    
    // Place on a group of nearby heroes
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 5.0, 100.0, -1, -1, false);    
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 
    
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(targetHero), 6.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
void AIThinkTychus (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }
    
    if (OdinRagnarokMissiles(player, aiUnit, scanGroup)) {
        return;
    }    

    if (OdinAnnihilate(player, aiUnit, scanGroup)) {
        return;
    }    
    
    if (OdinNuclearBlast(player, aiUnit, scanGroup)) {
        return;
    }

    if (TychusRunAndGun(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }
    
    if (TychusGrenade(player, aiUnit, scanGroup)) {
        return;
    }
        
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TychusDrakkenLaserDrill(player, aiUnit, scanGroup)) {
        return;
    }
       
    if (TychusOverkillTargeted(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TychusOdin(player, aiUnit, scanGroup)) {
            return;
        }

        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Tyrande Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_TyrandeLightofElune   = "TyrandeLightofElune";
const string c_AB_TyrandeSentinelShot   = "TyrandeSentinelShot";
const string c_AB_TyrandeLunarFlare     = "TyrandeLunarFlare";
const string c_AB_TyrandeShadowStalk    = "TyrandeShadowstalk";
const string c_AB_TyrandeStarfall       = "TyrandeStarfall";

//---------------------------------------------------------------------------------------------
bool TyrandeLightofElune (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals target ally and self
    unit targetUnit;
    order ord;
    fixed targetHealth;

    ord = StormHeroAICreateOrder(player, c_AB_TyrandeLightofElune, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weak nearby ally to heal
    targetUnit = FindBestHealTarget(player, aiUnit, 10.0);
    if (!UnitIsValid(targetUnit)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null);
}

//---------------------------------------------------------------------------------------------
bool TyrandeSentinelShot (int player, unit aiUnit, unitgroup scanGroup) {
    // Shoots an owl across the map that gives vision and damages an enemy hero if it hits them
    order spellOrd;
    order attackOrd;
    unit targetHero;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TyrandeSentinelShot, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Use on a weak enemy hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {         
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool TyrandeLunarFlare (int player, unit aiUnit, unitgroup scanGroup) {
    // Skillshot that damages/stuns first target hit
    order spellOrd;
    order attackOrd;
    unit targetHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyrandeLunarFlare, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 10.0, 50.0, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
bool TyrandeShadowStalk (int player, unit aiUnit, unitgroup scanGroup) {
    // Cloaks all nearby allies
    order spellOrd;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyrandeShadowStalk, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 8.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 8.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool TyrandeStarfall (int player, unit aiUnit, unitgroup scanGroup) {
    // Damage/slow nearby enemies
    order spellOrd;
    order attackOrd;
    unitgroup nearbyEnemies;
    unitgroup nearbyEnemyHeroes;
    point center;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyrandeStarfall, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 10.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 10.0, 1, 0) < 2) {
        return false;
    }

    // Center on heroes are any is nearby
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 7.0);
    nearbyEnemyHeroes = UnitGroupFilterHeroes(nearbyEnemies);
    
    if (UnitGroupCount(nearbyEnemyHeroes, c_unitCountAll) == 0) {
        return false;
    }

    center = UnitGroupCenterOfGroup(nearbyEnemyHeroes);
    
    if (CountEnemiesInArea(player, scanGroup, center, 6.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
void AIThinkTyrande (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (TyrandeLightofElune(player, aiUnit, scanGroup)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }    

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (TyrandeLunarFlare(player, aiUnit, scanGroup)) {
        return;
    }

    if (TyrandeSentinelShot(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TyrandeShadowStalk(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {       
        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TyrandeStarfall(player, aiUnit, scanGroup)) {
            return;
        }

        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Faerie Dragon Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_FaerieDragonArcaneFlare       = "FaerieDragonArcaneFlare";
const string c_AB_FaerieDragonPolymorph         = "FaerieDragonPolymorph";
const string c_AB_FaerieDragonPixieDust         = "FaerieDragonPixieDust";
const string c_AB_FaerieDragonBlinkHeal         = "FaerieDragonBlinkHeal";
const string c_AB_FaerieDragonBlinkHealDash     = "FaerieDragonBlinkHealDash";
const string c_AB_FaerieDragonEmeraldWind       = "FaerieDragonEmeraldWind";

//---------------------------------------------------------------------------------------------
bool FaerieDragonArcaneFlare (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to target area
    order spellOrd;
    unit targetHero;
    point targetPos;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_FaerieDragonArcaneFlare, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        healthReq = 95.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Shoot any nearby hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {
        // Shoot a group of minions if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetPos = UnitGroupCenterOfGroup(scanGroup);
        if (CountEnemiesInArea(player, scanGroup, targetPos, 2.5, 0, 1) < 3) {
            return false;
        }
    }
    else {
        targetPos = UnitGetPosition(targetHero);
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
bool FaerieDragonPolymorph (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages and debuffs target unit
    order spellOrd;
    fixed healthPercent = 50;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_FaerieDragonPolymorph, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
        
    // Always cast while retreating from attacker
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        healthPercent = 100.0;
    }

    // Always cast if there is a team fight
    if (HaveBeenAttackedRecently(aiUnit) && CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) >= 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) >= 2) {
        healthPercent = 100.0;
    }

    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, healthPercent, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool FaerieDragonPixieDust (int player, unit aiUnit, unitgroup scanGroup) {
    // Increases move speed and blocks a few incoming attacks, use if any ally has been attacked recently and is below 70% health
    //  Future use could be to chase down a fleeing enemy
    order ord;
    unit targetUnit;
    
    ord = StormHeroAICreateOrder(player, c_AB_FaerieDragonPixieDust, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weakest nearby ally hero that is being attacked
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 70.0, -1.0, false);

    if (UnitIsValid(targetUnit)) {
        if (HaveBeenAttackedRecently(targetUnit)) {
            if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null)) {
                return true;
            }
        }
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool FaerieDragonBlinkHeal (int player, unit aiUnit, unitgroup scanGroup) {
    // Gives us the ability to dash to nearby ally heros and heal them for 10 seconds.
    //   In the future could be used to save ourselves, but for now just used to heal allies
    order ord;
    unit targetUnit;
    fixed targetHealth;
    
    ord = StormHeroAICreateOrder(player, c_AB_FaerieDragonBlinkHeal, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) > 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2) {        
        // And any damaged ally hero
        targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 80.0, -1.0, false);
        if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null)) {
            return true;
        }
    }

    // Or if we find a nearby weak ally hero that is under attacked
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 65.0, -1.0, false);
    if (UnitIsValid(targetUnit)) {
        if (HaveBeenAttackedRecently(targetUnit)) {
            if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null)) {
                return true;
            }
        }
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool FaerieDragonBlinkHealDash (int player, unit aiUnit, unitgroup scanGroup) {
    // Dash to an ally giving them a heal & a hot
    order ord;
    unit targetUnit;
    
    ord = StormHeroAICreateOrder(player, c_AB_FaerieDragonBlinkHealDash, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weakest nearby ally hero that is being attacked
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 70.0, -1.0, false);

    if (UnitIsValid(targetUnit)) {
        if (HaveBeenAttackedRecently(targetUnit)) {
            if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, targetUnit, null)) {
                return true;
            }
        }
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool FaerieDragonEmeraldWind (int player, unit aiUnit, unitgroup scanGroup) {
    // AOE around hero that damages and pushes back enemies
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_FaerieDragonEmeraldWind, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
  
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkFaerieDragon (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (FaerieDragonPixieDust(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && FaerieDragonBlinkHeal(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && FaerieDragonBlinkHealDash(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }    
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (FaerieDragonPolymorph(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (FaerieDragonArcaneFlare(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && FaerieDragonEmeraldWind(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Tyrael Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_TyraelElDruinsMight                      = "TyraelElDruinsMight";
const string c_AB_TyraelElDruinsMightAngelicFlash          = "ElDruinsMightAngelicFlash";
const string c_AB_TyraelElDruinsMightAngelicFlashMastery   = "ElDruinsMightAngelicFlashMastery";
const string c_AB_TyraelRighteousness                       = "TyraelRighteousness";
const string c_AB_TyraelBlessingofSacrafice                 = "TyraelBlessingofSacrifice";
const string c_AB_TyraelJudgement                           = "TyraelJudgement";
const string c_AB_TyraelSanctification                      = "TyraelSanctificationUpdated";
const string c_AB_TyraelSanctificationCancel                = "CancelSanctification";

const string c_EldruinSwordUnit                             = "TyraelEldruinSword";
const string c_SanctificationBehavior                       = "SanctificationCaster";

//---------------------------------------------------------------------------------------------
bool TyraelElDruinsMight (int player, unit aiUnit, unitgroup scanGroup) {
	// Throw sword damaging and slowing all enemies in target area
    order spellOrd;
    point retreatPos;
    unit targetHero;
    point targetPos;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelElDruinsMight, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    // Throw sword toward spawn if low health to use the teleport to retreat faster
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 8.0); 

        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }
 
    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }
    
    // Shoot any nearby hero
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, false);    
    if (!UnitIsValid(targetHero)) {
        // Shoot a group of minions if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetPos = UnitGroupCenterOfGroup(scanGroup);
        if (CountEnemiesInArea(player, scanGroup, targetPos, 2.5, 0, 1) < 3) {
            return false;
        }
    }
    else {
        targetPos = UnitGetPosition(targetHero);
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, targetPos);
}

//---------------------------------------------------------------------------------------------
bool ElDruinsMightAngelicFlash (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
    // Teleports Tyrael to where the sword was thrown
    order spellOrd;
    order attackOrd;
    unitgroup nearbySwords;
    unit sword;
    fixed healthPercent = UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent);
    unitgroup enemiesNearSword;
    unit targetHero;
    fixed heroToHomeDistance;
    fixed swordToHomeDistance;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelElDruinsMightAngelicFlash, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelElDruinsMightAngelicFlashMastery, 0);
        if (!UnitOrderIsValid(aiUnit, spellOrd)) {
            return false;
        }
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    // Find the position of his sword
    nearbySwords = UnitGroup(c_EldruinSwordUnit, player, RegionCircle(UnitGetPosition(aiUnit), 500.0), UnitFilter(0, 0, (1 << c_targetFilterMissile), (1 << (c_targetFilterDead - 32)) | (1 << (c_targetFilterHidden - 32))), 0);
    if (UnitGroupCount(nearbySwords, c_unitCountAll) == 0) {
        return false;
    }

    sword = UnitGroupUnit(nearbySwords, 1);
    
    // Teleport if the sword is near a weak enemy
    if (!nearEnemyTower &&  healthPercent > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        enemiesNearSword = EnemyUnitsInArea(scanGroup, UnitGetPosition(sword), 3.0);
        targetHero = FindClosestWeakHero(enemiesNearSword, sword, 0.0, 3.0, 50.0, -1, -1, false);
    
        if (UnitIsValid(targetHero)) {
            // deliberately setting the attackOrd target here and passing in "null, null" for the target
            //      since ReturnSword itself has no target
            OrderSetTargetUnit(attackOrd, targetHero);
            if (!UnitOrderIsValid(aiUnit, attackOrd)) {
                return false;
            }
            
            return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueReplace, null, null);        
        }
    }
    
    // Teleport if you are low health and your sword is closer to home
    if (healthPercent < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        heroToHomeDistance = DistanceBetweenPoints(UnitGetPosition(aiUnit), libGame_gv_players[player].lv_spawnPoint);
        swordToHomeDistance = DistanceBetweenPoints(UnitGetPosition(sword), libGame_gv_players[player].lv_spawnPoint);
    
        if (swordToHomeDistance < heroToHomeDistance) {
            return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);                
        }
    }
   
    return false;
}

//---------------------------------------------------------------------------------------------
bool TyraelRighteousness (int player, unit aiUnit, unitgroup scanGroup) {
    // Casts a temporary shield on self
    order spellOrd;
  
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelRighteousness, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Use if you are being attacked while retreating
    if (!HaveBeenAttackedRecently(aiUnit) || UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);
}

//---------------------------------------------------------------------------------------------
bool TyraelBlessingofSacrifice (int player, unit aiUnit, unitgroup scanGroup) {
    // Cast buff on ally. Tyrael absorbs half of the damage done to target hero. Boosts health regen of Tyrael and target.
    unit targetUnit;
    order spellOrd;
  
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelBlessingofSacrafice, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Use on an ally that is trying to retreat
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 9.0, libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty], -1, false);
    if (!UnitIsValid(targetUnit) || !HaveBeenAttackedRecently(targetUnit)) {  
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetUnit, null);
}

//---------------------------------------------------------------------------------------------
bool TyraelJudgement (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages and stuns enemy. Knocks back all surrounding enemies.
    order spellOrd;
    order attackOrd;
    unit targetHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelJudgement, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 1.0, 11.0, 50.0, -1, -1, true);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool TyraelSanctification (int player, unit aiUnit, unitgroup scanGroup) {
    // Makes nearby allies invulnerable while channeling
    order spellOrd;
    unitgroup nearbyAllies;
    unitgroup nearbyAllyHeroes;
    point position = UnitGetPosition(aiUnit);
    point center;
    point behindDirection;
    point behindHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelSanctification, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Only activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 5.0, 1, 0) < 2) {
        return false;
    }
 
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIChannelTyraelSanctification (int player, unit aiUnit, unitgroup scanGroup) {
    // Give up channeling Santification
    order spellOrd;
    bool cancel = false;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_TyraelSanctificationCancel, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return;
    }
    
    if (CountAlliesInArea(player, UnitGetPosition(aiUnit), 8.0, 1, 0) < 2) {
        cancel = true;
    }
    
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        cancel = true;
    }

    if (cancel) {
        HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }
}

//---------------------------------------------------------------------------------------------
void AIThinkTyrael (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    // If Tyrael is channeling Sanctification check if he should cancel it
    if (UnitHasBehavior(aiUnit, c_SanctificationBehavior) == true) {
        AIChannelTyraelSanctification(player, aiUnit, scanGroup);
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }    
    
    if (TyraelBlessingofSacrifice(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (ElDruinsMightAngelicFlash(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }

    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (TyraelRighteousness(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TyraelSanctification(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (TyraelElDruinsMight(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && TyraelJudgement(player, aiUnit, scanGroup)) {
            return;
        }

        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Sonya Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_BarbarianBash                     = "BarbarianSeismicSlam";
const string c_AB_BarbarianWhirlwind                = "BarbarianWhirlwind";
const string c_AB_BarbarianWhirlwindCancel          = "BarbarianWhirlwindCancel";
const string c_AB_BarbarianAncientSpear             = "BarbarianAncientSpear";
const string c_AB_BarbarianLeap                     = "BarbarianLeap";
const string c_AB_BarbarianWrathOfTheBeserker       = "BarbarianWrathoftheBerserker";
const string c_AB_BarbarianShotofFury               = "BarbarianShotofFury";

const string c_BarbarianWhirlwindChannelBehavior    = "BarbarianWhirlwindChannel";

//---------------------------------------------------------------------------------------------
bool BarbarianBash (int player, unit aiUnit, unitgroup scanGroup) {
    // Stong melee attack that generates fury
    order spellOrd;
    order attackOrd;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_BarbarianBash, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    // Use on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 3.0, 100.0, -1, -1, false);    
    if (!UnitIsValid(targetEnemy)) {        
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 3.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool BarbarianWhirlwind (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE that damages nearby enemies over the next few seconds.
    order ord;
    
    ord = StormHeroAICreateOrder(player, c_AB_BarbarianWhirlwind, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    
    // If there are not enough enemy units around the hero, do not cast
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 3.5, 2, 1) < 4) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null);
}

//--------------------------------------------------------------------------------------------
void AIChannelBarbarianWhirlwind (int player, unit aiUnit, unitgroup scanGroup) {
    order cancelOrd;
    bool cancel = false;
    
    // If there are not enough enemy units around the hero, do not cast
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 4.5, 2, 1) == 0) {
        cancel = true;
    }
    
    // Stop chanelling if in danger
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        cancel = true;
    }    

    if (cancel) {
        cancelOrd = StormHeroAICreateOrder(player, c_AB_BarbarianWhirlwindCancel, 0);
        if (!UnitOrderIsValid(aiUnit, cancelOrd)) {
            return;
        }
        
        HeroIssueOrder (player, aiUnit, cancelOrd, null, c_orderQueueAddToFront, null, null);
    }
}

//---------------------------------------------------------------------------------------------
bool BarbarianAncientSpear (int player, unit aiUnit, unitgroup scanGroup) {
    // Skillshot that drags Barbarian to the target it hits
    order spellOrd;
    order attackOrd;
    unit targetHero;    
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_BarbarianAncientSpear, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
     
    // Don't jump into enemies if we are low health
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 60.0) { 
        return false;
    }
     
    // grab nearest enemy if they are low health
    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 3.0, 8.0, 50.0, -1, -1, true);
    if (!UnitIsValid(targetHero)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetHero, UnitGetPosition(targetHero));
}

//---------------------------------------------------------------------------------------------
bool BarbarianLeap (int player, unit aiUnit, unitgroup scanGroup, bool nearEnemyTower) {
   // Moves leaps to target dealing damage to it
    order spellOrd;
    order attackOrd;
    point retreatPos;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_BarbarianLeap, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Jump towards your spawn for safety if you are low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        // Find position that is 6 units from current position towards the base
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 8); 

        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }
    
    // Don't jump into battle if near a tower or have low health
    if (nearEnemyTower) {
        return false;
    }
	
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }
    
    // Find a target hero 
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 9.0, 50.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }    
         
    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool BarbarianWrathOfTheBeserker (int player, unit aiUnit, unitgroup scanGroup) {
    // Increase attack speed and become invulnerable to CC effects
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_BarbarianWrathOfTheBeserker, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
  
    // Activate if there is a team fight
    if (!HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool BarbarianShotofFury (int player, unit aiUnit, unitgroup scanGroup) {
    // Instantly gain fury
    order spellOrd;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);

    spellOrd = StormHeroAICreateOrder(player, c_AB_BarbarianShotofFury, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    if (energy > libAIAI_gv_aIHeroHighEnergyPercent) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkBarbarian (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UnitHasBehavior(aiUnit, c_BarbarianWhirlwindChannelBehavior) == true) {
        AIChannelBarbarianWhirlwind(player, aiUnit, scanGroup);
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }  
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }  
    
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && BarbarianLeap(player, aiUnit, scanGroup, nearEnemyTower)) {
        return;
    }  

    if (BarbarianShotofFury(player, aiUnit, scanGroup)) {
        return;
    }  

    if (BarbarianBash(player, aiUnit, scanGroup)) {
        return;
    }  

    if (BarbarianAncientSpear(player, aiUnit, scanGroup)) {
        return;
    } 

    if (BarbarianWhirlwind(player, aiUnit, scanGroup)) {
        return;
    }      
    
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {   
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }

        if (libAIAI_gf_HeroAIShouldUseUltimates(player) && BarbarianWrathOfTheBeserker(player, aiUnit, scanGroup)) {
            return;
        }  
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Raynor Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_RaynorPenetratingRound     = "RaynorPenetratingRound";
const string c_AB_RaynorInspire              = "RaynorInspire";
const string c_AB_RaynorAdrenalineRush       = "RaynorAdrenalineRush";
const string c_AB_RaynorHyperion             = "RaynorHyperion";
const string c_AB_RaynorRaynorsBanshee       = "RaynorRaynorsBanshee";

//---------------------------------------------------------------------------------------------
bool RaynorPenetratingRound (int player, unit aiUnit, unitgroup scanGroup) {
    // Skill-shot, high damage single target
    order spellOrd;
    unit targetEnemy;
    fixed energy = UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent);
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_RaynorPenetratingRound, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
 
    if (energy > libAIAI_gv_aIHeroLowEnergyPercent) {
        healthReq = 100.0;
    }
    else {
        healthReq = 60.0;
    }

    // Shoot any nearby enemy if there is a clear shot
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, healthReq, -1, -1, true);
    if (!UnitIsValid(targetEnemy)) {
        // Shoot a minion if high mana
        if (energy < libAIAI_gv_aIHeroHighEnergyPercent) {
            return false;
        }
         
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool RaynorInspire (int player, unit aiUnit, unitgroup scanGroup) {
    // Buffs the attack and movement speed of all nearby allies
    order spellOrd;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_RaynorInspire, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Use to run faster w \hile retreating
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);
    }
    
    // Activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 10.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 10.0, 1, 0) < 2) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);
}

//---------------------------------------------------------------------------------------------
bool RaynorAdrenalineRush (int player, unit aiUnit, unitgroup scanGroup) {
    // Cast heal over time on self
    order spellOrd;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_RaynorAdrenalineRush, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > 50.0) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool RaynorHyperion (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to large area in front of hero
    order spellOrd;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_RaynorHyperion, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    center = UnitGroupCenterOfGroup(scanGroup);

    if (CountEnemiesInArea(player, scanGroup, center, 10.0, 3, 0) < 6) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool RaynorRaynorsBanshee (int player, unit aiUnit, unitgroup scanGroup) {
    // Spawns two banshees that help raynor
    order spellOrd;
    unitgroup nearbyEnemies;
    point center;

    spellOrd = StormHeroAICreateOrder(player, c_AB_RaynorRaynorsBanshee, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
   
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 8.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);
    
    if (CountEnemiesInArea(player, scanGroup, center, 6.0, 2, 0) < 4) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkRaynor (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }  
    
    if (RaynorAdrenalineRush(player, aiUnit, scanGroup)) {
        return;
    }      
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }  
    
    if (RaynorInspire(player, aiUnit, scanGroup)) {
        return;
    } 

    if (RaynorPenetratingRound(player, aiUnit, scanGroup)) {
        return;
    } 
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && RaynorHyperion(player, aiUnit, scanGroup)) {
        return;
    } 

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && RaynorRaynorsBanshee(player, aiUnit, scanGroup)) {
        return;
    }
    
    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);

    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {   
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Abathur Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_AbathurSymbiote               = "AbathurSymbiote";
const string c_AB_AbathurToxicNest              = "AbathurToxicNest";
const string c_AB_AbathurDeepTunnel             = "AbathurDeepTunnel";
const string c_AB_AbathurUltimateEvolution      = "AbathurUltimateEvolution";

const string c_AB_AbathurAssumingDirectControlCancel = "AbathurAssumingDirectControlCancel";

const string c_AbathurAssumingDirectControlBehavior       = "AssumingDirectControlBehavior";

//---------------------------------------------------------------------------------------------
bool AbathurSymbiote (int player, unit aiUnit, unitgroup scanGroup) {
    // Place a symbiote on an ally hero that can cast abilities
    order spellOrd;
    unit allyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurSymbiote, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Don't start channeling if there is an enemy likely to attack Abathur
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 12.0, 1, 1) > 0) {
        return false;
    }
    
    allyHero = libAIAI_gf_HeroAIGetRandomAllyHeroInBattle(player, 500.0);
    if (!UnitIsValid(allyHero)) {
        return false;
    }
     
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, allyHero, null);
}

//---------------------------------------------------------------------------------------------
bool AbathurToxicNest (int player, unit aiUnit, unitgroup scanGroup) {
    // Places a proximity bomb anywhere on the map
    order spellOrd;
    point contentionPoint;
    region nearContentionPoint;
    point position;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurToxicNest, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
     
    contentionPoint = libAIAI_gf_HeroAIGetRandomContentionPoint(player, 70.0);
    if (contentionPoint == null) {
        return false;
    }

    nearContentionPoint = RegionCircle(contentionPoint, 4.0);
    position = RegionRandomPoint(nearContentionPoint);
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, position);
}

//---------------------------------------------------------------------------------------------
bool AbathurDeepTunnel (int player, unit aiUnit, unitgroup scanGroup) {
    // Teleport to the target position
    order spellOrd;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurDeepTunnel, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Currently unused by the Tactical AI
    return false;
}

//---------------------------------------------------------------------------------------------
bool AbathurUltimateEvolution (int player, unit aiUnit, unitgroup scanGroup) {
    // Create a copy of an enemy hero
    order spellOrd;
    unit enemyHero;
    bool casted;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurUltimateEvolution, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    enemyHero = libAIAI_gf_HeroAIGetRandomAllyHeroInBattle(player, 70.0);
    if (!UnitIsValid(enemyHero)) {
        return false;
    }
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, enemyHero, null);
}

//---------------------------------------------------------------------------------------------
void AIChannelAbathurAssumingDirectControl (int player, unit aiUnit, unitgroup scanGroup) {
    order cancelOrd;
    
    // Symbiote
    if (UnitIsValid(abathurSymbioteUnit[player - 1])) {
        cancelOrd = StormHeroAICreateOrder(player, c_AB_AbathurAssumingDirectControlCancel, 0);
        if (!UnitOrderIsValid(abathurSymbioteUnit[player - 1], cancelOrd)) {
            return;
        }

        if (HaveBeenAttackedRecently(aiUnit) || CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 8.0, 1, 1) > 0)  {
            UnitIssueOrder(abathurSymbioteUnit[player - 1], cancelOrd, c_orderQueueReplace);   
        }
    }

    // Ultimate evolution
    if (UnitIsValid(abathurUltimateEvolutionUnit[player - 1])) {
        // Hook it into the MinionAI if it has not been yet
        if (libAIAI_gf_MinionAIGetIndexForMinion(abathurUltimateEvolutionUnit[player - 1]) == 0) {
            libAIAI_gf_MinionAIStartMinionAISimple(abathurUltimateEvolutionUnit[player - 1]);
        }

        cancelOrd = StormHeroAICreateOrder(player, c_AB_AbathurAssumingDirectControlCancel, 0);
        if (!UnitOrderIsValid(abathurUltimateEvolutionUnit[player - 1], cancelOrd)) {
            return;
        }

        if (HaveBeenAttackedRecently(aiUnit))  {
            UnitIssueOrder(abathurUltimateEvolutionUnit[player - 1], cancelOrd, c_orderQueueReplace);   
        }
    }    
}

//---------------------------------------------------------------------------------------------
void AIThinkAbathur (int player, unit aiUnit, unitgroup scanGroup) {
    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UnitHasBehavior(aiUnit, c_AbathurAssumingDirectControlBehavior)) {
        AIChannelAbathurAssumingDirectControl(player, aiUnit, scanGroup);
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }  
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }  
    
    if (AbathurToxicNest(player, aiUnit, scanGroup)) {
        return;
    }  

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && AbathurUltimateEvolution(player, aiUnit, scanGroup)) {
        return;
    }  

    if (AbathurSymbiote(player, aiUnit, scanGroup)) {
        return;
    }    

    if (UseOffensiveItem(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Illidan Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_IllidanDive                       = "IllidanDive";
const string c_AB_IllidanSweepingStrike             = "IllidanSweepingStrike";
const string c_AB_IllidanEvasion                    = "IllidanEvasion";
const string c_AB_IllidanTheHunt                    = "IllidanTheHunt";
const string c_AB_IllidanMetamorphosisTargeted      = "IllidanMetamorphosisTargeted";

//---------------------------------------------------------------------------------------------
bool IllidanDive (int player, unit aiUnit, unitgroup scanGroup) {
    // Dives at the target and flips to the other side, does a small amount of damage and slows the target
    order spellOrd;
    order attackOrd;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_IllidanDive, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
    if (!UnitOrderIsValid(aiUnit, attackOrd)) {
        return false;
    }

    // Only use if we have enough health charge forward
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    // Only use on a weakened enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, 60.0, -1, -1, false);    
    if (!UnitIsValid(targetEnemy)) { 
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, attackOrd, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool IllidanSweepingStrike (int player, unit aiUnit, unitgroup scanGroup) {
    // Dashes to a point and damages units on the way + gives illidan a minor attack boost
    // Three uses for now:
    //   Run away to live
    //   Catch a weak enemy hero
    //   Damage a group of enemies
    order spellOrd;
    order attackOrd;
    unitgroup nearbyEnemies;
    unit targetEnemy;
    point retreatPos;
    point center;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_IllidanSweepingStrike, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Retreat if you have low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        // Dash back to retreat to safety
        retreatPos = libAIAI_gf_HeroAIGetRetreatPosition(player, 7.0);
       
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, retreatPos);
    }
    
    if (UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < 75.0) { 
        return false;
    }

    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, 60.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        attackOrd = StormHeroAICreateOrder(player, c_Storm_AB_Attack, 0);
        if (!UnitOrderIsValid(aiUnit, attackOrd)) {
            return false;
        }

        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetEnemy, UnitGetPosition(targetEnemy));
    }

    // Or cast on a group of enemies
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 4.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, nearbyEnemies, center, 2.0, 2, 1) < 3) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool IllidanEvasion (int player, unit aiUnit, unitgroup scanGroup) {
    // Dodges incoming attacks for 1.5 seconds, use anything we're under attack and lower health
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_IllidanEvasion, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Use evasion if you have low health
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
    }

    return false;
}

//---------------------------------------------------------------------------------------------
bool IllidanTheHunt (int player, unit aiUnit, unitgroup scanGroup) {
    // Long range dash to target unit that deals damage when hits
    order spellOrd;
    unit targetEnemy;

    spellOrd = StormHeroAICreateOrder(player, c_AB_IllidanTheHunt, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    } 

    // find a target hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 10.0, 60.0, -1, -1, false);
    if (!UnitIsValid(targetEnemy)) {  
        return false;
    }  
    
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetEnemy, null);
}

//---------------------------------------------------------------------------------------------
bool IllidanMetamorphosisTargeted (int player, unit aiUnit, unitgroup scanGroup) {
    // Transform and launch to target location. Deals AoE damage to target location and gives attack and movements speed buff.
    point center;    
    order spellOrd;

    spellOrd = StormHeroAICreateOrder(player, c_AB_IllidanMetamorphosisTargeted, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 10.0, 1, 0) < 2 || CountAlliesInArea(player, UnitGetPosition(aiUnit), 10.0, 1, 0) < 2) {
        return false;
    }
    
    // Dive onto a group of enemies
    center = UnitGroupCenterOfGroup(scanGroup);
    if (CountEnemiesInArea(player, scanGroup, center, 6.0, 3, 1) < 6) {
        return false;
    }
 
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
void AIThinkIllidan (int player, unit aiUnit, unitgroup scanGroup) {

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }

    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (IllidanDive(player, aiUnit, scanGroup)) {
        return;
    }

    if (IllidanSweepingStrike(player, aiUnit, scanGroup)) {
        return;
    }

    if (IllidanEvasion(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && IllidanMetamorphosisTargeted(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && IllidanTheHunt(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (UseOffensiveItem(player, aiUnit, scanGroup)) {
        return;
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Abathur Symbiote Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_AbathurSymbioteStab           = "AbathurSymbioteStab";
const string c_AB_AbathurSymbioteSpikeBurst     = "AbathurSymbioteSpikeBurst";
const string c_AB_AbathurSymbioteCarapace       = "AbathurSymbioteCarapace";

//---------------------------------------------------------------------------------------------
bool AbathurSymbioteStab (int player, unit aiUnit, unitgroup scanGroup) {
    // Skillshot damage
    order spellOrd;
    unit targetEnemy;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurSymbioteStab, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Shoot any nearby enemy if there is a clear shot
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, true);
    if (!UnitIsValid(targetEnemy)) {
        targetEnemy = FindClosestWeakMinion(scanGroup, aiUnit, 0.0, 8.0, 100.0, -1, -1, false);
        if (!UnitIsValid(targetEnemy)) {
            return false;
        }
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, UnitGetPosition(targetEnemy));
}

//---------------------------------------------------------------------------------------------
bool AbathurSymbioteSpikeBurst (int player, unit aiUnit, unitgroup scanGroup) {
    // PB-AOE damage
    order spellOrd;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurSymbioteSpikeBurst, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 4.0, 3, 1) < 3) {
        return false;
    }  

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);   
}

//---------------------------------------------------------------------------------------------
bool AbathurSymbioteCarapace (int player, unit aiUnit, unitgroup scanGroup) {
    // Places a shield on the host
    order spellOrd;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_AbathurSymbioteCarapace, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
     
    // Always cast when off cooldown
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);   
}

//---------------------------------------------------------------------------------------------
void AIThinkAbathurSymbiote (int player, unit aiUnit, unitgroup scanGroup) {
    if (player > libCore_gv_bALMaxPlayers) {
        return;
    }

    abathurSymbioteUnit[player - 1] = aiUnit;

    // Only control if it is an AI player
    if (libAIAI_gf_HeroAIIsAIEnabledForPlayer(player) == false) {
        return;
    }
    
    if (AbathurSymbioteCarapace(player, aiUnit, scanGroup)) {
        return;
    }  

    if (AbathurSymbioteSpikeBurst(player, aiUnit, scanGroup)) {
        return;
    }  

    if (AbathurSymbioteStab(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Li Li Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_LiLiHealingBrew       = "LiLiHealingBrew";
const string c_AB_LiLiCloudSerpent      = "LiLiCloudSerpent";
const string c_AB_LiLiBlindingWind      = "LiLiBlindingWind";
const string c_AB_LiLiJugof1000Cups     = "LiLiJugof1000Cups";
const string c_AB_LiLiWaterDragon       = "LiLiWaterDragon";

//---------------------------------------------------------------------------------------------
bool LiLiHealingBrew (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals a nearby ally, can't target LiLi herself (without a talent at least)
    order ord;
    unit targetUnit;
    
    ord = StormHeroAICreateOrder(player, c_AB_LiLiHealingBrew, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Find weakest nearby ally hero
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 85.0, -1.0, true);

    if (UnitIsValid(targetUnit)) {
        if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null)) {
            return true;
        }
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool LiLiCloudSerpent (int player, unit aiUnit, unitgroup scanGroup) {
    // buffs an ally hero with a cloud serpent that attacks
    //   for now casting on a high hp hero if we have plenty of mana
    order spellOrd;
    unit targetHero;
    point targetPos;
    fixed healthReq;

    spellOrd = StormHeroAICreateOrder(player, c_AB_LiLiCloudSerpent, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // only cast if we have high mana, otherwise save for heals
    if (UnitGetPropertyFixed(aiUnit, c_unitPropEnergyPercent, c_unitPropCurrent) < libAIAI_gv_aIHeroHighEnergyPercent) {
        return false;
    }
    
    // buff nearby ally hero
    targetHero = FindStrongestNearbyAllyHero(player, aiUnit, 8.0, 70.0, false);    
    if (!UnitIsValid(targetHero)) {
        return false;
    }
        
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, targetHero, null);
}

//---------------------------------------------------------------------------------------------
bool LiLiBlindingWind (int player, unit aiUnit, unitgroup scanGroup) {
    // Damages and blinds nearby enemy heroes
    //    Use if we're low on health, in a team fight or, trying to kill a hero
    order spellOrd;
    fixed healthPercent = 40;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_LiLiBlindingWind, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
        
    // Always cast while retreating from attacker
    if (HaveBeenAttackedRecently(aiUnit) && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) < libAIAI_gv_aIHeroTeleportPercent[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        healthPercent = 100.0;
    }

    // Always cast if there is a team fight
    if (HaveBeenAttackedRecently(aiUnit) && CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) >= 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) >= 2) {
        healthPercent = 100.0;
    }

    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 6.0, healthPercent, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
bool LiLiJugof1000Cups (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals all nearby allies, use if any ally is really hurt or if we're in a team fight
    order ord;
    unit targetUnit;
    fixed targetHealth;
    
    ord = StormHeroAICreateOrder(player, c_AB_LiLiJugof1000Cups, 0);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    // Activate if there is a team fight
    if (CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 6.0, 1, 0) > 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 6.0, 1, 0) < 2) {        
        // And any damaged ally hero
        targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 70.0, -1.0, false);
        if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null)) {
            return true;
        }
    }

    // Or if we find a nearby weak ally hero that is under attacked
    targetUnit = FindWeakestNearbyAllyHero(player, aiUnit, 10.0, 55.0, -1.0, false);
    if (UnitIsValid(targetUnit)) {
        if (HaveBeenAttackedRecently(targetUnit)) {
            if (HeroIssueOrder (player, aiUnit, ord, null, c_orderQueueAddToFront, null, null)) {
                return true;
            }
        }
    }
    
    return false;
}

//---------------------------------------------------------------------------------------------
bool LiLiWaterDragon (int player, unit aiUnit, unitgroup scanGroup) {
    // Summons a dragon that charges the nearest enemy hero after 3 seconds and does aoe damage
    order spellOrd;
    fixed healthPercent = 50;
    unit targetHero;

    spellOrd = StormHeroAICreateOrder(player, c_AB_LiLiWaterDragon, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Always cast if there is a team fight
    if (HaveBeenAttackedRecently(aiUnit) && CountEnemiesInArea(player, scanGroup, UnitGetPosition(aiUnit), 9.0, 1, 0) >= 2 && CountAlliesInArea(player, UnitGetPosition(aiUnit), 9.0, 1, 0) >= 2) {
        healthPercent = 100.0;
    }

    targetHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 7.0, healthPercent, -1, -1, false);
    if (!UnitIsValid(targetHero)) { 
        return false;
    } 

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkLiLi (int player, unit aiUnit, unitgroup scanGroup) {
    bool nearEnemyTower;

    if (HeroSkipTactical(player, aiUnit)) {
        return;
    }
    
    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && LiLiJugof1000Cups(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (UseDefensiveItem(player, aiUnit, scanGroup)) {
        return;
    }
        
    if (LiLiHealingBrew(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (HeroSkipOffensiveTactical(player, aiUnit)) {
        return;
    }

    if (LiLiBlindingWind(player, aiUnit, scanGroup)) {
        return;
    }
    
    if (LiLiCloudSerpent(player, aiUnit, scanGroup)) {
        return;
    }

    if (libAIAI_gf_HeroAIShouldUseUltimates(player) && LiLiWaterDragon(player, aiUnit, scanGroup)) {
        return;
    }

    // Don't be offensive if we're near a tower or low health
    nearEnemyTower = NearbyTower(aiUnit,scanGroup);
    if (!nearEnemyTower && UnitGetPropertyFixed(aiUnit, c_unitPropLifePercent, c_unitPropCurrent) > libAIAI_gv_aIHeroMinOffensiveHealth[libAIAI_gv_aIHeroes[player].lv_difficulty]) {
        if (UseOffensiveItem(player, aiUnit, scanGroup)) {
            return;
        }
    }

    if (RevealCloakedEnemies(player, aiUnit, scanGroup)) {
        return;
    }
}


//---------------------------------------------------------------------------------------------
// Vehicle Dragon Tactical
//---------------------------------------------------------------------------------------------
const string c_AB_VehicleDragonFireBreath   = "VehicleDragonFireBreath";
const string c_AB_VehicleDragonDragonRoar   = "VehicleDragonDragonRoar";
const string c_AB_VehicleDragonPunt         = "VehicleDragonPunt";

//---------------------------------------------------------------------------------------------
bool VehicleDragonFireBreath (int player, unit aiUnit, unitgroup scanGroup) {
    // Deals damage to all enemies in a cone
    order spellOrd;
    unitgroup nearbyEnemies;
    unit targetEnemy;
    point center;
    int minEnemyEval;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_VehicleDragonFireBreath, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast on a weak enemy hero
    targetEnemy = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 9.0, 40.0, -1, -1, false);   
    if (UnitIsValid(targetEnemy)) {
        return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, UnitGetPosition(targetEnemy));
    }
    
    // Cast if there is a large number of enemies
    nearbyEnemies = EnemyUnitsInArea(scanGroup, UnitGetPosition(aiUnit), 9.0);
    center = UnitGroupCenterOfGroup(nearbyEnemies);

    if (CountEnemiesInArea(player, nearbyEnemies, center, 4.0, 3, 1) < 3) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueAddToFront, null, center);
}

//---------------------------------------------------------------------------------------------
bool VehicleDragonDragonRoar (int player, unit aiUnit, unitgroup scanGroup) {
    // Heals and buffs nearby allies
    order spellOrd;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_VehicleDragonDragonRoar, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }
    
    // Always cast to at least heal yourself
    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, null, null);
}

//---------------------------------------------------------------------------------------------
bool VehicleDragonPunt (int player, unit aiUnit, unitgroup scanGroup) {
    // Charges up and then punts target back a large distance
    order spellOrd;
    unit nearbyHero;
    
    spellOrd = StormHeroAICreateOrder(player, c_AB_VehicleDragonPunt, 0);
    if (!UnitOrderIsValid(aiUnit, spellOrd)) {
        return false;
    }

    // Cast on a closest hero
    nearbyHero = FindClosestWeakHero(scanGroup, aiUnit, 0.0, 7.0, 100.0, -1, -1, false);   
    if (!UnitIsValid(nearbyHero)) {
        return false;
    }

    return HeroIssueOrder (player, aiUnit, spellOrd, null, c_orderQueueReplace, nearbyHero, null);
}

//---------------------------------------------------------------------------------------------
void AIThinkVehicleDragon (int player, unit aiUnit, unitgroup scanGroup) {
    // Make sure it is a computer hero driving the vehicle
    if (libAIAI_gf_HeroAIIsAIEnabledForPlayer(player) == false) {
        return;
    }

    if (VehicleDragonDragonRoar(player, aiUnit, scanGroup)) {
        return;
    }    
    
    if (VehicleDragonFireBreath(player, aiUnit, scanGroup)) {
        return;
    }    
        
    if (VehicleDragonPunt(player, aiUnit, scanGroup)) {
        return;
    }    
}