diff options
| author | metamuffin <metamuffin@disroot.org> | 2025-10-19 23:50:23 +0200 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2025-10-19 23:50:23 +0200 |
| commit | ab83f982601d93b2399102c4d030fd6e13c4c735 (patch) | |
| tree | c0536ca9e328707d6b4f4cfc7a2307713466a5be /server/game-core/src | |
| parent | 231a5ce21fcee9195fcc504ee672e4464d627c47 (diff) | |
| download | hurrycurry-ab83f982601d93b2399102c4d030fd6e13c4c735.tar hurrycurry-ab83f982601d93b2399102c4d030fd6e13c4c735.tar.bz2 hurrycurry-ab83f982601d93b2399102c4d030fd6e13c4c735.tar.zst | |
Refactor and move interaction code
Diffstat (limited to 'server/game-core/src')
| -rw-r--r-- | server/game-core/src/interaction.rs | 442 | ||||
| -rw-r--r-- | server/game-core/src/lib.rs | 4 |
2 files changed, 445 insertions, 1 deletions
diff --git a/server/game-core/src/interaction.rs b/server/game-core/src/interaction.rs new file mode 100644 index 00000000..407294d6 --- /dev/null +++ b/server/game-core/src/interaction.rs @@ -0,0 +1,442 @@ +/* + 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 <https://www.gnu.org/licenses/>. + +*/ +use crate::{Game, Involvement, Item}; +use hurrycurry_locale::{TrError, tre}; +use hurrycurry_protocol::{ItemLocation, PacketC, Recipe}; +use log::info; +use std::collections::{BTreeSet, VecDeque}; + +impl Game { + pub fn interact( + &mut self, + this_loc: ItemLocation, + other_loc: ItemLocation, + edge: bool, + ) -> Result<(), TrError> { + let automated = this_loc.is_tile() && other_loc.is_tile(); + let other_player_id = match other_loc { + ItemLocation::Player(pid, _) => Some(pid), + _ => None, + }; + let this_tile_kind = match this_loc { + ItemLocation::Tile(t) => self.tiles.get(&t).map(|t| t.kind), + _ => None, + }; + + let (this_slot, other_slot) = match (this_loc, other_loc) { + (ItemLocation::Tile(p1), ItemLocation::Tile(p2)) => { + if p1 == p2 { + return Err(tre!("s.error.self_interact")); + } + let [Some(x), Some(y)] = self.tiles.get_disjoint_mut([&p1, &p2]) else { + return Err(tre!("s.error.no_tile")); + }; + (&mut x.item, &mut y.item) + } + (ItemLocation::Tile(p1), ItemLocation::Player(p2, h2)) => { + let Some(x) = self.tiles.get_mut(&p1) else { + return Err(tre!("s.error.no_tile")); + }; + let Some(y) = self.players.get_mut(&p2) else { + return Err(tre!("s.error.no_player")); + }; + let Some(y) = y.items.get_mut(h2.0) else { + return Err(tre!("s.error.no_hand")); + }; + (&mut x.item, y) + } + (ItemLocation::Player(p1, h1), ItemLocation::Tile(p2)) => { + let Some(x) = self.players.get_mut(&p1) else { + return Err(tre!("s.error.no_player")); + }; + let Some(x) = x.items.get_mut(h1.0) else { + return Err(tre!("s.error.no_hand")); + }; + let Some(y) = self.tiles.get_mut(&p2) else { + return Err(tre!("s.error.no_tile")); + }; + (x, &mut y.item) + } + (ItemLocation::Player(p1, h1), ItemLocation::Player(p2, h2)) => { + if p1 == p2 { + return Err(tre!("s.error.self_interact")); + } + let [Some(x), Some(y)] = self.players.get_disjoint_mut([&p1, &p2]) else { + return Err(tre!("s.error.no_tile")); + }; + let Some(x) = x.items.get_mut(h1.0) else { + return Err(tre!("s.error.no_hand")); + }; + let Some(y) = y.items.get_mut(h2.0) else { + return Err(tre!("s.error.no_hand")); + }; + (x, y) + } + }; + + if other_slot.is_none() + && let Some(item) = this_slot + && let Some(inv) = &mut item.active + { + let recipe = &self.data.recipe(inv.recipe); + if recipe.supports_tile(this_tile_kind) + && let Recipe::Active { outputs, speed, .. } = recipe + { + if edge { + inv.players.extend(other_player_id); + } else { + if let Some(player) = other_player_id { + inv.players.remove(&player); + } + } + inv.speed = speed * inv.players.len() as f32; + + if inv.position >= 1. { + let this_had_item = this_slot.is_some(); + let other_had_item = other_slot.is_some(); + *other_slot = outputs[0].map(|kind| Item { kind, active: None }); + *this_slot = outputs[1].map(|kind| Item { kind, active: None }); + self.item_locations_index.remove(&this_loc); + self.item_locations_index.remove(&other_loc); + if this_slot.is_some() { + self.item_locations_index.insert(this_loc); + } + if other_slot.is_some() { + self.item_locations_index.insert(other_loc); + } + produce_events( + &mut self.events, + this_had_item, + other_had_item, + this_slot, + this_loc, + other_slot, + other_loc, + ); + } else { + self.events.push_back(PacketC::SetProgress { + players: inv.players.clone(), + item: this_loc, + position: inv.position, + speed: inv.speed, + warn: inv.warn, + }); + } + return Ok(()); + } + } + if !edge { + return Ok(()); + } + for (ri, recipe) in self.data.recipes() { + if !recipe.supports_tile(this_tile_kind) { + continue; + } + match recipe { + Recipe::Active { input, speed, .. } => { + if other_slot.is_none() + && let Some(item) = this_slot + && item.kind == *input + && item.active.is_none() + { + info!("start active {ri}"); + item.active = Some(Involvement { + players: other_player_id.into_iter().collect(), + recipe: ri, + speed: *speed, + position: 0., + warn: false, + }); + } + if this_slot.is_none() + && let Some(item) = &other_slot + && item.kind == *input + && item.active.is_none() + { + let mut item = other_slot.take().unwrap(); + info!("start active {ri}"); + item.active = Some(Involvement { + players: other_player_id.into_iter().collect(), + recipe: ri, + speed: *speed, + position: 0., + warn: false, + }); + self.item_locations_index.remove(&other_loc); + self.item_locations_index.insert(this_loc); + *this_slot = Some(item); + self.score.active_recipes += 1; + self.events.push_back(PacketC::MoveItem { + from: other_loc, + to: this_loc, + }); + self.events.push_back(PacketC::SetProgress { + players: other_player_id.into_iter().collect(), + item: this_loc, + position: 0., + speed: *speed, + warn: false, + }); + return Ok(()); + } + } + Recipe::Instant { + inputs, + outputs, + points: pd, + .. + } => { + let on_tile = this_slot.as_ref().map(|i| i.kind); + let in_hand = other_slot.as_ref().map(|i| i.kind); + let ok = inputs[0] == on_tile && inputs[1] == in_hand; + let ok_rev = inputs[1] == on_tile && inputs[0] == in_hand; + if ok || ok_rev { + info!("instant {ri} reversed={ok_rev}"); + let ok_rev = ok_rev as usize; + let this_had_item = this_slot.is_some(); + let other_had_item = other_slot.is_some(); + *other_slot = outputs[1 - ok_rev].map(|kind| Item { kind, active: None }); + *this_slot = outputs[ok_rev].map(|kind| Item { kind, active: None }); + self.item_locations_index.remove(&this_loc); + self.item_locations_index.remove(&other_loc); + if this_slot.is_some() { + self.item_locations_index.insert(this_loc); + } + if other_slot.is_some() { + self.item_locations_index.insert(other_loc); + } + self.score.points += pd; + self.score.instant_recipes += 1; + self.events.push_back(PacketC::Score(self.score.clone())); + produce_events( + &mut self.events, + this_had_item, + other_had_item, + this_slot, + this_loc, + other_slot, + other_loc, + ); + return Ok(()); + } + } + _ => (), + } + } + + let can_place = automated + || this_tile_kind.is_none_or(|tile| { + other_slot.as_ref().is_some_and(|other| { + self.data + .tile_placeable_items + .get(&tile) + .is_none_or(|pl| pl.contains(&other.kind)) + }) + }); + + if can_place + && this_slot.is_none() + && let Some(item) = other_slot.take() + { + self.item_locations_index.remove(&other_loc); + self.item_locations_index.insert(this_loc); + *this_slot = Some(item); + self.events.push_back(PacketC::MoveItem { + from: other_loc, + to: this_loc, + }); + return Ok(()); + } + if other_slot.is_none() + && let Some(item) = this_slot.take() + { + self.item_locations_index.remove(&this_loc); + self.item_locations_index.insert(other_loc); + *other_slot = Some(item); + self.events.push_back(PacketC::MoveItem { + from: this_loc, + to: other_loc, + }); + } + + Ok(()) + } + + pub fn tick_slot(&mut self, loc: ItemLocation, dt: f32) -> Result<(), TrError> { + let tile = match loc { + ItemLocation::Tile(t) => self.tiles.get(&t).map(|t| t.kind), + _ => None, + }; + let slot = match loc { + ItemLocation::Tile(p) => { + let Some(x) = self.tiles.get_mut(&p) else { + return Err(tre!("s.error.no_tile")); + }; + &mut x.item + } + ItemLocation::Player(p, h) => { + let Some(x) = self.players.get_mut(&p) else { + return Err(tre!("s.error.no_player")); + }; + let Some(x) = x.items.get_mut(h.0) else { + return Err(tre!("s.error.no_hand")); + }; + x + } + }; + + if let Some(item) = slot { + if let Some(a) = &mut item.active { + let r = &self.data.recipe(a.recipe); + let prev_speed = a.speed; + + if r.supports_tile(tile) { + if a.speed <= 0. + && let Recipe::Passive { speed, .. } = &self.data.recipe(a.recipe) + { + a.speed = *speed; + } + } else if let Some(revert_speed) = r.revert_speed() { + a.speed = -revert_speed + } else { + a.speed = 0.; + } + + if a.position < 0. { + item.active = None; + self.events.push_back(PacketC::ClearProgress { item: loc }); + return Ok(()); + } + if a.position >= 1. + && let Recipe::Passive { output, warn, .. } = &self.data.recipe(a.recipe) + { + *slot = output.map(|kind| Item { kind, active: None }); + self.score.passive_recipes += 1; + self.events.push_back(PacketC::Score(self.score.clone())); + self.events.push_back(PacketC::SetProgress { + players: BTreeSet::new(), + warn: *warn, + item: loc, + position: 1., + speed: 0., + }); + self.events.push_back(PacketC::SetItem { + location: loc, + item: slot.as_ref().map(|i| i.kind), + }); + return Ok(()); + }; + + a.position += dt * a.speed; + a.position = a.position.min(1.); + + if a.speed != prev_speed { + self.events.push_back(PacketC::SetProgress { + players: a.players.clone(), + position: a.position, + speed: a.speed, + warn: a.warn, + item: loc, + }); + } + } else if let Some(recipes) = self.data_index.recipe_passive_by_input.get(&item.kind) { + for &ri in recipes { + let recipe = self.data.recipe(ri); + if recipe.supports_tile(tile) + && let Recipe::Passive { + input, warn, speed, .. + } = recipe + && *input == item.kind + { + item.active = Some(Involvement { + players: BTreeSet::new(), + recipe: ri, + position: 0., + warn: *warn, + speed: *speed, + }); + self.events.push_back(PacketC::SetProgress { + players: BTreeSet::new(), + position: 0., + speed: *speed, + warn: *warn, + item: loc, + }); + return Ok(()); + } + } + } + } + Ok(()) + } +} + +pub enum TickEffect { + Progress { + speed: f32, + position: f32, + warn: bool, + }, + ClearProgress, + Produce, +} + +#[allow(clippy::too_many_arguments)] +fn produce_events( + events: &mut VecDeque<PacketC>, + this_had_item: bool, + other_had_item: bool, + this: &Option<Item>, + this_loc: ItemLocation, + other: &Option<Item>, + other_loc: ItemLocation, +) { + info!("produce {this_loc} <~ {other_loc}"); + if this_had_item { + events.push_back(PacketC::SetItem { + location: this_loc, + item: None, + }); + } + if other_had_item { + events.push_back(PacketC::MoveItem { + from: other_loc, + to: this_loc, + }); + events.push_back(PacketC::SetItem { + location: this_loc, + item: None, + }); + } + if let Some(i) = &other { + events.push_back(PacketC::SetItem { + location: this_loc, + item: Some(i.kind), + }); + events.push_back(PacketC::MoveItem { + from: this_loc, + to: other_loc, + }) + } + if let Some(i) = &this { + events.push_back(PacketC::SetItem { + location: this_loc, + item: Some(i.kind), + }); + } +} diff --git a/server/game-core/src/lib.rs b/server/game-core/src/lib.rs index cc77e570..994398c9 100644 --- a/server/game-core/src/lib.rs +++ b/server/game-core/src/lib.rs @@ -16,9 +16,11 @@ */ pub mod gamedata_index; +pub mod interaction; pub mod network; pub mod spatial_index; +use crate::gamedata_index::GamedataIndex; use hurrycurry_protocol::{ Character, Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PlayerClass, PlayerID, RecipeIndex, Score, TileIndex, glam::IVec2, movement::MovementBase, @@ -29,7 +31,6 @@ use std::{ sync::Arc, time::Instant, }; -use crate::gamedata_index::GamedataIndex; #[derive(Debug, Clone, PartialEq)] pub struct Involvement { @@ -78,6 +79,7 @@ pub struct Game { pub players_spatial_index: SpatialIndex<PlayerID>, pub walkable: HashSet<IVec2>, pub tile_index: HashMap<TileIndex, HashSet<IVec2>>, + pub item_locations_index: HashSet<ItemLocation>, pub events: VecDeque<PacketC>, } |