aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-09-30 01:19:01 +0200
committermetamuffin <metamuffin@disroot.org>2025-09-30 01:19:09 +0200
commit5033c326094edc1ff4234b994e95d987cb937fc4 (patch)
tree5fa426a77109722df163c15ce8d647170cd8fcea /server
parent727752b87bbe7146adb0f9e9e27d6e64b785ec2f (diff)
downloadhurrycurry-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.rs16
-rw-r--r--server/bot/src/algos/simple.rs41
-rw-r--r--server/bot/src/algos/waiter.rs2
-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.rs6
-rw-r--r--server/editor/src/main.rs6
-rw-r--r--server/protocol/src/helpers.rs6
-rw-r--r--server/protocol/src/lib.rs11
-rw-r--r--server/registry/src/lobby.rs6
-rw-r--r--server/src/data/mod.rs31
-rw-r--r--server/src/interaction.rs179
-rw-r--r--server/src/server.rs19
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()