AgentAI - ilikegoodfood/CommunityLib GitHub Wiki

Universal Agent AI

The AgentAI class contains a pre-built Agent AI with a wide variety of control features. It utilizes the AIChallenge class, and AITask class, as intermediates between the Universal Agent AI and the tasks and challenges in the base game.

By using these intermediates, each agent type can have unique profile, validity and utility functions for each challenge and task type, as well as the ability to validate Rituals against all locations on the map.

See the AIChallenge page for a breakdown of the class.
See the AITask page for a breakdown of the class.

It also makes use of several structs that store data vital to the Agent AI's operation:

  • The AIData struct contains the data that makes up the AI for a given agent type, includingthe agent type, all AIChallenges, all AITasks, and the ControlParameters struct.
  • The ControlParameters struct provides controls, in the form of boolean values, many aspects of the AI's behaviour, such as whether it should use challenge and or unit profile to choose from only visible challenges and units.
  • The debugProperties struct provides the ability to have certain data from the Agent AI printed to the player log. This can be useful when developing an agent AI, but should always be disabled for public releases.

These structs are listed at the end of this page, in the Structs section.

In order to make use of the Agent AI, you must create an instance of AIData, populate it with AIChallenges, AITasks, and define the ControlParameters, and then register it to the Community Library by using one of the RegisterAgentType functions defined in the AgentAI class.

Note 1: Registering an agent type to the Community Library's Agent AI does not automatically run the AI for that agent type. The agent type's turnTickAI function must call the Agent AI's turnTickAI function. See turnTickAI for more details.

Note 2: The valid and validFor functionality in the AIChallenge is there to control how the AI sees and chooses challenges, but if the challenge that it chooses is invalid, the agent will still abort the challenge on the challenge's turnTick.

Registering an Agent Type

There are four overloads of the AgentAI.RegisterAgentType that allow you to register an agent type to the Community Library's Agent AI. These functions allow the agent AI to be assigned with different levels of data, so you can take your pick to best suit your workflow. The recommended overload is the presented first in the fllowing list:

bool RegisterAgentType(Type tAgent, AIData aiData)
bool RegisterAgentType(Type tAgent, ControlParameters control)
bool RegisterAgentType(Type tAgent, List<AIChallenge> aiChallenges, List<AITask> aiTasks, ControlParameters control)
bool RegisterAgentType(Type tAgent, AIChallenge[] aiChallenges, AITask[] aiTasks, ControlParameters control)

All overloads of the RegisterAGentType function will return false if the provided type is not a subclasss of UA, or if the agent type has already been registered by a different mod.

The minimum amount of data required is the agent type and the control parameters for that agent type. These parameters can still be modified after registration. All of the RegisterAgentAI function overloads that do not take an AIData struct as an input create one when registering the agent type.

The maximum amount of data that you pass in is a complete instance of AIData. All of the values stored within the AIData struct can still be modified after registration.

turnTickAI

In order to actually run the Agent AI for your agent, all you need to do is call the turnTickAI(UA ua) function from within the active instance of the AgentAI class, which can be aquired by using the GetAgentAI() function in the Community Library's ModKernel.

The turnTickAI function will verify that the agent type has been registered to the Community Library's Agent AI, and make will then use the provided AIChallenges, AITasks and ControlParameters to operate.

NOTE: The Comunity Library's Agent AI is capable of running for registered agent types that have no AIChallenges or AITasks assigned to them, as may be case for a combat-only agent type.

The Agent AI is reliant on the following functions being properly overridden and defined in the agent class. If these functions are not overridden, the agent type will have the same attack, disrupt, and bodyguard utilities as the base game's good agents.

  • getVisibleUnits
  • getAttackUtility
  • getBodyguardUtility
  • getDisruptUtility.

Note: The AgentAI will not display unit interactions with a utilty value less than -1000 in the list of available challenges and tasks in the UI panel on the right-hand side of the screen. Return any value les than -1000 to prevent the interaction showing up.

Manipulating an Agent AI

