From 2c1d07bc049c42d8f6ff8c86fcf66a528ab229a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kat=20March=C3=A1n?= <kzm@zkat.tech>
Date: Wed, 7 Apr 2021 19:52:45 -0700
Subject: [PATCH] move big-brain to bevy (#1)

---
 .cargo/config.toml                    |  22 ++
 Cargo.toml                            |   8 +-
 README.md                             |  94 +++-----
 big-brain-derive/Cargo.toml           |   2 +-
 big-brain-derive/src/action.rs        |  15 +-
 big-brain-derive/src/consideration.rs |  15 +-
 examples/thirst.rs                    | 164 ++++++++++++++
 src/actions.rs                        |  48 +---
 src/choices.rs                        |  12 +-
 src/considerations.rs                 |   8 +-
 src/evaluators.rs                     |   1 -
 src/lib.rs                            |  71 +-----
 src/measures.rs                       |   1 -
 src/pickers.rs                        |  18 +-
 src/stopwatch.rs                      |  70 ------
 src/thinker.rs                        | 315 ++++++++++++--------------
 16 files changed, 418 insertions(+), 446 deletions(-)
 create mode 100644 .cargo/config.toml
 create mode 100644 examples/thirst.rs
 delete mode 100644 src/stopwatch.rs

diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..b65046a
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,22 @@
+# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.
+
+# NOTE: For maximum performance, build using a nightly compiler
+# If you are using rust stable, remove the "-Zshare-generics=y" below (as well as "-Csplit-debuginfo=unpacked" when building on macOS).
+
+[target.x86_64-unknown-linux-gnu]
+linker = "/usr/bin/clang"
+rustflags = ["-Clink-arg=-fuse-ld=lld"]
+
+# NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager:
+# `brew install michaeleisel/zld/zld`
+[target.x86_64-apple-darwin]
+rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]
+
+[target.x86_64-pc-windows-msvc]
+linker = "rust-lld.exe"
+rustflags = []
+
+# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
+# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
+#[profile.dev]
+#debug = 1
diff --git a/Cargo.toml b/Cargo.toml
index c8b1813..f360cc7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,18 +6,16 @@ edition = "2018"
 description = "Rusty Utility AI library"
 license-file = "LICENSE.md"
 readme = "README.md"
-keywords = ["utility-ai", "specs", "ecs"]
+keywords = ["utility-ai", "bevy", "ai", "ecs"]
 categories = ["game-development"]
 repository = "https://github.com/zkat/big-brain"
 homepage = "https://github.com/zkat/big-brain"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
-big-brain-derive = "0.1"
-specs = { version = "0.16.1", features = ["parallel", "shred-derive", "specs-derive"] }
+big-brain-derive = { path = "./big-brain-derive", version = "0.1" }
 serde = "1.0.111"
 typetag = "0.1.5"
 specs-derive = "0.4.1"
 ron = "0.6.0"
+bevy = "0.5.0"
 
