From 57827d53713e9bdf65a0bcc34fe670cfe8b356dd Mon Sep 17 00:00:00 2001
From: Silas Bartha <silas@exvacuum.dev>
Date: Thu, 21 Nov 2024 12:17:08 -0500
Subject: [PATCH] Thu Nov 21 12:17:08 PM EST 2024

---
 src/components.rs | 27 +++++++++++++++++++++++----
 src/lib.rs        | 37 +++++++++++++++++++++++++++++--------
 2 files changed, 52 insertions(+), 12 deletions(-)

diff --git a/src/components.rs b/src/components.rs
index 1be2f2c..c2b2547 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -6,7 +6,7 @@ use bevy::{prelude::*, utils::HashSet};
 ///
 /// An entity with an `Interactor` component can be passed to an `InteractorFiredEvent` in order to
 /// start an interaction with any nearby `Interactable` entities.
-#[derive(Component, Default)]
+#[derive(Component, Default, Debug)]
 pub struct Interactor {
     /// All `Interactable` targets in-range of this interactor.
     pub targets: HashSet<Entity>,
@@ -18,10 +18,19 @@ pub struct Interactor {
 ///
 /// An entity with an `Interactable` component might get passed to an `InteractionEvent` when an
 /// `Interactor` requests an interaction, if the interactable is in range.
-#[derive(Component)]
+#[derive(Component, Clone, Debug)]
 pub struct Interactable {
+    /// An optional name for this interactable
+    pub name: Option<String>,
+    /// An optional description of the action
+    pub description: Option<String>,
+    /// Predicate to check to see if interaction is possible
+    pub predicate: Option<fn(Entity, &mut World) -> bool>,
     pub(crate) exclusive: bool,
     pub(crate) max_distance_squared: f32,
+    pub(crate) possible: bool,
+    /// Whether this pickup is enabled
+    pub enabled: bool,
 }
 
 impl Interactable {
@@ -30,17 +39,27 @@ impl Interactable {
     /// If exclusive, this interactable will only be interacted with if it's the closest one to the
     /// interactor, and the interaction will *not* be processed for any other in-range
     /// interactables.
-    pub fn new(max_distance: f32, exclusive: bool) -> Self {
+    pub fn new(max_distance: f32, exclusive: bool, name: Option<String>, description: Option<String>, predicate: Option<fn(Entity, &mut World) -> bool>) -> Self {
         Self {
+            name,
+            description,
+            predicate,
             exclusive,
             max_distance_squared: max_distance * max_distance,
+            possible: true,
+            enabled: true,
         }
     }
+
+    /// Gets whether this interaction is currently possible. Set this value using predicate.
+    pub fn possible(&self) -> bool {
+        self.possible
+    }
 }
 
 impl Default for Interactable {
     fn default() -> Self {
-        Self::new(1.0, false)
+        Self::new(1.0, false, None, None, None)
     }
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index 2e3fc60..de42adf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,9 +26,12 @@ pub struct InteractionPlugin;
 
 impl Plugin for InteractionPlugin {
     fn build(&self, app: &mut App) {
-        app.add_systems(Update, (handle_interactor_events, update_interactor_targets))
-            .add_event::<InteractorFiredEvent>()
-            .add_event::<InteractionEvent>();
+        app.add_systems(
+            Update,
+            (handle_interactor_events, update_interactor_targets, update_interactable_predicates),
+        )
+        .add_event::<InteractorFiredEvent>()
+        .add_event::<InteractionEvent>();
     }
 }
 
@@ -43,7 +46,7 @@ fn handle_interactor_events(
 
         if let Some(interactable_entity) = interactor.closest {
             let interactable = interactable_query.get(interactable_entity).unwrap();
-            if interactable.exclusive {
+            if interactable.exclusive && interactable.possible {
                 event_writer.send(InteractionEvent {
                     interactor: *interactor_entity,
                     interactable: interactable_entity,
@@ -52,7 +55,7 @@ fn handle_interactor_events(
             } else {
                 for interactable_entity in &interactor.targets {
                     let interactable = interactable_query.get(*interactable_entity).unwrap();
-                    if !interactable.exclusive {
+                    if !interactable.exclusive && interactable.possible {
                         event_writer.send(InteractionEvent {
                             interactor: *interactor_entity,
                             interactable: *interactable_entity,
@@ -73,7 +76,11 @@ fn update_interactor_targets(
         let interactor_transform = transform_query.get(interactor_entity).unwrap();
 
         let mut closest_active_interactable: Option<(f32, Entity)> = None;
+        interactor.targets.clear();
         for (interactable_entity, interactable) in interactable_query.iter_mut() {
+            if !interactable.enabled {
+                continue;
+            }
             let interactable_transform = transform_query.get(interactable_entity).unwrap();
             let interactable_distance_squared = interactable_transform
                 .translation()
@@ -85,7 +92,7 @@ fn update_interactor_targets(
                 ),
             );
             if interactable_distance_squared < interactable.max_distance_squared
-                && interactable_arccosine < PI / 8.0
+                && interactable_arccosine < PI / 4.0
             {
                 interactor.targets.insert(interactable_entity);
                 if let Some((arccosine, _)) = closest_active_interactable {
@@ -97,8 +104,6 @@ fn update_interactor_targets(
                     closest_active_interactable =
                         Some((interactable_arccosine, interactable_entity));
                 }
-            } else {
-                interactor.targets.remove(&interactable_entity);
             }
         }
         interactor.closest = if let Some((_, interactable_entity)) = closest_active_interactable {
@@ -108,3 +113,19 @@ fn update_interactor_targets(
         }
     }
 }
+
+fn update_interactable_predicates(world: &mut World) {
+    let mut interactable_query = world.query::<(Entity, &mut Interactable)>();
+    let mut interactables = vec![];
+    for (interactable_entity, interactable) in interactable_query.iter(world) {
+        interactables.push((interactable_entity, (*interactable).clone()));
+    }
+    for (interactable_entity, interactable) in interactables.iter_mut() {
+        if let Some(predicate) = &interactable.predicate {
+            interactable.possible = predicate(*interactable_entity, world);
+        }
+    }
+    for ((_, mut interactable), (_, temp)) in interactable_query.iter_mut(world).zip(interactables.into_iter()) {
+        *interactable = temp;
+    }
+}