From edecc4c95f76bcd69c042372140486f744f4ccea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kat=20March=C3=A1n?= <kzm@zkat.tech>
Date: Fri, 14 Jan 2022 22:22:08 -0800
Subject: [PATCH] feat(example): update thirst example

---
 examples/thirst.rs | 123 +++++++++++++++++----------------------------
 1 file changed, 47 insertions(+), 76 deletions(-)

diff --git a/examples/thirst.rs b/examples/thirst.rs
index 0dea7ff..4007164 100644
--- a/examples/thirst.rs
+++ b/examples/thirst.rs
@@ -31,65 +31,50 @@ pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
 // The second step is to define an action. What can the AI do, and how does it
 // do it? This is the first bit involving Big Brain itself, and there's a few
 // pieces you need:
-
-// First, you need an Action and an ActionBuilder struct.
+//
+// 1. An Action Component. This is just a plain Component we will query
+//    against later.
+// 2. An ActionBuilder. This is anything that implements the ActionBuilder
+//    trait.
+// 3. A System that will run Action code.
 //
 // These actions will be spawned and queued by the game engine when their
 // conditions trigger (we'll configure what these are later).
+//
+// NOTE: In this example, we're not implementing ActionBuilder ourselves, but
+// instead just relying on the blanket implementation for anything that
+// implements Clone. So, the Clone derive matters here. This is enough in most
+// cases.
 #[derive(Clone, Component, Debug)]
-pub struct Drink;
-
-// The convention is to attach a `::build()` function to the Action type.
-impl Drink {
-    pub fn build() -> DrinkBuilder {
-        DrinkBuilder
-    }
-}
-
-// Then we define an ActionBuilder, which is responsible for making new
-// Action components for us.
-#[derive(Clone, Component, Debug)]
-pub struct DrinkBuilder;
-
-// All you need to implement heree is the `build()` method, which requires
-// that you attach your actual component to the action Entity that was created
-// and configured for you.
-impl ActionBuilder for DrinkBuilder {
-    fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
-        cmd.entity(action).insert(Drink);
-    }
-}
+pub struct Drink { until: f32, per_second: f32 }
 
