Character Control Script
Contents
- 1 AI overview
- 2 Controller Script Interface
- 3 Goals
- 4 Sub Goals
- 4.1 Unknown Sub Goal
- 4.2 Provoke Attack Sub Goal
- 4.3 Avoid Jump Kick Sub Goal
- 4.4 Knock Off Ledge Sub Goal
- 4.5 Wait And Attack Sub Goal
- 4.6 Rush And Attack Sub Goal
- 4.7 Defend Sub Goal
- 4.8 Surround Target Sub Goal
- 4.9 Escape Surround Sub Goal
- 4.10 Investigate Slow Sub Goal
- 4.11 Investigate Urgent Sub Goal
- 4.12 Investigate Body Sub Goal
- 4.13 Investigate Around Sub Goal
- 4.14 Investigate Attack Sub Goal
AI overview
TODO: This page is a work in progress, and I intend to finish it sooner rather than later. I wanted to save my work to make sure it doesn't get lost
AI in Overgrowth is handled with character controllers.
A character controller is a set of responses to events/stimuli that tell the character what physically to do at a given instant in time. It's sort of like a cockpit for an airplane.
Everything a human can do is technically available to an AI implementation, if it's smart enough to figure out how to properly trigger the right action at the right time.
The AI in Overgrowth is implemented using a goal system, and through simulations of sensory input. It makes decisions on what to do at a given moment in time based on its current goal/subgoal, the character's current status, and observations on the environment (sights, sounds).
- For the player, this control happens in a script
Data/Scripts/playercontrol.as
- For NPCs, this control happens in a script
Data/Scripts/enemycontrol.as
There's a layer of logic below the character controller that dictates what the character is physically allowed to do. That logic triggers events and consequences based on what happens to the character (via the Controller Script Interface), and listens to the inputs from the character controller to direct the character's motion. That base character layer won't be covered here in detail, though we will talk about the events and control queries that are sent from that layer into the Controller Script Interface.
- For both players and NPCs this logic is shared, in a script
Data/Scripts/aschar.as
Controller Script Interface
The functions in this control script interface tell the character what to do. If you implement these functions, then you will control the character.
The rest of the details on this page just detail how we chose to drive these functions in the base character controller scripts. You can feel free to drive them in some other way as well.
Functions called from other scripts
These are called both directly (aschar.as) and via movement_object.Execute(...)
(by the engine, other scripts, and character-to-character calls)
-
bool ActiveBlocking();
-
bool ActiveDodging(int attacker_id);
-
void AIEndAttack();
-
void AIMovementObjectDeleted(int id);
-
void BrainSpeciesUpdate();
-
void ChooseAttack(bool front, string& out attack_str);
-
bool DeflectWeapon();
-
void DrawStealthDebug();
-
vec3 GetDodgeDirection();
-
string GetIdleOverride();
-
vec3 GetTargetJumpVelocity();
-
vec3 GetTargetVelocity();
-
void HandleAIEvent(AIEvent event);
-
int IsAggressive();
-
int IsAggro();
-
bool IsAware();
-
int IsIdle();
-
int IsPassive();
-
int IsUnaware();
-
void MindReceiveMessage(string msg);
-
void ResetMind();
-
void ResetWaypointTarget();
-
void Startle();
-
bool StuckToNavMesh();
-
bool TargetedJump();
-
void UpdateBrain(const Timestep &in ts);
-
bool WantsReadyStance();
-
bool WantsToAccelerateJump();
-
bool WantsToAttack();
-
bool WantsToCancelAnimation();
-
bool WantsToCounterThrow();
-
bool WantsToCrouch();
-
bool WantsToDodge(const Timestep &in ts);
-
bool WantsToDragBody();
-
bool WantsToDropItem();
-
bool WantsToFeint();
-
bool WantsToFlip();
-
bool WantsToFlipOffWall();
-
bool WantsToGrabLedge();
-
bool WantsToJump();
-
bool WantsToJumpOffWall();
-
bool WantsToPickUpItem();
-
bool WantsToRoll();
-
bool WantsToRollFromRagdoll();
-
bool WantsToSheatheItem();
-
bool WantsToStartActiveBlock(const Timestep &in ts);
-
bool WantsToThroatCut();
-
bool WantsToThrowEnemy();
-
bool WantsToThrowItem();
-
bool WantsToUnSheatheItem(int &out src);
-
WalkDir WantsToWalkBackwards();
Data accessed from other scripts
These are accessed by aschar.as mostly. There's only one call to movement_object.Execute(...)
that modifies this data, in the older (no longer used) tutorial level.
Goals
The Goal is the current primary motivation for the AI.
Whenever the character is updated, this is consulted first when making decisions on what to do, before the Sub Goal is checked.
List of Goals:
- Patrol
- Attack
- Investigate
- Get Help (currently disabled - enable by editing the code to
bool kSeekHelpEnabled = true;
) - Escort
- Get Weapon
- Navigate
- Struggle
- Hold Still
- Flee
The inital Goal for all AI is Patrolling.
Patrol Goal
This patrol goal is when the character is on the alert, listening for sounds, looking for ally's bodies, and looking for awake enemies to fight.
The inital Goal for all AI is Patrolling, and this is the mode that they will fall back to when they're done in combat.
TODO: Is there a more compact/cleaner way to do this? Is the rest of this just too much detail?
When Patrol gets set:
- When the character is first spawned
- When the character is reset (through the
void ResetMind();
function) - When the Goal was Attack and the chase target is neutralized, and there are no other targets to pick from
- When the Goal was Struggle or Hold Still and the character was broken free or was released, while still awake
- When debug keys are enabled, the
C
is toggled in the editor, the character was previously not passive, and the character is now being set to to passive, then they will also now be set to patrol - When
void MindReceiveMessage(string msg);
is called for the character, with the message "set_hostile false" (basically when this message is sent to the character withmovement_object.RecieveMessage("...")
) - When the Goal was Investigate
- If the Sub Goal was Investigate Slow, there was an investigate target position, and the character has now reached it (within 1 unit)
- If the Sub Goal was Investigate Around or Investigate Attack, and the Sub Goal was selected more than 10 seconds ago
- When the Goal was Get Help and somehow you no longer have an ally to seek help from (TODO: I don't think this was completely implemented, so it's not clear how this can happen without this goal being switched ahead of this check)
- When
void AIMovementObjectDeleted(int id);
is called, and the player had a goal that was focused on the now-deleted character:
How character behaviors are affected by Patrol:
-
int IsUnaware();
returns 1. Other checks are also made, but this always returns 1 if on patrol -
int IsIdle();
returns 1, otherwise it returns 0 -
string GetIdleOverride();
returns a non-empty value only if the character is on patrol -
vec3 GetTargetVelocity();
returns waypoint based patrol movement if the character is on patrol, and isn't currently being startled -
WalkDir WantsToWalkBackwards();
returns WALK_BACKWARDS or STRAFE if the character is on patrol, they don't have a waypoint target, and they get pushed around. If they aren't on patrol, or they have a waypoint target, it returns FORWARDS -
bool WantsReadyStance();
returns true if the character is not on patrol
How Patrol works:
- Whenever you switch to Patrol from any other goal:
- Clear the patrol path wait timer - immediately begin your patrol route (unless otherwise specified)
- Whenever you switch from Patrol to any other goal:
- If you were asleep (and you're not immediately choked) then you enter the Wake Up state (todo: Link to it? Doc those?) before switching goals
- When your brain updates (at 30fps frequency for AI)
- If you're passive or just spawned or asleep
- Clear chase and look targets (so you don't pay attention to anyone)
- Else
- If you see a knocked out friendly body you haven't seen before
- Yelp, and set Goal to Investigate Sub-Goal to Investigate Urgent
- Else If you see an enemy who is awake, they're not ducking enough, they're not stationary + "invisible_when_stationary", and they're < 4 units from you
- Look in the direction they're going to move to (follow them with your eyes)
- Start incrementing onto the "seen" counter for that particular enemy (the counter is shared between all enemies. It fades over time)
- "invisible_when_stationary" reduces the speed by 2/3
- Inside your FOV is high (0.5 per tick), outside is lower, farther away is lower
- If your goal is patrol, your senses are more dull, so you don't get a 3x alertness buff
- If your "seen" counter > 0.5
- Set the goal to Investigate and sub-goal to Investigate Slow Sub Goal
- If your "seen" counter > 1.0
- Fully notice them - see next section
- If you see a knocked out friendly body you haven't seen before
- If you have a chase target and can see them (or are omniscient)
- Add a history marker so you know where they are and are heading
- Set "ai attacking" to false (so you don't automatically attack while on patrol, until at least after you see someone)
- If you're not on the nav mesh right now, decrement your "give up on finding a new path" timeout
- Set the head look target to the closest character you can find that you know about
- If you're passive or just spawned or asleep
- When you see an enemy for long enough to notice them (fades over time), or you are hit by any attack or when you are hit by any thrown item (always if a wolf or dog, only if you live if you're not), or when you're set to be omniscient by script messages
- Then some extra stuff happens for each of these events, such as taking damage, making noise, and ragdoll impacts. None of it is dependent on whether you are patrolling or not
-
void MindReceiveMessage(string msg);
is called for the character, with the message "notice <target character id>" (when this message is sent to the character withmovement_object.RecieveMessage("...")
)
- Whenever you take any damage, if your goal is Patrol, and if you're not set to passive
- Set the goal to Investigate and set the sub-goal to Investigate Around
- Set your nav position to the current spot, and clear your Investigate Target Id
- When
void MindReceiveMessage(string msg);
is called for the character, with the message "nearby_sound <... lots of args...>" (when a nearby character makes a noise, and you're close enough to be checked)- If you're close enough to hear it (rabbits hear 2x as far, and the "Hearing Modifier" param scales the distance), and if the goal is currently Patrol, and you are not set to be passive, and you aren't in the middle of a just-spawned cool-down, and the the character who made the sound still exists, and you are not static, and you aren't knocked out, and you didn't create the noise yourself
- If the noisy character is not awake, or if the noisy character is on the same team and you already have seen them at some point, and the sound is "foley" or "loudy foley" (not a yelp, etc)
- Ignore the sound and skip the rest of this
- If the sound is "foley" (not loud foley or other sound types) add 0.3 to suspicion Else add 1.0 to suspicion
- If suspicion is > 1.0
- Play the "suspicious" sound, and look in the direction of the sound (which is projected behind the character causing the collision) for 2 seconds
- If the sound is of type "foley" or "loud foley"
- Play the "startle" sound, do the startle action, set the goal to Investigate and set the sub-goal to Investigate Slow
- Else if sound is of type "voice" and the character is awake
- Play the "startle" sound, do the startle action (a longer one than normal), set the goal to Escort, and set the Escort Target Id to the sound creator
- Play the "startle" sound, do the startle action, set the goal to Investigate and set the sub-goal to Investigate Urgent
- Set the Investigate Target Id to -1 (clear it)
- If the noisy character is not awake, or if the noisy character is on the same team and you already have seen them at some point, and the sound is "foley" or "loudy foley" (not a yelp, etc)
- If you're close enough to hear it (rabbits hear 2x as far, and the "Hearing Modifier" param scales the distance), and if the goal is currently Patrol, and you are not set to be passive, and you aren't in the middle of a just-spawned cool-down, and the the character who made the sound still exists, and you are not static, and you aren't knocked out, and you didn't create the noise yourself
- When
void MindReceiveMessage(string msg);
is called for the character, with the message "collided <target character id>" (when a character collides with this character)- If the goal is currently Patrol, and you are not set to be passive, and you aren't in the middle of a just-spawned cool-down, and the the colliding target character still exists and you are not static, and you aren't knocked out, and you didn't somehow collide with yourself
- If the colliding target character is not awake, or if the colliding target character is on the same team and you already have seen them at some point
- Ignore the sound and skip the rest of this
- Add 0.3 to suspicion, since the collision "sound" is a normal foley sound
- If suspicion is > 1.0
- Play the "suspicious" sound, and look in the direction of the sound (which is projected behind the character causing the collision) for 2 seconds
- Play the "startle" sound, set the goal to Investigate and set the sub-goal to Investigate Slow, and set the Investigate Target Id to -1 (clear it)
- If the colliding target character is not awake, or if the colliding target character is on the same team and you already have seen them at some point
- If the goal is currently Patrol, and you are not set to be passive, and you aren't in the middle of a just-spawned cool-down, and the the colliding target character still exists and you are not static, and you aren't knocked out, and you didn't somehow collide with yourself
Attack Goal
TODO: Write this section
Investigate Goal
TODO: Write this section
Get Help Goal
TODO: Write this section
Escort Goal
TODO: Write this section
Get Weapon Goal
TODO: Write this section
TODO: Write this section
Struggle Goal
TODO: Write this section
Hold Still Goal
TODO: Write this section
Flee Goal
TODO: Write this section
Sub Goals
The Sub Goal is the current secondary motivation for the AI. Each goal has an entirely different set of sub-goals, and the sub-goals aren't really shared between goals. It isn't "a second goal", it is like a plan of action to solve the current goal.
Whenever the character is updated, this is consulted second when making decisions on what to do, after the current Goal is checked.
List of Sub Goals:
- Unknown
- Provoke Attack
- Avoid Jump Kick
- Knock Off Ledge
- Wait And Attack
- Rush And Attack
- Defend
- Surround Target
- Escape Surround
- Investigate Slow
- Investigate Urgent
- Investigate Body
- Investigate Around
- Investigate Attack
The inital Sub Goal for all AI is Wait And Attack.
Unknown Sub Goal
TODO: Write this section
Provoke Attack Sub Goal
TODO: Write this section
Avoid Jump Kick Sub Goal
TODO: Write this section
Knock Off Ledge Sub Goal
TODO: Write this section
Wait And Attack Sub Goal
TODO: Write this section
Rush And Attack Sub Goal
TODO: Write this section
Defend Sub Goal
TODO: Write this section
Surround Target Sub Goal
TODO: Write this section
Escape Surround Sub Goal
TODO: Write this section
Investigate Slow Sub Goal
TODO: Write this section
Investigate Urgent Sub Goal
TODO: Write this section
Investigate Body Sub Goal
TODO: Write this section
Investigate Around Sub Goal
TODO: Write this section
Investigate Attack Sub Goal
TODO: Write this section