/*
Hurry Curry! - a game about cooking
Copyright (C) 2025 Hurry Curry! Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
use crate::{
PacketSink,
pathfinding::{Path, find_path_to_neighbour},
};
use hurrycurry_game_core::Game;
use hurrycurry_protocol::{
Hand, ItemIndex, ItemLocation, PacketS, PlayerID,
glam::{IVec2, Vec2},
};
pub struct StepState {
path: Path,
interact_position: IVec2,
item_expected: Option,
item_current: Option,
hand: Hand,
wait_timer: f32,
interact_start_pending: bool,
interact_stop_pending: bool,
me: PlayerID,
}
impl StepState {
pub fn new_idle(me: PlayerID) -> Self {
Self::new_wait(me, 0.)
}
pub fn new_wait(me: PlayerID, timer: f32) -> Self {
Self {
me,
path: Path::EMPTY,
hand: Hand(0),
wait_timer: timer,
interact_start_pending: false,
interact_stop_pending: false,
interact_position: IVec2::ZERO,
item_expected: None,
item_current: None,
}
}
pub fn new_segment(
game: &Game,
me: PlayerID,
hand: Hand,
pos: IVec2,
interact: f32,
) -> Option {
let own_pos = game.players.get(&me)?.movement.position.as_ivec2();
let path = find_path_to_neighbour(game, own_pos, pos)?;
let destination_item = game
.tiles
.get(&pos)
.and_then(|t| t.item.as_ref().map(|i| i.kind));
Some(Self {
me,
path,
hand,
wait_timer: interact,
interact_start_pending: true,
interact_stop_pending: true,
interact_position: pos,
item_expected: destination_item,
item_current: destination_item,
})
}
pub fn is_busy(&self) -> bool {
(self.wait_timer > 0. || self.interact_stop_pending)
&& !self.path.is_stuck(3.)
&& (self.path.is_done() || self.item_current == self.item_expected)
}
pub fn tick(&mut self, out: &mut PacketSink, game: &Game, dt: f32) {
#[cfg(feature = "debug_events")]
{
use crate::debug_player_color;
use hurrycurry_protocol::{DebugEvent, DebugEventDisplay, glam::Vec2};
out.push(PacketS::Debug(DebugEvent {
key: format!("step-{}", self.me),
color: debug_player_color(self.me),
display: DebugEventDisplay::Label {
pos: self.interact_position.as_vec2() + Vec2::splat(0.5),
text: format!(
"D={} E={} T={:.01}",
self.path.is_done() as u8,
(self.item_expected == self.item_current) as u8,
self.wait_timer
),
},
timeout: 1.,
}));
}
if self.path.is_done() {
if self.interact_start_pending {
self.interact_start_pending = false;
out.push(PacketS::Interact {
player: self.me,
hand: self.hand,
target: Some(ItemLocation::Tile(self.interact_position)),
});
out.push(PacketS::Movement {
player: self.me,
dir: Vec2::ZERO,
boost: false,
pos: None,
});
} else if self.wait_timer > 0. {
self.wait_timer -= dt;
} else if self.interact_stop_pending {
self.interact_stop_pending = false;
out.push(PacketS::Interact {
player: self.me,
hand: self.hand,
target: None,
});
}
} else {
self.item_current = game
.tiles
.get(&self.interact_position)
.and_then(|t| t.item.as_ref().map(|i| i.kind));
let position = game
.players
.get(&self.me)
.as_ref()
.map(|p| p.movement.position)
.unwrap_or_default();
let dir = self.path.next_direction(position, dt);
#[cfg(feature = "debug_events")]
out.push(PacketS::Debug(self.path.debug(self.me)));
out.push(PacketS::Movement {
player: self.me,
dir,
boost: self.path.is_stuck(0.5),
pos: None,
});
}
}
}