diff --git a/README.md b/README.md
index 64e4c9a..bedf86d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 `big-brain` is a [Utility AI](https://en.wikipedia.org/wiki/Utility_system)
-library for games, built on the [`specs` ECS](https://docs.rs/specs).
+library for games, built for the [Bevy Game Engine](https://bevyengine.org/)
 
 It lets you define complex, intricate AI behaviors for your entities based on
 their perception of the world. Definitions are almost entirely data-driven,
@@ -17,39 +17,30 @@ First, you define actions and considerations, which are just plain old `specs`
 
 ### Considerations
 
-`Consideration`s are entities that look at the world and evaluate into `Utility`s.
+`Consideration`s are entities that look at the world and evaluate into `Utility` values.
 
 ```rust
-use specs::{Component, Entity, ReadStorage, System, WriteStorage};
-use big_brain::{Consideration, Utility};
+use bevy::prelude::*;
+use big_brain::*;
 
-use crate::components;
-
-#[derive(Debug, Component, Consideration)]
-pub struct Hunger {
-    pub actor: Entity,
+#[derive(Debug, Consideration)]
+pub struct ThirstConsideration {
     #[consideration(default)]
     pub evaluator: PowerEvaluator,
     #[consideration(param)]
     pub weight: f32,
 }
 
-pub struct ConsiderHunger;
-impl<'a> System<'a> for ConsiderHunger {
-    type SystemData = (
-        ReadStorage<'a, components::Hunger>,
-        WriteStorage<'a, Hunger>,
-        WriteStorage<'a, Utility>,
-    );
-
-    fn run(&mut self, (hungers, mut considerers, mut utilities): Self::SystemData) {
-        for (conser, util) in (&mut considerers, &mut utilities).join() {
-            if let Some(hunger) = hungers.get(conser.actor.clone()) {
-                *util = Utility {
-                    value: conser.evaluator.evaluate(hunger.hunger),
-                    weight: conser.weight,
-                };
-            }
+pub fn thirst_consideration_system(
+    thirsts: Query<&Thirst>,
+    mut query: Query<(&Parent, &ThirstConsideration, &mut Utility)>,
+) {
+    for (Parent(actor), conser, mut util) in query.iter_mut() {
+        if let Ok(thirst) = thirsts.get(*actor) {
+            *util = Utility {
+                value: conser.evaluator.evaluate(thirst.thirst),
+                weight: conser.weight,
+            };
         }
     }
 }
@@ -60,40 +51,25 @@ impl<'a> System<'a> for ConsiderHunger {
 `Action`s are the actual things your entities will _do_.
 
 ```rust
-use specs::{Component, Entity, System, WriteStorage};
-use big_brain::{Action, ActionState};
-
-use crate::components;
-
-#[derive(Debug, Clone, Component, Action)]
-pub struct Eat {
-    pub actor: Entity,
-    #[action(default)]
-    pub foo: f32,
-    #[action(param)]
-    pub reduce_by: f32,
-}
-
-pub struct EatSystem;
-impl<'a> System<'a> for EatSystem {
-    type SystemData = (
-        WriteStorage<'a, components::Hunger>,
-        WriteStorage<'a, Eat>,
-        WriteStorage<'a, ActionState>,
-    );
-    fn run(&mut self, (mut hungers, mut eat_actions, mut states): Self::SystemData) {
-        for (state, eat_action) in (&mut states, &mut eat_actions).join() {
-            if let Some(hunger) = hungers.get_mut(eat_action.actor.clone()) {
-                match state {
-                    ActionState::Requested => {
-                        hunger.hunger -= eat_action.reduce_by;
-                        *state = ActionState::Success;
-                    }
-                    ActionState::Cancelled => {
-                        *state = ActionState::Failure;
-                    }
-                    _ => {}
+#[derive(Debug, Action)]
+pub struct DrinkAction {}
+
+fn drink_action_system(
+    mut thirsts: Query<&mut Thirst>,
+    mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>,
+) {
+    for (Parent(actor), _drink_action, mut state) in query.iter_mut() {
+        if let Ok(mut thirst) = thirsts.get_mut(*actor) {
+            match *state {
+                ActionState::Requested => {
+                    thirst.thirst = 10.0;
+                    println!("drank some water");
+                    *state = ActionState::Success;
+                }
+                ActionState::Cancelled => {
+                    *state = ActionState::Failure;
                 }
+                _ => {}
             }
         }
     }
@@ -102,7 +78,7 @@ impl<'a> System<'a> for EatSystem {
 
 ### Thinker Definition
 
-Finally, you can define the `Thinker`:
+Finally, you can use it when define the `Thinker`:
 
 ```ron
 // behavior.ron
diff --git a/big-brain-derive/Cargo.toml b/big-brain-derive/Cargo.toml
index 6df7872..9df5616 100644
--- a/big-brain-derive/Cargo.toml
+++ b/big-brain-derive/Cargo.toml
@@ -16,6 +16,6 @@ proc-macro = true
 [dependencies]
 syn = "1.0.33"
 quote = "1.0.7"
-big-brain = "0.1.0"
+# big-brain = { path = "../", version = "0.1.0" }
 proc-macro2 = "1.0.18"
 darling = "0.10.2"
diff --git a/big-brain-derive/src/action.rs b/big-brain-derive/src/action.rs
index e0f5878..e1c01c6 100644
--- a/big-brain-derive/src/action.rs
+++ b/big-brain-derive/src/action.rs
@@ -1,7 +1,6 @@
 use darling::{ast, FromDeriveInput, FromField, ToTokens};
 use proc_macro2::TokenStream;
 use quote::quote;
-use syn;
 
 #[derive(Debug, FromDeriveInput)]
 #[darling(attributes(action), supports(struct_named))]
@@ -70,10 +69,10 @@ impl ToTokens for Action {
             }
         });
         let ts = quote! {
-            mod big_brain_builder {
+            mod big_brain_action_builder {
                 use super::#ident as Comp;
 
-                use big_brain::{typetag, serde::Deserialize, Action, ActionManager, ecs::{Entity, Entities, LazyUpdate}, ActionEnt};
+                use big_brain::{typetag, serde::Deserialize, Action, ActionManager, bevy::prelude::{Entity, Commands}, ActionEnt};
 
                 #[derive(Debug, Deserialize)]
                 struct #ident {
@@ -82,19 +81,19 @@ impl ToTokens for Action {
 
                 #[typetag::deserialize]
                 impl Action for #ident {
-                    fn build(self: Box<Self>, actor: Entity, action_ent: ActionEnt, ents: &Entities, lazy: &LazyUpdate) -> Box<dyn ActionManager> {
+                    fn build(self: Box<Self>, actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) -> Box<dyn ActionManager> {
                         self
                     }
                 }
 
                 impl ActionManager for #ident {
-                    fn activate(&self, actor: Entity, action_ent: ActionEnt, lazy: &LazyUpdate) {
-                        lazy.insert(action_ent.0.clone(), Comp {
+                    fn activate(&self, actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) {
+                        cmd.entity(action_ent.0).insert(Comp {
                             #(#field_assignments),*
                         });
                     }
-                    fn deactivate(&self, action_ent: ActionEnt, lazy: &LazyUpdate) {
-                        lazy.remove::<Comp>(action_ent.0);
+                    fn deactivate(&self, action_ent: ActionEnt, cmd: &mut Commands) {
+                        cmd.entity(action_ent.0).remove::<Comp>();
                     }
                 }
             }
diff --git a/big-brain-derive/src/consideration.rs b/big-brain-derive/src/consideration.rs
index 5c60270..14fdb96 100644
--- a/big-brain-derive/src/consideration.rs
+++ b/big-brain-derive/src/consideration.rs
@@ -1,7 +1,6 @@
 use darling::{ast, FromDeriveInput, FromField, ToTokens};
 use proc_macro2::TokenStream;
 use quote::quote;
-use syn;
 
 #[derive(Debug, FromDeriveInput)]
 #[darling(attributes(consideration), supports(struct_named))]
@@ -70,10 +69,11 @@ impl ToTokens for Consideration {
             }
         });
         let ts = quote! {
-            mod big_brain_builder {
+            mod big_brain_cons_builder {
                 use super::#ident as Comp;
 
-                use big_brain::{typetag, serde::Deserialize, Consideration, ecs::{Entity, Entities, LazyUpdate}, ConsiderationEnt};
+                use big_brain::{typetag, serde::Deserialize, Consideration, bevy::prelude::{Entity, Commands}, ConsiderationEnt};
+                // use typetag;
 
                 #[derive(Debug, Deserialize)]
                 struct #ident {
@@ -81,10 +81,11 @@ impl ToTokens for Consideration {
                 }
                 #[typetag::deserialize]
                 impl Consideration for #ident {
-                    fn build(&self, actor: Entity, ents: &Entities, lazy: &LazyUpdate) -> ConsiderationEnt {
-                        let ent = ConsiderationEnt(ents.create());
-                        lazy.insert(ent.clone().0, big_brain::Utility::default());
-                        lazy.insert(ent.clone().0, Comp {
+                    fn build(&self, actor: Entity, cmd: &mut Commands) -> ConsiderationEnt {
+                        let ent = ConsiderationEnt(cmd.spawn().id());
+                        cmd.entity(ent.0)
+                        .insert(big_brain::Utility::default())
+                        .insert(Comp {
                             #(#field_assignments),*
                         });
                         ent
diff --git a/examples/thirst.rs b/examples/thirst.rs
new file mode 100644
index 0000000..b7a29dd
--- /dev/null
+++ b/examples/thirst.rs
@@ -0,0 +1,164 @@
+use bevy::prelude::*;
+use big_brain::{evaluators::*, *};
+
+// First, we define a "Thirst" component and associated system. This is NOT
+// THE AI. It's a plain old system that just makes an entity "thirstier" over
+// time. This is what the AI will later interact with.
+//
+// There's nothing special here. It's a plain old Bevy component.
+#[derive(Debug)]
+pub struct Thirst {
+    pub per_second: f32,
+    pub thirst: f32,
+}
+
+impl Thirst {
+    pub fn new(thirst: f32, per_second: f32) -> Self {
+        Self { thirst, per_second }
+    }
+}
+
+pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
+    for mut thirst in thirsts.iter_mut() {
+        thirst.thirst +=
+            thirst.per_second * (time.delta().as_micros() as f32 / 1000000.0);
+        println!("Getting thirstier...{}%", thirst.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 struct, and derive Action.
+//
+// These actions will be spawned and queued by the game engine when their
+// conditions trigger (we'll configure what these are later).
+#[derive(Debug, Action)]
+pub struct DrinkAction {}
+
+// Associated with that DrinkAction, you then need to have a system that will
+// actually execute those actions when they're "spawned" by the Big Brain
+// engine.
+//
+// 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 DrinkAction belongs to a
+// *separate entity* from the owner of the Thirst component!
+fn drink_action_system(
+    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
+    // Consideration winning). But you can also cancel the actions yourself by
+    // setting the state in the Action system.
+    mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>,
+) {
+    for (Parent(actor), _drink_action, mut state) in query.iter_mut() {
+        // Use the drink_action's actor to look up the corresponding Thirst.
+        if let Ok(mut thirst) = thirsts.get_mut(*actor) {
+            match *state {
+                ActionState::Requested => {
+                    thirst.thirst = 10.0;
+                    println!("drank some water");
+                    *state = ActionState::Success;
+                }
+                ActionState::Cancelled => {
+                    *state = ActionState::Failure;
+                }
+                _ => {}
+            }
+        }
+    }
+}
+
+// Then, we have something called "Considerations". These are special
+// components that run in the background, calculating a "Utility" value, which
+// is what Big Brain will use to pick which actions to execute.
+//
+// Additionally, though, we pull in an evaluator and define a weight. Which is
+// just mathy stuff you can tweak to get the behavior you want. More on this
+// in the docs (later), but for now, just put them in there and trust the
+// system. :)
+#[derive(Debug, Consideration)]
+pub struct ThirstConsideration {
+    #[consideration(default)]
+    pub evaluator: PowerEvaluator,
+    #[consideration(param)]
+    pub weight: f32,
+}
+
+// Look familiar? Similar dance to Actions here.
+pub fn thirst_consideration_system(
+    thirsts: Query<&Thirst>,
+    // Same dance with the Parent here, but now we've added a Utility!
+    mut query: Query<(&Parent, &ThirstConsideration, &mut Utility)>,
+) {
+    for (Parent(actor), conser, mut util) in query.iter_mut() {
+        if let Ok(thirst) = thirsts.get(*actor) {
+            // This is really what the job of a Consideration is. To calculate
+            // a generic Utility value 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! You can customize that to
+            // your heart's extent using Measures and Pickers.
+            //
+            // A note: You don't actually *need* evaluators/weights. You can
+            // literally just use linear values here and set thresholds
+            // accordingly. The evaluator is just there to give the value a
+            // bit of a curve.
+            *util = Utility {
+                value: conser.evaluator.evaluate(thirst.thirst),
+                weight: conser.weight,
+            };
+        }
+    }
+}
+
+// Now that we hav eall that defined, it's time to add a Thinker to an entity!
+// The Thinker is the actual "brain" behind all the AI. Every entity you want
+// to have AI behavior should have one *or more* Thinkers attached to it.
+// Thinkers are configured using RON right now, with a DSL that makes it easy
+// to define, in data, the actual behavior you want.
+pub fn init_entities(mut cmd: Commands) {
+    let actor = cmd.spawn().insert(Thirst::new(80.0, 2.0)).id();
+
+    // Here's a very simple one that only has one consideration and one
+    // associated action. But you can have more of them, and even nest them by
+    // using more Thinkers (which are actually themselves Actions). See
+    // basic.ron in examples/ for a more involved Thinker definition.
+    //
+    // Ultimately, these Thinkers are meant to be usable by non-programmers:
+    // You, the developer, create Actions and Considerations, and someone else
+    // is then able to put them all together like LEGOs into all sorts of
+    // intricate logic.
+    Thinker::load_from_str(
+        r#"
+(
+    picker: {"FirstToScore": (threshold: 80.0)},
+    choices: [(
+        consider: [{"ThirstConsideration": (weight: 2.0)}],
+        then: {"DrinkAction": ()},
+    )],
+)
+"#,
+    )
+    .build(actor, &mut cmd);
+}
+
+fn main() {
+    // Once all that's done, we just add our systems and off we go!
+    App::build()
+        .add_plugins(DefaultPlugins)
+        .add_startup_system(init_entities.system())
+        .add_system(thirst_system.system())
+        .add_system(thirst_consideration_system.system())
+        .add_system(drink_action_system.system())
+        // Don't forget the Thinker system itself! This is the heart of it all!
+        .add_system(big_brain::thinker_system.system())
+        .run();
+}
diff --git a/src/actions.rs b/src/actions.rs
index 0a8904b..3d7d8d0 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -1,12 +1,11 @@
-use specs::{Component, DenseVecStorage, Entities, Entity, LazyUpdate, ReadStorage, WriteStorage};
-use typetag;
+use bevy::prelude::*;
 
 use crate::ActionEnt;
 
-#[derive(Debug, Component)]
+#[derive(Debug)]
 pub struct ActionManagerWrapper(pub(crate) Box<dyn ActionManager>);
 
-#[derive(Debug, Component, Clone, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq)]
 pub enum ActionState {
     Init,
     Requested,
@@ -21,34 +20,12 @@ impl ActionState {
         Self::default()
     }
 
-    pub fn get<'a>(state_ent: &ActionEnt, states: &'a ReadStorage<ActionState>) -> &'a Self {
-        states
-            .get(state_ent.0.clone())
-            .expect("ActionState doesn't exist?")
-    }
-
-    pub fn get_mut<'a>(
-        state_ent: &ActionEnt,
-        states: &'a mut WriteStorage<ActionState>,
-    ) -> &'a mut Self {
-        states
-            .get_mut(state_ent.0.clone())
-            .expect("ActionState doesn't exist?")
-    }
-
-    pub(crate) fn build(
-        builder: Box<dyn Action>,
-        actor: Entity,
-        ents: &Entities,
-        lazy: &LazyUpdate,
-    ) -> ActionEnt {
-        let action_ent = ActionEnt(ents.create());
-        let ent_clone = action_ent.clone();
-        lazy.insert(ent_clone.0, ActionState::default());
-        lazy.insert(
-            ent_clone.0,
-            ActionManagerWrapper(builder.build(actor, action_ent.clone(), ents, lazy)),
-        );
+    pub(crate) fn build(builder: Box<dyn Action>, actor: Entity, cmd: &mut Commands) -> ActionEnt {
+        let action_ent = ActionEnt(cmd.spawn().id());
+        let manager_wrapper = ActionManagerWrapper(builder.build(actor, action_ent, cmd));
+        cmd.entity(action_ent.0)
+            .insert(ActionState::default())
+            .insert(manager_wrapper);
         action_ent
     }
 }
@@ -68,12 +45,11 @@ pub trait Action: std::fmt::Debug + Send + Sync {
         self: Box<Self>,
         actor: Entity,
         action_ent: ActionEnt,
-        ents: &Entities,
-        lazy: &LazyUpdate,
+        cmd: &mut Commands,
     ) -> Box<dyn ActionManager>;
 }
 
 pub trait ActionManager: std::fmt::Debug + Send + Sync {
-    fn activate(&self, actor: Entity, action: ActionEnt, lazy: &LazyUpdate);
-    fn deactivate(&self, action: ActionEnt, lazy: &LazyUpdate);
+    fn activate(&self, actor: Entity, action: ActionEnt, cmd: &mut Commands);
+    fn deactivate(&self, action: ActionEnt, cmd: &mut Commands);
 }
diff --git a/src/choices.rs b/src/choices.rs
index bff8e2f..efd21c1 100644
--- a/src/choices.rs
+++ b/src/choices.rs
@@ -1,5 +1,5 @@
+use bevy::prelude::*;
 use serde::Deserialize;
-use specs::{Entities, Entity, LazyUpdate, ReadStorage};
 
 use crate::{
     actions::{Action, ActionState},
@@ -16,13 +16,13 @@ pub struct Choice {
     pub action_state: ActionEnt,
 }
 impl Choice {
-    pub fn calculate<'a>(&self, utilities: &ReadStorage<'a, Utility>) -> f32 {
+    pub fn calculate(&self, utilities: &Query<&Utility>) -> f32 {
         self.measure.calculate(
             self.utilities
                 .iter()
                 .map(|choice_cons| {
                     utilities
-                        .get(choice_cons.0.clone())
+                        .get(choice_cons.0)
                         .expect("Where did the utility go?")
                 })
                 .collect(),
@@ -36,16 +36,16 @@ pub struct ChoiceBuilder {
     pub then: Box<dyn Action>,
 }
 impl ChoiceBuilder {
-    pub fn build(self, actor: Entity, ents: &Entities, lazy: &LazyUpdate) -> Choice {
+    pub fn build(self, actor: Entity, cmd: &mut Commands) -> Choice {
         let action = self.then;
         Choice {
             measure: Box::new(WeightedMeasure),
             utilities: self
                 .consider
                 .iter()
-                .map(|cons| cons.build(actor.clone(), ents, lazy))
+                .map(|cons| cons.build(actor, cmd))
                 .collect(),
-            action_state: ActionState::build(action, actor, ents, lazy),
+            action_state: ActionState::build(action, actor, cmd),
         }
     }
 }
diff --git a/src/considerations.rs b/src/considerations.rs
index 3bfc10c..78687d8 100644
--- a/src/considerations.rs
+++ b/src/considerations.rs
@@ -1,19 +1,17 @@
-use specs::{Component, DenseVecStorage, Entities, Entity, LazyUpdate};
-use typetag;
+use bevy::prelude::*;
 
 use crate::ConsiderationEnt;
 
-#[derive(Debug, Clone, Default, Component)]
+#[derive(Debug, Clone, Default)]
 pub struct Utility {
     pub value: f32,
     pub weight: f32,
 }
 
-
 /**
 This trait defines new considerations. In general, you should use the [derive macro](derive.Consideration.html) instead.
 */
 #[typetag::deserialize]
 pub trait Consideration: std::fmt::Debug + Sync + Send {
-    fn build(&self, entity: Entity, ents: &Entities, lazy: &LazyUpdate) -> ConsiderationEnt;
+    fn build(&self, entity: Entity, cmd: &mut Commands) -> ConsiderationEnt;
 }
diff --git a/src/evaluators.rs b/src/evaluators.rs
index 5b6e039..9f5f591 100644
--- a/src/evaluators.rs
+++ b/src/evaluators.rs
@@ -1,5 +1,4 @@
 use serde::{Deserialize, Serialize};
-use typetag;
 
 #[typetag::serde]
 pub trait Evaluator: std::fmt::Debug + Sync + Send {
diff --git a/src/lib.rs b/src/lib.rs
index b008edc..f887f8e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,66 +1,6 @@
-//! `big-brain` is a [Utility
-//! AI](https://en.wikipedia.org/wiki/Utility_system) library for implementing
-//! rich, complex artificial intelligence mainly in video games using the
-//! [`specs`](https://docs.rs/specs) ECS system.
-//!
-//! `big-brain` not only allows you to define these complex behaviors, but it
-//! allows you to define them in a data-oriented format, such as
-//! [`RON`](https://docs.rs/ron), potentially allowing non-programmers to
-//! define many of these behaviors themselves.
-//!
-//! In general, the only things that need to be programmed are
-//! [Actions](derive.Action.html) and
-//! [Considerations](derive.Consideration.html). Everything else is included
-//! with `big-brain`.
-//!
-//! For example, this is what a basic thinker might look like:
-//!
-//! ```ignore
-//! // basic_needs.ron
-//! (
-//!   // The first Choice to score above the threshold will be executed.
-//!   picker: {"FirstToScore": (threshold: 80.0)},
-//!   // A list of choices, with their considerations evaluated in order,
-//!   // and picked using the Picker.
-//!   choices: [(
-//!     consider: [{"Bladder": ()}],
-//!     then: {"Pee": ()},
-//!   ), (
-//!     consider: [{"Hunger": ()}],
-//!     // Thinkers are also actions, so you can nest them indefinitely.
-//!     then: {"Thinker": (
-//!         picker: {"FirstToScore": (threshold: 80.0)},
-//!         choices: [(
-//!             consider: [{"FoodInRange": (range: 10.0)}],
-//!             then: {"EatFood": (range: 10.0)}
-//!         ), (
-//!             consider: [{"FoodNearby": (range: 1000.0)}],
-//!             then: {"WalkToFood": (range: 1000.0)}
-//!         )]
-//!     )},
-//!   )],
-//!   // If no Choice goes over the threshold, we just... wander around
-//!   otherwise: Some({"Meander": ()})
-//! )
-//! ```
-//!
-//! You would then load up the component into a `specs` entity like so:
-//!
-//! ```no_run
-//! use big_brain::ThinkerBuilder;
-//! use specs::{Entities, Entity, LazyUpdate, World, WorldExt};
-//! let mut world = World::new();
-//! let entity = world.create_entity().build();
-//! world.exec(|(entities, lazy): (Entities, Read<LazyUpdate>)| {
-//!     ThinkerBuilder::load_from("./path/to/basic.ron").build(entity, &entities, &lazy);
-//! });
-//! ```
-
-use specs::{World, WorldExt};
-
+pub use bevy;
 pub use big_brain_derive::*;
 pub use serde;
-pub use specs as ecs;
 pub use typetag;
 
 pub use actions::*;
@@ -75,13 +15,4 @@ pub mod pickers;
 mod actions;
 mod choices;
 mod considerations;
-mod stopwatch;
 mod thinker;
-
-pub fn register(world: &mut World) {
-    world.register::<Utility>();
-    world.register::<ActionState>();
-    world.register::<ActionManagerWrapper>();
-    world.register::<ActiveThinker>();
-    world.register::<Thinker>();
-}
diff --git a/src/measures.rs b/src/measures.rs
index e822d19..7866ef3 100644
--- a/src/measures.rs
+++ b/src/measures.rs
@@ -1,5 +1,4 @@
 use serde::{Deserialize, Serialize};
-use typetag;
 
 use crate::Utility;
 
diff --git a/src/pickers.rs b/src/pickers.rs
index 0d38080..463ab5a 100644
--- a/src/pickers.rs
+++ b/src/pickers.rs
@@ -1,16 +1,12 @@
+use bevy::prelude::*;
+
 use serde::{Deserialize, Serialize};
-use specs::ReadStorage;
-use typetag;
 
 use crate::{choices::Choice, considerations::Utility, thinker::ActionEnt};
 
 #[typetag::serde]
 pub trait Picker: std::fmt::Debug + Sync + Send {
-    fn pick<'a>(
-        &mut self,
-        _choices: &Vec<Choice>,
-        _utilities: &ReadStorage<'a, Utility>,
-    ) -> Option<ActionEnt>;
+    fn pick(&self, _choices: &[Choice], _utilities: &Query<&Utility>) -> Option<ActionEnt>;
 }
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -20,15 +16,11 @@ pub struct FirstToScore {
 
 #[typetag::serde]
 impl Picker for FirstToScore {
-    fn pick<'a>(
-        &mut self,
-        choices: &Vec<Choice>,
-        utilities: &ReadStorage<'a, Utility>,
-    ) -> Option<ActionEnt> {
+    fn pick(&self, choices: &[Choice], utilities: &Query<&Utility>) -> Option<ActionEnt> {
         for choice in choices {
             let value = choice.calculate(utilities);
             if value >= self.threshold {
-                return Some(choice.action_state.clone());
+                return Some(choice.action_state);
             }
         }
         None
diff --git a/src/stopwatch.rs b/src/stopwatch.rs
deleted file mode 100644
index 2fa90b9..0000000
--- a/src/stopwatch.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-// This file is taken from amethyst_core
-use std::time::{Duration, Instant};
-
-/// A stopwatch which accurately measures elapsed time.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum Stopwatch {
-    /// Initial state with an elapsed time value of 0 seconds.
-    Waiting,
-    /// Stopwatch has started counting the elapsed time since this `Instant`
-    /// and accumuluated time from previous start/stop cycles `Duration`.
-    Started(Duration, Instant),
-    /// Stopwatch has been stopped and reports the elapsed time `Duration`.
-    Ended(Duration),
-}
-
-impl Default for Stopwatch {
-    fn default() -> Stopwatch {
-        Stopwatch::Waiting
-    }
-}
-
-impl Stopwatch {
-    /// Creates a new stopwatch.
-    pub fn new() -> Stopwatch {
-        Default::default()
-    }
-
-    /// Retrieves the elapsed time.
-    pub fn elapsed(&self) -> Duration {
-        match *self {
-            Stopwatch::Waiting => Duration::new(0, 0),
-            Stopwatch::Started(dur, start) => dur + start.elapsed(),
-            Stopwatch::Ended(dur) => dur,
-        }
-    }
-
-    /// Stops, resets, and starts the stopwatch again.
-    pub fn restart(&mut self) {
-        *self = Stopwatch::Started(Duration::new(0, 0), Instant::now());
-    }
-
-    /// Starts, or resumes, measuring elapsed time. If the stopwatch has been
-    /// started and stopped before, the new results are compounded onto the
-    /// existing elapsed time value.
-    ///
-    /// Note: Starting an already running stopwatch will do nothing.
-    pub fn start(&mut self) {
-        match *self {
-            Stopwatch::Waiting => self.restart(),
-            Stopwatch::Ended(dur) => {
-                *self = Stopwatch::Started(dur, Instant::now());
-            }
-            _ => {}
-        }
-    }
-
-    /// Stops measuring elapsed time.
-    ///
-    /// Note: Stopping a stopwatch that isn't running will do nothing.
-    pub fn stop(&mut self) {
-        if let Stopwatch::Started(dur, start) = *self {
-            *self = Stopwatch::Ended(dur + start.elapsed());
-        }
-    }
-
-    /// Clears the current elapsed time value.
-    pub fn reset(&mut self) {
-        *self = Stopwatch::Waiting;
-    }
-}
diff --git a/src/thinker.rs b/src/thinker.rs
index 7a61fc9..41c979a 100644
--- a/src/thinker.rs
+++ b/src/thinker.rs
@@ -2,38 +2,36 @@ use std::fs::File;
 use std::path::Path;
 use std::time::{Duration, Instant};
 
+use bevy::prelude::*;
 use serde::Deserialize;
-use specs::{
-    Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System,
-    WriteStorage,
-};
 
 use crate::{
-    actions::{Action, ActionManager, ActionManagerWrapper, ActionState},
+    actions::{self, Action, ActionManager, ActionManagerWrapper, ActionState},
     choices::{Choice, ChoiceBuilder},
     considerations::Utility,
     pickers::Picker,
-    stopwatch::Stopwatch,
 };
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
 pub struct ActionEnt(pub Entity);
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
 pub struct ConsiderationEnt(pub Entity);
 
-#[derive(Component, Debug)]
+#[derive(Debug)]
 pub struct Thinker {
     pub picker: Box<dyn Picker>,
     pub otherwise: Option<ActionEnt>,
     pub choices: Vec<Choice>,
-    pub actor: Entity,
     pub current_action: Option<ActionEnt>,
-    pub timer: Stopwatch,
 }
 
 impl Thinker {
-    pub fn load_from<P: AsRef<Path>>(path: P) -> builder::Thinker {
+    pub fn load_from_str<S: AsRef<str>>(string: S) -> builder::Thinker {
+        ron::de::from_str(string.as_ref()).expect("Failed to parse RON")
+    }
+
+    pub fn load_from_path<P: AsRef<Path>>(path: P) -> builder::Thinker {
         let f = File::open(&path).expect("Failed to open file");
         ron::de::from_reader(f).expect("Failed to read .ron file")
     }
@@ -50,10 +48,11 @@ mod builder {
 }
 
 impl builder::Thinker {
-    pub fn build(self, actor: Entity, ents: &Entities, lazy: &LazyUpdate) -> ActionEnt {
-        let action_ent = ActionState::build(Box::new(self), actor, ents, lazy);
-        lazy.insert(action_ent.0.clone(), ActiveThinker(true));
-        lazy.insert(action_ent.0.clone(), ActionState::Requested);
+    pub fn build(self, actor: Entity, cmd: &mut Commands) -> ActionEnt {
+        let action_ent = ActionState::build(Box::new(self), actor, cmd);
+        cmd.entity(action_ent.0)
+            .insert(ActiveThinker(true))
+            .insert(ActionState::Requested);
         action_ent
     }
 }
@@ -64,51 +63,49 @@ impl Action for builder::Thinker {
         self: Box<Self>,
         actor: Entity,
         action_ent: ActionEnt,
-        ents: &Entities,
-        lazy: &LazyUpdate,
+        cmd: &mut Commands,
     ) -> Box<dyn ActionManager> {
-        lazy.insert(
-            action_ent.0.clone(),
-            Thinker {
-                picker: self.picker,
-                actor,
-                choices: self
-                    .choices
-                    .into_iter()
-                    .map(|choice| choice.build(actor.clone(), &ents, &lazy))
-                    .collect(),
-                otherwise: self
-                    .otherwise
-                    .map(|builder| ActionState::build(builder, actor.clone(), ents, lazy)),
-                current_action: None,
-                timer: Stopwatch::new(),
-            },
-        );
+        let choices = self
+            .choices
+            .into_iter()
+            .map(|choice| choice.build(actor, cmd))
+            .collect();
+        let otherwise = self
+            .otherwise
+            .map(|builder| ActionState::build(builder, actor, cmd));
+        cmd.entity(action_ent.0).insert(Thinker {
+            picker: self.picker,
+            choices,
+            otherwise,
+            current_action: None,
+        });
+        cmd.entity(actor).push_children(&[action_ent.0]);
         Box::new(ThinkerManager)
     }
 }
 
-#[derive(Debug, Component)]
+#[derive(Debug)]
 pub struct ActiveThinker(bool);
 
 #[derive(Debug)]
 pub struct ThinkerManager;
 
 impl ActionManager for ThinkerManager {
-    fn activate(&self, _: Entity, action_ent: ActionEnt, lazy: &LazyUpdate) {
-        lazy.insert(action_ent.0, ActiveThinker(false));
-        lazy.insert(action_ent.0, ActionState::Requested);
+    fn activate(&self, _: Entity, action_ent: ActionEnt, cmd: &mut Commands) {
+        cmd.entity(action_ent.0)
+            .insert(ActiveThinker(false))
+            .insert(ActionState::Requested);
     }
-    fn deactivate(&self, action_ent: ActionEnt, lazy: &LazyUpdate) {
-        lazy.remove::<ActiveThinker>(action_ent.0);
+    fn deactivate(&self, action_ent: ActionEnt, cmd: &mut Commands) {
+        cmd.entity(action_ent.0).remove::<ActiveThinker>();
     }
 }
 
-pub struct ThinkerSystem {
+pub struct ThinkerIterations {
     index: usize,
     max_duration: Duration,
 }
-impl ThinkerSystem {
+impl ThinkerIterations {
     pub fn new(max_duration: Duration) -> Self {
         Self {
             index: 0,
@@ -116,121 +113,116 @@ impl ThinkerSystem {
         }
     }
 }
+impl Default for ThinkerIterations {
+    fn default() -> Self {
+        Self::new(Duration::from_millis(10))
+    }
+}
 
-impl<'a> System<'a> for ThinkerSystem {
-    type SystemData = (
-        Entities<'a>,
-        WriteStorage<'a, Thinker>,
-        ReadStorage<'a, ActiveThinker>,
-        ReadStorage<'a, Utility>,
-        WriteStorage<'a, ActionState>,
-        ReadStorage<'a, ActionManagerWrapper>,
-        Read<'a, LazyUpdate>,
-    );
-    fn run(
-        &mut self,
-        (ents, mut thinkers, active_thinkers, utilities, mut action_states, builder_wrappers, lazy): Self::SystemData,
-    ) {
-        let start = Instant::now();
-        for (thinker, thinker_ent, active_thinker) in (&mut thinkers, &ents, &active_thinkers)
-            .join()
-            .skip(self.index)
-        {
-            self.index += 1;
+pub fn thinker_system(
+    mut cmd: Commands,
+    mut iterations: Local<ThinkerIterations>,
+    mut thinker_q: Query<(Entity, &Parent, &mut Thinker, &ActiveThinker)>,
+    utilities: Query<&Utility>,
+    mut action_states: Query<&mut actions::ActionState>,
+    builder_wrappers: Query<&ActionManagerWrapper>,
+) {
+    let start = Instant::now();
+    for (thinker_ent, Parent(actor), mut thinker, active_thinker) in thinker_q.iter_mut().skip(iterations.index) {
+        iterations.index += 1;
 
-            let thinker_state = action_states
-                .get(thinker_ent.clone())
-                .expect("Where is it?")
-                .clone();
-            match thinker_state {
-                ActionState::Init | ActionState::Success | ActionState::Failure => {
-                    if let ActiveThinker(true) = active_thinker {
-                        let act_state = action_states.get_mut(thinker_ent.clone()).expect("???");
-                        *act_state = ActionState::Requested;
-                    }
+        let thinker_state = action_states
+            .get_mut(thinker_ent)
+            .expect("Where is it?")
+            .clone();
+        match thinker_state {
+            ActionState::Init | ActionState::Success | ActionState::Failure => {
+                if let ActiveThinker(true) = active_thinker {
+                    let mut act_state = action_states.get_mut(thinker_ent).expect("???");
+                    *act_state = ActionState::Requested;
                 }
-                ActionState::Cancelled => {
-                    if let Some(current) = &mut thinker.current_action {
-                        let state = action_states.get(current.0.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.").clone();
-                        match state {
-                            ActionState::Success | ActionState::Failure => {
-                                let act_state =
-                                    action_states.get_mut(thinker_ent.clone()).expect("???");
-                                *act_state = state.clone();
-                                let state = action_states.get_mut(current.0.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
-                                *state = ActionState::Init;
-                                thinker.current_action = None;
-                            }
-                            _ => {
-                                let state = action_states.get_mut(current.0.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
-                                *state = ActionState::Cancelled;
-                            }
+            }
+            ActionState::Cancelled => {
+                if let Some(current) = &mut thinker.current_action {
+                    let state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.").clone();
+                    match state {
+                        ActionState::Success | ActionState::Failure => {
+                            let mut act_state = action_states.get_mut(thinker_ent).expect("???");
+                            *act_state = state.clone();
+                            let mut state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+                            *state = ActionState::Init;
+                            thinker.current_action = None;
+                        }
+                        _ => {
+                            let mut state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+                            *state = ActionState::Cancelled;
                         }
                     }
                 }
-                ActionState::Requested | ActionState::Executing => {
-                    if let Some(picked_action_ent) =
-                        thinker.picker.pick(&thinker.choices, &utilities)
-                    {
-                        // Think about what action we're supposed to be taking. We do this
-                        // every tick, because we might change our mind.
-                        // ...and then execute it (details below).
-                        exec_picked_action(
-                            thinker_ent,
-                            thinker,
-                            &picked_action_ent,
-                            &mut action_states,
-                            &builder_wrappers,
-                            &lazy,
-                        );
-                    } else if let Some(default_action_ent) = &thinker.otherwise {
-                        // Otherwise, let's just execute the default one! (if it's there)
-                        let default_action_ent = default_action_ent.clone();
-                        exec_picked_action(
-                            thinker_ent,
-                            thinker,
-                            &default_action_ent,
-                            &mut action_states,
-                            &builder_wrappers,
-                            &lazy,
-                        );
-                    } else if let Some(current) = &mut thinker.current_action {
-                        // If we didn't pick anything, and there's no default action,
-                        // we need to see if there's any action currently executing,
-                        // and cancel it. We also use this opportunity to clean up
-                        // stale action components so they don't slow down joins.
-                        let state = action_states.get_mut(current.0.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
-                        let factory = builder_wrappers.get(current.0.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
-                        match state {
-                            ActionState::Init | ActionState::Success | ActionState::Failure => {
-                                factory.0.deactivate(current.clone(), &lazy);
-                                *state = ActionState::Init;
-                                thinker.current_action = None;
-                            }
-                            _ => {
-                                *state = ActionState::Cancelled;
-                            }
+            }
+            ActionState::Requested | ActionState::Executing => {
+                if let Some(picked_action_ent) = thinker.picker.pick(&thinker.choices, &utilities) {
+                    // Think about what action we're supposed to be taking. We do this
+                    // every tick, because we might change our mind.
+                    // ...and then execute it (details below).
+                    exec_picked_action(
+                        &mut cmd,
+                        thinker_ent,
+                        *actor,
+                        &mut thinker,
+                        &picked_action_ent,
+                        &mut action_states,
+                        &builder_wrappers,
+                    );
+                } else if let Some(default_action_ent) = &thinker.otherwise {
+                    // Otherwise, let's just execute the default one! (if it's there)
+                    let default_action_ent = *default_action_ent;
+                    exec_picked_action(
+                        &mut cmd,
+                        thinker_ent,
+                        *actor,
+                        &mut thinker,
+                        &default_action_ent,
+                        &mut action_states,
+                        &builder_wrappers,
+                    );
+                } else if let Some(current) = &mut thinker.current_action {
+                    // If we didn't pick anything, and there's no default action,
+                    // we need to see if there's any action currently executing,
+                    // and cancel it. We also use this opportunity to clean up
+                    // stale action components so they don't slow down joins.
+                    let mut state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+                    let factory = builder_wrappers.get(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+                    match *state {
+                        actions::ActionState::Init
+                        | actions::ActionState::Success
+                        | actions::ActionState::Failure => {
+                            factory.0.deactivate(*current, &mut cmd);
+                            *state = ActionState::Init;
+                            thinker.current_action = None;
+                        }
+                        _ => {
+                            *state = ActionState::Cancelled;
                         }
                     }
                 }
             }
-            thinker.timer.reset();
-            thinker.timer.start();
-            if self.index % 500 == 0 && start.elapsed() > self.max_duration {
-                return;
-            }
         }
-        self.index = 0;
+        if iterations.index % 500 == 0 && start.elapsed() > iterations.max_duration {
+            return;
+        }
     }
+    iterations.index = 0;
 }
 
 fn exec_picked_action(
+    cmd: &mut Commands,
     thinker_ent: Entity,
-    thinker: &mut Thinker,
+    actor: Entity,
+    thinker: &mut Mut<Thinker>,
     picked_action_ent: &ActionEnt,
-    states: &mut WriteStorage<ActionState>,
-    builder_wrappers: &ReadStorage<ActionManagerWrapper>,
-    lazy: &Read<LazyUpdate>,
+    states: &mut Query<&mut ActionState>,
+    builder_wrappers: &Query<&ActionManagerWrapper>,
 ) {
     // If we do find one, then we need to grab the corresponding
     // component for it. The "action" that `picker.pick()` returns
@@ -248,26 +240,26 @@ fn exec_picked_action(
             // So we've picked a different action than we were
             // currently executing. Just like before, we grab the
             // actual Action component (and we assume it exists).
-            let curr_action_state = states.get_mut(current.0.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+            let mut curr_action_state = states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
             // If the action is executing, or was requested, we
             // need to cancel it to make sure it stops. The Action
             // system will take care of resetting its state as
             // needed.
-            match curr_action_state {
+            match *curr_action_state {
                 ActionState::Executing | ActionState::Requested => {
                     *curr_action_state = ActionState::Cancelled;
-                    let thinker_state = states.get_mut(thinker_ent.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+                    let mut thinker_state = states.get_mut(thinker_ent).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
                     *thinker_state = ActionState::Cancelled;
                 }
                 ActionState::Init | ActionState::Success | ActionState::Failure => {
-                    let current_action_factory = builder_wrappers.get(current.0.clone()).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
+                    let current_action_factory = builder_wrappers.get(current.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
                     current_action_factory
                         .0
-                        .deactivate(picked_action_ent.clone(), &lazy);
+                        .deactivate(*picked_action_ent, cmd);
                     let old_state = curr_action_state.clone();
                     *curr_action_state = ActionState::Init;
-                    *current = picked_action_ent.clone();
-                    let thinker_state = states.get_mut(thinker_ent.clone()).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
+                    *current = *picked_action_ent;
+                    let mut thinker_state = states.get_mut(thinker_ent).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
                     *thinker_state = old_state;
                 }
                 ActionState::Cancelled => {}
@@ -278,18 +270,13 @@ fn exec_picked_action(
             // it as Requested if for some reason it had finished
             // but the Action System hasn't gotten around to
             // cleaning it up.
-            let picked_action_state = states.get_mut(picked_action_ent.0.clone()).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
-            match picked_action_state {
-                ActionState::Init => {
-                    let picked_action_factory = builder_wrappers.get(picked_action_ent.0.clone()).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
-                    picked_action_factory.0.activate(
-                        thinker.actor.clone(),
-                        picked_action_ent.clone(),
-                        lazy,
-                    );
-                    *picked_action_state = ActionState::Requested;
-                }
-                _ => {}
+            let mut picked_action_state = states.get_mut(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
+            if *picked_action_state == ActionState::Init {
+                let picked_action_factory = builder_wrappers.get(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
+                picked_action_factory
+                    .0
+                    .activate(actor, *picked_action_ent, cmd);
+                *picked_action_state = ActionState::Requested;
             }
         }
     } else {
@@ -297,12 +284,12 @@ fn exec_picked_action(
         // current_action in the thinker. The logic here is pretty
         // straightforward -- we set the action, Request it, and
         // that's it.
-        let picked_action_factory = builder_wrappers.get(picked_action_ent.0.clone()).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
-        let picked_action_state = states.get_mut(picked_action_ent.0.clone()).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
+        let picked_action_factory = builder_wrappers.get(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
+        let mut picked_action_state = states.get_mut(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug.");
         picked_action_factory
             .0
-            .activate(thinker.actor.clone(), picked_action_ent.clone(), lazy);
-        thinker.current_action = Some(picked_action_ent.clone());
+            .activate(actor, *picked_action_ent, cmd);
+        thinker.current_action = Some(*picked_action_ent);
         *picked_action_state = ActionState::Requested;
     }
 }
-- 
GitLab