-// Associated with that Drink Action, you then need to have a system that will
-// actually execute those actions when they're "spawned" by the Big Brain
-// engine. This is the actual "act" part of the Action.
-//
-// In our case, we want the Thirst components, since we'll be changing those.
-// Additionally, we want to pick up the DrinkAction components, as well as
-// their associated ActionState. Note that the Drink Action belongs to a
-// *separate entity* from the owner of the Thirst component!
+// Action systems execute according to a state machine, where the states are
+// labeled by ActionState.
 fn drink_action_system(
+    time: Res<Time>,
     mut thirsts: Query<&mut Thirst>,
-    // We grab the Parent here, because individual Actions are parented to the
-    // entity "doing" the action.
-    //
-    // ActionState is an enum that described the specific run-state the action
-    // is in. You can think of Actions as state machines. They get requested,
-    // they can be cancelled, they can run to completion, etc. Cancellations
-    // usually happen because the target action changed (due to a different
-    // Scorer winning). But you can also cancel the actions yourself by
-    // setting the state in the Action system.
-    mut query: Query<(&Actor, &mut ActionState), With<Drink>>,
+    // We execute actions by querying for their associated Action Component
+    // (Drink in this case). You'll always need both Actor and ActionState.
+    mut query: Query<(&Actor, &mut ActionState, &Drink)>,
 ) {
-    for (Actor(actor), mut state) in query.iter_mut() {
-        // Use the drink_action's actor to look up the corresponding Thirst.
+    for (Actor(actor), mut state, drink) in query.iter_mut() {
+        // Use the drink_action's actor to look up the corresponding Thirst Component.
         if let Ok(mut thirst) = thirsts.get_mut(*actor) {
             match *state {
                 ActionState::Requested => {
-                    thirst.thirst = 10.0;
-                    println!("drank some water");
-                    *state = ActionState::Success;
+                    println!("Time to drink some water!");
+                    *state = ActionState::Executing;
+                }
+                ActionState::Executing => {
+                    println!("drinking some water");
+                    thirst.thirst -= drink.per_second * (time.delta().as_micros() as f32 / 1_000_000.0);
+                    if thirst.thirst <= drink.until {
+                        // To "finish" an action, we set its state to Success or
+                        // Failure.
+                        *state = ActionState::Success;
+                    }
                 }
+                // All Actions should make sure to handle cancellations!
                 ActionState::Cancelled => {
                     *state = ActionState::Failure;
                 }
@@ -101,40 +86,24 @@ fn drink_action_system(
 
 // Then, we have something called "Scorers". These are special components that
 // run in the background, calculating a "Score" value, which is what Big Brain
-// will use to pick which actions to execute.
+// will use to pick which Actions to execute.
 //
-// Just like with Actions, we use the convention of having separate
-// ScorerBuilder and Scorer components. While it might seem like a lot of
-// boilerplate, in a "real" application, you will almost certainly have data
-// and configuration concerns. This pattern separates those nicely.
+// Just like with Actions, there's two pieces to this: the Scorer and the
+// ScorerBuilder. And just like with Actions, there's a blanket implementation
+// for Clone, so we only need the Component here.
 #[derive(Clone, Component, Debug)]
 pub struct Thirsty;
 
-impl Thirsty {
-    fn build() -> ThirstyBuilder {
-        ThirstyBuilder
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct ThirstyBuilder;
-
-impl ScorerBuilder for ThirstyBuilder {
-    fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) {
-        cmd.entity(scorer).insert(Thirsty);
-    }
-}
-
 // Looks familiar? It's a lot like Actions!
 pub fn thirsty_scorer_system(
     thirsts: Query<&Thirst>,
-    // Same dance with the Parent here, but now Big Brain has added a Score component!
+    // Same dance with the Actor here, but now we use look up Score instead of ActionState.
     mut query: Query<(&Actor, &mut Score), With<Thirsty>>,
 ) {
     for (Actor(actor), mut score) in query.iter_mut() {
         if let Ok(thirst) = thirsts.get(*actor) {
             // This is really what the job of a Scorer is. To calculate a
-            // generic Utility value that the Big Brain engine will compare
+            // generic "Utility" score that the Big Brain engine will compare
             // against others, over time, and use to make decisions. This is
             // generally "the higher the better", and "first across the finish
             // line", but that's all configurable using Pickers!
@@ -150,13 +119,12 @@ pub fn thirsty_scorer_system(
 // to have AI behavior should have one *or more* Thinkers attached to it.
 pub fn init_entities(mut cmd: Commands) {
     // Create the entity and throw the Thirst component in there. Nothing special here.
-    cmd.spawn().insert(Thirst::new(70.0, 2.0)).insert(
-        // Thinker::build().component() will return a regular component you
-        // can attach normally!
+    cmd.spawn().insert(Thirst::new(75.0, 2.0)).insert(
         Thinker::build()
             .picker(FirstToScore { threshold: 0.8 })
-            // Note that what we pass in are _builders_, not components!
-            .when(Thirsty::build(), Drink::build()),
+            // Technically these are supposed to be ActionBuilders and
+            // ScorerBuilders, but our Clone impls simplify our code here.
+            .when(Thirsty, Drink { until: 70.0, per_second: 5.0 }),
     );
 }
 
@@ -167,7 +135,10 @@ fn main() {
         .add_plugin(BigBrainPlugin)
         .add_startup_system(init_entities)
         .add_system(thirst_system)
-        .add_system(drink_action_system)
-        .add_system(thirsty_scorer_system)
+        // Big Brain has specific stages for Scorers and Actions. If
+        // determinism matters a lot to you, you should add your action and
+        // scorer systems to these stages.
+        .add_system_to_stage(BigBrainStage::Actions, drink_action_system)
+        .add_system_to_stage(BigBrainStage::Scorers, thirsty_scorer_system)
         .run();
 }
-- 
GitLab