The _AiControl class is used as the base class for the AiControl system node ($AiControl). It has two methods that can be overridden which allow for the creation and retrieval of the _AiAgentRoot node.
method _GetAiAgentRootNode() as NodeRef of Class _AiAgentRoot
The above method fetches the AI Agent Root node, which is returned as a noderef.
method _GetOrMakeAiAgentRootNode() as NodeRef of Class _AiAgentRoot
The above method either gets or makes the AI Agent Root node.
Both of the above methods can be overridden in case it is necessary to create the root node from a different class (for example, to create a different class than _AiAgentRoot in order to override its methods).
_AiControlClassMethods is intended to be registered using the "/aicontrol" command. It implements the required shared functions and has methods that allow you to override or extend the provided functionality. The built-in commands are as follows:
/AiControl MakeAgent <Npc name or ID> [_AiAgent class] -- Gloms the class to the NPC if it isn't already an agent. The class you specify must inherit from _AiAgent. You can just glom the _AiAgent class, though that is probably only useful for very limited testing.
/AiControl Pop <Npc name or ID> -- Calls _PopAgentState on the agent to pop its current state.
/AiControl Push <Npc name or ID> <_AiState class> [arguments] -- Creates a node from the specified _AiState class. That node has the _AiStateFromCommand method called on it, which allows you to set additional properties. The state node is then pushed via _PushAgentState.
/AiControl Territory <Npc name or ID> <territory ID> -- Calls _SetAgentTerritory on the agent, passing in the specified territory.
The _AiAgentRoot class is the class for the node in every area which is associated to the area's root node (via base_hard_association). It is created by $AiControl methods. It has associations to all of the area's agents and a method to wake all agents up (when an area starts).
method _RegisterAgentToRoot(agent as NodeRef of Class _AiAgent)
If the agent does not have a hard association, the base_hard_association is added with the root node as the parent and the agent as a child. The _AiAgents association is always added with the root node as the parent and the agent as a child.
This can be called whenever all agents in an area need to be woken up (on area startup, for example). Waking up an agent causes it to clear out its state stack and make a callback to acquire a new state (assuming its _AgentWakeup method hasn't been overridden to do something different). _WakeupAllAgents uses a timer to wake agents up over time rather than hogging CPU by making them all start at the same time. By default, three agents will wake up every .25 seconds until all agents are awake. Just add these lines of code to your script to wakeup all agents:
var airoot = $AiControl._GetAiAgentRootNode() if airoot != None airoot._WakeupAllAgents() .
The _AiAgent class is the abstract class from which all AI agents should inherit. It should not be used as the only class to control an agent. NPCs (or whatever else has AI) should glom on some class that inherits from _AiAgent. Most likely, you want to make a single agent class which is your own abstract class, so that it alone handles all of the base overriding of _AiAgent methods.
You might have a tree of agent classes like this:
_AiAgent BaseAiAgent DumbCreatureAgent BoarAgent SnakeAgent SnakeOnAPlaneAgent SmartCreatureAgent OrcAgent OrcChiefAgent ElfAgent
_AiAgent, then, exposes the HeroEngine-provided API. BaseAiAgent handles all of your overrides to _AiAgent methods. Each class below that overrides methods as needed for the type of creature it handles. The methods for _AiAgent fall into three broad categories: those you shouldn't have to touch (as far as overriding them in child classes), those that your child classes essentially must implement because the base method doesn't really do anything, and those that support other optional systems (so what you need to do with them depends on how you use those optional systems).
Methods To Use
This method needs to be called by any system that makes agents. It goes to the root node and tells it about the agent so associations can be setup properly (via _RegisterAgentToRoot).
method _PushAgentState(state as NodeRef of Class _AiState, reason as String)
First, the agent can have a _PushAgentGoal method on it which can completely handle this method. If that doesn't happen, the currently running state gets called at _SuspendAiState, the new state is placed on the top of the stack and gets a call at _EnterAiState. The reason string can be used for debugging. The other trick with the reason string is that _PushAgentGoal can decide to push a state with the normal mechanics, and it can do this by calling this method with a well known (to itself) reason string. Then if it gets called with that reason string, it can return false and let the push happen normally to get the state on the stack.
method _DelayPushAgentState(state as NodeRef of Class _AiState, reason as String)
The state is placed in the agent's pending state list. This list is checked when a state is popped. Useful for allowing an agent to complete its current state before starting the new one.
method _PopAgentState(reason as String)
Like the push method, the agent is allowed an override at _PopAgentGoal first. If that doesn't happen, then the current state is called at _ExitAiState The pending state list is checked, and if there is a state waiting, it is pushed. If there isn't a pending state, then the next state in the stack is called at _ResumeAiState (which gets the popped state as a parameter). Finally, the popped state node is destroyed.
method _ReplaceAgentState(state as NodeRef of Class _AiState, reason as String)
An override is allowed on the agent at _ReplaceAgentGoal. The agent's current state is called at _ExitAiState and then destroyed. The state passed in is put on top of the stack and called at _EnterAiState. This is sort of like a pop and a push, but without the extra calls that happen to resume the previous state and so on.
method _CurrentAgentState() as NodeRef of Class _AiState
Returns the state at the top of the agent's stack (potentially None).
Methods to Override
All agents have a _AiAgentBrain timer field. The tick function calls this method for processing. This is where the agent can evaluate its surroundings or make other decisions. The default brain time is 5 seconds between each tick. Additionally, the agent can have a goal override of _AgentBrainTickGoal. Generally brain processing should check to see if the agent can go to sleep (or wake up if it's already sleeping), and check the state stack to see if a new state is needed, in addition to evaluating its current states and seeing if anything needs to change.
This method can be called at any time. However, it is intended to be called by the brain tick either when the agent's state stack is empty or when it needs to make some kind of new evaluation based on the environment. Ideally, the method should always result in a new state going on the stack (assuming the stack is empty to being with), though it may be enough to simply stand around and do nothing until the next request for a new state.
By default this method clears the agent's state stack (and any pending states) and resets the brain timer. (Which should mean the brain tick happens soon, which should detect an empty stack and figure out something to do.)
This method does nothing by default, but you may wish to notify states on the stack so they can pause timers or do other things to "shut down" while the agent sleeps.
method _AgentCanSleep() as Boolean
By default, agents can sleep in an edit instance, but otherwise are awake. This is where you would implement criteria for allowing an agent to sleep (because, for example, no players are within some minimum distance).
method _AgentIsAsleep() as Boolean
By default, agents are asleep in an edit instance, but otherwise are awake. You could add fields to your agents to track the sleep status and simply return its value here, but other things may need to be evaluated, so that concept was not implemented at this level.
method _GetAgentTerritory() as NodeRef
The optional creature territory system defines territories using a _Territory2TerritoryDefinition node (territories are then made up of one or more pathfinding nodes, as defined by that node). You may not want to use the territory system in lue of your own design, but if you do use it, these methods are available... otherwise they are safe to ignore. This method simply finds a node associated to the agent via the _AiAgentTerritory association and returns it. Depending on your implementation, that may be sufficient or you may need to change it.
method _SetAgentTerritory(territory as NodeRef)
This simply takes the territory and associates it to the agent via the _AiAgentTerritory association. Depending on your territory implementation, this may or may not be sufficient.
method _GetRandomPointInTerritory() as Vector3
The default behavior for this is to get the agent's territory and, if it is a _Territory2TerritoryDefinition node, it randomly chooses a pathfinding node from the territory and uses the external functions _PathSystem_GetRandomPointInPathFindingNode or _PathSystem_GetRandomPointInRegionNodeto choose a random spot. For other territory types, you would need to override this method and implement the appropriate code.
States are intended to be instantiated from specs via a Spec Oracle, so the class inherits from SpecDerivedObject. It has exactly one other field _AiStateAgent that tracks the agent to which it belongs. This is considered an abstract class from which all actual AI state classes inherit. That field has a getter/setter methods and is marked private. Other methods on states are called when an agent pushes or pops the state.
method _SetAiAgent(agent as NodeRef of Class _AiAgent)
Sets the _AiStateAgent field to the agent. Also uses base_hard_association to associate the state node to the agent node.
method _GetAiAgent() as NodeRef of Class _AiAgent
Called when the state is currently on top of an agent's stack and a new state is pushed on.
Called when the state is first pushed on to the agent's stack.
method _ResumeAiState(previous as NodeRef of Class _AiState)
Called when the state returns to the top of the stack.
Called when the state is popped off of the stack.
method _ValidateAgent(agent as NodeRef) as Boolean
Called when the state is pushed to ensure the state is appropriate to be used by the agent.
method _AiStateFromCommand(args as List of String, error references String) as Boolean
Intended to be called from a slash command (such as /aicontrol) that creates a state on the fly based on input. This method validates the input. Return false to make the state just destroy itself and not be used, and you can set the error string for the reason, which should be displayed to the user by whatever called this method. This allows for a generic way to create states from the command line and each one can accept arbitrary input for manipulating its properties before being given to an agent.