Added basic Gatherer node

master
hheik 2025-09-08 16:50:01 +03:00
parent 8c451c28e7
commit a7776b3c86
10 changed files with 195 additions and 75 deletions

View File

@ -7,6 +7,10 @@ extends Node
func predicate() -> bool: func predicate() -> bool:
return true return true
func get_action_name() -> String:
push_error("get_action_name is unimplemented!")
return "UNIMPLEMENTED"
# Called when the action starts # Called when the action starts
func action_ready(): func action_ready():
pass pass

View File

@ -26,11 +26,11 @@ func handle_decide_action():
#print("deciding action...") #print("deciding action...")
pass pass
func handle_perform_action(): func handle_perform_action(_action: String):
#print("performing action: ", actor.get_current_action()) #print("performing action: ", actor.get_current_action())
pass pass
func handle_turn_end(): func handle_turn_end(_action: String):
#print("turn end!") #print("turn end!")
pass pass
@ -51,7 +51,7 @@ func try_perform(action_node: Action) -> bool:
func inner_perform(action_node: Action): func inner_perform(action_node: Action):
current_action = action_node current_action = action_node
actor.perform_action(action_node.name) actor.perform_action(action_node.get_action_name())
current_action.done.connect(_on_action_done, CONNECT_ONE_SHOT) current_action.done.connect(_on_action_done, CONNECT_ONE_SHOT)
current_action.abort.connect(_on_action_abort, CONNECT_ONE_SHOT) current_action.abort.connect(_on_action_abort, CONNECT_ONE_SHOT)
current_action.action_ready() current_action.action_ready()

View File

@ -9,6 +9,9 @@ func _init(new_mover: GridPosition, new_dir: Vector2i) -> void:
mover = new_mover mover = new_mover
dir = new_dir dir = new_dir
func get_action_name() -> String:
return "move"
func predicate(): func predicate():
return mover.can_move(dir) return mover.can_move(dir)

View File

@ -2,5 +2,8 @@
class_name SkipAction class_name SkipAction
extends Action extends Action
func get_action_name() -> String:
return "skip"
func action_ready(): func action_ready():
done.emit() done.emit()

View File

@ -22,4 +22,4 @@ frame = 1
position = Vector2(16, 16) position = Vector2(16, 16)
zoom = Vector2(2, 2) zoom = Vector2(2, 2)
[node name="Gatherer" type="Node" parent="GridPosition"] [node name="Gatherer" type="Gatherer" parent="GridPosition"]

View File

@ -2,9 +2,9 @@ use godot::prelude::*;
pub struct VelhoExtension; pub struct VelhoExtension;
mod common; mod overworld;
mod level; mod turn;
mod turn_manager; mod utils;
#[gdextension] #[gdextension]
unsafe impl ExtensionLibrary for VelhoExtension {} unsafe impl ExtensionLibrary for VelhoExtension {}

View File

