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.
  • Monovertex Monovertex:
    How are you all? :D
    +1
  • Ghan Ghan:
    Howdy
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though

      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