The functions provided to manipulate the agent Ais are all included in the AgentAI class. The most common ones that you will need to know are as follows:

  • bool AddChallengeToAgentType(Type t, AIChallenge aiChallenge) - Returns false if the agent type is not a subtype of UA, has not been registered to the AgentAI class, or an AIChallenge for the same challenge type has already been assigned to that agent type.
  • bool AddChallengesToAgentType(Type t, List<AIChallenge> newAIChallenges) - Returns false if the agent type is not a subtype of UA, has not been registered to the AgentAI class, or an AIChallenge for the same challenge type has already been assigned to that agent type for ALL of the AIChallenges provided.
  • bool AddChallengesToAgentType(Type t, AIChallenge[] newAIChallenges) - Returns false if the agent type is not a subtype of UA, has not been registered to the AgentAI class, or an AIChallenge for the same challenge type has already been assigned to that agent type for ALL of the AIChallenges provided.
  • bool AddTaskToAgentType(Type tAgent, AITask aiTask) - Returns false if the agent type is not a subtype of UA, has not been registered to the AgentAI class, or an AITask for the same task type has already been assigned to that agent type.
  • bool AddTasksToAgentType(Type t, List<AITask> aiTasks) - Returns false if the agent type is not a subtype of UA, has not been registered to the AgentAI class, or an AITAsk has already been assigned to that agent type for ALL of the AITasks provided.
  • bool AddTasksToAgentType(Type tAgent, AITask[] aiTasks) - Returns false if the agent type is not a subtype of UA, has not been registered to the AgentAI class, or an AITAsk has already been assigned to that agent type for ALL of the AITasks provided.
  • bool TryGetAgentType(Type t, out List<AIChallenge> aiChallenges) - Returns false if the agent type is not a subclass of UA, or has not been registered to the AgentAI class, and outputs a null list. Returns true if the agent type has been registered to the AgentAI, and outputs the list of AIChallenges assigned to them.
  • AIChallenge GetAIChallengeFromAgentType(Type t, Type c) - Returns null if the agent type is not a subtype of UA, the agent type has not been registered to the AgentAI class, or there is no AIChallenge for that challenge type, otherwise returns the AIChallenge for that agent and challenge type.

In addition to the above functions, which are used to setup the AgentAI, or modify an AIChallenge added by another mod, the Agent AI is also supported by a full set of hooks.

NOTE: To avoid duplicate AIChallenges or AITasks for any given challenge or task type, do not directly add to the AIChallenge or AITask lists in the AIData returned by TryGetAgentType. Always use AddChallengeToAgentType instead.

Structs

The structs described in this section arecentral to the operation of the Community Library's Agent AI.

AIData

public struct AIData
{
    public Type agentType;
    public List<AIChallenge> aiChallenges;
    public List<AITask> aiTasks;
    public ControlParameters controlParameters;

    public AIData(Type agentType, ControlParameters controlParameters)
    {
        this.agentType = agentType;
        aiChallenges = new List<AIChallenge>();
        aiTasks = new List<AITask>();
        this.controlParameters = controlParameters;
    }

    public AIData(Type agentType, List<AIChallenge> aIChallenges, List<AITask> aiTasks, ControlParameters controlParameters)
    {
        this.agentType = agentType;
        this.aiChallenges = aIChallenges;
        this.aiTasks = aiTasks;
        this.controlParameters = controlParameters;
    }
}

Control Parameters

public struct ControlParameters
{
    public bool considerAllChallenges;
    public bool forceSafeMove;

    public bool respectChallengeVisibility;
    public bool respectDanger;
    public bool respectArmyIntercept;
    public bool respectChallengeAlignment;
    public bool respectTags;
    public bool respectTenets;
    public bool respectTraits;
    public bool valueTimeCost;

    public bool includeDangerousFoe;
    public bool includeNotHolyTask;

    public bool canAttack;
    public bool canDisrupt;
    public bool canGuard;

    public bool hideThoughts;
    public DebugProperties debugProperties;