@ -1,4 +1,6 @@
use godot::{classes::*, prelude::*}; use godot::{classes::*, obj::WithBaseField, prelude::*};
use crate::{turn::TurnActor, utils};
#[derive(Debug, GodotClass)] #[derive(Debug, GodotClass)]
#[class(init, base=Node)] #[class(init, base=Node)]
@ -20,6 +22,8 @@ impl INode for Level {
if self.foreground.is_none() { if self.foreground.is_none() {
self.foreground = self.base().try_get_node_as::<TileMapLayer>("./Foreground"); self.foreground = self.base().try_get_node_as::<TileMapLayer>("./Foreground");
} }
assert_ne!(self.background, None);
assert_ne!(self.foreground, None);
} }
} }
@ -50,6 +54,11 @@ impl Level {
.is_some_and(|layer| get_custom_data_bool(layer, coords, Self::HAS_COLLISION)) .is_some_and(|layer| get_custom_data_bool(layer, coords, Self::HAS_COLLISION))
} }
#[func]
pub fn get_bg(&self) -> Gd<TileMapLayer> {
self.background.clone().unwrap()
}
#[func] #[func]
pub fn get_fg(&self) -> Gd<TileMapLayer> { pub fn get_fg(&self) -> Gd<TileMapLayer> {
self.foreground.clone().unwrap() self.foreground.clone().unwrap()
@ -101,8 +110,6 @@ impl INode2D for GridPosition {
self.is_moving = false; self.is_moving = false;
self.signals().finished_moving().emit(target_coords); self.signals().finished_moving().emit(target_coords);
} else { } else {
// self.base_mut()
// .set_global_position(start.move_toward(end, movement_speed * delta));
self.base_mut() self.base_mut()
.set_global_position(start.lerp(end, movement_speed * delta as f32)); .set_global_position(start.lerp(end, movement_speed * delta as f32));
} }
@ -114,10 +121,22 @@ impl INode2D for GridPosition {
impl GridPosition { impl GridPosition {
const TILE_SNAP_DIST_SQR: f32 = 1.0; const TILE_SNAP_DIST_SQR: f32 = 1.0;
fn level(&self) -> Option<Gd<Level>> { pub fn level(&self) -> Option<Gd<Level>> {
Level::find_from_node(self.to_gd().upcast::<Node>()) Level::find_from_node(self.to_gd().upcast::<Node>())
} }
// #[func]
// pub fn find_from_node(node: Gd<Node>) -> Option<Gd<Self>> {
// let mut current = node.clone();
// while let Some(parent) = current.get_parent() {
// match parent.try_cast::<Self>() {
// Ok(level) => return Some(level),
// Err(other) => current = other,
// }
// }
// None
// }
#[func] #[func]
pub fn get_target_pos(&self) -> Vector2 { pub fn get_target_pos(&self) -> Vector2 {
self.target_coords.cast_float() * self.grid_size self.target_coords.cast_float() * self.grid_size
@ -172,3 +191,68 @@ impl GridPosition {
#[signal] #[signal]
fn finished_moving(coords: Vector2i); fn finished_moving(coords: Vector2i);
} }
#[derive(GodotConvert, Var, Export, Clone, Copy, Default, Debug, PartialEq, Eq)]
#[godot(via = GString)]
pub enum ItemKind {
#[default]
Blueberry,
}
#[derive(Debug, GodotClass)]
#[class(init, base=Node)]
pub struct Gatherer {
#[export]
#[init(val = 0)]
radius: i32,
level: Option<Gd<Level>>,
grid_position: Option<Gd<GridPosition>>,
base: Base<Node>,
}
#[godot_api]
impl INode for Gatherer {
fn ready(&mut self) {
self.level = utils::find_in_parents(self.to_gd());
self.grid_position = utils::find_in_parents(self.to_gd());
let actor = TurnActor::find_from_node(self.to_gd().upcast::<Node>())
.expect("Getting Actor in some parent");
actor
.signals()
.turn_ended()
.connect_other(self, |this, action| this.on_turn_end(action));
}
}
#[godot_api]
impl Gatherer {
fn on_turn_end(&self, action: GString) -> Option<()> {
let coords = self.grid_position.clone()?.bind().get_coords();
if let Some((item, count)) = self.try_gather_tile(coords) {
godot_print!("Gathered {count} x {:?}", item);
}
Some(())
}
fn try_gather_tile(&self, coords: Vector2i) -> Option<(ItemKind, i32)> {
let mut fg = self.level.clone()?.bind().get_fg();
// TODO: This is a horrible way to handle this
if get_custom_data_bool(&fg, coords, "is_berry") {
let source_id = fg.get_cell_source_id(coords);
let alternative_tile = fg.get_cell_alternative_tile(coords);
let new_atlas_coords = fg.get_cell_atlas_coords(coords) + Vector2i::RIGHT;
fg.set_cell_ex(coords)
.source_id(source_id)
.alternative_tile(alternative_tile)
.atlas_coords(new_atlas_coords)
.done();
return Some((ItemKind::Blueberry, 1));
}
None
}
#[signal]
fn gathered(item: ItemKind, count: i32);
}

View File

@ -1,4 +1,59 @@
use godot::{classes::*, prelude::*}; use godot::{
classes::{object::ConnectFlags, *},
prelude::*,
};
#[derive(Debug, GodotClass)]
#[class(init, base=Node)]
pub struct TurnManager {
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 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<Gd<TurnActor>> {
let mut actors: Array<Gd<TurnActor>> = Array::new();
self.base()
.get_parent()
.unwrap()
.get_children()
.iter_shared()
.filter_map(|node| node.try_cast::<TurnActor>().ok())
.for_each(|actor| actors.push(&actor));
actors
}
}
#[derive(GodotConvert, Var, Export, Clone, Copy, Default, Debug, PartialEq, Eq)] #[derive(GodotConvert, Var, Export, Clone, Copy, Default, Debug, PartialEq, Eq)]
#[godot(via = GString)] #[godot(via = GString)]
@ -44,6 +99,18 @@ impl TurnActor {
self.state self.state
} }
#[func]
pub fn find_from_node(node: Gd<Node>) -> Option<Gd<Self>> {
let mut current = node.clone();
while let Some(parent) = current.get_parent() {
match parent.try_cast::<Self>() {
Ok(level) => return Some(level),
Err(other) => current = other,
}
}
None
}
#[func] #[func]
pub fn get_current_action(&self) -> Variant { pub fn get_current_action(&self) -> Variant {
match self.current_action.clone() { match self.current_action.clone() {
@ -104,8 +171,8 @@ impl TurnActor {
); );
} }
self.state = TurnActorState::PerformingAction; self.state = TurnActorState::PerformingAction;
self.current_action = Some(action_name); self.current_action = Some(action_name.clone());
self.signals().performing_action().emit(); self.signals().performing_action().emit(&action_name);
} }
/// Should be called by an action script after it's done. /// Should be called by an action script after it's done.
@ -118,8 +185,9 @@ impl TurnActor {
); );
} }
self.state = TurnActorState::WaitingForTurn; self.state = TurnActorState::WaitingForTurn;
let action = self.current_action.clone().unwrap_or_default();
self.current_action = None; self.current_action = None;
self.signals().turn_ended().emit(); self.signals().turn_ended().emit(&action);
} }
#[signal] #[signal]
@ -129,8 +197,8 @@ impl TurnActor {
pub fn deciding_action(); pub fn deciding_action();
#[signal] #[signal]
pub fn performing_action(); pub fn performing_action(action_name: GString);
#[signal] #[signal]
pub fn turn_ended(); pub fn turn_ended(action_name: GString);
} }

View File

@ -1,58 +0,0 @@
use godot::{
classes::{object::ConnectFlags, *},
prelude::*,
};
use crate::common::TurnActor;
#[derive(Debug, GodotClass)]
#[class(init, base=Node)]
pub struct TurnManager {
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 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| this.start_next_turn());
actor.bind_mut().start_turn();
}
}
fn find_sibling_actors(&self) -> Array<Gd<TurnActor>> {
let mut actors: Array<Gd<TurnActor>> = Array::new();
self.base()
.get_parent()
.unwrap()
.get_children()
.iter_shared()
.filter_map(|node| node.try_cast::<TurnActor>().ok())
.for_each(|actor| actors.push(&actor));
actors
}
}

16
rust/src/utils.rs Normal file
View File

@ -0,0 +1,16 @@
use godot::prelude::*;
pub fn find_in_parents<T, U>(from: Gd<U>) -> Option<Gd<T>>
where
T: Inherits<Node>,
U: Inherits<Node>,
{
let mut current = from.clone().upcast::<Node>();
while let Some(parent) = current.get_parent() {
match parent.try_cast::<T>() {
Ok(level) => return Some(level),
Err(other) => current = other,
}
}
None
}