254 lines
7.9 KiB
Rust
254 lines
7.9 KiB
Rust
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<Gd<TimeManager>>,
|
|
|
|
registered_actors: HashSet<Gd<TurnActor>>,
|
|
round_queue: Array<Gd<TurnActor>>,
|
|
current_actor: Option<Gd<TurnActor>>,
|
|
|
|
base: Base<Node>,
|
|
}
|
|
|
|
#[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<TurnActor>) {
|
|
self.registered_actors.insert(actor);
|
|
}
|
|
|
|
fn unregister(&mut self, actor: &Gd<TurnActor>) {
|
|
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<Gd<TurnActor>> {
|
|
let mut actors: Array<Gd<TurnActor>> = 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<TurnActor>, 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<GString>,
|
|
base: Base<Node2D>,
|
|
}
|
|
|
|
#[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);
|
|
}
|