Discussion Tatical AI - Make AI autoload firebats into bunkers

Dave312

Censored for your safe viewing
Reaction score
269
I have started looking into creating AI systems and unfortunately there seems to be very little information around on the subject. I have an AI controlled player which starts the game with some bunkers under its control and some marines and firebats placed next to them for the AI to autoload. The problem I'm having is that the marines are loaded into the bunkers but the firebats aren't. There is room left in the bunkers for the firebats to jump in.

I have managed to figure out that the autoload script for the bunker AI is specified in the AI: Tactical AI Function property on the bunker unit in the Data Editor. The value for this property is AIThinkBunker which references this function in the TactTerrAI.galaxy file (I have pasted the relevant script into the spoiler tags below). While I have no problem reading the galaxy script syntax, I can't seem to make head or tails as to why the marines will autoload but not the firebats?

Code:
void AIThinkBunker(int player, unit aiUnit, unitgroup scanGroup) {
    CargoDefend(player, aiUnit, scanGroup, 8, 10, c_emptyString, c_AB_BunkerChange);
}
 
bool CargoDefend (int player, unit aiUnit, unitgroup scanGroup, int searchRange, int loadRange, string wanted, string command) {
    aifilter filter;
    unitgroup nearBunkerGroup;
    int bunkerCount;
    unit unitToCheck;    
    order ord = null;
    bool autoLoad = false;
    bool wantsToBeInBunker;
    unitgroup targetGroup;

    targetGroup = UnitGroupFilterThreat(scanGroup, aiUnit, null, 0);
    targetGroup = UnitGroupFilterRegion(targetGroup, RegionCircle(UnitGetPosition(aiUnit), searchRange), 0);
    
    if (UnitGroupCount(targetGroup, c_unitCountAlive) == 0) { // no nearby enemies.

        //  Both checks are needed because auto loading bunkers is needed on campaign before the
        //  AI is active.....
        if (AIIsCampaign(player)) {
            autoLoad = true;
        }
        else if (AIGetDifficulty(player, c_diffAutoLoadBunkers)) {
            autoLoad = true;
        }

        if (autoLoad && (command == c_AB_BunkerChange)) {
            // handle bunkers on campaign differently.
            unitToCheck = CampaignWantsToBeInBunker(player, aiUnit, UnitCargoGroup(aiUnit), c_bunkerUnload);
            if (unitToCheck != null) {
                ord = AICreateOrder(player, command, e_AB_TransportUnloadUnit); // unload the bunker.
                OrderSetTargetPassenger(ord, unitToCheck);
            }

            if (ord == null) {
                nearBunkerGroup = AIFindUnits(player, wanted, UnitGetPosition(aiUnit), c_campaignBunkerLoadRange, c_noMaxCount);
                if (wanted == c_emptyString) {
                    filter = AIFilter(player);
                    AISetFilterMelee(filter, c_onlyRanged);
                    AISetFilterValidPassenger(filter, aiUnit);
                    nearBunkerGroup = AIGetFilterGroup(filter, nearBunkerGroup);
                }

                unitToCheck = CampaignWantsToBeInBunker(player, aiUnit, nearBunkerGroup, c_bunkerLoad);
                if (unitToCheck != null) {
                    ord = AICreateOrder(player, command, e_AB_TransportLoadUnit); // load the bunker.
                    OrderSetTargetUnit(ord, unitToCheck);
                }
            }
        }
        else { // not a campaign bunker
            if (UnitCargoValue(aiUnit, c_unitCargoSpaceUsed) == 0) { // nothing to unload
                return false;
            }
            ord = AICreateOrder(player, command, e_AB_TransportUnloadAll); // unload bunker
        }
    }
    else { // nearby enemies found.
        if (UnitCargoValue(aiUnit, c_unitCargoSpaceFree) == 0) { // check for space
            return false;
        }

        if (command == c_AB_CommandCenterChange) {
            if (!AIAnyWorkersFleeingNearby(player,UnitGetPosition(aiUnit),8.0)) {
                return false;
            }
        }

        nearBunkerGroup = AIFindUnits(player, wanted, UnitGetPosition(aiUnit), loadRange, c_noMaxCount);
        if (wanted == c_emptyString) {
            filter = AIFilter(player);
            AISetFilterMelee(filter, c_onlyRanged);
            AISetFilterValidPassenger(filter, aiUnit);
            nearBunkerGroup = AIGetFilterGroup(filter, nearBunkerGroup);
        }

        bunkerCount = UnitGroupCount(nearBunkerGroup, c_unitCountAll);
        while (bunkerCount > 0) {
            unitToCheck = UnitGroupUnit(nearBunkerGroup, bunkerCount);
            bunkerCount = bunkerCount - 1;

            if (!UnitIsAlive(unitToCheck)) {
                continue;
            }
            if (AIIsScriptControlled(unitToCheck)) {
                continue;
            }
            if (UnitTestState(unitToCheck, c_unitStateInsideTransport)) {
                continue;
            }
            
            if (command == c_AB_CommandCenterChange) {
                ord = AICreateOrder(player, command, e_AB_TransportLoadAll);
            }
            else {
                ord = AICreateOrder(player, command, e_AB_TransportLoadUnit);
                OrderSetTargetUnit(ord, unitToCheck);
            }
            break;
        }
    }
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }
    AICast(aiUnit, ord, c_noMarker, c_castHold);
    return true;
}
 
