diff options
author | metamuffin <metamuffin@noreply.codeberg.org> | 2024-12-25 19:05:05 +0000 |
---|---|---|
committer | metamuffin <metamuffin@noreply.codeberg.org> | 2024-12-25 19:05:05 +0000 |
commit | cc6b50debb9d5b740adbe6f803755413c972659a (patch) | |
tree | b34a0a5669707992f1334f88a1959d5b1e120415 /server | |
parent | 2ceeea0e5fc245602618ec47f6ff1f91a094e130 (diff) | |
parent | 53cf167c08986caf346957d1f357cefaee1bd6b5 (diff) | |
download | hurrycurry-cc6b50debb9d5b740adbe6f803755413c972659a.tar hurrycurry-cc6b50debb9d5b740adbe6f803755413c972659a.tar.bz2 hurrycurry-cc6b50debb9d5b740adbe6f803755413c972659a.tar.zst |
Merge pull request 'Two-handed players' (#236) from two-handed into master
Reviewed-on: https://codeberg.org/hurrycurry/hurrycurry/pulls/236
Diffstat (limited to 'server')
-rw-r--r-- | server/bot/src/algos/customer.rs | 14 | ||||
-rw-r--r-- | server/bot/src/algos/simple.rs | 4 | ||||
-rw-r--r-- | server/bot/src/main.rs | 3 | ||||
-rw-r--r-- | server/client-lib/src/lib.rs | 62 | ||||
-rw-r--r-- | server/protocol/Cargo.toml | 2 | ||||
-rw-r--r-- | server/protocol/src/helpers.rs | 11 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 9 | ||||
-rw-r--r-- | server/src/data/mod.rs | 2 | ||||
-rw-r--r-- | server/src/entity/bot.rs | 3 | ||||
-rw-r--r-- | server/src/entity/tutorial.rs | 4 | ||||
-rw-r--r-- | server/src/server.rs | 142 |
11 files changed, 165 insertions, 91 deletions
diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs index b243bd55..b0ece9dd 100644 --- a/server/bot/src/algos/customer.rs +++ b/server/bot/src/algos/customer.rs @@ -22,7 +22,7 @@ use crate::{ use hurrycurry_client_lib::Game; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, - DemandIndex, Message, PacketS, PlayerClass, PlayerID, Score, + DemandIndex, Hand, Message, PacketS, PlayerClass, PlayerID, Score, }; use log::info; use rand::{random, seq::IndexedRandom, thread_rng}; @@ -313,6 +313,7 @@ impl CustomerState { PacketS::Interact { pos: Some(pos), player: me, + hand: Hand(0), }, PacketS::ApplyScore(Score { demands_completed: 1, @@ -322,6 +323,7 @@ impl CustomerState { PacketS::Interact { pos: None, player: me, + hand: Hand(0), }, ], ..Default::default() @@ -354,6 +356,7 @@ impl CustomerState { extra: vec![PacketS::ReplaceHand { player: me, item: demand.output, + hand: Hand(0), }], ..Default::default() }; @@ -369,7 +372,12 @@ impl CustomerState { cooldown, } => { *cooldown -= dt; - if game.players.get(&me).is_some_and(|pl| pl.item.is_none()) { + if game + .players + .get(&me) + .is_some_and(|pl| pl.items[0].is_none()) + // TODO index out of bounds? + { if let Some(path) = find_path(&game.walkable, pos.as_ivec2(), *origin) { *self = CustomerState::Exiting { path }; } @@ -383,10 +391,12 @@ impl CustomerState { PacketS::Interact { player: me, pos: Some(*table), + hand: Hand(0), }, PacketS::Interact { player: me, pos: None, + hand: Hand(0), }, ], direction, diff --git a/server/bot/src/algos/simple.rs b/server/bot/src/algos/simple.rs index 14eb38c4..452f59d3 100644 --- a/server/bot/src/algos/simple.rs +++ b/server/bot/src/algos/simple.rs @@ -109,13 +109,13 @@ impl<S> Context<'_, S> { self.game .players .get(&self.me) - .is_some_and(|p| p.item.as_ref().is_some_and(|i| i.kind == item)) + .is_some_and(|p| p.items[0].as_ref().is_some_and(|i| i.kind == item)) } pub fn is_hand_occupied(&self) -> bool { self.game .players .get(&self.me) - .map(|p| p.item.is_some()) + .map(|p| p.items[0].is_some()) .unwrap_or(false) } pub fn find_demand(&self) -> Option<(ItemIndex, IVec2)> { diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs index 61ae1c1c..918be7e1 100644 --- a/server/bot/src/main.rs +++ b/server/bot/src/main.rs @@ -19,7 +19,7 @@ use anyhow::Result; use clap::Parser; use hurrycurry_bot::{algos::ALGO_CONSTRUCTORS, BotAlgo, BotInput}; use hurrycurry_client_lib::{network::sync::Network, Game}; -use hurrycurry_protocol::{PacketC, PacketS, PlayerClass, PlayerID}; +use hurrycurry_protocol::{Hand, PacketC, PacketS, PlayerClass, PlayerID}; use log::warn; use std::{thread::sleep, time::Duration}; @@ -109,6 +109,7 @@ fn main() -> Result<()> { network.queue_out.push_back(PacketS::Interact { player: b.id, pos: interact, + hand: Hand(0), }) } network.queue_out.push_back(PacketS::Movement { diff --git a/server/client-lib/src/lib.rs b/server/client-lib/src/lib.rs index 5d5e55d5..a40eafc1 100644 --- a/server/client-lib/src/lib.rs +++ b/server/client-lib/src/lib.rs @@ -20,7 +20,7 @@ pub mod network; pub mod spatial_index; use hurrycurry_protocol::{ - glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message, + glam::IVec2, movement::MovementBase, Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PlayerClass, PlayerID, RecipeIndex, Score, TileIndex, }; use spatial_index::SpatialIndex; @@ -54,8 +54,8 @@ pub struct Player { pub name: String, pub class: PlayerClass, pub character: i32, - pub interacting: Option<IVec2>, - pub item: Option<Item>, + pub interacting: Option<(IVec2, Hand)>, + pub items: Vec<Option<Item>>, pub communicate_persist: Option<(Message, MessageTimeout)>, pub movement: MovementBase, @@ -95,7 +95,7 @@ impl Game { character, class, interacting: None, - item: None, + items: (0..self.data.hand_count).map(|_| None).collect(), communicate_persist: None, movement: MovementBase::new(position), }, @@ -117,15 +117,27 @@ impl Game { p.movement.rotation = rot; } } - PacketC::MoveItem { from, to } => { - *self.get_item(to) = self.get_item(from).take(); + if let Some(item) = self.get_item(to).map(|e| e.take()) { + if let Some(to) = self.get_item(from) { + *to = item; + } else { + // TODO perhaps restore to original position? + } + } } PacketC::SetItem { location, item } => { - *self.get_item(location) = item.map(|kind| Item { kind, active: None }); + let location = self.get_item(location); + if let Some(location) = location { + *location = item.map(|kind| Item { kind, active: None }); + } } PacketC::ClearProgress { item } => { - self.get_item(item).as_mut().unwrap().active = None; + if let Some(slot) = self.get_item(item) { + if let Some(item) = slot { + item.active = None; + } + } } PacketC::SetProgress { item, @@ -134,13 +146,17 @@ impl Game { speed, warn, } => { - self.get_item(item).as_mut().unwrap().active = Some(Involvement { - player, - speed, - warn, - position, - recipe: RecipeIndex(0), - }); + if let Some(slot) = self.get_item(item) { + if let Some(item) = slot { + item.active = Some(Involvement { + player, + speed, + warn, + position, + recipe: RecipeIndex(0), + }); + } + } } PacketC::UpdateMap { tile, @@ -200,9 +216,11 @@ impl Game { } for player in self.players.values_mut() { - if let Some(item) = &mut player.item { - if let Some(active) = &mut item.active { - active.position += active.speed; + for item in &mut player.items { + if let Some(item) = item { + if let Some(active) = &mut item.active { + active.position += active.speed; + } } } } @@ -223,10 +241,12 @@ impl Game { }); } - pub fn get_item(&mut self, location: ItemLocation) -> &mut Option<Item> { + pub fn get_item(&mut self, location: ItemLocation) -> Option<&mut Option<Item>> { match location { - ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item, - ItemLocation::Player(pid) => &mut self.players.get_mut(&pid).unwrap().item, + ItemLocation::Tile(pos) => Some(&mut self.tiles.get_mut(&pos)?.item), + ItemLocation::Player(pid, hand) => { + Some(self.players.get_mut(&pid)?.items.get_mut(hand.0)?) + } } } } diff --git a/server/protocol/Cargo.toml b/server/protocol/Cargo.toml index 44533f9d..b6da120a 100644 --- a/server/protocol/Cargo.toml +++ b/server/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hurrycurry-protocol" -version = "7.5.0" +version = "8.0.0" edition = "2021" [dependencies] diff --git a/server/protocol/src/helpers.rs b/server/protocol/src/helpers.rs index 924d0886..b85c2f84 100644 --- a/server/protocol/src/helpers.rs +++ b/server/protocol/src/helpers.rs @@ -1,7 +1,8 @@ use std::fmt::Display; use crate::{ - DocumentElement, Gamedata, ItemIndex, ItemLocation, PlayerID, Recipe, RecipeIndex, TileIndex, + DocumentElement, Gamedata, Hand, ItemIndex, ItemLocation, PlayerID, Recipe, RecipeIndex, + TileIndex, }; impl Gamedata { @@ -98,11 +99,17 @@ impl Display for ItemLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ItemLocation::Tile(pos) => write!(f, "tile({pos})"), - ItemLocation::Player(PlayerID(id)) => write!(f, "player({id})"), + ItemLocation::Player(PlayerID(id), hand) => write!(f, "player({id}_{hand})"), } } } +impl Display for Hand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "h{}", self.0) + } +} + impl Default for DocumentElement { fn default() -> Self { Self::Document { es: vec![] } diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index 2ef07015..74d463a1 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -71,6 +71,10 @@ pub struct RecipeIndex(pub usize); #[serde(transparent)] pub struct DemandIndex(pub usize); +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, Hash)] +#[serde(transparent)] +pub struct Hand(pub usize); + #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct MapMetadata { pub name: String, @@ -98,6 +102,7 @@ pub struct Gamedata { pub bot_algos: Vec<String>, pub recipes: Vec<Recipe>, pub demands: Vec<Demand>, + pub hand_count: usize, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] @@ -124,6 +129,7 @@ pub enum PacketS { }, Interact { player: PlayerID, + hand: Hand, #[bincode(with_serde)] pos: Option<IVec2>, }, @@ -144,6 +150,7 @@ pub enum PacketS { /// For internal use only (customers) ReplaceHand { player: PlayerID, + hand: Hand, item: Option<ItemIndex>, }, #[serde(skip)] @@ -344,7 +351,7 @@ pub enum Recipe { #[serde(rename_all = "snake_case")] pub enum ItemLocation { Tile(#[bincode(with_serde)] IVec2), - Player(PlayerID), + Player(PlayerID, Hand), } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] diff --git a/server/src/data/mod.rs b/server/src/data/mod.rs index 57051fe9..64f40217 100644 --- a/server/src/data/mod.rs +++ b/server/src/data/mod.rs @@ -73,6 +73,7 @@ pub struct MapDecl { walkable: Vec<String>, chef_spawn: char, customer_spawn: char, + #[serde(default)] hand_count: Option<usize>, #[serde(default)] entities: Vec<EntityDecl>, #[serde(default)] tile_entities: HashMap<char, EntityDecl>, #[serde(default)] score_baseline: i64, @@ -314,6 +315,7 @@ pub fn build_data( item_names, demands, tile_names, + hand_count: map_in.hand_count.unwrap_or(1), }, Serverdata { initial_map, diff --git a/server/src/entity/bot.rs b/server/src/entity/bot.rs index fe4d711f..6e6c9162 100644 --- a/server/src/entity/bot.rs +++ b/server/src/entity/bot.rs @@ -18,7 +18,7 @@ use super::{Entity, EntityContext}; use anyhow::Result; use hurrycurry_bot::{BotAlgo, DynBotAlgo}; -use hurrycurry_protocol::{PacketS, PlayerClass, PlayerID}; +use hurrycurry_protocol::{Hand, PacketS, PlayerClass, PlayerID}; use log::info; use rand::random; use std::any::Any; @@ -72,6 +72,7 @@ impl<T: BotAlgo + Any> Entity for BotDriver<T> { c.packet_in.push_back(PacketS::Interact { player: self.id, pos: input.interact, + hand: Hand(0), }) } c.packet_in.push_back(PacketS::Movement { diff --git a/server/src/entity/tutorial.rs b/server/src/entity/tutorial.rs index 44244862..33c0e507 100644 --- a/server/src/entity/tutorial.rs +++ b/server/src/entity/tutorial.rs @@ -144,7 +144,7 @@ impl StepContext<'_> { .game .players .get(&self.player) - .is_some_and(|p| p.item.as_ref().is_some_and(|i| i.kind == item)) + .is_some_and(|p| p.items.iter().flatten().any(|i| i.kind == item)) } pub fn find_demand(&self, item: ItemIndex) -> Option<IVec2> { self.ent @@ -228,7 +228,7 @@ impl StepContext<'_> { .game .players .get(&self.player) - .is_some_and(|p| p.item.as_ref().is_some_and(|i| i.kind == item)) + .is_some_and(|p| p.items.iter().flatten().any(|i| i.kind == item)) { if let Some(pos) = self.find_demand(item) { Err((Some(pos), trm!("s.tutorial.serve"))) diff --git a/server/src/server.rs b/server/src/server.rs index 462e95d4..0889cd71 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -28,8 +28,8 @@ use hurrycurry_client_lib::{Game, Involvement, Item, Player, Tile}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, - Gamedata, ItemLocation, Menu, MessageTimeout, PacketC, PacketS, PlayerClass, PlayerID, Score, - TileIndex, + Gamedata, Hand, ItemLocation, Menu, MessageTimeout, PacketC, PacketS, PlayerClass, PlayerID, + Score, TileIndex, }; use log::{info, warn}; use rand::random; @@ -163,26 +163,28 @@ impl GameServerExt for Game { character: player.character, name: player.name.clone(), }); - if let Some(item) = &player.item { - out.push(PacketC::SetItem { - location: ItemLocation::Player(id), - item: Some(item.kind), - }); - if let Some(Involvement { - player, - position, - speed, - warn, - .. - }) = item.active - { - out.push(PacketC::SetProgress { + for (i, item) in player.items.iter().enumerate() { + if let Some(item) = &item { + out.push(PacketC::SetItem { + location: ItemLocation::Player(id, Hand(i)), + item: Some(item.kind), + }); + if let Some(Involvement { player, - item: ItemLocation::Player(id), position, speed, warn, - }); + .. + }) = item.active + { + out.push(PacketC::SetProgress { + player, + item: ItemLocation::Player(id, Hand(i)), + position, + speed, + warn, + }); + } } } if let Some((message, timeout)) = &player.communicate_persist { @@ -252,7 +254,7 @@ impl GameServerExt for Game { self.players.insert( id, Player { - item: None, + items: (0..self.data.hand_count).map(|_| None).collect(), character, class, movement: MovementBase::new(position), @@ -403,15 +405,18 @@ impl Server { self.game.players_spatial_index.remove_entry(player); - if let Some(item) = p.item { - let pos = p.movement.position.floor().as_ivec2(); - if let Some(tile) = self.game.tiles.get_mut(&pos) { - if tile.item.is_none() { - self.packet_out.push_back(PacketC::SetItem { - location: ItemLocation::Tile(pos), - item: Some(item.kind), - }); - tile.item = Some(item); + // TODO if holding two, one is destroyed + for item in p.items { + if let Some(item) = item { + let pos = p.movement.position.floor().as_ivec2(); + if let Some(tile) = self.game.tiles.get_mut(&pos) { + if tile.item.is_none() { + self.packet_out.push_back(PacketC::SetItem { + location: ItemLocation::Tile(pos), + item: Some(item.kind), + }); + tile.item = Some(item); + } } } } @@ -449,7 +454,7 @@ impl Server { } } } - PacketS::Interact { pos, player } => { + PacketS::Interact { pos, player, hand } => { for e in &mut self.entities { if e.interact( EntityContext { @@ -477,7 +482,9 @@ impl Server { .get_mut(&pid) .ok_or(tre!("s.error.no_player"))?; - let (pos, edge) = match (pos, player.interacting) { + let pos = pos.map(|p| (p, hand)); + + let ((pos, hand), edge) = match (pos, player.interacting) { (None, None) => return Ok(()), // this is silent because of auto release (None, Some(pos)) => (pos, false), (Some(pos), None) => (pos, true), @@ -497,7 +504,7 @@ impl Server { // No going back from here on - player.interacting = if edge { Some(pos) } else { None }; + player.interacting = if edge { Some((pos, hand)) } else { None }; let other_pid = if !self.game.data.is_tile_interactable(tile.kind) { self.game @@ -523,15 +530,18 @@ impl Server { return Err(tre!("s.error.customer_interact")); } + let this_hslot = this.items.get_mut(hand.0).ok_or(tre!("s.error.no_hand"))?; + let other_hslot = other.items.get_mut(hand.0).ok_or(tre!("s.error.no_hand"))?; + interact( &self.game.data, edge, None, Some(pid), - &mut this.item, - ItemLocation::Player(base_pid), - &mut other.item, - ItemLocation::Player(pid), + this_hslot, + ItemLocation::Player(base_pid, hand), + other_hslot, + ItemLocation::Player(pid, hand), &mut self.game.score, &mut self.score_changed, false, @@ -544,6 +554,11 @@ impl Server { .get_mut(&pid) .ok_or(tre!("s.error.no_player"))?; + let hslot = player + .items + .get_mut(hand.0) + .ok_or(tre!("s.error.no_hand"))?; + interact( &self.game.data, edge, @@ -551,8 +566,8 @@ impl Server { Some(pid), &mut tile.item, ItemLocation::Tile(pos), - &mut player.item, - ItemLocation::Player(pid), + hslot, + ItemLocation::Player(pid, hand), &mut self.game.score, &mut self.score_changed, false, @@ -592,14 +607,16 @@ impl Server { timeout, }); } - PacketS::ReplaceHand { item, player } => { + PacketS::ReplaceHand { item, player, hand } => { let pdata = self.game.players.get_mut(&player).ok_or(tre!(""))?; - pdata.item = item.map(|i| Item { - kind: i, - active: None, - }); + if let Some(slot) = pdata.items.get_mut(hand.0) { + *slot = item.map(|i| Item { + kind: i, + active: None, + }); + } self.packet_out.push_back(PacketC::SetItem { - location: ItemLocation::Player(player), + location: ItemLocation::Player(player, hand), item, }) } @@ -665,17 +682,19 @@ impl Server { rot: player.movement.rotation, }); - tick_slot( - dt, - &self.game.data, - &self.gamedata_index, - None, - &mut player.item, - ItemLocation::Player(pid), - &mut self.game.score, - &mut self.score_changed, - &mut self.packet_out, - ); + for (i, item) in player.items.iter_mut().enumerate() { + tick_slot( + dt, + &self.game.data, + &self.gamedata_index, + None, + item, + ItemLocation::Player(pid, Hand(i)), + &mut self.game.score, + &mut self.score_changed, + &mut self.packet_out, + ); + } } let mut players_auto_release = Vec::new(); @@ -686,20 +705,27 @@ impl Server { player.communicate_persist = None; } } - if let Some(pos) = player.interacting { + if let Some((pos, hand)) = player.interacting { if let Some(tile) = self.game.tiles.get(&pos) { if let Some(item) = &tile.item { if let Some(involvement) = &item.active { if involvement.position >= 1. { - players_auto_release.push(*pid); + players_auto_release.push((*pid, hand)); } } } } } } - for player in players_auto_release.drain(..) { - let _ = self.packet_in(PacketS::Interact { pos: None, player }, &mut vec![]); + for (player, hand) in players_auto_release.drain(..) { + let _ = self.packet_in( + PacketS::Interact { + pos: None, + player, + hand, + }, + &mut vec![], + ); } let mut load_map = None; |