Action Tree Fundamentals
Action trees in .uri are essential for defining the interaction logic between the player and NPCs or objects, as well as determining NPC behavior within the game world. These trees are used to control everything from simple dialogues to complex behavior sequences, including 1-on-1 interactions and managing both local and global states at the NPC and tree level.
Action Concept
The action is the most fundamental aspect of the tree, representing its nodes. It is a basic unit of behavior that is executed and does something in its context. Actions allow you to create dialogues, movements, state changes, calculations, and any other behavior you want to implement in the game. Each action can have a set of parameters that define what it does.
Actions can be used for interactions, behavior, or both. When an action is executed, the next action is executed unless the tree ends. It is the action itself that affects the flow of actions that follow—it can execute its first child action or its second, based on its result, which can be 1, 2, 3...
Action Structure
An action consists of:
- The action name: This is the action identifier and determines what type of behavior will be executed (e.g.,
say
,ask
,go
). - Parameters: These are the data or additional information the action needs to execute (e.g., the message an NPC should say, the options for a question, the movement coordinates).
- Results: Some actions return a numeric result that can be used to control the flow of the action tree (e.g., in the case of a question, the user's response determines the result: if they choose the first option, the result is one; if they choose the second, the result is two, and so on).
Action Examples
say "Welcome to the world of .uri"
In this example, say
is the action that indicates the NPC should display a message, and "Welcome to the world of .uri"
is the parameter that contains the message.
ask "Onion in your tortilla or without?" "With onion" "Without onion"
tell "So, with onion..."
tell "So, without onion..."
In this other example, ask
is an action that allows the user to choose within an interaction and conditions the conversation based on their choice. For instance, if the user chooses "With onion," the result of this action is 1, so the NPC will say the first of its next actions, "So, with onion."
States and Variables
In .uri, both states and variables play a crucial role in managing NPC logic and interactions within the game. Variables are data containers that store information and can be either local or global. A local variable exists only for a single execution of the tree, and when it ends, it is erased. For example, if a player inputs their name in a dialogue, it could be stored in a local variable called player_name
. This variable would only exist for that player until the actions in the tree end. On the other hand, a global variable is available even after the entire tree execution, and its value is shared and accessible at any time until the server is restarted. This is useful for data that must remain consistent across the game, such as a counter tracking how many times an NPC has been interacted with.
States, meanwhile, describe the current condition or behavior of an NPC and can also be local or global. A local state reflects the NPC's relationship with a specific player; for instance, a local state could indicate whether an NPC is following the player who activated it. This state is exclusive to that interaction. A global state, in contrast, affects how the NPC behaves with all players, such as if the NPC is in patrol or rest mode, and is visible and consistent for all users in the game. These states persist even if the server restarts.
Example: If an NPC has a global state of patrolling
and a player interacts with it, it might activate a local state of following
, causing the NPC to follow only that player. At the same time, other interactions would not affect its global patrol state. Additionally, the player's name could be stored in a local variable to personalize future interactions with that NPC during the same session. This differentiated use of states and variables allows for great flexibility and control in programming dynamic and specific behaviors in .uri.
Example of a Local Variable
ask color "Tell me your favorite color:" "Enter the color..." "Blue" 20
tell "So, your favorite color is $color"
In this example: 1. The ask
action stores the color the player inputs in the local variable color
. 2. Then, the say
action uses this local variable to personalize the NPC's dialogue.
Example of a Global Variable
// This first line increments the counter by 1, or sets it to 0 and adds 1 if it's the first time.
set mission_counter {$mission_counter or 0 + 1}
// This line checks if it's already 10, and if so, the result of the action is 1.
if {$mission_counter == 10}
tell "We have completed $mission_counter missions."
tell "We're not at 10 yet. We're at $mission_counter."
In this example: 1. The set
action stores a global variable mission_counter
and increments it. 2. Then, the if
action checks if the counter has reached 10. 3. Finally, the NPC uses say
to inform the player how the counter is progressing.
Types of Action Trees
There are two main types of action trees in .uri: interaction trees and behavior trees. While both function similarly, they have key differences that make them suitable for different purposes in the game.
Interaction Tree
An interaction tree is a type of action tree that is executed when a player directly interacts with an NPC or object. This tree defines what happens when the player talks to the NPC, allowing for a series of chained actions related to the player. Interaction trees have default variables such as the player being spoken to as "player" and the NPC's name as "name."
Example:
Imagine a player talking to an NPC named Gustavo. Upon starting the conversation, the interaction tree might define the following sequence:
- Action Say: The NPC says: "Hi, my name is Gustavo."
- Action Ask: The NPC asks: "Are you from here?"
- Response 1: If the player answers "Yes" (returns
1
), the NPC says: "Nice, I like you." - Response 2: If the player answers "No" (returns
2
), the NPC says: "Get out of here, we don't want you."
tell "Hi, my name is Gustavo."
ask "Are you from here?" "Yes" "No"
tell "Nice, I like you."
tell "Get out of here, we don't want you."
Each action within the tree returns a result that can be used to determine the next action in the sequence, allowing for logical and dynamic interactions.
Behavior Tree
A behavior tree, on the other hand, defines the actions that an NPC performs within the game world, independent of direct interaction with a player. This tree can control whether an NPC is patrolling, following a player, driving a vehicle, or performing any other autonomous action. Unlike the interaction tree, which runs once during an interaction, this tree runs on every tick (every few seconds). The behavior tree has the NPC's name as a variable called "name."
Example:
A behavior tree might define that an NPC patrols a specific area and changes its state to follow the player if it detects their presence.
- Action FindNearestPlayer: If a player is nearby, the NPC goes toward them.
- Response 1: The NPC is close to the player. Action Nothing: The NPC does nothing.
- Response 2: The NPC is moving toward the player. Action Nothing: The NPC does nothing.
- Response 3: No player is nearby. Action Wander: The NPC continues patrolling.
getClosestPlayer 15.0 3.0
nothing
nothing
wander -1020.72 4839.78 258.30 10 5 5
Branch Locking
Behavior trees have a special feature that distinguishes them from interaction trees. Due to their nature of running every few seconds, an action might become useless if it doesn’t have time to finish before the next execution. To address this, there is the concept of locking the branch, meaning for a specific sequence of actions within the tree, you ensure the first action completes fully before allowing the next ones to proceed.
Example:
getClosestPlayer player;
*surround $player 5 3 5
wait 10
playAnimation "anim@mp_player_intcelebrationmale@smoke_flick" "smoke_flick"
playAnimation "anim@mp_player_intcelebrationmale@smoke_flick" "smoke_flick"
playAnimation "mini@strip_club@lap_dance@ld_girl_a_exit" "ld_girl_a_exit_f"
In this example, we ensure that the NPC is circling around the player before waiting 10 seconds and then chaining the animations. If the NPC stops circling, it will recheck and resume circling.
Using Tokens and Variables in Parameters
In .uri, tokens are used as parameters within the nodes of an action tree. A token can be a word, a number, or a variable. Variables can be used within text strings to be interpreted in real-time.
Example:
tell "Hello, $player_name, welcome to the game."
Here, $player_name
is a variable that will be replaced in real-time by the previously stored player’s name.
Arithmetic-Logical Expressions in .uri
In .uri, arithmetic-logical expressions allow for real-time calculations and evaluations within the code. These expressions are defined between curly braces {}
and can include mathematical operations, logical comparisons, and the use of variables. The expressions behave similarly to Lua, but with minor differences, such as using !=
instead of ~=
for inequality comparison.
Expressions can combine addition, subtraction, multiplication, division, and more, allowing for dynamic logic within action trees.
Arithmetic Expression Example
set result {2 + 3}
tell "The result is $result."
In this example: 1. The expression {2 + 3}
performs an addition, and the value 5
is stored in the variable result
. 2. Then, the say
action uses the $result
variable to display the result in the dialogue.
Example of strings with variables:
Strings can also be concatenated with other expressions within the curly braces {}
, allowing for greater flexibility in messages and calculations involving text.
set player_name "Carlos"
if {"Carlos" == $player_name}
tell "Hello, $player_name, welcome to the game."
In this example: 1. The player_name
variable is set to "Carlos"
. 2. A check is performed to see if two strings are equal. 3. The say
action uses this variable within a string to create a dynamic message that greets the player by name.
Using Variables in Expressions
You can use variables within expressions to make calculations dynamic. Variables must be preceded by $
to indicate that their current value is being used.
set points 50
set bonus {$points + 10}
tell "You have earned $bonus experience points."
In this example: 1. A points
variable is set to 50
. 2. Then, the expression {$points + 10}
adds 10
to the points
variable, and the result (60
) is stored in the bonus
variable. 3. Finally, the NPC informs the player of their experience points using the $bonus
variable.
Logical Comparisons
.uri also allows for logical comparisons within expressions. You can compare values using operators such as >
, <
, >=
, <=
, ==
(equality), and !=
(inequality). These comparisons evaluate to true
or false
.
set current_health 30
set is_injured {$current_health < 50}
tell "Are you injured?: $is_injured."
In this example: 1. The current_health
variable has a value of 30
. 2. The expression {$current_health < 50}
evaluates whether the health is less than 50
. Since the condition is true, the is_injured
variable is set to true
. 3. The NPC informs the player whether they are injured based on the expression's evaluation.
Complex Expressions
Expressions in .uri also support combinations of arithmetic and logical operations in a single expression.
set health 100
set damage 30
set remaining_health {$health - $damage}
set in_danger {$remaining_health <= 20}
tell "You have $remaining_health health points left. Are you in danger?: $in_danger."
In this example: 1. The expression {$health - $damage}
calculates the remaining health after receiving damage. 2. The expression {$remaining_health <= 20}
evaluates whether the remaining health is less than or equal to 20
, and if so, the in_danger
variable is set to true
. 3. The NPC informs the player of their remaining health and whether they are in danger.
Support for Arithmetic and Logical Operations
.uri supports almost all common operations, including:
- Addition:
{a + b}
- Subtraction:
{a - b}
- Multiplication:
{a * b}
- Division:
{a / b}
- Integer division:
{a // b}
- Comparisons:
{a > b}
,{a <= b}
,{a == b}
,{a != b}
These operations can be combined and nested to create complex logic that is evaluated in real-time during action execution.
Conclusion
Action trees in .uri offer a flexible and powerful structure for managing behavior and interactions in the game. Through actions, variables, states, and expressions, complex logic can be created that makes NPCs dynamically react and interact with players.