    public ControlParameters(bool isDark)
    {
        if (isDark)
        {
            {
                considerAllChallenges = false;
                forceSafeMove = false;

                respectChallengeVisibility = false;
                respectDanger = true;
                respectArmyIntercept = true;
                respectChallengeAlignment = false;
                respectTags = true;
                respectTenets = true;
                respectTraits = true;
                valueTimeCost = false;

                includeDangerousFoe = true;
                includeNotHolyTask = false;

                hideThoughts = false;
                debugProperties = new DebugProperties(false);
            }
        }
        else
        {
            {
                considerAllChallenges = true;
                forceSafeMove = false;

                respectChallengeVisibility = true;
                respectDanger = true;
                respectArmyIntercept = true;
                respectChallengeAlignment = true;
                respectTags = true;
                respectTenets = true;
                respectTraits = true;
                valueTimeCost = true;

                includeDangerousFoe = true;
                includeNotHolyTask = false;

                hideThoughts = false;
                debugProperties = new DebugProperties(false);
            }
        }
    }
}

The Control Parameters struct stores a number of booleans that are used to determine the behaviour of the Agent AI, along with a constructor that configures the values to the defaults used for good agents, which inherit from UAG, and evil agents, which inherit from UAEN.

  • considerAllChallenges - If true, the AI will use the base profile, validity, and utility functions of challenge Types for which it has not been given an AIChallenge. Rituals for which an AIChallenge has not been provided can only be tested against the agent's own location, not against all locations.
  • forceSafeMove - If true, the AI will assume that the agent requires a safeMove path to all challenges, regardless of the AIChallenge's own safeMove value.
  • respectChallengeVisibility - If true, the AgentAI will only consider challenges, or locations for rituals, such that the challenge or ritual is visible to it. This makes use of the Profile system.
  • respectDanger - If true, the AgentAI will account for danger when deciding to eprform a challenge.
  • respectArmyIntercept - If true, the AgentAI will account for an agent being intercepted by a hostile army at the challenge location.
  • respectChallengeAlignment - If true, the Agent will consider the isGoodTernary of the challenge when deciding if it should be considered. Evil agents will not perform good challenges, and uncorrupted or unenshadowed good agents will not perform evil challenges.
  • respectTags - If true, the Agent AI will check an agent's likes and dislikes against those of the challenge, and modify the challenge's utility accordingly.
  • respectTenets - If true, the Agent Ai will, for agent's whose society is a holy order, check the holy order's tenets for tenets that modify a the utility of challenges.
  • respectTraits - If true, the Agent AI will check an agent's traits for traits that modify the utility of challenges.
  • valueTimeCost - If true, the time AgentAI will utilize the Time Divisor utility modifier used by good agents. This will also show up correctly in the utility UI popup, on the right hand side of thhe screen.
  • includeDangerousFoe - If true, the AgentAI will consider dangerous foes to be valid for attack interactions. The normal utility penalties for dangerous foes will apply.
  • includeNotHolyTask - If true, the utility of tasks will be effected by the 'Not Holy Task' that Acolytes use to bias them towards doing only holy tasks.
  • canAttack - If ture, the agent AI will check attack utility for all visible units.
  • canDisrupt - If true, the agent AI will check disrupt utility for all visible units.
  • canGuard - If true, the agent AI will check bogyguard utility for all visible units.
  • hideThoughts - If true, the challenge list, for agents of this type, which the player can view will contaijn only a "Veiled Thoughts" dummy object, indicating that the thoughts are hidden.
  • debugProperties - If debugProperties.debug, and any of the other debug control booleans, are true, the appropriate debug messages will be printed to the games log file, irrespective of the Agent AI's overall debug status. This should only be enabled for testing developement of specific units in your mod which make use of the Universal Agent AI, and should be disabled for all release versions.

When registering an Agent Type to the AgentAI, make sure that you have correctly configured the ControlParameters.

These parameters are the same for all agents of that agent type. It is possible to use the agent's own turnTickAI function, or the AgentAI hooks, to dynamically change these parameters for some agents of that type.

To do so, use the agent's own turnTickAI function, or, if you do not have the ability to edit that function, the onAgentAI_StartOfProcess hook, to change it for that specific agent.

If you do this, make sure to change it back to the general configuration in the onAgentAI_EndOfProcess hook.

DebugProperties

public struct DebugProperties
{
    public bool debug;

    public bool outputProfile_AllChallenges;
    public bool outputProfile_VisibleChallenges;
    public bool outputProfile_VisibleAgents;

    public bool outputVisibility_AllChallenges;
    public bool outputVisibility_VisibleChallenges;

    public bool outputValidity_AllChallenges;
    public bool outputValidity_ValidChallenges;

    public bool outputUtility_ValidChallenges;

