diff options
author | metamuffin <metamuffin@disroot.org> | 2025-09-30 01:19:01 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-09-30 01:19:09 +0200 |
commit | 5033c326094edc1ff4234b994e95d987cb937fc4 (patch) | |
tree | 5fa426a77109722df163c15ce8d647170cd8fcea /server | |
parent | 727752b87bbe7146adb0f9e9e27d6e64b785ec2f (diff) | |
download | hurrycurry-5033c326094edc1ff4234b994e95d987cb937fc4.tar hurrycurry-5033c326094edc1ff4234b994e95d987cb937fc4.tar.bz2 hurrycurry-5033c326094edc1ff4234b994e95d987cb937fc4.tar.zst |
Implement tile placeable items for server-side (#433)
Diffstat (limited to 'server')
-rw-r--r-- | server/bot/src/algos/customer.rs | 16 | ||||
-rw-r--r-- | server/bot/src/algos/simple.rs | 41 | ||||
-rw-r--r-- | server/bot/src/algos/waiter.rs | 2 | ||||
-rw-r--r-- | server/client-lib/src/gamedata_index.rs (renamed from server/src/data/index.rs) | 8 | ||||
-rw-r--r-- | server/client-lib/src/lib.rs | 6 | ||||
-rw-r--r-- | server/editor/src/main.rs | 6 | ||||
-rw-r--r-- | server/protocol/src/helpers.rs | 6 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 11 | ||||
-rw-r--r-- | server/registry/src/lobby.rs | 6 | ||||
-rw-r--r-- | server/src/data/mod.rs | 31 | ||||
-rw-r--r-- | server/src/interaction.rs | 179 | ||||
-rw-r--r-- | server/src/server.rs | 19 |
12 files changed, 172 insertions, 159 deletions
diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs index 043e5358..cb5c5b59 100644 --- a/server/bot/src/algos/customer.rs +++ b/server/bot/src/algos/customer.rs @@ -134,15 +134,17 @@ impl CustomerState { let check = *ticks % 10 == 0; if path.is_done() { let demand = DemandIndex(random::<u32>() as usize % game.data.demands.len()); + let requested_item = game.data.demands[demand.0].input; info!("{me:?} -> waiting"); let timeout = 90. + random::<f32>() * 60.; let mut facing = Vec2::ZERO; for off in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] { - if game - .tiles - .get(&(off + *chair)) - .is_some_and(|t| game.data.is_tile_interactable(t.kind)) - { + if game.tiles.get(&(off + *chair)).is_some_and(|t| { + game.data + .tile_placeable_items + .get(&t.kind) + .map_or(true, |p| p.contains(&requested_item)) + }) { facing = off.as_vec2(); } } @@ -158,9 +160,9 @@ impl CustomerState { let message_item = if config.unknown_order { game.data .get_item_by_name("unknown-order") - .unwrap_or(game.data.demands[demand.0].input) + .unwrap_or(requested_item) } else { - game.data.demands[demand.0].input + requested_item }; BotInput { extra: vec![PacketS::Communicate { diff --git a/server/bot/src/algos/simple.rs b/server/bot/src/algos/simple.rs index f232062a..42cc4223 100644 --- a/server/bot/src/algos/simple.rs +++ b/server/bot/src/algos/simple.rs @@ -118,28 +118,8 @@ impl<S> Context<'_, S> { .map(|p| p.items[0].is_some()) .unwrap_or(false) } - pub fn find_demand(&self) -> Option<(ItemIndex, IVec2)> { - self.game - .players - .iter() - .find_map(|(_, pl)| match &pl.communicate_persist { - Some((Message::Item(item), _)) => { - let pos = pl.movement.position.as_ivec2(); - [IVec2::X, IVec2::Y, -IVec2::X, -IVec2::Y] - .into_iter() - .find(|off| { - self.game - .tiles - .get(&(pos + *off)) - .is_some_and(|t| self.game.data.tile_interact[t.kind.0]) - }) - .map(|off| pos + off) - .map(|pos| (*item, pos)) - } - _ => None, - }) - } - pub fn find_demands_with_table(&self) -> Vec<(ItemIndex, IVec2, f32)> { + /// Returns (requested_item, table_pos, remaining_time) + pub fn find_demands(&self) -> Vec<(ItemIndex, IVec2, f32)> { self.game .players .iter() @@ -149,10 +129,13 @@ impl<S> Context<'_, S> { [IVec2::X, IVec2::Y, -IVec2::X, -IVec2::Y] .into_iter() .find(|off| { - self.game - .tiles - .get(&(pos + *off)) - .is_some_and(|t| self.game.data.tile_interact[t.kind.0]) + self.game.tiles.get(&(pos + *off)).is_some_and(|t| { + self.game + .data + .tile_placeable_items + .get(&t.kind) + .map_or(true, |placable| placable.contains(item)) + }) }) .map(|off| pos + off) .map(|pos| (*item, pos, timeout.remaining)) @@ -202,7 +185,7 @@ impl<S> Context<'_, S> { .tiles .iter() .find(|(_, t)| { - self.game.data.tile_interact[t.kind.0] + !self.game.data.tile_placeable_items.contains_key(&t.kind) && t.item.is_none() && self.game.data.tile_names[t.kind.0] == name }) @@ -228,7 +211,7 @@ impl<S: State> Context<'_, S> { self.game .tiles .iter() - .find(|(_, t)| self.game.data.tile_interact[t.kind.0] && t.item.is_none()) + .find(|(_, t)| !self.game.data.tile_placeable_items.contains_key(&t.kind)) // TODO filter by placable item .map(|(p, _)| *p) } pub fn clear_tile(&mut self, pos: IVec2) -> LogicRes { @@ -378,7 +361,7 @@ impl Context<'_, Simple> { Err(()) } pub fn update(&mut self) -> LogicRes { - if let Some((item, table)) = self.find_demand() { + if let Some((item, table, _)) = self.find_demands().pop() { if self.game.data.item_name(item) == "unknown-order" { self.interact_with(table, 0.)?; } else { diff --git a/server/bot/src/algos/waiter.rs b/server/bot/src/algos/waiter.rs index ab0402e6..bde87f94 100644 --- a/server/bot/src/algos/waiter.rs +++ b/server/bot/src/algos/waiter.rs @@ -102,7 +102,7 @@ impl Context<'_, Waiter> { if let Some(pos) = self.find_occupied_table_or_floor() { self.assert_tile_is_clear(pos)?; } - let mut dems = self.find_demands_with_table(); + let mut dems = self.find_demands(); dems.sort_by_key(|(_, _, x)| (*x * 1000.) as i32); for (item, table, _) in dems { if self.game.data.item_name(item) == "unknown-order" { diff --git a/server/src/data/index.rs b/server/client-lib/src/gamedata_index.rs index a9ffbb81..8aa5e4af 100644 --- a/server/src/data/index.rs +++ b/server/client-lib/src/gamedata_index.rs @@ -15,17 +15,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -use hurrycurry_protocol::{Gamedata, ItemIndex, Recipe, RecipeIndex}; +use hurrycurry_protocol::{Gamedata, ItemIndex, Recipe, RecipeIndex, TileIndex}; use std::collections::HashMap; #[derive(Debug, Default)] pub struct GamedataIndex { + pub tile_collide: Vec<bool>, pub recipe_passive_by_input: HashMap<ItemIndex, Vec<RecipeIndex>>, } impl GamedataIndex { pub fn update(&mut self, data: &Gamedata) { self.recipe_passive_by_input.clear(); + self.tile_collide.clear(); + + for tile in (0..data.tile_names.len()).map(TileIndex) { + self.tile_collide.push(!data.tile_walkable.contains(&tile)); + } for (ri, r) in data.recipes() { if let Recipe::Passive { input, .. } = r { diff --git a/server/client-lib/src/lib.rs b/server/client-lib/src/lib.rs index 23394cc4..76ef1e77 100644 --- a/server/client-lib/src/lib.rs +++ b/server/client-lib/src/lib.rs @@ -16,6 +16,7 @@ */ #![feature(let_chains)] +pub mod gamedata_index; pub mod network; pub mod spatial_index; @@ -30,6 +31,8 @@ use std::{ time::Instant, }; +use crate::gamedata_index::GamedataIndex; + #[derive(Debug, PartialEq)] pub struct Involvement { pub position: f32, @@ -64,6 +67,7 @@ pub struct Player { #[derive(Default)] pub struct Game { pub data: Arc<Gamedata>, + pub data_index: GamedataIndex, pub tiles: HashMap<IVec2, Tile>, pub walkable: HashSet<IVec2>, pub players: HashMap<PlayerID, Player>, @@ -161,7 +165,7 @@ impl Game { } => { if let Some(kind) = kind { self.tiles.insert(tile, Tile { kind, item: None }); - if self.data.tile_collide[kind.0] { + if self.data_index.tile_collide[kind.0] { self.walkable.remove(&tile); } else { self.walkable.insert(tile); diff --git a/server/editor/src/main.rs b/server/editor/src/main.rs index 506d79cf..5fb8f1a4 100644 --- a/server/editor/src/main.rs +++ b/server/editor/src/main.rs @@ -13,7 +13,7 @@ use hurrycurry_protocol::{ use log::{debug, info, warn}; use save::{export_state, import_state}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, fs::{File, read_to_string}, io::Write, net::SocketAddr, @@ -143,8 +143,8 @@ async fn handle_conn( }); state.out.push(PacketC::Data { data: Gamedata { - tile_collide: TILES.iter().map(|_| false).collect(), - tile_interact: TILES.iter().map(|_| true).collect(), + tile_walkable: (0..TILES.len()).map(TileIndex).collect(), + tile_placeable_items: BTreeMap::new(), tile_names: TILES.iter().map(|(name, _, _)| name.to_string()).collect(), current_map: "editor".to_owned(), ..Default::default() diff --git a/server/protocol/src/helpers.rs b/server/protocol/src/helpers.rs index b5c0e82b..92668df4 100644 --- a/server/protocol/src/helpers.rs +++ b/server/protocol/src/helpers.rs @@ -5,12 +5,6 @@ impl Gamedata { pub fn tile_name(&self, index: TileIndex) -> &str { &self.tile_names[index.0] } - pub fn is_tile_colliding(&self, index: TileIndex) -> bool { - self.tile_collide[index.0] - } - pub fn is_tile_interactable(&self, index: TileIndex) -> bool { - self.tile_interact[index.0] - } pub fn item_name(&self, index: ItemIndex) -> &str { &self.item_names[index.0] } diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index c32bff39..00ef62d7 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -17,7 +17,10 @@ */ use glam::{IVec2, Vec2}; use serde::{Deserialize, Deserializer, Serialize}; -use std::{collections::HashSet, sync::LazyLock}; +use std::{ + collections::{BTreeMap, HashSet}, + sync::LazyLock, +}; pub use glam; @@ -76,14 +79,14 @@ pub struct Demand { pub points: i64, } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[rustfmt::skip] pub struct Gamedata { pub current_map: String, pub item_names: Vec<String>, pub tile_names: Vec<String>, - pub tile_collide: Vec<bool>, - pub tile_interact: Vec<bool>, + pub tile_walkable: HashSet<TileIndex>, + pub tile_placeable_items: BTreeMap<TileIndex, HashSet<ItemIndex>>, pub maps: Vec<(String, MapMetadata)>, pub bot_algos: Vec<String>, pub recipes: Vec<Recipe>, diff --git a/server/registry/src/lobby.rs b/server/registry/src/lobby.rs index 36ef9e44..b951f0db 100644 --- a/server/registry/src/lobby.rs +++ b/server/registry/src/lobby.rs @@ -9,7 +9,7 @@ use hurrycurry_protocol::{ use log::{error, info, warn}; use rocket::futures::{SinkExt, StreamExt}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, f32::consts::PI, net::SocketAddr, sync::Arc, @@ -75,8 +75,8 @@ async fn handle_conn(sock: TcpStream, addr: SocketAddr, entries: &[Entry]) -> Re }); out.push(PacketC::Data { data: Gamedata { - tile_collide: TILES.iter().map(|(_, c)| *c).collect(), - tile_interact: TILES.iter().map(|_| false).collect(), + tile_walkable: (0..TILES.len()).map(TileIndex).collect(), + tile_placeable_items: BTreeMap::new(), tile_names: TILES.iter().map(|(s, _)| s.to_string()).collect(), current_map: "registry".to_owned(), ..Default::default() diff --git a/server/src/data/mod.rs b/server/src/data/mod.rs index d19b32a3..e5cd4552 100644 --- a/server/src/data/mod.rs +++ b/server/src/data/mod.rs @@ -16,7 +16,6 @@ */ pub mod demands; -pub mod index; use crate::entity::{construct_entity, Entities, EntityDecl}; use anyhow::{anyhow, bail, Context, Result}; @@ -29,7 +28,7 @@ use hurrycurry_protocol::{ }; use serde::{Deserialize, Serialize}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, fs::{read_to_string, File}, path::PathBuf, str::FromStr, @@ -307,16 +306,24 @@ pub fn build_data( maps.sort_unstable_by_key(|(_, m)| m.difficulty); maps.sort_by_key(|(_, m)| m.players); + let mut tile_placeable_items = BTreeMap::new(); + for tile_name in map_in.collider.iter().chain(map_in.walkable.iter()) { + let tile = reg.register_tile(tile_name.to_string()); + let whitelist = recipes + .iter() + .filter(|r| r.tile() == Some(tile)) + .flat_map(|e| e.inputs()) + .collect(); + tile_placeable_items.insert(tile, whitelist); + } + let tile_walkable = map_in + .walkable + .into_iter() + .map(|name| reg.register_tile(name)) + .collect(); + let item_names = reg.items.into_inner().unwrap(); let tile_names = reg.tiles.into_inner().unwrap(); - let tile_collide = tile_names - .iter() - .map(|i| !map_in.walkable.contains(i)) - .collect(); - let tile_interact = tile_names - .iter() - .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i)) - .collect(); let default_timer = if map_name.ends_with("lobby") { None @@ -329,8 +336,8 @@ pub fn build_data( bot_algos, current_map: map_name, maps, - tile_collide, - tile_interact, + tile_walkable, + tile_placeable_items, recipes, item_names, demands, diff --git a/server/src/interaction.rs b/server/src/interaction.rs index 624d9893..8f591db1 100644 --- a/server/src/interaction.rs +++ b/server/src/interaction.rs @@ -15,8 +15,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -use crate::data::index::GamedataIndex; -use hurrycurry_client_lib::{Involvement, Item}; +use hurrycurry_client_lib::{gamedata_index::GamedataIndex, Involvement, Item}; use hurrycurry_protocol::{Gamedata, ItemLocation, PacketC, PlayerID, Recipe, Score, TileIndex}; use log::info; use std::collections::VecDeque; @@ -36,11 +35,11 @@ pub fn interact( automated: bool, packet_out: &mut VecDeque<PacketC>, ) { - let interactable = automated - || tile - .map(|tile| data.is_tile_interactable(tile)) - .unwrap_or(true); - if interactable && other.is_none() { + // let interactable = automated + // || tile + // .map(|tile| data.is_tile_interactable(tile)) + // .unwrap_or(true); + if other.is_none() { if let Some(item) = this { if let Some(active) = &mut item.active { let recipe = &data.recipe(active.recipe); @@ -85,96 +84,102 @@ pub fn interact( if !edge { return; } - if interactable { - for (ri, recipe) in data.recipes() { - if !recipe.supports_tile(tile) { - continue; - } - match recipe { - Recipe::Active { input, speed, .. } => { - if other.is_none() { - if let Some(item) = this { - if item.kind == *input && item.active.is_none() { - info!("start active recipe {ri:?}"); - item.active = Some(Involvement { - player, - recipe: ri, - speed: *speed, - position: 0., - warn: false, - }); - } + for (ri, recipe) in data.recipes() { + if !recipe.supports_tile(tile) { + continue; + } + match recipe { + Recipe::Active { input, speed, .. } => { + if other.is_none() { + if let Some(item) = this { + if item.kind == *input && item.active.is_none() { + info!("start active recipe {ri:?}"); + item.active = Some(Involvement { + player, + recipe: ri, + speed: *speed, + position: 0., + warn: false, + }); } } - if this.is_none() { - if let Some(item) = &other { - if item.kind == *input && item.active.is_none() { - let mut item = other.take().unwrap(); - info!("start active recipe {ri:?}"); - item.active = Some(Involvement { - player, - recipe: ri, - speed: *speed, - position: 0., - warn: false, - }); - *this = Some(item); - score.active_recipes += 1; - packet_out.push_back(PacketC::MoveItem { - from: other_loc, - to: this_loc, - }); - packet_out.push_back(PacketC::SetProgress { - player, - item: this_loc, - position: 0., - speed: *speed, - warn: false, - }); - return; - } + } + if this.is_none() { + if let Some(item) = &other { + if item.kind == *input && item.active.is_none() { + let mut item = other.take().unwrap(); + info!("start active recipe {ri:?}"); + item.active = Some(Involvement { + player, + recipe: ri, + speed: *speed, + position: 0., + warn: false, + }); + *this = Some(item); + score.active_recipes += 1; + packet_out.push_back(PacketC::MoveItem { + from: other_loc, + to: this_loc, + }); + packet_out.push_back(PacketC::SetProgress { + player, + item: this_loc, + position: 0., + speed: *speed, + warn: false, + }); + return; } } } - Recipe::Instant { - inputs, - outputs, - points: pd, - .. - } => { - let on_tile = this.as_ref().map(|i| i.kind); - let in_hand = other.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 recipe {ri:?} reversed={ok_rev}"); - let ok_rev = ok_rev as usize; - let this_had_item = this.is_some(); - let other_had_item = other.is_some(); - *other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None }); - *this = outputs[ok_rev].map(|kind| Item { kind, active: None }); - score.points += pd; - score.instant_recipes += 1; - *score_changed = true; - produce( - this_had_item, - other_had_item, - this, - this_loc, - other, - other_loc, - score_changed, - packet_out, - ); - return; - } + } + Recipe::Instant { + inputs, + outputs, + points: pd, + .. + } => { + let on_tile = this.as_ref().map(|i| i.kind); + let in_hand = other.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 recipe {ri:?} reversed={ok_rev}"); + let ok_rev = ok_rev as usize; + let this_had_item = this.is_some(); + let other_had_item = other.is_some(); + *other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None }); + *this = outputs[ok_rev].map(|kind| Item { kind, active: None }); + score.points += pd; + score.instant_recipes += 1; + *score_changed = true; + produce( + this_had_item, + other_had_item, + this, + this_loc, + other, + other_loc, + score_changed, + packet_out, + ); + return; } - _ => (), } + _ => (), } } - if interactable && this.is_none() { + let can_place = tile.map_or(true, |tile| { + other.as_ref().map_or(false, |other| { + data.tile_placeable_items + .get(&tile) + .map_or(true, |pl| pl.contains(&other.kind)) + }) + }); + + if can_place && this.is_none() { if let Some(item) = other.take() { *this = Some(item); packet_out.push_back(PacketC::MoveItem { diff --git a/server/src/server.rs b/server/src/server.rs index 57b26122..14244394 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -16,7 +16,7 @@ */ use crate::{ - data::{index::GamedataIndex, DataIndex, Serverdata}, + data::{DataIndex, Serverdata}, entity::{Entities, EntityContext}, interaction::{interact, tick_slot}, message::TrError, @@ -24,7 +24,7 @@ use crate::{ tre, ConnectionID, }; use anyhow::{Context, Result}; -use hurrycurry_client_lib::{Game, Involvement, Item, Player, Tile}; +use hurrycurry_client_lib::{gamedata_index::GamedataIndex, Game, Involvement, Item, Player, Tile}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, @@ -137,6 +137,7 @@ impl GameServerExt for Game { self.lobby = gamedata.current_map == "lobby"; self.data = gamedata.into(); + self.data_index.update(&self.data); self.score = Score { time_remaining: timer.map(|dur| dur.as_secs_f64()).unwrap_or(0.), ..Default::default() @@ -154,7 +155,7 @@ impl GameServerExt for Game { }), }, ); - if !self.data.tile_collide[tile.0] { + if !self.data_index.tile_collide[tile.0] { self.walkable.insert(p); } } @@ -304,7 +305,7 @@ impl GameServerExt for Game { ) { if let Some(kind) = kind { self.tiles.insert(tile, Tile::from(kind)); - if self.data.is_tile_colliding(kind) { + if self.data_index.tile_collide[kind.0] { self.walkable.remove(&tile); } else { self.walkable.insert(tile); @@ -547,7 +548,15 @@ impl Server { player.interacting = if edge { Some((pos, hand)) } else { None }; - let other_pid = if !self.game.data.is_tile_interactable(tile.kind) { + // Dont try interacting with player it tile is interactable + let other_pid = if !self + .game + .data + .tile_placeable_items + .get(&tile.kind) + .map_or(false, |p| !p.is_empty()) + // TODO check for hand item + { self.game .players .iter() |