use std::collections::HashSet; use godot::{ classes::{object::ConnectFlags, *}, prelude::*, }; use crate::prelude::*; #[derive(Debug, GodotClass)] #[class(init, base=Node)] pub struct TurnManager { #[export] time_manager: Option>, registered_actors: HashSet>, round_queue: Array>, current_actor: Option>, base: Base, } #[godot_api] impl INode for TurnManager { fn process(&mut self, _delta: f64) { if let Some(current) = self.current_actor.as_ref() { if !current.is_instance_valid() { self.start_next_turn(); } } if self.can_start_round() && self.current_actor.is_none() && self.round_queue.is_empty() { self.start_round(); } } } #[godot_api] impl TurnManager { pub fn register(&mut self, actor: Gd) { self.registered_actors.insert(actor); } fn unregister(&mut self, actor: &Gd) { self.registered_actors.remove(actor); } fn unregister_deleted(&mut self) { self.registered_actors .retain(|actor| actor.is_instance_valid() && !actor.is_queued_for_deletion()); } fn can_start_round(&self) -> bool { !self .time_manager .clone() .expect("Getting TimeManager for TurnManager") .bind() .is_day_over() } fn new_round(&self) -> Array> { let mut actors: Array> = self.registered_actors.iter().cloned().collect(); actors.sort_unstable_by(|a, b| a.instance_id().cmp(&b.instance_id())); actors } fn start_round(&mut self) { self.unregister_deleted(); self.current_actor = None; self.round_queue = self.new_round(); if !self.round_queue.is_empty() { GameManager::get(self.to_gd()).propagate_call(calls::ON_ROUND_START); 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() { if actor.is_instance_valid() && !actor.is_queued_for_deletion() { actor .signals() .turn_ended() .builder() // `ConnectFlags::Deferred` prevents double-borrow errors. // The double-borrow occurs when there are 2 actors, and the first one // immediately skips their turn. // // This should be investigated further. .flags(ConnectFlags::ONE_SHOT | ConnectFlags::DEFERRED) .connect_other_mut(self, Self::on_actor_turn_end); actor.bind_mut().start_turn(); GameManager::get(self.to_gd()).propagate_call(calls::ON_TURN_START); } else { self.unregister(&actor); self.start_next_turn(); } } else { GameManager::get(self.to_gd()).propagate_call(calls::ON_ROUND_END); } } fn on_actor_turn_end(&mut self, action: GString) { GameManager::get(self.to_gd()).propagate_call(calls::ON_TURN_END); let actor = self.current_actor.clone().unwrap(); self.signals().action_taken().emit(&actor, &action); self.start_next_turn(); } #[signal] pub fn action_taken(actor: Gd, action: GString); } #[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) {} fn enter_tree(&mut self) { let managers = GameManager::get(self.to_gd()); managers.bind().turn().bind_mut().register(self.to_gd()) } } #[godot_api] impl TurnActor { #[func] pub fn get_state(&self) -> TurnActorState { self.state } #[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); }