    public bool outputUtility_VisibleAgentsAttack;
    public bool outputUtility_VisibleAgentsBodyguard;
    public bool outputUtility_VisibleAgentsDisrupt;

    public bool outputValidity_AllTasks;
    public bool outputValidity_ValidTasks;
    public bool outputUtility_ValidTasks;

    public bool outputUtility_ChosenAction;

    public void setOff()
    {
        debug = false;
        outputProfile_AllChallenges = false;
        outputProfile_VisibleChallenges = false;
        outputProfile_VisibleAgents = false;
        outputVisibility_AllChallenges = false;
        outputVisibility_VisibleChallenges = false;
        outputValidity_AllChallenges = false;
        outputValidity_ValidChallenges = false;
        outputUtility_ValidChallenges = false;
        outputUtility_VisibleAgentsAttack = false;
        outputUtility_VisibleAgentsBodyguard = false;
        outputUtility_VisibleAgentsDisrupt = false;
        outputValidity_AllTasks = false;
        outputValidity_ValidTasks = false;
        outputUtility_ValidTasks = false;
        outputUtility_ChosenAction = false;
    }

    public void setOn()
    {
        debug = true;
        outputProfile_AllChallenges = true;
        outputProfile_VisibleChallenges = true;
        outputProfile_VisibleAgents = true;
        outputVisibility_AllChallenges = true;
        outputVisibility_VisibleChallenges = true;
        outputValidity_AllChallenges = true;
        outputValidity_ValidChallenges = true;
        outputUtility_ValidChallenges = true;
        outputUtility_VisibleAgentsAttack = true;
        outputUtility_VisibleAgentsBodyguard = true;
        outputUtility_VisibleAgentsDisrupt = true;
        outputValidity_AllTasks = true;
        outputValidity_ValidTasks = true;
        outputUtility_ValidTasks = true;
        outputUtility_ChosenAction = true;
    }

    public DebugProperties(bool isOn)
    {
        if (isOn)
        {
            debug = true;
            outputProfile_AllChallenges = true;
            outputProfile_VisibleChallenges = true;
            outputProfile_VisibleAgents = true;
            outputVisibility_AllChallenges = true;
            outputVisibility_VisibleChallenges = true;
            outputValidity_AllChallenges = true;
            outputValidity_ValidChallenges = true;
            outputUtility_ValidChallenges = true;
            outputUtility_VisibleAgentsAttack = true;
            outputUtility_VisibleAgentsBodyguard = true;
            outputUtility_VisibleAgentsDisrupt = true;
            outputValidity_AllTasks = true;
            outputValidity_ValidTasks = true;
            outputUtility_ValidTasks = true;
            outputUtility_ChosenAction = true;
        }
        else
        {
            debug = false;
            outputProfile_AllChallenges = false;
            outputProfile_VisibleChallenges = false;
            outputProfile_VisibleAgents = false;
            outputVisibility_AllChallenges = false;
            outputVisibility_VisibleChallenges = false;
            outputValidity_AllChallenges = false;
            outputValidity_ValidChallenges = false;
            outputUtility_ValidChallenges = false;
            outputUtility_VisibleAgentsAttack = false;
            outputUtility_VisibleAgentsBodyguard = false;
            outputUtility_VisibleAgentsDisrupt = false;
            outputValidity_AllTasks = true;
            outputValidity_ValidTasks = true;
            outputUtility_ValidTasks = true;
            outputUtility_ChosenAction = false;
        }
    }
}

ChallengeData

public struct ChallengeData
{
    public AIChallenge aiChallenge;
    public Challenge challenge;
    public Location location;
}

TaskData

public struct TaskData
{
    public AITask aiTask;
    public AITask.TargetCategory targetCategory;
    public Location targetLocation;
    public SocialGroup targetSocialGroup;
    public Unit targetUnit;
}

Hooks

The community library' Hooks class includes two hooks for the Agent AI. These hooks can be used to perform additional custom logic before or after the main body of the Ai has run, such as Deep Ones needing to travel to the ocean to change their move type.

See the Hooks page.

For examples of these hooks in action, see the HooksInternal class, a subclass of Hooks which the CommunityLibrary uses to call it's own hooks.

⚠️ **GitHub.com Fallback** ⚠️