unit CampaignWantsToBeInBunker (int player, unit aiUnit, unitgroup bunkerGroup, bool unload) {
    int bunkerCount;
    unit unitToCheck;
    bool wantsToBeInBunker;
    order unitOrder;

    //  When loading, check to see if there is space in the bunker at all.
    //
    if (!unload) {
        if (UnitCargoValue(aiUnit, c_unitCargoSpaceFree) == 0) {
            return null;
        }
    }

    bunkerCount = UnitGroupCount(bunkerGroup, c_unitCountAll);
    while (bunkerCount > 0) {
        unitToCheck = UnitGroupUnit(bunkerGroup, bunkerCount);
        bunkerCount = bunkerCount - 1;

        //  Make sure the unit is alive.
        //
        if (!UnitIsAlive(unitToCheck)) {
            continue;
        }
        if (AIIsScriptControlled(unitToCheck)) {
            continue;
        }
        //  When loading, make sure the unit is not allready in a transport.
        //
        if (!unload) {
            if (UnitTestState(unitToCheck, c_unitStateInsideTransport)) {
                continue;
            }
        }

        //  The unit wants to be somewhere far away, do not load it.
        //
        wantsToBeInBunker = true;

        if (AIControlForceToMove(unitToCheck)) {
            // If the unit is forced to move, it shouldn't be in the bunker even in combat
            wantsToBeInBunker = false;
        }
        else if (!AIUnitIsInCombat(unitToCheck) && !AIUnitIsInCombat(aiUnit)) {
            // Otherwise the unit will only want to be out of the bunker if not combat is happening
            
            //  The unit wants to execute a non attack order.
            unitOrder = UnitOrder(aiUnit, 0);
            if (unitOrder != null && !AIIsAttackOrder(unitOrder)) {
                wantsToBeInBunker = false;
            }
            //  The unit wants to move.
            else if (AIControlWantsToMove(unitToCheck)) {
                wantsToBeInBunker = false;
            }
            // Unit has no home point
            else if (AIGetHomePosition(unitToCheck) == c_nullPoint) {
                wantsToBeInBunker = false;
            }
            // Unit's home point is too far away
            else if (!PointsInRange(UnitGetPosition(aiUnit), AIGetHomePosition(unitToCheck), c_campaignBunkerLoadRange)) {
                wantsToBeInBunker = false;
            }
        }

        //  Do not care about units that want to be in bunker when we want to unload.
        //  Similarly, do not care about units that do not want to be in bunker when we want to load.
        //
        if (wantsToBeInBunker == unload) {
            continue;
        }

        return unitToCheck;
    }
    return null;
}
 

X-maul

AKA: Demtrod
Reaction score
201
Hmm - I'm not 100% into the galaxy script but I cant see any reason why it should difer between the two units. Have you checked the data editor to see if there is something under the unit that makes it not be handled by the AI? (Maybe the AI: AI Evaluation)?
 

Dave312

Censored for your safe viewing
Reaction score
269
Thanks for the suggestion. I have had a look through all the AI related data properties I can think of but nothing seems to help. I tried changing the AI: AI Evaluation property to Marine thinking that it might make the AI think the Firebat was a Marine and load the unit into the bunker but with no success.

The reason why I think the problem has something to do with the script functions above is that it seems to determine whether the selected unit can be loaded by running through various conditions. I have a feeling that one of these conditions is causing the Firebat to be rejected. However the only method I know of debugging the above functions is by trying to recreate them in the trigger editor, which is a fair amount of work.
 

X-maul

AKA: Demtrod
Reaction score
201
You're welcome. I'm sorry I cant help, but recreating it might be the only way to solve this, othervise you could ask Blizzard on their forum? (If that is still possible)
 

Dave312

Censored for your safe viewing
Reaction score
269
I ended up having to recreate it and I managed to trace it down to this section of the code where it finds suitable nearby units to load into the bunker:
Code:
filter = AIFilter(player);
AISetFilterMelee(filter, c_onlyRanged);
AISetFilterValidPassenger(filter, aiUnit);
nearBunkerGroup = AIGetFilterGroup(filter, nearBunkerGroup);
On the second line, it filters the unit group to find only units with a ranged attack. I thought this would have been ok given the Firebat has a range of 2 and the Melee flag is not checked. It turns out that any weapon with a range of 2 or less is considered melee regardless of whether the melee flag is checked. So if I increase the Firebats range to 2.1 it works without any problems.

Using the weapon range to determine if the unit is melee rather than the melee flag seems very backwards to me. Infact, I now have no clue as to what the melee flag actually does. I might post this on the blizzard forums and see if I can get the developers to clarify the reasoning behind this.
 

X-maul

AKA: Demtrod
Reaction score
201
I ended up having to recreate it and I managed to trace it down to this section of the code where it finds suitable nearby units to load into the bunker:
Code:
filter = AIFilter(player);
AISetFilterMelee(filter, c_onlyRanged);
AISetFilterValidPassenger(filter, aiUnit);
nearBunkerGroup = AIGetFilterGroup(filter, nearBunkerGroup);
On the second line, it filters the unit group to find only units with a ranged attack. I thought this would have been ok given the Firebat has a range of 2 and the Melee flag is not checked. It turns out that any weapon with a range of 2 or less is considered melee regardless of whether the melee flag is checked. So if I increase the Firebats range to 2.1 it works without any problems.

Using the weapon range to determine if the unit is melee rather than the melee flag seems very backwards to me. Infact, I now have no clue as to what the melee flag actually does. I might post this on the blizzard forums and see if I can get the developers to clarify the reasoning behind this.
Good to know :) I'm glad you figured it out, this might help some one at a later point :)
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top