use godot::{ classes::{object::ConnectFlags, *}, prelude::*, }; #[derive(Debug, GodotClass)] #[class(init, base=Node)] pub struct TurnManager { round_queue: Array>, current_actor: Option>, base: Base, } #[godot_api] impl INode for TurnManager { fn process(&mut self, _delta: f64) { if self.current_actor.is_none() && self.round_queue.is_empty() { self.start_round(); } } } #[godot_api] impl TurnManager { fn start_round(&mut self) { self.round_queue = self.find_sibling_actors(); godot_print!("New round: {:?}", self.round_queue); self.start_next_turn(); } fn start_next_turn(&mut self) { self.current_actor = self.round_queue.pop(); if let Some(mut actor) = self.current_actor.clone() { actor .signals() .turn_ended() .builder() .flags(ConnectFlags::ONE_SHOT) .connect_other_mut(self, |this, _action| this.start_next_turn()); actor.bind_mut().start_turn(); } } fn find_sibling_actors(&self) -> Array> { let mut actors: Array> = Array::new(); self.base() .get_parent() .unwrap() .get_children() .iter_shared() .filter_map(|node| node.try_cast::().ok()) .for_each(|actor| actors.push(&actor)); actors } } #[derive(GodotConvert, Var, Export, Clone, Copy, Default, Debug, PartialEq, Eq)] #[godot(via = GString)] pub enum TurnActorState { /// The default value in Godot. /// Turn manager should call `start_turn` to advance. #[default] WaitingForTurn, /// Currently just automatically and immediately advances to `DecidingAction` state. /// This is reserved for animations and such in the future. TurnStarted, /// An action decision script should call `take_control` to advance from this state. DecidingAction, /// An action script should call `end_turn` to advance from this state. PerformingAction, } /// Turn breakdown: /// /// Actor can be in 4 different states: /// 1. Waiting for the turn to start. `TurnActor`s start in this state. (`turn_ended` emitted) /// 2. Turn has started, waiting for enabled controls (`turn_started` emitted) /// 3. Deciding what to do (`deciding_action` emitted) /// 4. Performing the action (`performing_action` emitted) /// ``` #[derive(Debug, GodotClass)] #[class(init, base=Node2D)] pub struct TurnActor { state: TurnActorState, current_action: Option, base: Base, } #[godot_api] impl INode2D for TurnActor { fn ready(&mut self) {} } #[godot_api] impl TurnActor { #[func] pub fn get_state(&self) -> TurnActorState { self.state } #[func] pub fn find_from_node(node: Gd) -> Option> { let mut current = node.clone(); while let Some(parent) = current.get_parent() { match parent.try_cast::() { Ok(level) => return Some(level), Err(other) => current = other, } } None } #[func] pub fn get_current_action(&self) -> Variant { match self.current_action.clone() { Some(action) => action.to_variant(), None => Variant::nil(), } } #[func] pub fn is_deciding(&self) -> bool { self.state == TurnActorState::DecidingAction } #[func] pub fn is_my_turn(&self) -> bool { match self.state { TurnActorState::TurnStarted | TurnActorState::DecidingAction | TurnActorState::PerformingAction => true, TurnActorState::WaitingForTurn => false, } } /// Should be called by a turn manager #[func] pub fn start_turn(&mut self) { if self.state != TurnActorState::WaitingForTurn { godot_error!( "TurnActor: incorrect state transfer. Called 'start_turn' while the actor is in state {:?} (expected WaitingForTurn)", self.state, ); } self.state = TurnActorState::TurnStarted; self.signals().turn_started().emit(); self.start_deciding(); } /// Currently called automatically after the turn is started #[func] pub fn start_deciding(&mut self) { if self.state != TurnActorState::TurnStarted { godot_error!( "TurnActor: incorrect state transfer. Called 'start_deciding' while the actor is in state {:?} (expected TurnStarted)", self.state, ); } self.state = TurnActorState::DecidingAction; self.signals().deciding_action().emit(); } /// Should be called by an action script. #[func] pub fn perform_action(&mut self, action_name: GString) { if self.state != TurnActorState::DecidingAction { godot_error!( "TurnActor: incorrect state transfer. Called 'take_control(\"{action_name}\")' while the actor is in state {:?} (expected DecidingAction)", self.state, ); } self.state = TurnActorState::PerformingAction; self.current_action = Some(action_name.clone()); self.signals().performing_action().emit(&action_name); } /// Should be called by an action script after it's done. #[func] pub fn end_turn(&mut self) { if self.state != TurnActorState::PerformingAction { godot_error!( "TurnActor: incorrect state transfer. Called 'end_turn' while the actor is in state {:?} (expected PerformingAction)", self.state, ); } self.state = TurnActorState::WaitingForTurn; let action = self.current_action.clone().unwrap_or_default(); self.current_action = None; self.signals().turn_ended().emit(&action); } #[signal] pub fn turn_started(); #[signal] pub fn deciding_action(); #[signal] pub fn performing_action(action_name: GString); #[signal] pub fn turn_ended(action_name: GString); }