http://wiki.offgridthegame.com/api.php?action=feedcontributions&user=Andre&feedformat=atomOff Grid Wiki - User contributions [en]2024-03-29T06:43:29ZUser contributionsMediaWiki 1.41.0http://wiki.offgridthegame.com/index.php?title=AI_Gestures&diff=1623AI Gestures2022-05-25T17:37:49Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= AI Gestures =<br />
== Description ==<br />
The enums on this page can be used by AI agent definitions to play an animation when an action is taken. These enums map directly on to states in the character animator, which is shared by all characters. Make sure you type the name exactly as it appears here, it's case sensitive.<br />
== Fields ==<br />
=== LookingAround ===<br />
Looks from side to side.<br />
<br />
=== LookAtPhone ===<br />
Retrieves phone from pocket, has a look at it, then puts it away.<br />
<br />
=== StandingTypingCalm ===<br />
Typing from a standing position, in a fairly normal manner.<br />
<br />
=== StandingTypingStealth ===<br />
Typing from a standing position, in a sneaky manner.<br />
<br />
=== StandingFilingCabinetSearch ===<br />
Searches a filing cabinet.<br />
<br />
=== StandingLookAtHipHeight ===<br />
Not sure.<br />
<br />
=== StandingAmokDeviceHit ===<br />
Hits a device that's in front of the character.<br />
<br />
=== UseSodaMachine ===<br />
Press button, retrieve soda, drink it, discard can.<br />
<br />
=== UseSodaMachineHacked ===<br />
Press button, get hit by can, keel over.<br />
<br />
=== UseCoffeeMachine ===<br />
Press button, retrieve coffee cup, drink.<br />
<br />
=== UseCoffeeMachineHacked ===<br />
Press button, get hit by steam, panic and writhe in pain.<br />
<br />
=== DanceHipHop ===<br />
Hip hop dancing.<br />
<br />
=== DanceGuitar ===<br />
Air guitar!<br />
<br />
=== DanceSalsa ===<br />
Strictly come Off Grid.<br />
<br />
=== Yawn ===<br />
Yawn, with arms outstretched.<br />
<br />
=== StandingTalkingCalmly ===<br />
Standing talking calmly with hands near waist.<br />
<br />
=== StandingTalkingAngrily ===<br />
Standing talking angrily with hand gestures.<br />
<br />
=== StandingUseTazer ===<br />
Standing raises right arm in front.<br />
<br />
=== StandingPhoneMalfunctioning ===<br />
Gets phone out, swipes frantically, puts phone away and looks around.<br />
<br />
=== StandingLookingHunched ===<br />
Standing, almost crouches down and peers around.<br />
<br />
=== StandingLookingSideToSide ===<br />
Standing, looks side to side.<br />
<br />
=== StandingHitFace ===<br />
Standing, quickly brings hands to face.<br />
<br />
=== PickupLow ===<br />
Standing pick up from ankle hight.<br />
<br />
=== PickupMed ===<br />
Standing pick up from waist hight.<br />
<br />
=== PickupHigh ===<br />
Standing pick up from head hight.<br />
<br />
=== LockedDoor ===<br />
Standing, puts hand on handle, struggles.<br />
<br />
=== WashingDryingHands ===<br />
Standing, hands out in front rubbing together.<br />
<br />
=== WaitingHandsOnHips ===<br />
Standing with hands on hips.<br />
<br />
=== WashFace ===<br />
Standing washing face.<br />
<br />
=== UseSnackMachine ===<br />
<br />
<br />
=== UseSnackMachineHacked ===<br />
<br />
<br />
=== AttackPlayerDefault ===<br />
Attack player without weapons<br />
<br />
=== Sitting ===<br />
Sitting on a chair<br />
<br />
=== Shouting ===<br />
Shouting<br />
<br />
=== StandingIdle ===<br />
Idle on standing with some movement<br />
<br />
=== PhoneAimingUpperBody ===<br />
Used for the upper body to aim phone<br />
<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 25/05/2022 18:30<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=AI_Lua_API&diff=1622AI Lua API2022-05-25T17:36:01Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= AI =<br />
== Description ==<br />
API to control the logic of AI in the mission<br />
== Functions ==<br />
=== Pause ===<br />
<syntaxhighlight source lang="lua">AI.Pause(characterName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|}<br />
'''Description''': Pauses the agent, and stops it from doing anything.<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': The AI will be hidden from networks, meaning it will no longer interact with devices or send/receive messages.<br />
=== Unpause ===<br />
<syntaxhighlight source lang="lua">AI.Unpause(characterName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|}<br />
'''Description''': Unpauses a hidden agent, resuming standard behaviour.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== IsPaused ===<br />
<syntaxhighlight source lang="lua">AI.IsPaused(characterName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|}<br />
'''Description''': Returns a bool based on if a character is currently paused or not<br />
<br />
'''Returns''': bool<br />
<br />
=== AddGoal ===<br />
<syntaxhighlight source lang="lua">AI.AddGoal(characterName, goal)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| goal || Lua Table<br />
|}<br />
'''Description''': Adds a defined goal<br />
<br />
'''Returns''': Nothing<br />
<br />
=== RemoveGoal ===<br />
<syntaxhighlight source lang="lua">AI.RemoveGoal(characterName, goalName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| goalName || string<br />
|}<br />
'''Description''': Removes any defined goals<br />
<br />
'''Returns''': Nothing<br />
<br />
=== AddTemporaryGoal ===<br />
<syntaxhighlight source lang="lua">AI.AddTemporaryGoal(characterName, goal)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| goal || Lua Table<br />
|}<br />
'''Description''': Adds a goal, taking top priority, to the named AI<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': This goal will be removed from the AI once complete. If it isn't achievable it will be removed immediately.<br />
=== GetNPCProfileByTag ===<br />
<syntaxhighlight source lang="lua">AI.GetNPCProfileByTag(characterName, tag)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| tag || string<br />
|}<br />
'''Description''': Get all values of a tag from a chaarcter's profile, if the tag exists.<br />
<br />
'''Returns''': System.String[]<br />
<br />
=== Lua_CheckNPCProfile ===<br />
<syntaxhighlight source lang="lua">AI.Lua_CheckNPCProfile(characterName, tag, value)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| tag || string<br />
|-<br />
| value || string<br />
|}<br />
'''Description''': Check if character's profile data contains a specific tag wiht a specific value<br />
<br />
'''Returns''': bool<br />
<br />
=== GetNPCStat ===<br />
<syntaxhighlight source lang="lua">AI.GetNPCStat(characterName, statName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| statName || string<br />
|}<br />
'''Description''': Returns current value of NPC's stat<br />
<br />
'''Returns''': number<br />
<br />
'''Notes''': This is only intended for debugging purposes, and not recommended in any actual game scripts.<br />
=== SetNPCStat ===<br />
<syntaxhighlight source lang="lua">AI.SetNPCStat(characterName, statName, newValue)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| statName || string<br />
|-<br />
| newValue || number<br />
|}<br />
'''Description''': Sets an NPC's AI stat to absolute value<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': This is only intended for debugging purposes, and not recommended in any actual game scripts.<br />
=== AlterNPCStat ===<br />
<syntaxhighlight source lang="lua">AI.AlterNPCStat(characterName, statName, statDelta)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| statName || string<br />
|-<br />
| statDelta || number<br />
|}<br />
'''Description''': Alters an NPC's AI stat by relative value<br />
<br />
'''Returns''': Nothing<br />
<br />
=== AlterNPCWorldState ===<br />
<syntaxhighlight source lang="lua">AI.AlterNPCWorldState(characterName, state, value)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| state || string<br />
|-<br />
| value || Lua Type<br />
|}<br />
'''Description''': Change the World State of an NPC.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== FavourInterest ===<br />
<syntaxhighlight source lang="lua">AI.FavourInterest(characterName, device, permanent)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| device || string<br />
|-<br />
| permanent || bool<br />
|}<br />
'''Description''': Reduce the cost of an AI using a particular Interest<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': If permanent is false, the cost will revert to normal the next time this is successfully used<br />
=== AvoidInterest ===<br />
<syntaxhighlight source lang="lua">AI.AvoidInterest(characterName, device, permanent)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| device || string<br />
|-<br />
| permanent || bool<br />
|}<br />
'''Description''': Increase the cost of an AI using a particular Interest<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': If permanent is false, the cost will revert to normal the next time this is successfully used<br />
=== SetNPCFavouredComputer ===<br />
<syntaxhighlight source lang="lua">AI.SetNPCFavouredComputer(characterName, computer)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| computer || MissionObject<br />
|}<br />
'''Description''': Set NPC's computer, this will be used for a variety of actions<br />
<br />
'''Returns''': Nothing<br />
<br />
=== AddAction ===<br />
<syntaxhighlight source lang="lua">AI.AddAction(characterName, action)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| action || Lua Table<br />
|}<br />
'''Description''': Adds an action that can be used immediately<br />
<br />
'''Returns''': Nothing<br />
<br />
=== RemoveAction ===<br />
<syntaxhighlight source lang="lua">AI.RemoveAction(characterName, actionName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| actionName || string<br />
|}<br />
'''Description''': Removes any action<br />
<br />
'''Returns''': Nothing<br />
<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 25/05/2022 18:30<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1619Agent Definitions2022-05-23T10:43:08Z<p>Andre: /* World State */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|-<br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state.<br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
<br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
=== Personality Requirement ===<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking.<br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music.<br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat.<br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual.<br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]].<br />
<br />
It also exist some special worlds states that add some features to the agent.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|-<br />
! Name !! Value!! Description<br />
|-<br />
| hasFlashlight || true || Adds a flashlight prop for the agent's character to use.<br />
|}<br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]].<br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again.<br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an action that slowly makes an agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. Each stat value can be created on the agent definition or on the respective [[Character Profiles#Character personality files|character personality file]], on the agent definition it’s possible to set the stat default value, the above and below fields; on the character personality it’s only possible to set its value but that value will have priority, i.e. if the value is defined on both then the stat value will be the same as the one set by the personality file.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|-<br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = {<br />
{ id = "elated", threshold = 1.00 },<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|-<br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique.<br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|-<br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, some action's parameters are hardcoded and can't be changed in the agent file, so even if they aren't in the Lua file they will be added automatically, this is because some parameters could break some more specific actions or alter the intended behaviour, these parameters on each special action can be seen in the next table m as "Unchangeable parameters added automatically". Other variables like "targetAgent" and "targetPlayer" never affect the behaviour of these actions and they should be only used in a generic action.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|-<br />
! Name !! Precondition !! Unchangeable Parameters Added Automatically !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script characters definition. Check [[Character Profiles#Enemies|here]] for more information. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || The intruder is visible. || required = <br /> { state = "intruderVisible", value = true } || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || | The intruder is visible. || required = <br /> { state = "intruderVisible", value = true } || The player is tased and caught with this action, if the player is within range.<br />
|-<br />
| ''DefaultAttackAction'' || The intruder is visible. || required = <br /> { state = "intruderVisible", value = true } || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || required = <br /> { state = "intruderSpotted", value = true } <br /><br /> effect = <br /> { state = "intruderVisible", value = true} || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || Some untrusted was heard. || required = <br /> { state = "heardUntrustedNoise", value = false }, -- It means that the guard already check the origin of the untrusted noise [Only for readability]|| Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''RestAction'' || style="text-align:center;"| - || required = <br /> { { state = "usedSitting", value = false }, <br /> { state = "canUseSitting", value = false } } <br /><br /> effect = <br /> { state = "sleepy", value = false} || Rest for a bit in the closest chair, if the agent is also sleepy then it'll fall asleep as well.<br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|-<br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''heardUntrustedNoise'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid.<br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|-<br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|-<br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied.<br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest.<br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
fails =<br />
{<br />
"WaitingHandsOnHips",<br />
},<br />
<br />
world = -- What they know on the beginning<br />
{<br />
-- [Automatically added] --<br />
{ state = "heardUntrustedNoise", value = false }, -- NPC heard some untrust noise? [This state exists on all NPCs. Will be added if missing] --<br />
{ state = "intruderVisible", value = false }, -- Player is visible? [This state exists on all NPCs. Will be added if missing] --<br />
{ state = "intruderSpotted", value = false }, -- Player was visible and SearchIntruderAction still wasn't performed? [This state exists on all NPCs. Will be added if missing] --<br />
<br />
-- [NOT automatically added] --<br />
{ state = "patrolCompleted", value = false }, -- To perform the patrolAction<br />
{ state = "intruderInRange", value = false }, -- If true the CatchIntruderAction was peformed successfully<br />
{ state = "hasPrisoner", value = false }, -- The main objective of this AI<br />
},<br />
<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above =<br />
{<br />
{ id = "energized", threshold = 1.0 },<br />
},<br />
below =<br />
{<br />
{ id = "tired", threshold = 0.4 },<br />
{ id = "exhausted", threshold = 0.0 }<br />
}<br />
},<br />
},<br />
<br />
goals =<br />
{<br />
---- MainGoal -------<br />
{<br />
states =<br />
{<br />
{ state = "hasPrisoner", value = true }, -- Order of wanted events : IntruderVisible = true -> IntruderInRange = true -> hasPrisoner == true<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
<br />
----- Investigate the unstrusted noise --<br />
{<br />
states =<br />
{<br />
{ state = "heardUntrustedNoise", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
<br />
-- Energy goals --<br />
{<br />
states =<br />
{<br />
{ state = "exhausted", value = false },<br />
},<br />
priority = 51,<br />
},<br />
<br />
{<br />
states =<br />
{<br />
{ state = "tired", value = false },<br />
},<br />
priority = 50,<br />
},<br />
<br />
-- Motivation goal --<br />
{<br />
states =<br />
{<br />
{ state = "hasMotivation", value = true },<br />
},<br />
priority = 25,<br />
},<br />
<br />
<br />
-- Patrol goal --<br />
{<br />
states =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false }, -- To be able to patrol always<br />
}<br />
},<br />
},<br />
<br />
actions =<br />
{<br />
-- [Special action] --<br />
-- Patrol in the points defined in the mission character's table --<br />
{<br />
name = "PatrolAction",<br />
effect = { state = "patrolCompleted", value = true},<br />
range = 0, -- Actions default value [Only for readability]<br />
speed = 1, -- Actions default value [Only for readability]<br />
},<br />
<br />
-- [Special action] --<br />
-- Agent is going to investigate the position of the heard noise to see if is going to find the intruder --<br />
{<br />
name = "InvestigateAction",<br />
effect =<br />
{<br />
{ state = "heardUntrustedNoise", value = false }, -- It means that the guard already check the origin of the untrusted noise [Only for readability]<br />
},<br />
required =<br />
{<br />
{ state = "hasPrisoner", value = false} -- To not investigate a sound right after the player is captured --<br />
},<br />
range = 2,<br />
speed = 2, -- Jog speed (should be speed = "Jog")<br />
npcStatusIcon = "Confused",<br />
},<br />
<br />
-- [Special action] --<br />
-- Agent is going to search for the player in the level SearchPoints if it was spotted, if the action is done successfully it means that the intruder is now visible --<br />
{<br />
name = "SearchIntruderAction",<br />
effect =<br />
{<br />
{ state = "intruderVisible", value = true}, -- Objective of this action is for the intruder to be visible again [Only for readability since this is always added when using this action] --<br />
},<br />
required =<br />
{<br />
{ state = "intruderSpotted", value = true }, -- [Only for readability since this is always added when using this action] --<br />
{ state = "heardUntrustedNoise", value = false }, -- If the agent heards a untrusted noise after losing the player then the investigate action should have proprioty because it could indicate the player's position better than the SearchAction //<br />
},<br />
range = 1,<br />
speed = 2, -- Jog speed<br />
npcStatusIcon = "Confused",<br />
},<br />
<br />
-- [Special action] --<br />
-- The objective is be in range of the player to attack it --<br />
{<br />
name = "CatchIntruderAction",<br />
effect =<br />
{<br />
{ state = "intruderInRange", value = true} -- State to indicate that the agent is near the player<br />
},<br />
required =<br />
{<br />
{ state = "intruderVisible", value = true }, -- The player needs to be visible to use this action [Only for readability since this is always added when using this action] --<br />
},<br />
range = 0, -- Actions default value [Only for readability]<br />
speed = 3, -- Run speed<br />
npcStatusIcon = "Alert",<br />
},<br />
<br />
-- [Special action] --<br />
-- Attack the player with a taser --<br />
{<br />
name = "TaserAction",<br />
effect = { state = "hasPrisoner", value = true}, -- The main goal --<br />
required =<br />
{<br />
{ state = "intruderInRange", value = true },<br />
{ state = "intruderVisible", value = true }, -- The player needs to be visible to use this action [Only for readability since this is always added when using this action] --<br />
},<br />
range = 2,<br />
speed = 3, -- Run speed<br />
cost = 1, -- Actions default value [Only for readability]<br />
},<br />
<br />
-- [Special action] --<br />
-- Not fully functional, default action to attack the player without any prop --<br />
{<br />
name = "DefaultAttackAction", -- Specific action<br />
effect = { state = "hasPrisoner", value = true},<br />
required =<br />
{<br />
{ state = "intruderInRange", value = true },<br />
{ state = "intruderVisible", value = true }, -- The player needs to be visible to use this action [Only for readability since this is always added when using this action] --<br />
},<br />
range = 1.5,<br />
speed = 3, -- Run speed<br />
cost = 5, -- Cost is higher than normal because its the default attack action, the action with the lest cost will be one that will be used<br />
},<br />
<br />
<br />
-- [Special action] --<br />
-- Rest in a sittable object with an interest "chair" --<br />
{<br />
name = "RestAction",<br />
effect =<br />
{<br />
{state = "exhausted", value = false },<br />
{state = "sleepy", value = false }, -- If the agent is sleepy then it will sleep when resting [Only for readability since this is always added when using this action] --<br />
},<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
npcStatusIcon = "Resting",<br />
},<br />
<br />
-- Other actions --<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
personalityEffect = { stat = "energy", adjust = 0.3 },<br />
},<br />
},<br />
<br />
<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
},<br />
gesture = "UseSodaMachine",<br />
npcStatusIcon = "Resting",<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
},<br />
gesture = "UseCoffeeMachine",<br />
npcStatusIcon = "Resting",<br />
},<br />
<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
gesture = "UseSnackMachine",<br />
npcStatusIcon = "Resting",<br />
},<br />
},<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
<br />
=== World State (world) ===<br />
In this section we have some states that are going to be added automatically on every NPC/agent, as well some that are set manually like the "patrolCompleted" with the value as false, to patrol when none other can be achieved or the "hasPrisoner" that is principal objective of the AI.<br />
<br />
=== Stats ===<br />
We define want we can call the two main states of every AI, "motivation" and "energy", some other stats can be defined in the character file or overwrite the values defined in the agent file. But is important to notice that these two states need to be defined in this agent because both use the above and below threshold, this is going to create some world states that are needed for our case.<br />
=== Goals ===<br />
The goals are want the agent wants to achieve, in our agent we have the following goals:<br />
* "hasPrisoner", true : The main goal of the AI that is used to catch the player.<br />
* "heardUntrustedNoise", false : If some untrusted sound is heard the agent will investigate the position of that sound, this goal is responsible for this.<br />
* "exhausted", false and "tired", false : The agent are going to try to increase the "energy" stat to achieve this goal<br />
* "hasMotivation", true : Very similar with the goal above, but in this case the stat responsible is the "motivation", this stat influence for example how many points the agent are going to search when doing the "SearchIntruderAction"<br />
* "patrolCompleted", true : Goal to achieve if all the above all impossible to, this is a loop goal, that means that as soon as the state "patrolCompleted" is true (by using the PatrolAction") this goal will set that same state as false, this can be seen in the "onCompletion" part of the goal, this is specially useful for having always a plan with achievable goals.<br />
<br />
=== Actions ===<br />
In here we have two different types of actions the ones that we call special, that are used to reach specific results with some unchangeable parameters that are added automatically, their behaviour is very rigid; and the other type of actions, are fully set in the agent file, like the "Yawn" action, it will play a gesture and adjust the "energy" stat when the "tired" state is true, it doesn't need a "required" parameter because is only going to be used when the "tired" state have the opposite value than the action's "effect", so "required = { state = "tired", value = true }" is not needed.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1618Agent Definitions2022-05-20T19:49:18Z<p>Andre: </p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|-<br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state.<br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
<br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
=== Personality Requirement ===<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking.<br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music.<br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat.<br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual.<br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]].<br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]].<br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again.<br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an action that slowly makes an agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. Each stat value can be created on the agent definition or on the respective [[Character Profiles#Character personality files|character personality file]], on the agent definition it’s possible to set the stat default value, the above and below fields; on the character personality it’s only possible to set its value but that value will have priority, i.e. if the value is defined on both then the stat value will be the same as the one set by the personality file.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|-<br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|-<br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = {<br />
{ id = "elated", threshold = 1.00 },<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|-<br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique.<br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|-<br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, some action's parameters are hardcoded and can't be changed in the agent file, so even if they aren't in the Lua file they will be added automatically, this is because some parameters could break some more specific actions or alter the intended behaviour, these parameters on each special action can be seen in the next table m as "Unchangeable parameters added automatically". Other variables like "targetAgent" and "targetPlayer" never affect the behaviour of these actions and they should be only used in a generic action.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|-<br />
! Name !! Precondition !! Unchangeable Parameters Added Automatically !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script characters definition. Check [[Character Profiles#Enemies|here]] for more information. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || The intruder is visible. || required = <br /> { state = "intruderVisible", value = true } || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || | The intruder is visible. || required = <br /> { state = "intruderVisible", value = true } || The player is tased and caught with this action, if the player is within range.<br />
|-<br />
| ''DefaultAttackAction'' || The intruder is visible. || required = <br /> { state = "intruderVisible", value = true } || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || required = <br /> { state = "intruderSpotted", value = true } <br /><br /> effect = <br /> { state = "intruderVisible", value = true} || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || Some untrusted was heard. || required = <br /> { state = "heardUntrustedNoise", value = false }, -- It means that the guard already check the origin of the untrusted noise [Only for readability]|| Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''RestAction'' || style="text-align:center;"| - || required = <br /> { { state = "usedSitting", value = false }, <br /> { state = "canUseSitting", value = false } } <br /><br /> effect = <br /> { state = "sleepy", value = false} || Rest for a bit in the closest chair, if the agent is also sleepy then it'll fall asleep as well.<br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|-<br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''heardUntrustedNoise'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid.<br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|-<br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|-<br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied.<br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest.<br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
fails =<br />
{<br />
"WaitingHandsOnHips",<br />
},<br />
<br />
world = -- What they know on the beginning<br />
{<br />
-- [Automatically added] --<br />
{ state = "heardUntrustedNoise", value = false }, -- NPC heard some untrust noise? [This state exists on all NPCs. Will be added if missing] --<br />
{ state = "intruderVisible", value = false }, -- Player is visible? [This state exists on all NPCs. Will be added if missing] --<br />
{ state = "intruderSpotted", value = false }, -- Player was visible and SearchIntruderAction still wasn't performed? [This state exists on all NPCs. Will be added if missing] --<br />
<br />
-- [NOT automatically added] --<br />
{ state = "patrolCompleted", value = false }, -- To perform the patrolAction<br />
{ state = "intruderInRange", value = false }, -- If true the CatchIntruderAction was peformed successfully<br />
{ state = "hasPrisoner", value = false }, -- The main objective of this AI<br />
},<br />
<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above =<br />
{<br />
{ id = "energized", threshold = 1.0 },<br />
},<br />
below =<br />
{<br />
{ id = "tired", threshold = 0.4 },<br />
{ id = "exhausted", threshold = 0.0 }<br />
}<br />
},<br />
},<br />
<br />
goals =<br />
{<br />
---- MainGoal -------<br />
{<br />
states =<br />
{<br />
{ state = "hasPrisoner", value = true }, -- Order of wanted events : IntruderVisible = true -> IntruderInRange = true -> hasPrisoner == true<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
<br />
----- Investigate the unstrusted noise --<br />
{<br />
states =<br />
{<br />
{ state = "heardUntrustedNoise", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
<br />
-- Energy goals --<br />
{<br />
states =<br />
{<br />
{ state = "exhausted", value = false },<br />
},<br />
priority = 51,<br />
},<br />
<br />
{<br />
states =<br />
{<br />
{ state = "tired", value = false },<br />
},<br />
priority = 50,<br />
},<br />
<br />
-- Motivation goal --<br />
{<br />
states =<br />
{<br />
{ state = "hasMotivation", value = true },<br />
},<br />
priority = 25,<br />
},<br />
<br />
<br />
-- Patrol goal --<br />
{<br />
states =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false }, -- To be able to patrol always<br />
}<br />
},<br />
},<br />
<br />
actions =<br />
{<br />
-- [Special action] --<br />
-- Patrol in the points defined in the mission character's table --<br />
{<br />
name = "PatrolAction",<br />
effect = { state = "patrolCompleted", value = true},<br />
range = 0, -- Actions default value [Only for readability]<br />
speed = 1, -- Actions default value [Only for readability]<br />
},<br />
<br />
-- [Special action] --<br />
-- Agent is going to investigate the position of the heard noise to see if is going to find the intruder --<br />
{<br />
name = "InvestigateAction",<br />
effect =<br />
{<br />
{ state = "heardUntrustedNoise", value = false }, -- It means that the guard already check the origin of the untrusted noise [Only for readability]<br />
},<br />
required =<br />
{<br />
{ state = "hasPrisoner", value = false} -- To not investigate a sound right after the player is captured --<br />
},<br />
range = 2,<br />
speed = 2, -- Jog speed (should be speed = "Jog")<br />
npcStatusIcon = "Confused",<br />
},<br />
<br />
-- [Special action] --<br />
-- Agent is going to search for the player in the level SearchPoints if it was spotted, if the action is done successfully it means that the intruder is now visible --<br />
{<br />
name = "SearchIntruderAction",<br />
effect =<br />
{<br />
{ state = "intruderVisible", value = true}, -- Objective of this action is for the intruder to be visible again [Only for readability since this is always added when using this action] --<br />
},<br />
required =<br />
{<br />
{ state = "intruderSpotted", value = true }, -- [Only for readability since this is always added when using this action] --<br />
{ state = "heardUntrustedNoise", value = false }, -- If the agent heards a untrusted noise after losing the player then the investigate action should have proprioty because it could indicate the player's position better than the SearchAction //<br />
},<br />
range = 1,<br />
speed = 2, -- Jog speed<br />
npcStatusIcon = "Confused",<br />
},<br />
<br />
-- [Special action] --<br />
-- The objective is be in range of the player to attack it --<br />
{<br />
name = "CatchIntruderAction",<br />
effect =<br />
{<br />
{ state = "intruderInRange", value = true} -- State to indicate that the agent is near the player<br />
},<br />
required =<br />
{<br />
{ state = "intruderVisible", value = true }, -- The player needs to be visible to use this action [Only for readability since this is always added when using this action] --<br />
},<br />
range = 0, -- Actions default value [Only for readability]<br />
speed = 3, -- Run speed<br />
npcStatusIcon = "Alert",<br />
},<br />
<br />
-- [Special action] --<br />
-- Attack the player with a taser --<br />
{<br />
name = "TaserAction",<br />
effect = { state = "hasPrisoner", value = true}, -- The main goal --<br />
required =<br />
{<br />
{ state = "intruderInRange", value = true },<br />
{ state = "intruderVisible", value = true }, -- The player needs to be visible to use this action [Only for readability since this is always added when using this action] --<br />
},<br />
range = 2,<br />
speed = 3, -- Run speed<br />
cost = 1, -- Actions default value [Only for readability]<br />
},<br />
<br />
-- [Special action] --<br />
-- Not fully functional, default action to attack the player without any prop --<br />
{<br />
name = "DefaultAttackAction", -- Specific action<br />
effect = { state = "hasPrisoner", value = true},<br />
required =<br />
{<br />
{ state = "intruderInRange", value = true },<br />
{ state = "intruderVisible", value = true }, -- The player needs to be visible to use this action [Only for readability since this is always added when using this action] --<br />
},<br />
range = 1.5,<br />
speed = 3, -- Run speed<br />
cost = 5, -- Cost is higher than normal because its the default attack action, the action with the lest cost will be one that will be used<br />
},<br />
<br />
<br />
-- [Special action] --<br />
-- Rest in a sittable object with an interest "chair" --<br />
{<br />
name = "RestAction",<br />
effect =<br />
{<br />
{state = "exhausted", value = false },<br />
{state = "sleepy", value = false }, -- If the agent is sleepy then it will sleep when resting [Only for readability since this is always added when using this action] --<br />
},<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
npcStatusIcon = "Resting",<br />
},<br />
<br />
-- Other actions --<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
personalityEffect = { stat = "energy", adjust = 0.3 },<br />
},<br />
},<br />
<br />
<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
},<br />
gesture = "UseSodaMachine",<br />
npcStatusIcon = "Resting",<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
},<br />
gesture = "UseCoffeeMachine",<br />
npcStatusIcon = "Resting",<br />
},<br />
<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
gesture = "UseSnackMachine",<br />
npcStatusIcon = "Resting",<br />
},<br />
},<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
<br />
=== World State (world) ===<br />
In this section we have some states that are going to be added automatically on every NPC/agent, as well some that are set manually like the "patrolCompleted" with the value as false, to patrol when none other can be achieved or the "hasPrisoner" that is principal objective of the AI.<br />
<br />
=== Stats ===<br />
We define want we can call the two main states of every AI, "motivation" and "energy", some other stats can be defined in the character file or overwrite the values defined in the agent file. But is important to notice that these two states need to be defined in this agent because both use the above and below threshold, this is going to create some world states that are needed for our case.<br />
=== Goals ===<br />
The goals are want the agent wants to achieve, in our agent we have the following goals:<br />
* "hasPrisoner", true : The main goal of the AI that is used to catch the player.<br />
* "heardUntrustedNoise", false : If some untrusted sound is heard the agent will investigate the position of that sound, this goal is responsible for this.<br />
* "exhausted", false and "tired", false : The agent are going to try to increase the "energy" stat to achieve this goal<br />
* "hasMotivation", true : Very similar with the goal above, but in this case the stat responsible is the "motivation", this stat influence for example how many points the agent are going to search when doing the "SearchIntruderAction"<br />
* "patrolCompleted", true : Goal to achieve if all the above all impossible to, this is a loop goal, that means that as soon as the state "patrolCompleted" is true (by using the PatrolAction") this goal will set that same state as false, this can be seen in the "onCompletion" part of the goal, this is specially useful for having always a plan with achievable goals.<br />
<br />
=== Actions ===<br />
In here we have two different types of actions the ones that we call special, that are used to reach specific results with some unchangeable parameters that are added automatically, their behaviour is very rigid; and the other type of actions, are fully set in the agent file, like the "Yawn" action, it will play a gesture and adjust the "energy" stat when the "tired" state is true, it doesn't need a "required" parameter because is only going to be used when the "tired" state have the opposite value than the action's "effect", so "required = { state = "tired", value = true }" is not needed.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1613Agent Definitions2022-04-08T11:08:42Z<p>Andre: </p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
<br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
=== Personality Requirement ===<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an action that slowly makes an agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. Each stat value can be created on the agent definition or on the respective [[Character Profiles#Character personality files|character personality file]], on the agent definition it’s possible to set the stat default value, the above and below fields; on the character personality it’s only possible to set its value but that value will have priority, i.e. if the value is defined on both then the stat value will be the same as the one set by the personality file.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 },<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1612Agent Definitions2022-03-30T17:12:53Z<p>Andre: </p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
<br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
=== Personality Requirement ===<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an action that slowly makes an agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. Each stat value can be created on the agent definition or on the respective [[Character Profiles#Character personality files|character personality file]], on the agent definition it’s possible to set the stat default value, the above and below fields; on the character personality it’s only possible to set its value but that value will have priority, i.e. if the value is defined on both then the stat value will be the same as the one set by the personality file.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 },<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Character_Profiles&diff=1611Character Profiles2021-11-30T18:32:13Z<p>Andre: </p>
<hr />
<div>Characters in Off Grid are defined in two places. First, the character's name, type, and few other details are set in your level's mission Lua script, in the ''characters''-table. Then, for certain character types, you'll also need another Lua script that describes their personality and contains a profile to describe the character's background.<br />
<br />
There are some pre-made personality scripts available in the [[Common folder]], but be aware that a background profile should be used for one character only, so if you grab a certain guard's personality script and use it in your level, that will be ''the same guard that was walking around some other level''. If you want a new character, it's usually the best to just create a duplicate of one of the existing files inside your mission's folders, and edit the values to fill in some background info about your new character.<br />
<br />
Another thing worth keeping in mind is that if you have same character in multiple levels, you should always use the same name for the character, both in your mission scripts and in the personality script. This makes sure the social engineering mechanics recognize that character correctly and any new information the player learns is matched with the correct profile.<br />
<br />
You ''can'', however, use a different personality file for the same character in different missions. One reason to do so would be to have some background information about your character that the player only learns about later on in your multi-level story line.<br />
<br />
== Mission script and different character types ==<br />
<br />
=== Player ===<br />
<br />
For the player character you only need few pieces of information in the mission script. Set the ''internalName'', ''characterType'' and ''prefab'' all to "player", and ''spawnPoint'' to name of the MissionObject you are using as player's starting location.<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
-- Character definitions:<br />
characters = {<br />
player = {<br />
displayName = "Joe Harman",<br />
internalName = "player",<br />
characterType = "player",<br />
prefab = "player",<br />
spawnpoint = "PlayerSpawn",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
{| class="wikitable"<br />
!colspan="2" | Character Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''displayName'' || The name that will be used any game UI referencing the character <br />
|-<br />
| ''internalName'' || The internalName can be thought of as the id for the character, you'll reference the character by this value in a few places including conversations (Must be unique!)<br />
|- <br />
| ''characterType'' || The 'type' of the character, see [[Character Types and Prefabs]] for more information on possible values<br />
|-<br />
| 'prefab' || The prefab for the character, see [[Character Types and Prefabs]] for more information on possible values<br />
|-<br />
| ''spawnpoint'' || The name of the spawn mission object, the position of this object will be where the character is spawned<br />
|}<br />
<br />
=== Enemies ===<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
-- Character definitions:<br />
characters = {<br />
guard1 = {<br />
displayName = "Marcus Fordham",<br />
internalName = "guard1",<br />
prefab = "M_Lrg_LongJacket-01",<br />
characterType = "enemy",<br />
colorTexture = "M_Lrg_LongJacket-01_Col_Docker01.png",<br />
metalSmoothTexture = "M_Lrg_LongJacket-01_Met_Docker01.png",<br />
profile = "BigGuard-Mk3-4.lua",<br />
spawnpoint = "GuardSpawn-1",<br />
patrolroute = {<br />
points = {<br />
"PatrolPoint-1",<br />
"PatrolPoint-2",<br />
"PatrolPoint-3",<br />
"PatrolPoint-4",<br />
"PatrolPoint-5",<br />
"PatrolPoint-6",<br />
},<br />
cyclic = true,<br />
},<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
{| class="wikitable"<br />
!colspan="2" | Character Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''displayName'' || The name that will be used any game UI referencing the character <br />
|-<br />
| ''internalName'' || The internalName can be thought of as the id for the character, you'll reference the character by this value in a few places including conversations (Must be unique!)<br />
|- <br />
| ''characterType'' || The 'type' of the character, see [[Character Types and Prefabs]] for more information on possible values<br />
|-<br />
| 'prefab' || The prefab for the character, see [[Character Types and Prefabs]] for more information on possible values<br />
|-<br />
| ''spawnpoint'' || The name of the spawn mission object, the position of this object will be where the character is spawned<br />
|-<br />
|''colorTexture'' || Name of the color look-up texture used to customize the character model<br />
|-<br />
|''metalSmoothTexture'' || Name of the metallic/smoothness look-up texture used to customize the character model<br />
|-<br />
|''profile'' || name of the character's profile file<br />
|-<br />
|''patrolroute'' || list of PatrolPoint objects placed in the scene, defining the guard's patrol route; and setting for changing between cyclic and back-and-forth patrol modes.<br />
|}<br />
<br />
=== Drones ===<br />
<br />
=== Neutral characters ===<br />
<br />
=== Virtual characters ===<br />
<br />
Virtual characters are not physically present in your scene, but still exist "somewhere else". They can take part in conversations, own files, and can be connected to networks so they can send files to the player, and player to them.<br />
<br />
As such, they only need minimal information in your mission scripts. Just setting the ''characterType'' to ''virtual'', and filling in ''internalName'' and ''displayName'' is enough.<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
-- Character definitions:<br />
characters = {<br />
smedley = {<br />
displayName = "Smedley Butler",<br />
internalName = "Smedley",<br />
characterType = "virtual",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
== Character personality files ==<br />
<br />
Personality files are divided into 3 sections; "Stats", for numeric data used to set default values for various personality traits and tracked values; "Colors", which is used for color data and for now should only contain single entry for "FavouriteColor"; and finally "Profile, which contains the main bulk of the character's background information.<br />
<br />
All information is listed using the same format, data + tags. And both of these can either be individual strings, or lists of strings. (So you can in one go define multiple pieces of information that all have same tag, or single string of information that has multiple tags. In case of Stats or Colors, the "data" should of course be a float between 0 and 1, or a list of four floats (to represent red, green, blue and alpha of RGBA color).<br />
<br />
At the moment, the required information every character must have includes:<br />
* "FavouriteColor"<br />
* "FirstName"<br />
* "LastName"<br />
<br />
In addition, having a value for "Gluttony" in Stats section will change the character model between thin and fat one based on the value.<br />
<br />
Due to how our social engineering mechanics work, the more information there is defined about a character, the easier it is for the player to collect enough unique pieces of knowledge to gain access to that character's devices etc. If there's only few pieces of information, the player is more likely to just find character metadata he already knows about, which won't then contribute to the total gained knowledge. Since the data player can find about characters is procedural, and randomized, it's best to make sure there's decent amount of information about every character you create, as searching through duplicate data to collect large enough profile about a character would be more tedious than enjoyable for the player. If the goal is to make certain character's devices more difficult to access, it's recommended to increase the difficulty on the device script rather than by limiting the character's profile size.<br />
<br />
...and, of course, the tags we have used in our existing profiles are not a limitation, feel free to add any new ones as you go, there is no limitation to what you can add!<br />
<br />
(If you end adding lots of new interesting tags, it might be worth checking out the Lua scripting for [[Message Templates]] as well, so you can put those tags into good use)<br />
<br />
<syntaxhighlight source lang="lua" line><br />
Character = {<br />
Stats = {<br />
{ data = 0.6, tags = {"motivation"}},<br />
{ data = 0.5, tags = {"sociability"}},<br />
{ data = 1.0, tags = {"gluttony"}},<br />
},<br />
Colors = {<br />
{data = { 0.1490196, 1, 0.5819339, 1 }, tags = {"FavouriteColor"}},<br />
},<br />
Profile = {<br />
{ data = "Simon", tags = {"FirstName", "name"} },<br />
{ data = "Wasserman", tags = {"LastName", "name"} },<br />
{ data = {<br />
"Tommy",<br />
"T-dog",<br />
"Bumpkin",<br />
"Essex-boy",<br />
},<br />
tags = {"NickName", "name"}<br />
},<br />
{ data = "Dickhead", tags = {"DerogatoryName"} },<br />
{ data = "Asshat", tags = {"FavouriteSwear"} },<br />
{ data = "bread meat bread", tags = {"FavouriteFood", "food", "favourite"} },<br />
{ data = "crisps", tags = {"FavouriteSnack", "food", "favourite"} },<br />
{ data = "Irn Bru", tags = {"FavouriteDrink", "drink", "favourite"} },<br />
{ data = "Tristan", tags = {"friend", "BestFriend"} },<br />
{ data = {<br />
"Sweetums",<br />
"Dimps",<br />
"Tata",<br />
},<br />
tags = {"PetName", "name"}<br />
},<br />
{ data = {<br />
"Golly",<br />
"Oh man",<br />
"Cripes",<br />
"Gosh",<br />
"Geez",<br />
"Geewhiz",<br />
"Grrr",<br />
"gahhh!",<br />
"Ahhh",<br />
"WTF",<br />
},<br />
tags = {"exclamation"}<br />
},<br />
{ data = {<br />
"#ROFL",<br />
"#Grrr",<br />
"LMAO",<br />
"LOL",<br />
"#Tots",<br />
"#BigGuns",<br />
"#Life",<br />
"#WTF",<br />
},<br />
tags = {"hashtag"}<br />
},<br />
{ data = {<br />
"happy",<br />
"excited",<br />
"sad",<br />
"frustrated",<br />
"upset",<br />
"down",<br />
"good",<br />
},<br />
tags = {"mood"}<br />
},<br />
{ data = {<br />
"ate",<br />
"had lunch",<br />
"went for a walk",<br />
"got up",<br />
"broke into your house",<br />
"spoke to my doctor",<br />
"released a mobile game",<br />
"checked the scores",<br />
"procrastinated",<br />
},<br />
tags = {"PastEvent"} },<br />
{ data = {<br />
"eat",<br />
"have lunch",<br />
"take a nap",<br />
"go skydiving",<br />
"sleep",<br />
"speak to your wife",<br />
"speak to my doctor",<br />
"get my doctor's degree",<br />
"run in the rain",<br />
},<br />
tags = {"FutureEvent"}<br />
},<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
== customizing character color ==<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Noise_Lua_API&diff=1582Noise Lua API2021-04-06T16:32:50Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= Noise =<br />
== Description ==<br />
The Noise system provides a method of producing sounds that the AI can be aware of and respond to.<br />
<br />
<br />
== Tables ==<br />
=== Noise ===<br />
Table representing a noise<br />
<br />
'''Table definition'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type !! Default !! Optional !! Description<br />
|-<br />
| soundName || string || || No || The name of the sound event<br />
|-<br />
| attraction || number || || No || How much attraction this noise will cause (range 0-1)<br />
|-<br />
| volume || number || 1.0 || Yes || The volume of the sound event (range (0-1)<br />
|-<br />
| duration || number || 0.0 || Yes || How long does this noise last? 0 will loop the sound and require it to be explicitly ended<br />
|-<br />
| trusted || boolean || false || Yes || Whether this noise is trusted by AIs<br />
|-<br />
| offEvent || string || || Yes || Sound to emit when the source is silenced <br />
|}<br />
== Functions ==<br />
=== Emit ===<br />
<syntaxhighlight source lang="lua">Noise.Emit(deviceName, noiseTable)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| deviceName || string<br />
|-<br />
| noiseTable || Lua Table<br />
|}<br />
'''Description''': Emits a noise from deviceName with attributes in noiseTable<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': Attraction value is required, volume is optional (default 1.0), as is duration (defaults to 0, which loops the sound).<br />
=== Silence ===<br />
<syntaxhighlight source lang="lua">Noise.Silence(deviceName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| deviceName || string<br />
|}<br />
'''Description''': Silences the noise<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': Silences the noise, principally from an AI perspective. Will attempt to stop the audio if this noise was set up with an end event.<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 06/04/2021 17:30<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Noise_Lua_API&diff=1581Noise Lua API2021-03-31T15:59:20Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= Noise =<br />
== Description ==<br />
The Noise system provides a method of producing sounds that the AI can be aware of and respond to.<br />
<br />
<br />
== Tables ==<br />
=== Noise ===<br />
Table representing a noise<br />
<br />
'''Table definition'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type !! Default !! Optional !! Description<br />
|-<br />
| soundName || string || || No || The name of the sound event<br />
|-<br />
| attraction || number || || No || How much attraction this noise will cause (range 0-1)<br />
|-<br />
| volume || number || 1.0 || Yes || The volume of the sound event (range (0-1)<br />
|-<br />
| duration || number || 0.0 || Yes || How long does this noise last? 0 will loop the sound and require it to be explicitly ended<br />
|-<br />
| trusted || boolean || false || Yes || Whether this noise is trusted by AIs<br />
|}<br />
== Functions ==<br />
=== Emit ===<br />
<syntaxhighlight source lang="lua">Noise.Emit(deviceName, noiseTable)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| deviceName || string<br />
|-<br />
| noiseTable || Lua Table<br />
|}<br />
'''Description''': Emits a noise from deviceName with attributes in noiseTable<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': Attraction value is required, volume is optional (default 1.0), as is duration (defaults to 0, which loops the sound).<br />
=== Silence ===<br />
<syntaxhighlight source lang="lua">Noise.Silence(deviceName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| deviceName || string<br />
|}<br />
'''Description''': Silences the noise<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': Silences the noise, principally from an AI perspective. Will attempt to stop the audio if this noise was set up with an end event.<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 31/03/2021 16:48<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Constants&diff=1580Constants2021-03-09T14:56:55Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= Constants =<br />
== Enums ==<br />
=== DataType ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| DataType.Generic || Any random data. Who knows, could be anything.<br />
|-<br />
| DataType.Text || Plain text or rich text content.<br />
|-<br />
| DataType.SMS || SMS. Don't use this! Legacy stuff.<br />
|-<br />
| DataType.Image || Image file. We'll try to display this for you in the File Viewer.<br />
|-<br />
| DataType.Audio || Audio file. There's no viewer for these yet.<br />
|-<br />
| DataType.Video || Video file. There's no viewer for these yet.<br />
|-<br />
| DataType.Location || Location data. just the coordinates, no much use apart from being a location marker in level<br />
|-<br />
| DataType.Key || PGP key. For encryption, device/lock access etc.<br />
|-<br />
| DataType.UUID || Generic identifier for something.<br />
|}<br />
=== MissionObjectType ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| MissionObjectType.Trigger ||<br />
|-<br />
| MissionObjectType.Interaction ||<br />
|-<br />
| MissionObjectType.Spawn ||<br />
|-<br />
| MissionObjectType.Hackable ||<br />
|-<br />
| MissionObjectType.Generic ||<br />
|-<br />
| MissionObjectType.Deprecated ||<br />
|-<br />
| MissionObjectType.Timeline ||<br />
|}<br />
=== InteractionType ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| InteractionType.Grab ||<br />
|-<br />
| InteractionType.OpenDoor ||<br />
|-<br />
| InteractionType.Scanning ||<br />
|}<br />
=== AIReactionType ===<br />
The type of reaction that can affect NPCs<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| AIReactionType.FixAmokDevice || The NPC will attempt to fix the amok device<br />
|}<br />
=== AppState ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| AppState.unavailable || The player doesn't have this app yet.<br />
|-<br />
| AppState.disabled || App can't be used at the moment, for example no network connection<br />
|-<br />
| AppState.off || App is not doing anything<br />
|-<br />
| AppState.on || App is switched on and running in the background<br />
|-<br />
| AppState.alert || App displays alert to notify the player about something.<br />
|}<br />
=== AppMenuState ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| AppMenuState.any || Allowed in any of configuration<br />
|-<br />
| AppMenuState.never || Never allowed in the selected menu<br />
|-<br />
| AppMenuState.always || Always in the selected menu<br />
|-<br />
| AppMenuState.byDefault || In the selected menu by default<br />
|-<br />
| AppMenuState.target || Will be in the radial menu when its target types are selected<br />
|}<br />
=== TrackingStates ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| TrackingStates.off || Not connected to SPECTRUM.<br />
|-<br />
| TrackingStates.tracking || Connected to SPECTRUM.<br />
|-<br />
| TrackingStates.alert || Connected to SPECTRUM, almost out of time.<br />
|-<br />
| TrackingStates.overtime || Connected to SPECTRUM, over time, restricted to minimum range<br />
|-<br />
| TrackingStates.cooldown || Disconnected from SPECTRUM, recovering.<br />
|-<br />
| TrackingStates.unlimited || Unlimited connection. Cheater.<br />
|}<br />
=== NetworkType ===<br />
The type of network, different devices and characters will only connect to certain network types<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| NetworkType.mobile || Mobile network (4G)- This will mainly just contain mobile phone devices<br />
|-<br />
| NetworkType.wifi || Wifi networks will be the most common, and will contain all sorts of devices<br />
|-<br />
| NetworkType.mesh || Mesh networks are mainly set up as part of a storyline<br />
|-<br />
| NetworkType.nfc ||<br />
|}<br />
=== TargetType ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| TargetType.None ||<br />
|-<br />
| TargetType.Data ||<br />
|-<br />
| TargetType.Interaction ||<br />
|-<br />
| TargetType.Hackable ||<br />
|-<br />
| TargetType.Character ||<br />
|}<br />
=== NotificationType ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| NotificationType.sms ||<br />
|-<br />
| NotificationType.generic || Generic notification<br />
|-<br />
| NotificationType.message || Communicatiosn message<br />
|-<br />
| NotificationType.download || Receicing files<br />
|-<br />
| NotificationType.newObjective ||<br />
|-<br />
| NotificationType.completedObjective ||<br />
|}<br />
=== PopupType ===<br />
{| class="wikitable"<br />
|-<br />
! Usage !! Description<br />
|-<br />
| PopupType.generic || A generic popup<br />
|-<br />
| PopupType.message || A message to the player<br />
|-<br />
| PopupType.warning || A warning to the player<br />
|-<br />
| PopupType.download || Begin a timed download window<br />
|-<br />
| PopupType.progress || Trigger a progress bar<br />
|}<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 09/03/2021 14:54<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Mission_Lua_API&diff=1579Mission Lua API2021-03-09T14:56:05Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= Mission =<br />
== Description ==<br />
The Mission API controls the flow of the mission, this includes starting and stopping the mission, and dealing with objectives and other mission-related information<br />
== Functions ==<br />
=== MissionStarted ===<br />
<syntaxhighlight source lang="lua">Mission.MissionStarted()</syntaxhighlight><br />
'''Description''': This should be called once the initial state of the mission has been set<br />
It will cause the game to begin gameplay<br />
<br />
'''Returns''': Nothing<br />
<br />
=== MissionCompleted ===<br />
<syntaxhighlight source lang="lua">Mission.MissionCompleted()</syntaxhighlight><br />
'''Description''': This should be called when the final objective of the mission has been completed<br />
It will trigger the game to fade to black and return to the main menu<br />
<br />
'''Returns''': Nothing<br />
<br />
=== MissionFailed ===<br />
<syntaxhighlight source lang="lua">Mission.MissionFailed()</syntaxhighlight><br />
'''Description''': This function will fail the current mission, as if Joe had been tazed (or similar).<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SpawnCharacter ===<br />
<syntaxhighlight source lang="lua">Mission.SpawnCharacter(internalName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| internalName || string<br />
|}<br />
'''Description''': Spawn a registered character in the game<br />
<br />
'''Returns''': Nothing<br />
<br />
=== ObjectiveIsActive ===<br />
<syntaxhighlight source lang="lua">Mission.ObjectiveIsActive(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Returns true if objective has been started but not completed yet.<br />
<br />
'''Returns''': bool<br />
<br />
=== ObjectiveIsCompleted ===<br />
<syntaxhighlight source lang="lua">Mission.ObjectiveIsCompleted(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Returns true if objective has been completed.<br />
<br />
'''Returns''': bool<br />
<br />
=== StartObjective ===<br />
<syntaxhighlight source lang="lua">Mission.StartObjective(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Start the provided objective<br />
<br />
'''Returns''': Nothing<br />
<br />
=== CompleteObjective ===<br />
<syntaxhighlight source lang="lua">Mission.CompleteObjective(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Complete the provided objective<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetCurrentObjective ===<br />
<syntaxhighlight source lang="lua">Mission.GetCurrentObjective()</syntaxhighlight><br />
'''Description''': Get the name of the current objective<br />
<br />
'''Returns''': string<br />
<br />
=== TriggerAutoSave ===<br />
<syntaxhighlight source lang="lua">Mission.TriggerAutoSave()</syntaxhighlight><br />
'''Description''': Triggers an autosave, only if the game is current in a mission<br />
<br />
'''Returns''': Nothing<br />
<br />
=== DevicesConnected ===<br />
<syntaxhighlight source lang="lua">Mission.DevicesConnected()</syntaxhighlight><br />
'''Description''': Call this AFTER all Devices have been connected to their networks. This allows DataPoints to be processed correctly.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SetPlayerControlled ===<br />
<syntaxhighlight source lang="lua">Mission.SetPlayerControlled(tf)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| tf || bool<br />
|}<br />
'''Description''': Set whether the player is currently in control (or not). Used for cutscenes, and other things that we haven't thought of yet.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetBool ===<br />
<syntaxhighlight source lang="lua">Mission.GetBool(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a boolean value of a key in mission Lua script. Returns false if key doesn't exist.<br />
<br />
'''Returns''': bool<br />
<br />
=== SetBool ===<br />
<syntaxhighlight source lang="lua">Mission.SetBool(key, newBool)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newBool || bool<br />
|}<br />
'''Description''': Sets a boolean value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetString ===<br />
<syntaxhighlight source lang="lua">Mission.GetString(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a string value of a key in mission Lua script. Returns empty if key doesn't exist.<br />
<br />
'''Returns''': string<br />
<br />
=== SetString ===<br />
<syntaxhighlight source lang="lua">Mission.SetString(key, newString)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newString || string<br />
|}<br />
'''Description''': Sets a string value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetNumber ===<br />
<syntaxhighlight source lang="lua">Mission.GetNumber(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a numeric value of a key in mission Lua script. Returns 0 if key doesn't exist.<br />
<br />
'''Returns''': number<br />
<br />
=== SetNumber ===<br />
<syntaxhighlight source lang="lua">Mission.SetNumber(key, newValue)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newValue || number<br />
|}<br />
'''Description''': Sets a numeric value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== StartVibrationEvent ===<br />
<syntaxhighlight source lang="lua">Mission.StartVibrationEvent(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Start a mission object vibration event<br />
<br />
'''Returns''': Nothing<br />
<br />
=== StopVibrationEvent ===<br />
<syntaxhighlight source lang="lua">Mission.StopVibrationEvent(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Stop a mission object vibration event<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SpawnWanderNPCs ===<br />
<syntaxhighlight source lang="lua">Mission.SpawnWanderNPCs(spawnPoint, spawnNum, characterPrefabs, headProps, colorTextures, metalTextures, gestures)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| spawnPoint || Lua Type<br />
|-<br />
| spawnNum || number<br />
|-<br />
| characterPrefabs || Lua Type<br />
|-<br />
| headProps || Lua Type<br />
|-<br />
| colorTextures || Lua Type<br />
|-<br />
| metalTextures || Lua Type<br />
|-<br />
| gestures || Lua Type<br />
|}<br />
'''Description''': Spawns various Wander NPCs in a randomized way<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': All the parameters, expect the spawnNum, can be a string or a table of strings. For the head props and the the gestures the string "None" can be used to have the possibility to not have any head props and gestures, respectively, in the wander NPCs. Also for the color textures and metal textures the string "Default" can be used for the character's default textures.<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 09/03/2021 14:54<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Mission_Lua_API&diff=1578Mission Lua API2021-03-09T14:51:12Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= Mission =<br />
== Description ==<br />
The Mission API controls the flow of the mission, this includes starting and stopping the mission, and dealing with objectives and other mission-related information<br />
== Functions ==<br />
=== MissionStarted ===<br />
<syntaxhighlight source lang="lua">Mission.MissionStarted()</syntaxhighlight><br />
'''Description''': This should be called once the initial state of the mission has been set<br />
It will cause the game to begin gameplay<br />
<br />
'''Returns''': Nothing<br />
<br />
=== MissionCompleted ===<br />
<syntaxhighlight source lang="lua">Mission.MissionCompleted()</syntaxhighlight><br />
'''Description''': This should be called when the final objective of the mission has been completed<br />
It will trigger the game to fade to black and return to the main menu<br />
<br />
'''Returns''': Nothing<br />
<br />
=== MissionFailed ===<br />
<syntaxhighlight source lang="lua">Mission.MissionFailed()</syntaxhighlight><br />
'''Description''': This function will fail the current mission, as if Joe had been tazed (or similar).<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SpawnCharacter ===<br />
<syntaxhighlight source lang="lua">Mission.SpawnCharacter(internalName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| internalName || string<br />
|}<br />
'''Description''': Spawn a registered character in the game<br />
<br />
'''Returns''': Nothing<br />
<br />
=== ObjectiveIsActive ===<br />
<syntaxhighlight source lang="lua">Mission.ObjectiveIsActive(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Returns true if objective has been started but not completed yet.<br />
<br />
'''Returns''': bool<br />
<br />
=== ObjectiveIsCompleted ===<br />
<syntaxhighlight source lang="lua">Mission.ObjectiveIsCompleted(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Returns true if objective has been completed.<br />
<br />
'''Returns''': bool<br />
<br />
=== StartObjective ===<br />
<syntaxhighlight source lang="lua">Mission.StartObjective(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Start the provided objective<br />
<br />
'''Returns''': Nothing<br />
<br />
=== CompleteObjective ===<br />
<syntaxhighlight source lang="lua">Mission.CompleteObjective(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Complete the provided objective<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetCurrentObjective ===<br />
<syntaxhighlight source lang="lua">Mission.GetCurrentObjective()</syntaxhighlight><br />
'''Description''': Get the name of the current objective<br />
<br />
'''Returns''': string<br />
<br />
=== TriggerAutoSave ===<br />
<syntaxhighlight source lang="lua">Mission.TriggerAutoSave()</syntaxhighlight><br />
'''Description''': Triggers an autosave, only if the game is current in a mission<br />
<br />
'''Returns''': Nothing<br />
<br />
=== DevicesConnected ===<br />
<syntaxhighlight source lang="lua">Mission.DevicesConnected()</syntaxhighlight><br />
'''Description''': Call this AFTER all Devices have been connected to their networks. This allows DataPoints to be processed correctly.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SetPlayerControlled ===<br />
<syntaxhighlight source lang="lua">Mission.SetPlayerControlled(tf)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| tf || bool<br />
|}<br />
'''Description''': Set whether the player is currently in control (or not). Used for cutscenes, and other things that we haven't thought of yet.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetBool ===<br />
<syntaxhighlight source lang="lua">Mission.GetBool(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a boolean value of a key in mission Lua script. Returns false if key doesn't exist.<br />
<br />
'''Returns''': bool<br />
<br />
=== SetBool ===<br />
<syntaxhighlight source lang="lua">Mission.SetBool(key, newBool)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newBool || bool<br />
|}<br />
'''Description''': Sets a boolean value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetString ===<br />
<syntaxhighlight source lang="lua">Mission.GetString(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a string value of a key in mission Lua script. Returns empty if key doesn't exist.<br />
<br />
'''Returns''': string<br />
<br />
=== SetString ===<br />
<syntaxhighlight source lang="lua">Mission.SetString(key, newString)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newString || string<br />
|}<br />
'''Description''': Sets a string value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetNumber ===<br />
<syntaxhighlight source lang="lua">Mission.GetNumber(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a numeric value of a key in mission Lua script. Returns 0 if key doesn't exist.<br />
<br />
'''Returns''': number<br />
<br />
=== SetNumber ===<br />
<syntaxhighlight source lang="lua">Mission.SetNumber(key, newValue)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newValue || number<br />
|}<br />
'''Description''': Sets a numeric value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== StartVibrationEvent ===<br />
<syntaxhighlight source lang="lua">Mission.StartVibrationEvent(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Start a mission object vibration event<br />
<br />
'''Returns''': Nothing<br />
<br />
=== StopVibrationEvent ===<br />
<syntaxhighlight source lang="lua">Mission.StopVibrationEvent(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Stop a mission object vibration event<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SpawnWanderNPCs ===<br />
<syntaxhighlight source lang="lua">Mission.SpawnWanderNPCs(spawnPoint, spawnNum, characterPrefabs, headProps, colorTextures, metalTextures, gestures)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| spawnPoint || Lua Type<br />
|-<br />
| spawnNum || number<br />
|-<br />
| characterPrefabs || Lua Type<br />
|-<br />
| headProps || Lua Type<br />
|-<br />
| colorTextures || Lua Type<br />
|-<br />
| metalTextures || Lua Type<br />
|-<br />
| gestures || Lua Type<br />
|}<br />
'''Description''': Spawns various Wander NPCs in a randomized way<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': All the parameters, expect the spawnNum, can be a string or a table of strings.For the head props and the the gestures the string "None" can be used to have the possibility to not have any head props and gestures, respectively, in the wander NPCs.Also for the color textures and metal textures the string "Default" can be used for the character's default textures.<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 09/03/2021 14:50<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Mission_Lua_API&diff=1577Mission Lua API2021-03-09T14:46:26Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= Mission =<br />
== Description ==<br />
The Mission API controls the flow of the mission, this includes starting and stopping the mission, and dealing with objectives and other mission-related information<br />
== Functions ==<br />
=== MissionStarted ===<br />
<syntaxhighlight source lang="lua">Mission.MissionStarted()</syntaxhighlight><br />
'''Description''': This should be called once the initial state of the mission has been set<br />
It will cause the game to begin gameplay<br />
<br />
'''Returns''': Nothing<br />
<br />
=== MissionCompleted ===<br />
<syntaxhighlight source lang="lua">Mission.MissionCompleted()</syntaxhighlight><br />
'''Description''': This should be called when the final objective of the mission has been completed<br />
It will trigger the game to fade to black and return to the main menu<br />
<br />
'''Returns''': Nothing<br />
<br />
=== MissionFailed ===<br />
<syntaxhighlight source lang="lua">Mission.MissionFailed()</syntaxhighlight><br />
'''Description''': This function will fail the current mission, as if Joe had been tazed (or similar).<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SpawnCharacter ===<br />
<syntaxhighlight source lang="lua">Mission.SpawnCharacter(internalName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| internalName || string<br />
|}<br />
'''Description''': Spawn a registered character in the game<br />
<br />
'''Returns''': Nothing<br />
<br />
=== ObjectiveIsActive ===<br />
<syntaxhighlight source lang="lua">Mission.ObjectiveIsActive(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Returns true if objective has been started but not completed yet.<br />
<br />
'''Returns''': bool<br />
<br />
=== ObjectiveIsCompleted ===<br />
<syntaxhighlight source lang="lua">Mission.ObjectiveIsCompleted(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Returns true if objective has been completed.<br />
<br />
'''Returns''': bool<br />
<br />
=== StartObjective ===<br />
<syntaxhighlight source lang="lua">Mission.StartObjective(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Start the provided objective<br />
<br />
'''Returns''': Nothing<br />
<br />
=== CompleteObjective ===<br />
<syntaxhighlight source lang="lua">Mission.CompleteObjective(objectiveName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectiveName || string<br />
|}<br />
'''Description''': Complete the provided objective<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetCurrentObjective ===<br />
<syntaxhighlight source lang="lua">Mission.GetCurrentObjective()</syntaxhighlight><br />
'''Description''': Get the name of the current objective<br />
<br />
'''Returns''': string<br />
<br />
=== TriggerAutoSave ===<br />
<syntaxhighlight source lang="lua">Mission.TriggerAutoSave()</syntaxhighlight><br />
'''Description''': Triggers an autosave, only if the game is current in a mission<br />
<br />
'''Returns''': Nothing<br />
<br />
=== DevicesConnected ===<br />
<syntaxhighlight source lang="lua">Mission.DevicesConnected()</syntaxhighlight><br />
'''Description''': Call this AFTER all Devices have been connected to their networks. This allows DataPoints to be processed correctly.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SetPlayerControlled ===<br />
<syntaxhighlight source lang="lua">Mission.SetPlayerControlled(tf)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| tf || bool<br />
|}<br />
'''Description''': Set whether the player is currently in control (or not). Used for cutscenes, and other things that we haven't thought of yet.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetBool ===<br />
<syntaxhighlight source lang="lua">Mission.GetBool(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a boolean value of a key in mission Lua script. Returns false if key doesn't exist.<br />
<br />
'''Returns''': bool<br />
<br />
=== SetBool ===<br />
<syntaxhighlight source lang="lua">Mission.SetBool(key, newBool)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newBool || bool<br />
|}<br />
'''Description''': Sets a boolean value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetString ===<br />
<syntaxhighlight source lang="lua">Mission.GetString(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a string value of a key in mission Lua script. Returns empty if key doesn't exist.<br />
<br />
'''Returns''': string<br />
<br />
=== SetString ===<br />
<syntaxhighlight source lang="lua">Mission.SetString(key, newString)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newString || string<br />
|}<br />
'''Description''': Sets a string value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetNumber ===<br />
<syntaxhighlight source lang="lua">Mission.GetNumber(key)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|}<br />
'''Description''': Get a numeric value of a key in mission Lua script. Returns 0 if key doesn't exist.<br />
<br />
'''Returns''': number<br />
<br />
=== SetNumber ===<br />
<syntaxhighlight source lang="lua">Mission.SetNumber(key, newValue)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| key || string<br />
|-<br />
| newValue || number<br />
|}<br />
'''Description''': Sets a numeric value of a key in mission Lua script.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== StartVibrationEvent ===<br />
<syntaxhighlight source lang="lua">Mission.StartVibrationEvent(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Start a mission object vibration event<br />
<br />
'''Returns''': Nothing<br />
<br />
=== StopVibrationEvent ===<br />
<syntaxhighlight source lang="lua">Mission.StopVibrationEvent(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Stop a mission object vibration event<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SpawnWanderNPCs ===<br />
<syntaxhighlight source lang="lua">Mission.SpawnWanderNPCs(spawnPoint, spawnNum, characterPrefabs, headProps, colorTextures, metalTextures, gestures)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| spawnPoint || Lua Type<br />
|-<br />
| spawnNum || number<br />
|-<br />
| characterPrefabs || Lua Type<br />
|-<br />
| headProps || Lua Type<br />
|-<br />
| colorTextures || Lua Type<br />
|-<br />
| metalTextures || Lua Type<br />
|-<br />
| gestures || Lua Type<br />
|}<br />
'''Description''': Spawns various Wander NPCs in a randomized way<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': All the parameters, expect the spawnNum, can be a string or a table of strings.<br />
For the head props and the the gestures the string "None" can be used to have the possibility to not have any head props and gestures, respectively, in the wander NPCs.<br />
Also for the color textures and metal textures the string "None" can be used to use the character default textures.<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 09/03/2021 14:43<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1570Agent Definitions2020-11-03T13:09:48Z<p>Andre: /* Stats */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an action that slowly makes an agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. Each stat value can be created on the agent definition or on the respective [[Character Profiles#Character personality files|character personality file]], on the agent definition it’s possible to set the stat default value, the above and below fields; on the character personality it’s only possible to set its value but that value will have priority, i.e. if the value is defined on both then the stat value will be the same as the one set by the personality file.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1569Agent Definitions2020-11-02T10:48:30Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1568Agent Definitions2020-11-02T10:47:40Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1567Agent Definitions2020-10-30T18:29:11Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the player is visible in the field vision of the agent<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1566Agent Definitions2020-10-30T18:08:53Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with specific types of behaviour. These actions are set as any normal action, but it have some differences: to add a specific special action its name must be exactly the same as any of the actions mentioned on the table below, the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions in any way and certain special GOAP states are required for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states, these states are also special because its values are updated automatically in the game AI code, depending on the state of the game.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1565Agent Definitions2020-10-30T16:30:16Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of these actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1564Agent Definitions2020-10-30T16:03:56Z<p>Andre: /* Stats */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1563Agent Definitions2020-10-30T16:03:14Z<p>Andre: /* Stats */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for. <br/><br />
Each stat value that is defined on the agent definition can be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1562Agent Definitions2020-10-30T15:44:25Z<p>Andre: /* Stats */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
Each stat value can also be overwritten on the respective [[Character Profiles#Character personality files|character personality file]], but only the value and not the "above" and "below" parameters.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1561Agent Definitions2020-10-30T14:51:35Z<p>Andre: /* Personality */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1560Agent Definitions2020-10-30T14:51:06Z<p>Andre: /* Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || style="text-align:center;"| ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || style="text-align:center;"| ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || style="text-align:center;"| ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || style="text-align:center;"| ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || style="text-align:center;"| 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || style="text-align:center;"| 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || style="text-align:center;"| 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || style="text-align:center;"| False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1559Agent Definitions2020-10-30T14:48:35Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended.<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states values are updated automatically in the game AI code.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1558Agent Definitions2020-10-29T23:40:10Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Special action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states are updated automatically in the game AI code. In the follow table it is described the logic between each of these states.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP State Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1557Agent Definitions2020-10-29T23:39:37Z<p>Andre: /* Special Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions with various types of behaviour. These actions are set as any normal action, in addition to the very specific behaviour the only difference is that the fields "targetAgent" and "targetPlayer" do not affect the behaviour of this actions and certain special GOAP states are required, for these actions to work as intended<br />
<br />
{| class="wikitable"<br />
!colspan="4" | Action Table<br />
|- <br />
! Name !! Precondition !! GOAP states required !! Description<br />
|-<br />
| ''PatrolAction'' || Patrol points must be defined on the mission script character definition. || style="text-align:center;"| - || To move the agent between defined points. Motivation is subtracted when arriving on a patrol point.<br />
|-<br />
| ''CatchIntruderAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || Action to get closer to the player (to caught it with the help of another action), if it is visible, or its last known position is known.<br />
|-<br />
| ''TaserAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is tased and caught with this action, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''DefaultAttackAction'' || style="text-align:center;"| - || intruderVisible <br/> intruderSpotted || The player is attacked and caught with a melee attack, if the player is within range. Set "hasPrisoner" state to true.<br />
|-<br />
| ''SearchIntruderAction'' || The level must contain one or more search points (i.e. gameobjects with the SearchPoint component). || intruderVisible <br/> intruderSpotted || This action searches for the player, if the player has been seen but is no longer visible.<br />
|-<br />
| ''InvestigateAction'' || style="text-align:center;"| - || investigate || Action to investigate a noise position if it's a non trusted sound.<br />
|-<br />
| ''CheckPhoneAction'' || style="text-align:center;"| - || unreadMessages || Action to read unread messages <br />
|}<br />
<br />
<br />
Like mentioned above certain special actions need specific GOAP states to work, these states are updated automatically in the game AI code. In the follow table it is described the logic between each of these states.<br />
<br />
{| class="wikitable"<br />
!colspan="2" | Required GOAP States Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''intruderVisible'' || If true, the agent is seeing the player at the moment<br />
|-<br />
| ''intruderSpotted'' || If true, the player was spotted and the agent is trying to catch it<br />
|-<br />
| ''investigate'' || If true, the agent heard a non trusted sound<br />
|-<br />
| ''unreadMessages'' || If true, the agent have unread messages<br />
|}<br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1556Agent Definitions2020-10-29T11:34:01Z<p>Andre: /* Actions */</p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="5" | Action Table<br />
|- <br />
! Name !! Type !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || string || ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || string || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || string || ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || table || ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || table || ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || number || || 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || number || || 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || number || || 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || table || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || table || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || boolean || || False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || boolean || || False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || table || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions. These can be turned off if not required, but are on by default. You may like to explicitly mark them as "on" in your Agents.<br />
<br />
=== Patrol ===<br />
The patrol action looks for Patrol points in the Mission definition, and uses the "patrolCompleted" world state; this state is set to true once a patrol is finished, and the common thing to do is to set it to false once this has happened (to repeat it), or after some other Action has happened (drinking coffee, receiving certain data).<br />
<syntaxhighlight source lang="lua" line start=5><br />
canPatrol = true,<br />
</syntaxhighlight><br />
<br />
=== Taser ===<br />
This action tasers the player, if the player is within range. It uses the "hasPrisoner" world state; this is set to true after the player has been tasered, which is useful for ceasing all other activity after.<br />
<syntaxhighlight source lang="lua" line start=6><br />
canTaser = true,<br />
</syntaxhighlight><br />
<br />
=== Search ===<br />
This action searches for the player, if the player has been seen but is no longer visible. It uses the "intruderVisible" world state, aiming to move the agent into a position such that this becomes true, allowing other behaviours (requiring the player to be in sight) to become available.<br />
<syntaxhighlight source lang="lua" line start=7><br />
canSearch = true,<br />
</syntaxhighlight><br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1555Agent Definitions2020-10-29T11:22:45Z<p>Andre: </p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="4" | Action Table<br />
|- <br />
! Name !! Required !! Default Value !! Description<br />
|-<br />
| ''name'' || ✓ || || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || || || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || ✓ || || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || ✓ || || The effect GOAP State. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || ✓ || || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || || 1 ||The agent velocity to reach a determined position, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''range'' || || 0.5 || The distance between the agent and a certain target, if it requires to be in a specific location to be performed.<br />
|-<br />
| ''cost'' || || 1 || The cost to be performed, this should only be manually set to untie similar actions (with the same "goal", "effect" and "required") on the calculation of the agent plan.<br />
|-<br />
| ''personalityEffect'' || || ||The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || || || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || || False || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || || False || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || || || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions. These can be turned off if not required, but are on by default. You may like to explicitly mark them as "on" in your Agents.<br />
<br />
=== Patrol ===<br />
The patrol action looks for Patrol points in the Mission definition, and uses the "patrolCompleted" world state; this state is set to true once a patrol is finished, and the common thing to do is to set it to false once this has happened (to repeat it), or after some other Action has happened (drinking coffee, receiving certain data).<br />
<syntaxhighlight source lang="lua" line start=5><br />
canPatrol = true,<br />
</syntaxhighlight><br />
<br />
=== Taser ===<br />
This action tasers the player, if the player is within range. It uses the "hasPrisoner" world state; this is set to true after the player has been tasered, which is useful for ceasing all other activity after.<br />
<syntaxhighlight source lang="lua" line start=6><br />
canTaser = true,<br />
</syntaxhighlight><br />
<br />
=== Search ===<br />
This action searches for the player, if the player has been seen but is no longer visible. It uses the "intruderVisible" world state, aiming to move the agent into a position such that this becomes true, allowing other behaviours (requiring the player to be in sight) to become available.<br />
<syntaxhighlight source lang="lua" line start=7><br />
canSearch = true,<br />
</syntaxhighlight><br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Agent_Definitions&diff=1554Agent Definitions2020-10-28T19:04:27Z<p>Andre: </p>
<hr />
<div>Each AI's behaviour is defined by its Agent definition.<br />
<br />
= Concepts =<br />
<br />
== GOAP State ==<br />
The World State and Goal states are made up of GOAP States. GOAP stands for "Goal-Oriented Action Planning". Each state comprises a unique string ID, and a boolean (true/false) value. The state as a whole is made up of multiple state items.<br />
{| class="wikitable"<br />
!colspan="2" | GOAP State Item<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''state'' || The unique name of the state. <br />
|-<br />
| ''value'' || The true/false value.<br />
|}<br />
A state is made up of 1-n items. This can be represented either as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
{ state = "amused", value = false },<br />
{ state = "tired", value = false },<br />
}<br />
</syntaxhighlight><br />
or, for a state with a single item in, as<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ state = "amused", value = false }<br />
</syntaxhighlight><br />
...which saves some slightly untidy extra braces. Note that in the former example, the items are just an anonymous list, the keys are implicit. GOAP State is a type that will appear throughout this guide and it is always parsed in the same way.<br />
<br />
== Personality ==<br />
Each AI (TBC: Human AI?) should have a [[Character Profiles#Character personality files|personality profile]]. This describes the AI's likes, loves, family, dog... anything you like. This is one of the main mechanisms for differentiating behaviour between agents - thus allowing a player's actions to affect multiple agents in multiple ways, and allows for complex behaviour. The mechanism for doing this is the Personality Requirement.<br />
{| class="wikitable"<br />
!colspan="2" | Personality Requirement<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''subject'' || string || The primary tag that this requirement is seeking. <br />
|-<br />
| ''value'' || string || Optional. The value that has a tag matching ''subject''.<br />
|-<br />
| ''other'' || string || Optional. Another tag, or list of tags, that the value must match with for this requirement to be satisfied.<br />
|-<br />
| ''inverse'' || boolean || Defaults to false. Setting to true swaps the result of the requirement - so a match means the requirement fails, and the lack of a match means the requirement passes.<br />
|}<br />
<br />
Here's a snippet of a Personality Profile.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "HipHop", "Pop"}, tags = { "music", "likes" } },<br />
{ data = { "Rock"}, tags = { "music", "dislikes" } },<br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
{ data = { "LiverpoolReds" }, tags = { "sport", "likes", "celebrates" } },<br />
</syntaxhighlight><br />
So, this AI considers that HipHop & Pop are both music, and they like it. They consider Rock to be music, but they dislike it. They consider Classical and HipHop to be music that relaxes them. They consider LiverpoolReds to be related to sport, they like it, and they celebrate it.<br />
<br />
The only mandatory part of a requirement is the subject. The subject is merely a tag, but it's the tag we look for first, and it's the tag that can be specified by [[AI Lua API#Change Subject|API call ChangeSubject]]. Let's write a requirement that will pass using only the subject.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music" },<br />
</syntaxhighlight><br />
This requirement, wherever it's used, will pass if the subject of "music" is set to "HipHop", "Pop", "Rock", or "Classical". So for instance, we might trigger a Dance Action if music is set to any of these. But that might not make a huge amount of sense for this AI, because they're not so keen on Rock music. So let's add an extra tag that we need for this requirement to be satisfied.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This means the requirement will no longer pass for "Classical" or "Rock", because they aren't tagged as "likes" in their profile. That makes more sense! But... do we want the AI to perform the same dance to "HipHop" ''and'' "Rock"? Possibly not. This is where the value attribute is useful.<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
</syntaxhighlight><br />
This requirement only passes for agents who like Rock music. <br />
<br />
Finally, what's inverse for? Essentially, it's for checking for the absence of something. Consider the requirement<br />
<syntaxhighlight source lang="lua" line start=5><br />
personalityRequirement = { value = "ManchesterBlues", subject = "celebrates", inverse = true },<br />
</syntaxhighlight><br />
Well spotted - "ManchesterBlues" is not present in our profile snippet. This means that, without the inverse flag, this requirement would fail. But with it, it passes! So, supposing we had a radio which set the subject as "celebrates", and the value as "ManchesterBlues", we could get this AI to sob gently to himself, while the one stood next to him is overcome with joy.<br />
<br />
== Statistics ==<br />
Each statistic is a tracked, saved, numerical value that represents a particular aspect of the AI. The value is clamped between 0 and 1. Each stat has two lists, above and below, of names and thresholds. These will become world states that become true when the value becomes greater or equal/lesser or equal (respectively) to the threshold value. Statistics can be adjusted by [[#Actions|actions]], [[#Responses|responses]], and [[#Reactions|reactions]]. In doing so the [[#World State|world state]] may change, and new [[#Goals|goals]] become achievable.<br />
<br />
Personality Effects will be referred to throughout this, so let's dig into them here.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''stat'' || string || The unique name of the stat. <br />
|-<br />
| ''adjust'' || number || The value to be added to the current value of this stat. Use a negative number to reduce it!<br />
|}<br />
One of the simpler tables in the Agent Definition. Simply put, when this effect happens, the named ''stat'' will have ''adjust'' added to it. The stat will then be clamped in the range 0 - 1, and the world states that rely on it will be recalculated.<br />
<br />
=== Usage / Intention ===<br />
Personality Effects may or may not be a great name for these, but we are stuck with them! You may consider them to be side effects or secondary effects - things that happen as a result of an Action, that aren't to be taken into account when planning. Also, because they act upon statistics ranging from 0-1, the effects can be gradual. <br />
A simple example would be a Soda machine. An Agent may plan to use the soda machine because they are thirsty, because they need energy, because they are bored - or a combination. All valid use cases. However, a side effect of drinking is the need to go to the toilet! Nobody has a drink with the aim of going to the toilet, but it's something that happens. So a Soda machine could quite feasibly stop an Agent from being thirsty (so a requirement of "isThirsty" = true, and an effect of "isThirsty" = false). But you might add a personalityEffect of "bladder-o-meter" adjust = 0.2. Thus, each time an Agent uses the Soda machine, their bladder-o-meter is incremented by 0.2. Depending on the threshold of the bladder-o-meter stat, a world state change will eventually happen, and the Agent will have to consider going to the toilet - depending on the priority of the toilet goal, of course!<br />
<br />
= File Format =<br />
The definition is a single table, named Agent, containing several tables that define different aspects of an Agent.<br />
<br />
== Fails ==<br />
This table contains a string or strings that are turned into [[AI_Gestures]] and used when the Agent no longer has a valid goal. So if you add "Yawn", and see your agent yawning, constantly, they probably don't have anything better to do! Ensure you type the gesture precisely - it's case sensitive.<br />
<br />
== World State ==<br />
The World State is a description of everything an AI knows about, in the context of planning. It is simply a [[#GOAP State|GOAP State]]. <br />
<br />
== Goals ==<br />
A Goal is a state that an Agent desires to be in. The planner will seek to use the Actions at its disposal to come up with a plan (set of Actions) that it can run to adjust the current World State so that it includes the Goal state. At its heart is a [[#GOAP State|GOAP State]], but it has some extra wizardry too.<br />
{| class="wikitable"<br />
!colspan="3" | Personality Effect<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''goal'' || table || A [[#GOAP State|GOAP State]]. <br />
|-<br />
| ''interrupts'' || boolean || Defaults to false. When set to true, this Goal will interrupt any of lower priority if it becomes achievable. For instance, chasing the player is more important than eating a snack.<br />
|-<br />
| ''priority'' || number || The higher the priority a goal is, the more important it is. As a result it will be attempted before lower priority goals.<br />
|-<br />
| ''onCompletion'' || table || Optional. This is a [[#GOAP State|GOAP State]] that will be applied to the [[#World State|World State]] when this goal is successful. Useful for cyclic tasks (e.g. patrolling).<br />
|}<br />
<br />
So the goal state is what we would like our world state to include (it doesn't have to be an exhaustive list of all the state items we know about). Any difference between goal and world mean it is a candidate for planning, where we try to use Actions we have that we are able to perform to turn our world state into the goal state. If the goal interrupts, it means that the agent will stop what its doing if it's suddenly possible for this goal to be achieved.<br />
<br />
The onCompletion state is useful for undoing changes made in the course of planning (or 'unlocking' state for another goal). So it might be that once your AI has patrolled you reset "patrolled" back to false so that it can patrol again. <br />
<br />
== Stats ==<br />
List of [[#Statistics|Statistics]].<br />
Statistics are adjusted by PersonalityEffects. They're used to change the world state in a gradual way - so you might have an Action that slowly makes an Agent more and more tired, eventually triggering a change in world state that allows new goals to be planned for.<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''name'' || string || Unique name for this statistic.<br />
|-<br />
| ''default'' || number || Default value for this statistic.<br />
|- <br />
| ''above'' || table || List of states that will become true when above or equal to the specified threshold (see next table).<br />
|-<br />
| ''below'' || table || List of states that will become true when below or equal to the specified threshold (see next table).<br />
|}<br />
<br />
{| class="wikitable"<br />
!colspan="3" | Statistic-State Table<br />
|- <br />
! Name !! Type !! Description<br />
|-<br />
| ''id'' || string || The name of the world state to be created.<br />
|-<br />
| ''threshold'' || number || Threshold for this statistic. Behaviour depends upon whether this state is in the above or below table.<br />
|}<br />
<br />
So, what might this look like?<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { <br />
{ id = "elated", threshold = 1.00 }<br />
{ id = "cheerful", threshold = 0.8 }<br />
}<br />
},<br />
</syntaxhighlight><br />
This stat is called happiness. It starts at 0.5. It has two world states, "elated", which becomes true at maximum happiness (1.0), and "cheerful", which happens when it's merely 0.8.<br />
<syntaxhighlight source lang="lua" line start=8><br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
</syntaxhighlight><br />
Notice that this one has a single entry in both above and below, missing out the nested brackets.<br />
<br />
== Actions ==<br />
An Actions is something that the AI '''does'''. In order to '''do''' it, it must have a particular world state. After having '''done''' it, it will change its world state.<br />
{| class="wikitable"<br />
!colspan="3" | Action Table<br />
|- <br />
! Name !! Required !! Description<br />
|-<br />
| ''name'' || Yes || The name of the Action, which should be unique. <br />
|-<br />
| ''gesture'' || No || The gesture to play when performing this Action.<br />
|-<br />
| ''audio'' || No || The Wwise event to play when performing this Action.<br />
|- <br />
| ''effect'' || Yes || The effect GOAP State. This will be applied to the World State when this Action is performed. See XXXX<br />
|-<br />
| ''required'' || Yes || The required GOAP State. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''speed'' || No <br />(Default value is 1) || The agent velocity to reach a determined position, if it requires to be in a specific location to be performed<br />
|-<br />
| ''range'' || No <br />(Default value is 0.5) || The distance between the agent and a certain target, if it requires to be in a specific location to be performed<br />
|-<br />
| ''cost'' || No <br />(Default value is 1) || The cost to be performed, this should only be manually set to untie similiar actions (with the same goal, effect and required) for when the agent plan is calculated<br />
|-<br />
| ''personalityEffect'' || No || The effect on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || No || The required [[Character Profiles#Character personality files|personality profile]] this AI needs for this Action to run.<br />
|-<br />
| ''targetAgent'' || No <br />(Default value is false) || If true, the Agent requires that there be another agent nearby for this Action to be performed.<br />
|-<br />
| ''targetPlayer'' || No <br />(Default value is false) || If true, the Agent requires that the player be nearby for this Action to be performed.<br />
|-<br />
| ''data'' || No || When this Action happens, the data will be sent. The recipient of the data is held in the sending agent's worldstate. A state named {this action's name} with "DataRecipient" appended will be used for this. See the further explanation below.<br />
|}<br />
=== Sending Data as part of an Action ===<br />
This is where things become a little more complicated. Actions can result in an agent sending data. The data is fixed in the Agent profile, but the recipient is not - this is because this would mean this Action would require the presence of a particular device (which could be the mobile phone device of another agent). The solution to this issue is to store the name of this device in the world state (yes, states can hold data other than booleans!). The name of the state that this is stored in is derived from the name of the action itself.<br />
<br />
Let's consider the following Action:<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
name = "SendEmail",<br />
effect = { state = "hasMotivation", value = true },<br />
data = {<br />
internalName = "AI Email",<br />
name = "Data Name",<br />
description = "A description of this name",<br />
immutable = true,<br />
dataType = 3,<br />
creatorName = "Top Secret Source",<br />
dataString = "All Your Base Are Belong To Us",<br />
},<br />
},<br />
</syntaxhighlight><br />
<br />
First we must work out where this is going to be sent, and change the relevant state within the AI that is performing the Action! The name of the Action is "SendEmail". The state that will be inspected to find the recipient is this name, with "DataRecipient" appended to it. So the state to store it in is "SendEmailDataRecipient". Let's see what this looks like for an example AI - in this case the AI is called "Edward", and the recipient is called "Julian":<br />
<syntaxhighlight source lang="lua" line start=65><br />
AI.AlterNPCWorldState("Edward", "SendEmailDataRecipient", "Julian")<br />
</syntaxhighlight><br />
Now, if we were to inspect Edward's AI state, we would see that the state "SendEmailDataRecipient" is now set to the string, "Julian". This will be used by the Action. If and when Edward runs this Action, this data will be sent from him to Julian. If this state is not set, the Action will still run, but the data will not be sent (because there is nowhere for it to go).<br />
<br />
== Special Actions ==<br />
There are a number of special actions. These can be turned off if not required, but are on by default. You may like to explicitly mark them as "on" in your Agents.<br />
<br />
=== Patrol ===<br />
The patrol action looks for Patrol points in the Mission definition, and uses the "patrolCompleted" world state; this state is set to true once a patrol is finished, and the common thing to do is to set it to false once this has happened (to repeat it), or after some other Action has happened (drinking coffee, receiving certain data).<br />
<syntaxhighlight source lang="lua" line start=5><br />
canPatrol = true,<br />
</syntaxhighlight><br />
<br />
=== Taser ===<br />
This action tasers the player, if the player is within range. It uses the "hasPrisoner" world state; this is set to true after the player has been tasered, which is useful for ceasing all other activity after.<br />
<syntaxhighlight source lang="lua" line start=6><br />
canTaser = true,<br />
</syntaxhighlight><br />
<br />
=== Search ===<br />
This action searches for the player, if the player has been seen but is no longer visible. It uses the "intruderVisible" world state, aiming to move the agent into a position such that this becomes true, allowing other behaviours (requiring the player to be in sight) to become available.<br />
<syntaxhighlight source lang="lua" line start=7><br />
canSearch = true,<br />
</syntaxhighlight><br />
<br />
== Responses ==<br />
A response is a method of adjusting an AI's personality stats when another AI performs an Action on them.<br />
{| class="wikitable"<br />
!colspan="2" | Response Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''Action'' || The name of the Action, which should be unique. <br />
|-<br />
| ''personalityEffect'' || The effect on personality stats that being the victim of this Action has.<br />
|}<br />
So what's the point of this? Basically, its purpose is to create a mechanism of having one AI's behaviour directly affect another. Recall the "targetAgent" attribute of the [[#Action|Action]] table. When this is true, the Action is performed ''on'' another AI. If we are that AI, our Responses are looked at, and if there is a Response that matches the Action that has been performed on us, our [[#Statistics|effect]] is applied to our stats. This is a neat way of creating chains of sociable Actions among AI. A player could send an SMS to two agents, resulting in one becoming sad and the other happy. The happy agent could then tease the sad agent, angering them, causing an argument! All allowing the player to sneak by.<br />
<br />
== Reactions ==<br />
A reaction is a method of adjusting an AI's personality stats when they do something, based on their [[Character Profiles#Character personality files|personality profile]]. These effects will be performed when [[AI Lua API#ReactTo|ReactTo]] is called, if the requirement is satisfied.<br />
{| class="wikitable"<br />
!colspan="2" | Reaction Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''personalityRequirement'' || The [[#Personality|requirement]], which, when reacted to, will bring about the effect. <br />
|-<br />
| ''personalityEffect'' || The [[#Statistics|effect]] on personality stats that occurs.<br />
|}<br />
<br />
Let's look at a quick example of how to use this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
</syntaxhighlight><br />
Here we have an effect that requires a personality entry that is tagged both as "music", and "relaxes". How does this get used? Well, for the purpose of this example, let's assume this AI has stepped into earshot of a radio, which has its own script. As a result, the following is called<br />
<syntaxhighlight source lang="lua" line start=5><br />
AI.ReactTo(theAI, "music", "Classical")<br />
</syntaxhighlight><br />
What does this do? This says that the AI (which will be referred to by the variable theAI, a string referencing the AI's ID) should "React To" some external influence of tag "music", with the value of "Classical". If this requirement holds, the effect applies.<br />
<br />
So the AI with this personality will have the effect applied, in this situation...<br />
<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Classical", "HipHop"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
...and this AI won't.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "Dance"}, tags = { "music", "relaxes" } },<br />
</syntaxhighlight><br />
<br />
=== Reactions to Data ===<br />
Agents can also react to data, and the key to this is the (optional) metadata within the DataPoint. This metadata can be compared with an Agent's personality, and Reactions can occur in much the same way.<br />
<br />
Let's look at the following DataPoint table, that might appear in a mission script:<br />
<syntaxhighlight source lang="lua"><br />
CoffeeOffer = {<br />
internalName = "CoffeeOffer",<br />
name = "theapostle_data_Coffee_name",<br />
dataType = 1,<br />
creatorName = "Baltar Beans",<br />
description = "text/UTF8",<br />
dataColor = {1.0, 1.0, 1.0, 1.0},<br />
meta = { { data = { "coffee" }, tags = { "drink" } } },<br />
},<br />
</syntaxhighlight><br />
Notice the meta table on the end. This tells the AI that the data is about "coffee", and that "coffee" is a "drink". How could we make use of this in a level?<br />
<br />
Let's make a Reaction that makes use of this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{<br />
personalityEffect = { stat = "thirst", adjust = 0.5 },<br />
personalityRequirement = { subject = "drink", other = "likes" },<br />
}<br />
</syntaxhighlight><br />
This Reaction is looking for something that is tagged as a drink, and that the agent likes. To complete this example, we need to inspect some personality files.<br />
<br />
This agent will React to this DataPoint, because they know that coffee is a drink, and they like it.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee", "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
This AI won't, because while they know coffee is a drink, they don't like it (it's tagged as "dislikes").<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "coffee"}, tags = { "drink", "dislikes" } },<br />
</syntaxhighlight><br />
...and nor will this one. This AI doesn't even know what coffee is.<br />
<syntaxhighlight source lang="lua" line start=5><br />
{ data = { "tea"}, tags = { "drink", "likes" } },<br />
</syntaxhighlight><br />
<br />
==== One last thing... ====<br />
It might be that you have a cunning piece of data that has to do something very specific. Don't forget you can use AI.ReactTo() (and many other API functions) in a DataPoint's luaScript - which is a lua script that is run when the data is received.<br />
<br />
== Interests ==<br />
An Interest may be a Device, or may simply be a particular part of a level that an AI needs to get to in order to perform an Action. An Interest is created by adding an InterestPoint to a GameObject (or making a new GameObject with an InterestPoint added). Be sure to orient the GameObject such that the Z axis is pointing in the direction the AI should use the InterestPoint from.<br />
<br />
Note that each Interest may define its own World State which will be added to any AI able to use it - thereby guaranteeing that any adjustments the Device makes to AI using it are valid. <br />
=== canUse ===<br />
This is a using Action. The Agent will attempt to reach the nearest Interest that they know to be working, and try to use it. If it's in an Amok state, this may fail. The table is pretty similar to a standard [[#Action|Action]].<br />
{| class="wikitable"<br />
!colspan="2" | canUse Table<br />
|- <br />
! Name !! Description<br />
|-<br />
| ''interest'' || The name of the InterestPoint this Action will take place at.<br />
|- <br />
| ''effect'' || The effect [[#GOAP State|GOAP State]]. This will be applied to the World State when this Action is performed.<br />
|-<br />
| ''required'' || The required [[#GOAP State|GOAP State]]. The World State must include this state in order for the Action to be used.<br />
|-<br />
| ''personalityEffect'' || Optional. The [[#Statistics|effect]] on personality stats that performing this Action has.<br />
|-<br />
| ''personalityRequirement'' || Optional. For this Action to be performed, this [[#Personality|requirement]] must be satisfied. <br />
|-<br />
| ''gesture'' || Optional. This is the gesture to be played when the agent uses a working instance of this Interest. <br />
|-<br />
| ''gestureAmok'' || Optional. This is the gesture to be played when the agent uses an instance of this Interest that is in its Amok state.<br />
|}<br />
<br />
==== World State ====<br />
Adding a canUse to an Agent also adds a state to its world state, in the form of "usedXXX", where "XXX" is the name of the interest; so an agent able to use a "Printer" will have a "usedPrinter" state, initially set to false. This is useful to create a sequence of "uses", perhaps within a goal where each state is reset upon completion.<br />
<br />
=== canFix ===<br />
This will eventually be a fixing Action. (TODO!)<br />
<br />
= Case Study =<br />
Brace yourselves for a long, long example from the game, with some discussion afterwards.<br />
<br />
== Guard Example ==<br />
The Guard is the standard 'enemy' AI currently in the game. Please be aware that the game is still in development and there may (will!) be bugs in this.<br />
<syntaxhighlight source lang="lua" line start=5><br />
<br />
Agent =<br />
{<br />
canPatrol = true,<br />
canTaser = true,<br />
canSearch = true,<br />
fails =<br />
{<br />
"Yawn",<br />
"WaitingHandsOnHips",<br />
},<br />
world =<br />
{<br />
{ state = "unreadMessages", value = false },<br />
},<br />
stats =<br />
{<br />
{<br />
name = "motivation",<br />
default = 0.5,<br />
above = { id = "hasMotivation", threshold = 0.01 }<br />
},<br />
{<br />
name = "energy",<br />
default = 0.5,<br />
above = { id = "energized", threshold = 1.0 },<br />
below = { id = "tired", threshold = 0.0 }<br />
},<br />
{<br />
name = "bladder",<br />
default = 0.0,<br />
above = { id = "needsToilet", threshold = 1.0 }<br />
},<br />
{<br />
name = "happiness",<br />
default = 0.5,<br />
above = { id = "happy", threshold = 1.0 },<br />
below = { id = "sad", threshold = 0.0 }<br />
},<br />
{<br />
name = "anger",<br />
default = 0.0,<br />
above = { id = "angry", threshold = 1.0 }<br />
},<br />
},<br />
goals =<br />
{<br />
{<br />
goal =<br />
{<br />
{ state = "hasPrisoner", value = true },<br />
},<br />
interrupts = true,<br />
priority = 100,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "investigate", value = false },<br />
},<br />
interrupts = true,<br />
priority = 99,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "intruderVisible", value = true },<br />
},<br />
interrupts = true,<br />
priority = 98,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "happy", value = false },<br />
{ state = "sad", value = false },<br />
{ state = "tired", value = false },<br />
{ state = "energized", value = false },<br />
{ state = "angry", value = false },<br />
{ state = "needsToilet", value = false },<br />
},<br />
priority = 50,<br />
--interrupts = true,<br />
},<br />
{<br />
goal =<br />
{<br />
{ state = "patrolCompleted", value = true },<br />
{ state = "hasMotivation", value = true },<br />
{ state = "unreadMessages", value = false },<br />
},<br />
priority = 10,<br />
onCompletion =<br />
{<br />
{ state = "patrolCompleted", value = false },<br />
}<br />
},<br />
},<br />
actions =<br />
{<br />
{<br />
name = "Argue",<br />
effect = { state = "angry", value = false },<br />
required = { state = "angry", value = true },<br />
personalityEffect = { stat = "anger", adjust = -1.0 },<br />
targetRequirement = { state = "angry", value = true },<br />
targetAgent = true,<br />
<br />
},<br />
{<br />
name = "Tease",<br />
effect = { state = "amused", value = false },<br />
required = { state = "amused", value = true },<br />
targetRequirement = { state = "sad", value = true },<br />
targetAgent = true,<br />
},<br />
{<br />
name = "Laugh",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "amusing" },<br />
},<br />
{<br />
name = "Celebrate",<br />
effect = { state = "happy", value = false },<br />
required = { state = "happy", value = true },<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
personalityRequirement = { subject = "celebrates" }<br />
},<br />
{<br />
name = "Yawn",<br />
gesture = "Yawn",<br />
effect = { state = "tired", value = false },<br />
required = { state = "tired", value = true },<br />
personalityEffect = { stat = "energy", adjust = 0.5 },<br />
},<br />
{<br />
name = "Despair",<br />
effect = { state = "sad", value = false },<br />
required = { state = "sad", value = true },<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", inverse = true }<br />
},<br />
{<br />
name = "DanceGuitar",<br />
gesture = "DanceGuitar",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Rock", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceHipHop",<br />
gesture = "DanceHipHop",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "HipHop", subject = "music", other = "likes" },<br />
},<br />
{<br />
name = "DanceSalsa",<br />
gesture = "DanceSalsa",<br />
effect = { state = "energized", value = false },<br />
required = { state = "energized", value = true },<br />
personalityEffect = { stat = "energy", adjust = -1.0 },<br />
personalityRequirement = { value = "Salsa", subject = "music", other = "likes" },<br />
},<br />
},<br />
responses =<br />
{<br />
{<br />
action = "Tease",<br />
personalityEffect = { stat = "anger", adjust = 1.0 },<br />
},<br />
{<br />
action = "Argue",<br />
personalityEffect = { stat = "happiness", adjust = -0.5 },<br />
}<br />
},<br />
reactions =<br />
{<br />
{<br />
personalityEffect = { stat = "happiness", adjust = 0.5 },<br />
personalityRequirement = { subject = "celebrates", other = "likes" },<br />
},<br />
{<br />
personalityEffect = { stat = "energy", adjust = -0.5 },<br />
personalityRequirement = { subject = "music", other = "relaxes" },<br />
}<br />
},<br />
canUse =<br />
{<br />
{<br />
interest = "Soda",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
}<br />
},<br />
{<br />
interest = "Coffee",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect =<br />
{<br />
{ stat = "motivation", adjust = 1.0 },<br />
{ stat = "bladder", adjust = 0.1 },<br />
{ stat = "energy", adjust = 1.0 },<br />
}<br />
},<br />
{<br />
interest = "Snacks",<br />
effect = { state = "hasMotivation", value = true },<br />
personalityEffect = { stat = "motivation", adjust = 1.0 },<br />
},<br />
{<br />
interest = "Sink",<br />
effect = { state = "tooHot", value = false },<br />
},<br />
{<br />
interest = "Toilet",<br />
effect = { state = "needsToilet", value = false },<br />
personalityEffect = { stat = "bladder", adjust = -1.0 },<br />
required = { state = "needsToilet", value = true },<br />
}<br />
},<br />
canFix = { },<br />
}<br />
</syntaxhighlight><br />
<br />
Phew. Let's go over the sections in turn.<br />
<br />
=== Special Action Toggles ===<br />
These are true by default, but let's include them explicitly. We want this Agent to be able to Patrol, Taser the player, and Search for the player. This allows us to make Goals later that use these Actions.<br />
<br />
=== World State ===<br />
Brief - the single added state here is necessary to use the 'Read Messages' Action. Otherwise nothing of note here.<br />
<br />
=== Stats ===<br />
We define five statistics here, and a total of seven world states. It's hopefully pretty clear what each is for. One point to note is that for the "motivation" stat, the threshold is 0.01 (an arbitrarily small number). This is because the 'above' threshold is greater than ''or equal''. If we set the threshold to 0.0, "hasMotivation" could never be false.<br />
<br />
=== Goals ===<br />
Let's look at this from top to bottom. The goals are in priority order (which isn't mandatory, but it makes working with large definitions easier). <br />
<br />
The first three goals are special cases and use "special" states:<br />
* "hasPrisoner", true: this occurs when an AI has caught the player. This is the ultimate goal of any Guard, interrupting all others. <br />
* "investigate", false: If investigate is true, the AI must stop what it's doing and satisfy itself that the investigation is complete.<br />
* "intruderVisible", true: This may appear counter-intuitive but it makes sense - the AI is always seeking Actions that enable it to see where the player is. However usually it has no set of Actions that enable it to achieve this state.<br />
<br />
Next up we have a much bigger goal. You'll notice that all of these are mood related. The thinking behind this is that, if the AI ever gets into a non-default "mood" state, it should do something to get back to equilibrium. So if it's sad, it should cry a little and feel better. If it's happy, laugh a little - etc.<br />
<br />
The final goal, our lowest, is the one that we want this AI to be doing most of the time. This is its bog-standard goal. It requires the agent have motivation, no unread phone messages, and that it hasn't completed patrolling already. Cunningly, it resets patrolling back to false every time it finishes, meaning it can repeat.<br />
<br />
=== Actions === <br />
These are all variations on the same theme; to 'undo' states that are caused by statistics reaching their extremes. Note that some have the same effect, but may occur when the AI is near another agent. In the case of the three dance actions, they all do the same job, but play different animations depending on their personality and environment.<br />
<br />
=== Responses ===<br />
These mirror what exists in Actions, where there are two that target other agents. Because we (currently) have several guards running the same definition, it's important that if agent A does something to agent B, B will have some kind of response to it. Agent definitions by their nature will have dependencies on each other in this way, because without them the agents will not fully interact with each other.<br />
<br />
=== Reactions ===<br />
These are two reactions that are used by the Radio device to adjust Agent stats, inducing the above Actions to take place. The first will aim to grant extra happiness to the Guard who supports the correct sports team, and the second will try to get a yawn out of an agent who finds the played music to be relaxing.<br />
<br />
=== canUse ===<br />
The first three Interests are methods of recovering motivation, which is reduced by patrolling. Each modifies an agent's stats in different ways. The next Interest is the Sink, which is used to cool down a player who has been burnt by a malevolent coffee machine. The final Interest is the Toilet, which hopefully needs no explanation!<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=AI_Lua_API&diff=1553AI Lua API2020-10-28T18:13:38Z<p>Andre: </p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= AI =<br />
== Description ==<br />
API to control the logic of AI in the mission<br />
== Functions ==<br />
=== Pause ===<br />
<syntaxhighlight source lang="lua">AI.Pause(characterName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|}<br />
'''Description''': Pauses the agent, and stops it from doing anything.<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': The AI will be hidden from networks, meaning it will no longer interact with devices or send/receive messages.<br />
=== Unpause ===<br />
<syntaxhighlight source lang="lua">AI.Unpause(characterName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|}<br />
'''Description''': Unpauses a hidden agent, resuming standard behaviour.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== IsPaused ===<br />
<syntaxhighlight source lang="lua">AI.IsPaused(characterName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|}<br />
'''Description''': Returns a bool based on if a character is currently paused or not<br />
<br />
'''Returns''': bool<br />
<br />
=== AddGoal ===<br />
<syntaxhighlight source lang="lua">AI.AddGoal(characterName, goal)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| goal || Lua Table<br />
|}<br />
'''Description''': Adds a defined goal<br />
<br />
'''Returns''': Nothing<br />
<br />
=== RemoveGoal ===<br />
<syntaxhighlight source lang="lua">AI.RemoveGoal(characterName, goalName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| goalName || string<br />
|}<br />
'''Description''': Removes any defined goals<br />
<br />
'''Returns''': Nothing<br />
<br />
=== AddTemporaryGoal ===<br />
<syntaxhighlight source lang="lua">AI.AddTemporaryGoal(characterName, goal)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| goal || Lua Table<br />
|}<br />
'''Description''': Adds a goal, taking top priority, to the named AI<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': This goal will be removed from the AI once complete. If it isn't achievable it will be removed immediately.<br />
=== AlterNPCMotivation ===<br />
<syntaxhighlight source lang="lua">AI.AlterNPCMotivation(characterName, motivationDelta)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| motivationDelta || number<br />
|}<br />
'''Description''': Alters an NPCS motivation state<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': When an NPCs motivation hits 0, they're no longer motivated and will attempt to take a break<br />
=== AlterNPCWorldState ===<br />
<syntaxhighlight source lang="lua">AI.AlterNPCWorldState(characterName, state, value)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| state || string<br />
|-<br />
| value || Lua Type<br />
|}<br />
'''Description''': Change the World State of an NPC.<br />
<br />
'''Returns''': Nothing<br />
<br />
=== FavourInterest ===<br />
<syntaxhighlight source lang="lua">AI.FavourInterest(characterName, device, permanent)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| device || string<br />
|-<br />
| permanent || bool<br />
|}<br />
'''Description''': Reduce the cost of an AI using a particular Interest<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': If permanent is false, the cost will revert to normal the next time this is successfully used<br />
=== AvoidInterest ===<br />
<syntaxhighlight source lang="lua">AI.AvoidInterest(characterName, device, permanent)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| device || string<br />
|-<br />
| permanent || bool<br />
|}<br />
'''Description''': Increase the cost of an AI using a particular Interest<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': If permanent is false, the cost will revert to normal the next time this is successfully used<br />
=== ChangeSubject ===<br />
<syntaxhighlight source lang="lua">AI.ChangeSubject(characterName, subject, value)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| subject || string<br />
|-<br />
| value || string<br />
|}<br />
'''Description''': Set the value of a particular subject, enabling Actions with Personality requirements<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': value can be null or empty (to unset/reset the subject)<br />
=== ReactTo ===<br />
<syntaxhighlight source lang="lua">AI.ReactTo(characterName, subject, value)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| subject || string<br />
|-<br />
| value || string<br />
|}<br />
'''Description''': Much like ChangeSubject, but instead of enabling Actions, this will alter stats within the Agent, modifying its WorldState (and as a result, enabling Actions)<br />
<br />
'''Returns''': Nothing<br />
<br />
'''Notes''': value can be null or empty (to unset/reset the subject)<br />
=== CreateReactable ===<br />
<syntaxhighlight source lang="lua">AI.CreateReactable(actionType, attraction, targetObject)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| actionType || AIReaction+Type<br />
|-<br />
| attraction || number<br />
|-<br />
| targetObject || MissionObject<br />
|}<br />
'''Description''': Create a new distraction that AI can pick up on<br />
<br />
'''Returns''': Nothing<br />
<br />
=== SetNPCFavouredComputer ===<br />
<syntaxhighlight source lang="lua">AI.SetNPCFavouredComputer(characterName, computer)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| computer || MissionObject<br />
|}<br />
'''Description''': Set NPC's computer, this will be used for a variety of actions<br />
<br />
'''Returns''': Nothing<br />
<br />
=== AddAction ===<br />
<syntaxhighlight source lang="lua">AI.AddAction(characterName, action)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| action || Lua Table<br />
|}<br />
'''Description''': Adds an action that can be used immediately<br />
<br />
'''Returns''': Nothing<br />
<br />
=== RemoveAction ===<br />
<syntaxhighlight source lang="lua">AI.RemoveAction(characterName, actionName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| characterName || string<br />
|-<br />
| actionName || string<br />
|}<br />
'''Description''': Removes any action<br />
<br />
'''Returns''': Nothing<br />
<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 28/10/2020 18:12<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Mission_Object_Set_Up&diff=1418Mission Object Set Up2020-06-30T11:41:28Z<p>Andre: Setup to affect a player and NPC on amok</p>
<hr />
<div><br />
==Choose a Mesh==<br />
<br />
Take a prop that is going to represent your hack-able object and place it in your scene.<br />
<br />
It can be anything really, but to to make full use of the options available either:<br />
* Choose from the Hackable Devices Collection scene in LevelKit.<br />
[[File:HackableObject-Collection.png|400px|thumb|none|The scene to pick hackable objects from]]<br />
* Piece your own together from pre-made models and parts of models in the LevelKit>Models>Props>Devices and it's sub-folder 'ModularTechParts' <br />
* or follow this guide to [[Modeling your own Hackable Devices]]<br />
<br />
==Add the Hackable component==<br />
<br />
Decide how you want it too behave in it's different states, as explained on the [[Devices]] page.<br />
<br />
Eg. <br />
Amok() sets of a sparks particle effect <br />
RunOnce() spits out a sheet of paper<br />
<br />
Create the corresponding effects, animations or behaviours you want and connect them to the corresponding Unity Events in the Hackable component<br />
<br />
==Add the MissionObject component==<br />
<br />
This is explained in more depth in the [[Mission Objects]] page, but essentially in this example we just need to add the MissionObject component and set it to 'Hackable'.<br />
<br />
==Setup a Lua Device Script==<br />
Follow this guide to [[Device Scripting]] to create a device script and set up the devices buttons to call the functions you setup in the 'Hackable' component.<br />
<br />
Or even call anything else from the [[:Category:LuaAPI|Lua Apis]].<br />
<br />
==Add to your mission script and connect to networks==<br />
<br />
* [[Mission Scripting#Devices|Setup the device]] and point is to your Device Script as explained above<br />
* [[Mission Scripting#Adding in Hackable Devices|Add the device]]<br />
* [[Mission Scripting#Connect Devices to the Network|Connect the device to a Network]]<br />
<br />
==Add item prop==<br />
When a NPC interacts with a hackable device sometimes that action needs some item to show on the NPC hand (for example: the soda machine makes a soda can appear on the NPC hand when the drinking animation is triggered). This item is set in the inspector, having a reference to the wanted object in the field 'Animation Item Prop', it's also possible to select the slot that the item will show (right hand is the default slot). <br />
<br />
==Setup to affect a player and NPC on amok==<br />
It's possible to make a hackable object to knock out an enemy and affect the player when it's running amok, it's important to have attention to some details for everything to work as intended. <br />
<br />
The first thing that needs to be set manually is the NPC animation, the Failed Use Gesture animation set on the Interest Point component must have an animation event for the system to know when to stun the NPC, this event need to call the function '''NPCUseHackableDevice''', if this animation event doesn't exist the character will not be knocked out, it's also important to time this event with the animation for the user to know when is NPC is stunned, for example, adding this event when the character is on the ground is a good idea.<br />
<br />
The hackable object can also affect the player, for that the device's game object needs to have an '''AmokRangeOnPlayer''' prefab as a child object. After that it's recommended to set that object Sphere Collider center and radius to make the wanted area that the player should be affected.<br />
Note: When the player is being affected the '''AmokDeviceReaction''' blend tree is played, but this is only a template animation.<br />
<br />
<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Data_Points_Lua_Api&diff=1344Data Points Lua Api2020-02-25T12:20:12Z<p>Andre: Created page with "<!-- This file is auto generated, please don't edit manually! --> = DataPoints = == Description == The DataPoints API returns and manage the wanted available data points == Fu..."</p>
<hr />
<div><!-- This file is auto generated, please don't edit manually! --><br />
= DataPoints =<br />
== Description ==<br />
The DataPoints API returns and manage the wanted available data points<br />
== Functions ==<br />
=== GetAllDataPoints ===<br />
<syntaxhighlight source lang="lua">DataPoints.GetAllDataPoints()</syntaxhighlight><br />
'''Description''': Return all the data points that are currently in the level<br />
<br />
'''Returns''': System.Collections.Generic.List`1[DataPoint]<br />
<br />
=== GetObjectDataPoints ===<br />
<syntaxhighlight source lang="lua">DataPoints.GetObjectDataPoints(objectName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| objectName || string<br />
|}<br />
'''Description''': Return all the data points received by an object or a character <br />
<br />
'''Returns''': System.Collections.Generic.List`1[DataPoint]<br />
<br />
=== FilterNetwork ===<br />
<syntaxhighlight source lang="lua">DataPoints.FilterNetwork(dataPoints, networkName)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataPoints || System.Collections.Generic.List`1[DataPoint]<br />
|-<br />
| networkName || string<br />
|}<br />
'''Description''': Filter data points with the network name<br />
<br />
'''Returns''': System.Collections.Generic.List`1[DataPoint]<br />
<br />
=== FilterDataType ===<br />
<syntaxhighlight source lang="lua">DataPoints.FilterDataType(dataPoints, dataType)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataPoints || System.Collections.Generic.List`1[DataPoint]<br />
|-<br />
| dataType || string<br />
|}<br />
'''Description''': Filter data points with the data type<br />
<br />
'''Returns''': System.Collections.Generic.List`1[DataPoint]<br />
<br />
=== DeleteDataPoints ===<br />
<syntaxhighlight source lang="lua">DataPoints.DeleteDataPoints(dataPoints)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataPoints || System.Collections.Generic.List`1[DataPoint]<br />
|}<br />
'''Description''': Remove data points from the level<br />
<br />
'''Returns''': Nothing<br />
<br />
=== GetData ===<br />
<syntaxhighlight source lang="lua">DataPoints.GetData(dataPoints)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataPoints || System.Collections.Generic.List`1[DataPoint]<br />
|}<br />
'''Description''': Return all the data string from the given data points<br />
<br />
'''Returns''': System.String[]<br />
<br />
=== GetDataPointInfo ===<br />
<syntaxhighlight source lang="lua">DataPoints.GetDataPointInfo(dataPoints)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataPoints || System.Collections.Generic.List`1[DataPoint]<br />
|}<br />
'''Description''': Return all the data info from the given data points<br />
<br />
'''Returns''': System.Collections.Generic.List`1[DataPointInfo]<br />
<br />
=== PlayersInventorySave ===<br />
<syntaxhighlight source lang="lua">DataPoints.PlayersInventorySave(dataFile)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataFile || DataPointInfo<br />
|}<br />
'''Description''': Save data info in the the players inventory<br />
<br />
'''Returns''': Nothing<br />
<br />
=== PlayersInventoryRemove ===<br />
<syntaxhighlight source lang="lua">DataPoints.PlayersInventoryRemove(dataFile)</syntaxhighlight><br />
'''Expected parameter types'''<br />
{| class="wikitable"<br />
|-<br />
! Name !! Type<br />
|-<br />
| dataFile || DataPointInfo<br />
|}<br />
'''Description''': Save data info in the the players inventory<br />
<br />
'''Returns''': Nothing<br />
<br />
<br />
<br />
This file is auto generated, please don't edit manually!<br />
<br />
'''Docs last hacked together on''': 25/02/2020 12:19<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Player_Path_Tool&diff=1034Player Path Tool2019-08-02T14:35:23Z<p>Andre: Created page with "== Introduction == The player path tool is a simple tool to create multiples lines with various points, giving a visual aid for creating levels, helping the visualization of..."</p>
<hr />
<div>== Introduction ==<br />
<br />
The player path tool is a simple tool to create multiples lines with various points, giving a visual aid for creating levels, helping the visualization of the possible paths that the player can go through the level.<br />
<br />
== How to use it ==<br />
<br />
To getting started, it's only needed to drag the tool prefab in the wanted scene. The prefab is located in 'Assets/LevelKit/Tools/PlayerPathTool.prefab'.<br />
<br />
You can add new points in the path just by clicking on the wanted location in the scene window, but first, a path must be selected (a path can be created in the 'Add new path' button).<br />
<br />
<br />
[[File:PlayerPathToolInTheScene.png]]<br />
<br />
To select a point click on the blue button, to select multiple points hold the spacebar and select the wanted points. When a point is selected you can have access in the following functionalities:<br />
<br />
* Change the points position with the position handle<br />
* Remove the selected point on the red button<br />
* Add a new point near the selected point on the green button<br />
<br />
== Inspector UI ==<br />
<br />
[[File:PlayerPathToolInspector.png]]<br />
<br />
==== Path Mode ====<br />
<br />
This tool have various modes that can be selected, they're the follow:<br />
<br />
* '''Default''' : All the UI and functionalities are enabled<br />
* '''UI Only On Selected Path''' : Is only shown the UI of the selected path<br />
* '''UI Disabled''' : All the UI is disabled in the scene, expect the path line<br />
* '''Add Points Disabled''' : Disabled the creation of new points<br />
* '''Disabled''' : Everything is disabled, expect the path line<br />
<br />
==== Default Path Color ====<br />
<br />
It's the default path color when a new path it's created.<br />
<br />
==== Paths ====<br />
<br />
This field has all the path points information, like the position of the points, so that can be used for debugging reasons.<br />
<br />
==== Default GUI Style ====<br />
<br />
It's the GUI Style used for the optional points text.<br />
<br />
=== Options ===<br />
<br />
==== Add new path ====<br />
<br />
This tool can contain various paths, to do that this button can add them.<br />
<br />
=== Selected Path ===<br />
<br />
The first parameter indicates the path selected, it can be changed in the dropdown list. Followed by that is the Remove Button, as the names imply, it removes the selected path. The following fields are:<br />
<br />
* '''Name''' : path name<br />
* '''Color''' : path line color<br />
* '''Text''' '''points''' : show all the path points text if it exists, the text style is determined by the 'Default GUI Style'</div>Andrehttp://wiki.offgridthegame.com/index.php?title=File:PlayerPathToolInspector.png&diff=1033File:PlayerPathToolInspector.png2019-08-02T14:16:16Z<p>Andre: </p>
<hr />
<div></div>Andrehttp://wiki.offgridthegame.com/index.php?title=File:PlayerPathToolInTheScene.png&diff=1032File:PlayerPathToolInTheScene.png2019-08-02T14:11:51Z<p>Andre: </p>
<hr />
<div></div>Andrehttp://wiki.offgridthegame.com/index.php?title=Light_and_Shadow_Mechanics&diff=1031Light and Shadow Mechanics2019-08-01T15:48:42Z<p>Andre: </p>
<hr />
<div>explain here:<br />
<br />
* light and shadow for stealth<br />
* script-controlled lamp components & use<br />
* light groups<br />
* level design considerations<br />
* light level debug tool.<br />
<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Head_props&diff=1028Head props2019-07-23T09:42:32Z<p>Andre: </p>
<hr />
<div>__FORCETOC__ <br />
== Introduction ==<br />
<br />
When setting a character in Mission Script it's possible to define their head props, like hair, hats, glasses and earpieces, doesn't exist any limit to the number of props used in a character. If isn't defined any prop then the default ones will be added in the character.<br />
<br />
== Script Example ==<br />
<br />
<syntaxhighlight source lang="lua"><br />
characters = {<br />
guard1 = {<br />
displayName = "Marcus Fordham",<br />
internalName = "guard1",<br />
prefab = "Masculine_Med_Vest_Enemy",<br />
headProps =<br />
{<br />
"F_Lrg_Earpiece_01", "M_Lrg_Hair-Long-Ponytail-Fringe_01", "F_Med_Glasses-01"<br />
},<br />
}<br />
</syntaxhighlight><br />
== Props List ==<br />
<br />
{|class= "wikitable"<br />
!width="29%"| Prefab Name<br />
!width="10%"| Image<br />
!width="20%"| Note<br />
|-<br />
|F_Lrg_Earpiece_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Glasses-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair_Short_SideFringe-03<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hat-HardHat-01<br />
|<br />
|<br />
|-<br />
|F_Med_Earpiece_01<br />
|<br />
|<br />
|-<br />
|F_Med_Glasses-01<br />
|<br />
|<br />
|-<br />
|F_Med_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|F_Med_Hair_Short_SideFringe-03<br />
|<br />
|<br />
|-<br />
|F_Med_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|F_Med_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|F_Med_Hat-HardHat-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Earpiece_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Glasses-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hat-HardHat-01<br />
|<br />
|<br />
|-<br />
|M_Med_Earpiece_01<br />
|<br />
|<br />
|-<br />
|M_Med_Glasses-01<br />
|<br />
|<br />
|-<br />
|M_Med_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Wavey_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|M_Med_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|M_Med_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|M_Med_Hat-HardHat-01<br />
|<br />
|<br />
|}<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Navcloud&diff=1027Navcloud2019-07-23T08:27:27Z<p>Andre: __FORCETOC__ added</p>
<hr />
<div>__FORCETOC__ <br />
==Introduction==<br />
<br />
Navcloud it's needed for the drones navigation, they can only move in the navcloud area.<br />
<br />
==Set Navcloud Boundaries==<br />
<br />
Before generate a navcloud it's recommended to set it's boundaries to avoid creating larger volumes than needed. To set the navcloud boundaries first must be added the 'Nav Cloud Boundaries' component in any game object of the wanted scene. With the component added with will be showed in the scene a yellow cube, that can be changed it's position and size, this cube is a helper to create the red cube that represent the navcloud area that will be generated (cubes colors can be changed in the inspector), this area have all it's sides in the same size. We can see an example in the screenshot bellow: <br />
<br />
[[File:NavCloudBoundariesExample.png]]<br />
<br />
==Navcloud Generation==<br />
<br />
To generate the navcloud it's only needed to select the option in the Unity menubar: Offgrid -&gt; Generate NavCloud.<br />
<br />
If doesn't exist any navcloud boundaries component in the scene it's still possible to generate the navcloud, but it will be the size of the level (the generation can take some time to finish in this case). In the case that will be found more than one navcloud boundaries component, it will show a dialog box warning that the generation it will only take into account the first component found. <br />
<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Head_props&diff=1026Head props2019-07-23T08:27:18Z<p>Andre: __FORCETOC__ added</p>
<hr />
<div>__FORCETOC__ <br />
== Introduction ==<br />
<br />
When setting a character in Mission Script it's possible to define their head props, like hair, hats, glasses and earpieces, doesn't exist any limit to the number of props used in a character. If isn't defined any prop then the default ones will be added in the character.<br />
<br />
== Script Example ==<br />
<br />
<syntaxhighlight source lang="lua"><br />
characters = {<br />
guard1 = {<br />
displayName = "Marcus Fordham",<br />
internalName = "guard1",<br />
prefab = "Masculine_Med_Vest_Enemy",<br />
headProps =<br />
{<br />
"F_Lrg_Earpiece_01", "M_Lrg_Hair-Long-Ponytail-Fringe_01", "F_Med_Glasses-01"<br />
},<br />
}<br />
</syntaxhighlight><br />
== Props List ==<br />
<br />
{|class= "wikitable"<br />
!width="29%"| Prefab Name<br />
!width="10%"| Image<br />
!width="20%"| Note<br />
|-<br />
|F_Lrg_Earpiece_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Glasses-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hair_Short_SideFringe-03<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|F_Lrg_Hat-HardHat-01<br />
|<br />
|<br />
|-<br />
|F_Med_Earpiece_01<br />
|<br />
|<br />
|-<br />
|F_Med_Glasses-01<br />
|<br />
|<br />
|-<br />
|F_Med_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|F_Med_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|F_Med_Hair_Short_SideFringe-03<br />
|<br />
|<br />
|-<br />
|F_Med_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|F_Med_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|F_Med_Hat-HardHat-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Earpiece_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Glasses-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|M_Lrg_Hat-HardHat-01<br />
|<br />
|<br />
|-<br />
|M_Med_Earpiece_01<br />
|<br />
|<br />
|-<br />
|M_Med_Glasses-01<br />
|<br />
|<br />
|-<br />
|M_Med_Glasses-Aviators-01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Ponytail-Fringe_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Wavey_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Long-Wavey_02<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-Buzz_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-Curly-Clip_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-Pointy_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair-Short-SideFringe_01<br />
|<br />
|<br />
|-<br />
|M_Med_Hair_Short_SideFringe-02<br />
|<br />
|<br />
|-<br />
|M_Med_Hat-Beanie-01<br />
|<br />
|<br />
|-<br />
|M_Med_Hat-Cap-01<br />
|<br />
|<br />
|-<br />
|M_Med_Hat-HardHat-01<br />
|<br />
|<br />
|}<br />
[[Category:Modding]][[Category:LuaAPI]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Navcloud&diff=1025Navcloud2019-07-22T17:59:20Z<p>Andre: </p>
<hr />
<div>= Navcloud =<br />
<br />
==Introduction==<br />
<br />
Navcloud it's needed for the drones navigation, they can only move in the navcloud area.<br />
<br />
==Set Navcloud Boundaries==<br />
<br />
Before generate a navcloud it's recommended to set it's boundaries to avoid creating larger volumes than needed. To set the navcloud boundaries first must be added the 'Nav Cloud Boundaries' component in any game object of the wanted scene. With the component added with will be showed in the scene a yellow cube, that can be changed it's position and size, this cube is a helper to create the red cube that represent the navcloud area that will be generated (cubes colors can be changed in the inspector), this area have all it's sides in the same size. We can see an example in the screenshot bellow: <br />
<br />
[[File:NavCloudBoundariesExample.png]]<br />
<br />
==Navcloud Generation==<br />
<br />
To generate the navcloud it's only needed to select the option in the Unity menubar: Offgrid -&gt; Generate NavCloud.<br />
<br />
If doesn't exist any navcloud boundaries component in the scene it's still possible to generate the navcloud, but it will be the size of the level (the generation can take some time to finish in this case). In the case that will be found more than one navcloud boundaries component, it will show a dialog box warning that the generation it will only take into account the first component found.<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Navcloud&diff=1024Navcloud2019-07-22T17:56:11Z<p>Andre: </p>
<hr />
<div><br />
= Introduction =<br />
<br />
Navcloud it's needed for the drones navigation, they can only move in the navcloud area.<br />
<br />
= Set Navcloud Boundaries =<br />
<br />
Before generate a navcloud it's recommended to set it's boundaries to avoid creating larger volumes than needed. To set the navcloud boundaries first must be added the 'Nav Cloud Boundaries' component in any game object of the wanted scene. With the component added with will be showed in the scene a yellow cube, that can be changed it's position and size, this cube is a helper to create the red cube that represent the navcloud area that will be generated (cubes colors can be changed in the inspector), this area have all it's sides in the same size. We can see an example in the screenshot bellow: <br />
<br />
[[File:NavCloudBoundariesExample.png]]<br />
<br />
= Navcloud Generation =<br />
<br />
To generate the navcloud it's only needed to select the option in the Unity menubar: Offgrid -&gt; Generate NavCloud.<br />
<br />
If doesn't exist any navcloud boundaries component in the scene it's still possible to generate the navcloud, but it will be the size of the level (the generation can take some time to finish in this case). In the case that will be found more than one navcloud boundaries component, it will show a dialog box warning that the generation it will only take into account the first component found.<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Navcloud&diff=1023Navcloud2019-07-22T17:50:24Z<p>Andre: </p>
<hr />
<div>== Introduction ==<br />
<br />
Navcloud it's needed for the drones navigation, they can only move in the navcloud area.<br />
<br />
== Set Navcloud Boundaries ==<br />
<br />
Before generate a navcloud it's recommended to set it's boundaries to avoid creating larger volumes than needed. To set the navcloud boundaries first must be added the 'Nav Cloud Boundaries' component in any game object of the wanted scene. With the component added with will be showed in the scene a yellow cube, that can be changed it's position and size, this cube is a helper to create the red cube that represent the navcloud area that will be generated (cubes colors can be changed in the inspector), this area have all it's sides in the same size. We can see an example in the screenshot bellow: <br />
<br />
[[File:NavCloudBoundariesExample.png]]<br />
<br />
== Navcloud Generation ==<br />
<br />
To generate the navcloud it's only needed to select the option in the Unity menubar: Offgrid -&gt; Generate NavCloud.<br />
<br />
If doesn't exist any navcloud boundaries component in the scene it's still possible to generate the navcloud, but it will be the size of the level (the generation can take some time to finish in this case). In the case that will be found more than one navcloud boundaries component, it will show a dialog box warning that the generation it will only take into account the first component found.<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Navcloud&diff=1022Navcloud2019-07-22T17:33:13Z<p>Andre: </p>
<hr />
<div>= Navcloud =<br />
<br />
== Introduction ==<br />
<br />
Navcloud it's needed for the drones navigation, they can only move in the navcloud area.<br />
<br />
== Set Navcloud Boundaries ==<br />
<br />
Before generate a navcloud it's recommended to set it's boundaries to avoid creating larger volumes than needed. To set the navcloud boundaries first must be added the 'Nav Cloud Boundaries' component in any game object of the wanted scene. With the component added with will be showed in the scene a yellow cube, that can be changed it's position and size, this cube is a helper to create the red cube that represent the navcloud area that will be generated (cubes colors can be changed in the inspector), this area have all it's sides in the same size. We can see an example in the screenshot bellow: <br />
<br />
[[File:NavCloudBoundariesExample.png]]<br />
<br />
== Navcloud Generation ==<br />
<br />
To generate the navcloud it's only needed to select the option in the Unity menubar: Offgrid -&gt; Generate NavCloud.<br />
<br />
If doesn't exist any navcloud boundaries component in the scene it's still possible to generate the navcloud, but it will be the size of the level (the generation can take some time to finish in this case). In the case that will be found more than one navcloud boundaries component, it will show a dialog box warning that the generation it will only take into account the first component found.<br />
<br />
[[Category:Modding]]</div>Andrehttp://wiki.offgridthegame.com/index.php?title=Navcloud&diff=1021Navcloud2019-07-22T17:32:00Z<p>Andre: Added</p>
<hr />
<div>= Navcloud =<br />
<br />
== Introduction ==<br />
<br />
Navcloud it's needed for the drones navigation, they can only move in the navcloud area.<br />
<br />
== Set Navcloud Boundaries ==<br />
<br />
Before generate a navcloud it's recommended to set it's boundaries to avoid creating larger volumes than needed. To set the navcloud boundaries first must be added the 'Nav Cloud Boundaries' component in any game object of the wanted scene. With the component added with will be showed in the scene a yellow cube, that can be changed it's position and size, this cube is a helper to create the red cube that represent the navcloud area that will be generated (cubes colors can be changed in the inspector), this area have have all it's sides in the same size. We can see an example in the screenshot bellow: <br />
<br />
[[File:NavCloudBoundariesExample.png]]<br />
<br />
== Navcloud Generation ==<br />
<br />
To generate the navcloud it's only needed to select the option in the Unity menubar: Offgrid -&gt; Generate NavCloud.<br />
<br />
If doesn't exist any navcloud boundaries component in the scene it's still possible to generate the navcloud, but it will be the size of the level (the generation can take some time to finish in this case). In the case that will be found more than one navcloud boundaries component, it will show a dialog box warning that the generation it will only take into account the first component found.<br />
<br />
[[Category:Modding]]</div>Andre