Difference between revisions of "Character Control Script"

From Wolfire Games Wiki
Jump to: navigation, search
m (Controller Script Interface)
m (Added to category: Modding, Overgrowth)
 
(19 intermediate revisions by one other user not shown)
Line 14: Line 14:
 
* For NPCs, this control happens in a script <code>'''Data/Scripts/enemycontrol.as'''</code>
 
* For NPCs, this control happens in a script <code>'''Data/Scripts/enemycontrol.as'''</code>
  
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, and listens to the inputs from the character controller to direct the character's motion. That 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 character controller.
+
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|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|Controller Script Interface]].
  
 
* For both players and NPCs this logic is shared, in a script <code>'''Data/Scripts/aschar.as'''</code>
 
* For both players and NPCs this logic is shared, in a script <code>'''Data/Scripts/aschar.as'''</code>
Line 26: Line 26:
 
=== Functions called from other scripts ===
 
=== Functions called from other scripts ===
  
These are called both directly (aschar.as) and via execute (the engine, other scripts, peer-to-peer calls)
+
These are called both directly (aschar.as) and via <code>'''movement_object.Execute(...)'''</code> (by the engine, other scripts, and character-to-character calls)
  
 
* [[#ActiveBlocking Function|<code>'''bool ActiveBlocking();'''</code>]]
 
* [[#ActiveBlocking Function|<code>'''bool ActiveBlocking();'''</code>]]
Line 80: Line 80:
 
* [[#WantsToWalkBackwards Function|<code>'''WalkDir WantsToWalkBackwards();'''</code>]]
 
* [[#WantsToWalkBackwards Function|<code>'''WalkDir WantsToWalkBackwards();'''</code>]]
  
Data accessed from other scripts (aschar.as mostly, one call to Execute from the old tutorial level):
+
=== Data accessed from other scripts ===
 +
 
 +
These are accessed by aschar.as mostly. There's only one call to <code>'''movement_object.Execute(...)'''</code> that modifies this data, in the older (no longer used) tutorial level.
  
 
* [[#Situation class|<code>'''Situation situation;'''</code>]]
 
* [[#Situation class|<code>'''Situation situation;'''</code>]]
Line 108: Line 110:
  
 
The inital [[#Goals|Goal]] for all AI is [[#Patrol Goal|Patrolling]], and this is the mode that they will fall back to when they're done in combat.
 
The inital [[#Goals|Goal]] for all AI is [[#Patrol Goal|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 Goal|Patrol]] gets set''':
 
'''When [[#Patrol Goal|Patrol]] gets set''':
Line 125: Line 129:
 
** If the [[#Goals|Goal]] was [[#Escort Goal|Escort]] and the target being escorted is deleted
 
** If the [[#Goals|Goal]] was [[#Escort Goal|Escort]] and the target being escorted is deleted
 
** If the [[#Goals|Goal]] was [[#Attack Goal|Attack]] and the chase target is deleted
 
** If the [[#Goals|Goal]] was [[#Attack Goal|Attack]] and the chase target is deleted
 
'''How [[#Patrol Goal|Patrol]] works''':
 
TODO: Write this section - or does this get delgated to sub-goals?
 
  
 
'''How character behaviors are affected by [[#Patrol Goal|Patrol]]''':
 
'''How character behaviors are affected by [[#Patrol Goal|Patrol]]''':
Line 136: Line 137:
 
* [[#WantsToWalkBackwards Function|<code>'''WalkDir WantsToWalkBackwards();'''</code>]] 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
 
* [[#WantsToWalkBackwards Function|<code>'''WalkDir WantsToWalkBackwards();'''</code>]] 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
 
* [[#WantsReadyStance Function|<code>'''bool WantsReadyStance();'''</code>]] returns true if the character is not on patrol
 
* [[#WantsReadyStance Function|<code>'''bool WantsReadyStance();'''</code>]] returns true if the character is not on patrol
 +
 +
'''How [[#Patrol Goal|Patrol]] works''':
 +
 +
* Whenever you switch to [[#Patrol Goal|Patrol]] from any other goal:
 +
** Clear the patrol path wait timer - immediately begin your patrol route (unless otherwise specified)
 +
 +
 +
* Whenever you switch from [[#Patrol Goal|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 Goal|Investigate]] and sub-goal to [[#Investigate Slow Sub Goal|Investigate Slow Sub Goal]]
 +
*** If your "seen" counter > 1.0
 +
**** Fully notice them - see next section
 +
** 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
 +
 +
 +
* 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
 +
** [[#MindReceiveMessage Function|<code>'''void MindReceiveMessage(string msg);'''</code>]] is called for the character, with the message "notice <target character id>" (when this message is sent to the character with <code>'''movement_object.RecieveMessage("...")'''</code>)
 +
*** If the goal is currently [[#Patrol Goal|Patrol]], and the noticed target character is not static, and they're not on the same team
 +
**** If you don't already know about the character
 +
***** Startle, play the startled sound, and alert nearby allies with your noise
 +
**** Set the goal to [[#Attack Goal|Attack]]
 +
 +
 +
* Whenever you take any damage, if your goal is [[#Patrol Goal|Patrol]], and if you're not set to passive
 +
** Set the goal to [[#Investigate Goal|Investigate]] and set the sub-goal to [[#Investigate Around Sub Goal|Investigate Around]]
 +
** Set your nav position to the current spot, and clear your Investigate Target Id
 +
 +
 +
* When [[#MindReceiveMessage Function|<code>'''void MindReceiveMessage(string msg);'''</code>]] 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 Goal|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 Goal|Investigate]] and set the sub-goal to [[#Investigate Slow Sub Goal|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 Goal|Escort]], and set the Escort Target Id to the sound creator
 +
***** Play the "startle" sound, do the startle action, set the goal to [[#Investigate Goal|Investigate]] and set the sub-goal to [[#Investigate Urgent Sub Goal|Investigate Urgent]]
 +
**** Set the Investigate Target Id to -1 (clear it)
 +
 +
 +
* When [[#MindReceiveMessage Function|<code>'''void MindReceiveMessage(string msg);'''</code>]] 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 Goal|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 Goal|Investigate]] and set the sub-goal to [[#Investigate Slow Sub Goal|Investigate Slow]], and set the Investigate Target Id to -1 (clear it)
  
 
=== Attack Goal ===
 
=== Attack Goal ===
Line 207: Line 279:
 
=== Investigate Attack Sub Goal ===
 
=== Investigate Attack Sub Goal ===
 
TODO: Write this section
 
TODO: Write this section
 +
 +
 +
[[Category: Overgrowth]]
 +
[[Category: Modding]]

Latest revision as of 17:40, 8 March 2018

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)

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:

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 with movement_object.RecieveMessage("..."))
  • When the Goal was Investigate
  • 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:
    • If the Goal was Get Help and the target ally being sought for help is deleted
    • If the Goal was Investigate and the target being investigated is deleted
    • If the Goal was Escort and the target being escorted is deleted
    • If the Goal was Attack and the chase target is deleted

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
      • If your "seen" counter > 1.0
        • Fully notice them - see next section
    • 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


  • 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 with movement_object.RecieveMessage("..."))
      • If the goal is currently Patrol, and the noticed target character is not static, and they're not on the same team
        • If you don't already know about the character
          • Startle, play the startled sound, and alert nearby allies with your noise
        • Set the goal to Attack


  • 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"
        • 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)


  • 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)

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

Navigate Goal

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:

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