Skip to content

Commit

Permalink
v1.2.5
Browse files Browse the repository at this point in the history
Added: ^{} code block syntax. Probably too many ways of doing code now.
Fixes: A bunch of issues with behaviour
Added: Pass behaviour owner into the behaviour tree
Changes: Some RezBehaviour methods are now properties
Added: More behaviour docs
  • Loading branch information
mmower committed Mar 2, 2024
1 parent 9e2ed74 commit 5f09622
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 66 deletions.
2 changes: 1 addition & 1 deletion assets/templates/runtime/rez_0prototype.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ const basic_object = {
const options = tree_spec["options"];
const children = tree_spec["children"].map((spec) => this.instantiateBehaviourTree(spec));

return behaviour_template.instantiate(options, children);
return behaviour_template.instantiate(this, options, children);
},

createTraceryGrammarAttribute(attr_name, value) {
Expand Down
39 changes: 18 additions & 21 deletions assets/templates/runtime/rez_behaviour.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ RezBehaviour.prototype = {
__proto__: basic_object,
constructor: RezBehaviour,

get firstChild() {
return this.children[0];
},

get secondChild() {
return this.children[1];
},

get childCount() {
return this.children.length;
},

configure() {
const config_fn = this.getAttribute("configure");
if(typeof(config_fn) === "function") {
Expand Down Expand Up @@ -47,26 +59,10 @@ RezBehaviour.prototype = {
this.options[name] = value;
},

firstChild() {
return this.children[0];
},

secondChild() {
return this.children[1];
},

getChild(idx) {
getChildAt(idx) {
return this.children[idx];
},

children() {
return this.children;
},

childCount() {
return this.children.length;
},

result(wmem, success) {
return {
id: this.id,
Expand All @@ -78,9 +74,9 @@ RezBehaviour.prototype = {
executeBehaviour(wmem) {
// By definition this is a function of two attributes
// (behaviour, wmem)
const handler = this.getAttribute("execute");
if(typeof(handler) === "function") {
return handler(this, wmem);
const execute = this.getAttribute("execute");
if(typeof(execute) === "function") {
return execute(this.owner, this, wmem);
} else {
return {
id: this.id,
Expand All @@ -91,8 +87,9 @@ RezBehaviour.prototype = {
}
},

instantiate(options, children = []) {
instantiate(owner, options, children = []) {
const behaviour = this.copyWithAutoId();
behaviour.owner = owner;
behaviour.options = options;
behaviour.children = children;
behaviour.configure();
Expand Down
73 changes: 37 additions & 36 deletions assets/templates/stdlib.rez.eex
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@
options: []
min_children: 2

execute: (behaviour, wmem) => {
execute: (owner, behaviour, wmem) => {
let result = {success: true, wmem: wmem};
for(const child of behaviour.children) {
result = child.executeBehaviour(result.wmem);
Expand Down Expand Up @@ -1003,10 +1003,10 @@
}
}

execute: (behaviour, wmem) => {
execute: (owner, behaviour, wmem) => {
const p = behaviour.intOption("p");
const die = new RezDie(1, 100, 0);
let result = {success: false, id: behaviour.id, wmem};
let result = {success: false, wmem};
for(const child of behaviour.children) {
if(die.roll() < p) {
result = child.executeBehaviour(result.wmem);
Expand All @@ -1028,11 +1028,11 @@
options: []
min_children: 2

execute: (behaviour, wmem) => {
let result = {success: true, wmem: wmem};
execute: (owner, behaviour, wmem) => {
let result;
for(const child of behaviour.children) {
result = child.executeBehaviour(result.wmem);
if(result.success) {
result = child.executeBehaviour(wmem);
if(!result.success) {
break;
}
}
Expand All @@ -1051,11 +1051,11 @@
min_children: 1
max_children: 1

execute: (behaviour, wmem) => {
const count = behaviour.option("count");
execute: (owner, behaviour, wmem) => {
const count = behaviour.intOption("count");
let result = {success: false, wmem: wmem};
for(let i=0; i<count; i++) {
result = this.firstChild().executeBehaviour(result.wmem);
result = this.firstChild.executeBehaviour(result.wmem);
if(!result.success) {
return result;
}
Expand All @@ -1076,11 +1076,11 @@
min_children: 1
max_children: 1
execute: (behaviour, wmem) => {
const attempts = behaviour.option("attempts");
execute: (owner, behaviour, wmem) => {
const attempts = behaviour.intOption("attempts");
let result = {success: false, wmem: wmem};
for(let i=0; i<attempts; i++) {
result = behaviour.firstChild().executeBehaviour(result.db);
result = behaviour.firstChild.executeBehaviour(result.wmem);
if(result.success) {
break;
}
Expand All @@ -1101,12 +1101,12 @@
min_children: 1
max_children: 1

execute: (behaviour, wmem) => {
const p = behaviour.option("p");
execute: (owner, behaviour, wmem) => {
const p = behaviour.intOption("p");
const die = new RezDie(1, 100, 0);

if(die.roll() < p) {
return behaviour.firstChild().executeBehaviour(wmem);
return behaviour.firstChild.executeBehaviour(wmem);
} else {
return {success: false, id: behaviour.id, error: "Didn't execute"};
}
Expand All @@ -1124,15 +1124,15 @@
min_children: 2
max_children: 2

execute: (behaviour, wmem) => {
const p = behaviour.option("p");
execute: (owner, behaviour, wmem) => {
const p = behaviour.intOption("p");
const die = new RezDie(1, 100, 0);
const roll = die.roll();

if(roll < p) {
return behaviour.firstChild().executeBehaviour(wmem);
return behaviour.firstChild.executeBehaviour(wmem);
} else {
return behaviour.secondChild().executeBehaviour(wmem);
return behaviour.secondChild.executeBehaviour(wmem);
}
}
}
Expand All @@ -1146,9 +1146,9 @@
options: []
min_children: 2

execute: (behaviour, wmem) => {
const die = new RezDie(1, behaviour.childCount(), 0);
const child = behaviour.getChild(die.roll());
execute: (owner, behaviour, wmem) => {
const die = new RezDie(1, behaviour.childCount, -1);
const child = behaviour.getChildAt(die.roll());
return child.executeBehaviour(wmem);
}
}
Expand All @@ -1164,16 +1164,17 @@
options: []
min_children: 2

execute: (behaviour, wmem) => {
let child_walk = wmem[this];
execute: (owner, behaviour, wmem) => {
let child_walk = wmem[behaviour.id];

if(typeof(child_walk) == "undefined" || child_walk.length == 0) {
child_walk = Array.from(Array(behaviour.childCount()).keys()).shuffle();
child_walk = Array.from(Array(behaviour.childCount).keys()).fy_shuffle();
}

const child = child_walk.shift();
wmem[this] = child_walk;
const child_idx = child_walk.shift();
wmem[behaviour.id] = child_walk;

const child = behaviour.getChildAt(child_idx);
return child.executeBehaviour(wmem);
}
}
Expand All @@ -1187,8 +1188,8 @@
min_children: 1
max_children: 1

execute: (behaviour, wmem) => {
const result = behaviour.firstChild().executeBehaviour(wmem);
execute: (owner, behaviour, wmem) => {
const result = behaviour.firstChild.executeBehaviour(wmem);
result.success = true;
return result;
}
Expand All @@ -1203,8 +1204,8 @@
min_children: 1
max_children: 1

execute: (behaviour, wmem) => {
const result = behaviour.firstChild().executeBehaviour(wmem);
execute: (owner, behaviour, wmem) => {
const result = behaviour.firstChild.executeBehaviour(wmem);
result.success = false;
return result;
}
Expand All @@ -1219,8 +1220,8 @@
min_children: 1
max_children: 1

execute: (behaviour, wmem) => {
const result = behaviour.firstChild().executeBehaviour(wmem);
execute: (owner, behaviour, wmem) => {
const result = behaviour.firstChild.executeBehaviour(wmem);
if(result.success) {
result.success = false;
result.error = "Inversion";
Expand All @@ -1238,7 +1239,7 @@
options: []
max_children: 0

execute: (behaviour, wmem) => {
execute: (owner, behaviour, wmem) => {
return {success: false, wmem: wmem};
}
}
Expand All @@ -1250,7 +1251,7 @@
options: []
max_children: 0

execute: (behaviour, wmem) => {
execute: (owner, behaviour, wmem) => {
return {success: true, wmem: wmem};
}
}
86 changes: 86 additions & 0 deletions docs/REZ.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,92 @@ The Rez stdlib defines a range of xref:behaviour_catalog.adoc[core behaviours] w

The core behaviours are intended to provide an overall structure for creating different kinds of behaviour. In particular look at behaviours like xref:behaviour_catalog.adoc#_either[$either], xref:behaviour_catalog.adoc#_random_choice[$random_choice] and xref:behaviour_catalog:[$random_each] which can introduce variability into behaviour patterns.

=== Writing Your Own Behaviours

The `@behaviour` element allows you to write your own behaviours, typically these will be conditions and actions that query & modify, respectively, your game world. Let's take a look at a query first:

....
@behaviour actor_is_armed {
execute: function(owner, behaviour, wmem) {
return {
success: ($player.main_inventory.weapon_contents.length > 0),
wmem: wmem
}
}
}
....

First note that the only thing we are required to define is the `execute:` attribute, which must be a regular function (an arrow function will not do). All `execute:` handler functions are required to return an object with two keys: `success` and `wmem`. The value for `success` should a boolean. In this case we return `true` if the players weapon slot has something in it, false if not. The value for `wmem` should be the working memory we were passed in.

In this example we've assumed the actor is the #player. But we could make it more flexible using an option.

....
@behaviour actor_is_armed {
options: ["actor"]
execute: function(owner, behaviour, wmem) {
const actor = $(actor);
return {
success: (actor.main_inventory.weapon_contents.length > 0),
wmem: wmem
}
}
}
....

Now we'd specify the behaviour as

....
^[actor_is_armed actor=#sam_spade]
....

An as long as Sam's `@actor` definition contained a `main_inventory` all would be well.

Now let's examine how we would define an action:

....
@behaviour actor_drinks {
execute: function(owner, behaviour, wmem) {
$player.drunk += 1;
return {
success: true,
wmem: wmem
};
}
}
....

We can see that this is even simpler. Most actions will return a successful result since you have, likely, already queried whether they be executed or not. But if an action can be executed and fail you can return a different results as per the query above.

Working memory is assumed to be an object that is passed between the different behaviours in the tree. For example one behaviour could store a value that a later behaviour will use. We can see how `actor_sees_item` could return success and store the id of the item in the working memory for `actor_equips_item` to put into their inventory.

Lastly the owner is the object that owns the behaviour tree. This allows you to make use of the owner's attributes within the behaviour. Here's an example of doing this. Let's create a generic `query` behaviour that lets us test a property of any object and compare it with an attribute of the owning element:

....
@behaviour query {
options: ["if", "obj"]
execute: function(owner, behaviour, wmem) {
const f = behaviour.option("if").bind(owner);
const o = $(behaviour.option("obj"));
return {
success: f(o),
wmem: wmem
};
}
}
@object foo {
cost: 2
}
@object bar {
cash: 10
test: ^[query obj="foo" if=^{obj.cost < this.cash}]
}
....

Might take you a while to see what is going on here but we are making use of `bind` to make `this` into the owner object within the context of the code block being passed to the `query` via its `if` option.

=== Using Behaviour Trees

Having defined a behaviour tree, how do you use it?
Expand Down
Loading

0 comments on commit 5f09622

Please sign in to comment.