diff options
-rw-r--r-- | data/maps/bar.yaml | 2 | ||||
-rw-r--r-- | data/maps/campaign/lobby.yaml | 3 | ||||
-rw-r--r-- | data/maps/debug.yaml | 1 | ||||
-rw-r--r-- | data/maps/depot.yaml | 1 | ||||
-rw-r--r-- | data/maps/factory.yaml | 1 | ||||
-rw-r--r-- | data/maps/junior.yaml | 2 | ||||
-rw-r--r-- | data/maps/lobby.yaml | 2 | ||||
-rw-r--r-- | data/maps/senior.yaml | 2 | ||||
-rw-r--r-- | data/maps/sushibar.yaml | 2 | ||||
-rw-r--r-- | server/src/commands.rs | 65 | ||||
-rw-r--r-- | server/src/entity/book.rs | 21 | ||||
-rw-r--r-- | server/src/entity/mod.rs | 8 | ||||
-rw-r--r-- | server/src/state.rs | 2 | ||||
-rw-r--r-- | test-client/main.ts | 9 |
14 files changed, 105 insertions, 16 deletions
diff --git a/data/maps/bar.yaml b/data/maps/bar.yaml index 3f5674f1..7c3706ba 100644 --- a/data/maps/bar.yaml +++ b/data/maps/bar.yaml @@ -68,6 +68,8 @@ items: entities: - !customers +tile_entities: + "b": !book chef_spawn: "~" customer_spawn: "!" diff --git a/data/maps/campaign/lobby.yaml b/data/maps/campaign/lobby.yaml index fbaa96ed..39dde694 100644 --- a/data/maps/campaign/lobby.yaml +++ b/data/maps/campaign/lobby.yaml @@ -26,7 +26,7 @@ map: - "''''''''''''''*'" - "'*'''''''''''*''" - "''*'''''''''''*'" - - "'''''''''''''*''" + - "''''b''''''''*''" - "'*'''''''''''*''" - "''*'''*'''*''*''" - "'*''*''**'''**''" @@ -52,6 +52,7 @@ customer_spawn: "!" tile_entities: "1": !map { name: "campaign/01" } "a": !gate { condition: !all [!stars [sophomore, 2], !stars [paris, 1]] } + "b": !book walkable: - floor diff --git a/data/maps/debug.yaml b/data/maps/debug.yaml index f3472e22..e292a398 100644 --- a/data/maps/debug.yaml +++ b/data/maps/debug.yaml @@ -112,6 +112,7 @@ tile_entities: "L": !conveyor { dir: [1, 0], filter: leek } "ö": !conveyor { dir: [1, 0], filter: tomato-soup-pot } "ü": !conveyor { dir: [1, 0], filter: tomato-soup-plate } + "b": !book chef_spawn: "~" customer_spawn: "!" diff --git a/data/maps/depot.yaml b/data/maps/depot.yaml index 8b730859..9a9f81c2 100644 --- a/data/maps/depot.yaml +++ b/data/maps/depot.yaml @@ -77,6 +77,7 @@ tile_entities: "<": !conveyor { dir: [-1, 0] } "v": !conveyor { dir: [0, 1] } "^": !conveyor { dir: [0, -1] } + "b": !book items: "S": pot diff --git a/data/maps/factory.yaml b/data/maps/factory.yaml index 176ef716..c941ce00 100644 --- a/data/maps/factory.yaml +++ b/data/maps/factory.yaml @@ -70,6 +70,7 @@ tile_entities: "<": !conveyor { dir: [-1, 0] } "v": !conveyor { dir: [0, 1] } "^": !conveyor { dir: [0, -1] } + "b": !book items: "S": pot diff --git a/data/maps/junior.yaml b/data/maps/junior.yaml index 9acd4dd1..f8b316ce 100644 --- a/data/maps/junior.yaml +++ b/data/maps/junior.yaml @@ -71,6 +71,8 @@ customer_spawn: "!" entities: - !customers +tile_entities: + "b": !book walkable: - door diff --git a/data/maps/lobby.yaml b/data/maps/lobby.yaml index 17d8dd1d..d1d3aa8e 100644 --- a/data/maps/lobby.yaml +++ b/data/maps/lobby.yaml @@ -47,6 +47,8 @@ tiles: items: "t": tomato-soup-plate "T": bread-slice-plate +tile_entities: + "b": !book chef_spawn: "~" customer_spawn: "!" diff --git a/data/maps/senior.yaml b/data/maps/senior.yaml index b10b3ab8..67442916 100644 --- a/data/maps/senior.yaml +++ b/data/maps/senior.yaml @@ -68,6 +68,8 @@ items: entities: - !customers +tile_entities: + "b": !book chef_spawn: "~" customer_spawn: "!" diff --git a/data/maps/sushibar.yaml b/data/maps/sushibar.yaml index e086fd37..f244d92c 100644 --- a/data/maps/sushibar.yaml +++ b/data/maps/sushibar.yaml @@ -74,6 +74,8 @@ items: entities: - !customers +tile_entities: + "b": !book chef_spawn: "~" customer_spawn: "!" diff --git a/server/src/commands.rs b/server/src/commands.rs index e2d40f05..96d9ab75 100644 --- a/server/src/commands.rs +++ b/server/src/commands.rs @@ -19,8 +19,8 @@ use crate::{entity::bot::BotDriver, server::Server}; use anyhow::{anyhow, bail, Result}; use clap::{Parser, ValueEnum}; use hurrycurry_bot::algos::ALGO_CONSTRUCTORS; -use hurrycurry_protocol::{Message, PacketC, PlayerID}; -use std::time::Duration; +use hurrycurry_protocol::{Menu, Message, PacketC, PlayerID}; +use std::{fmt::Write, time::Duration}; #[derive(Parser)] #[clap(multicall = true)] @@ -34,6 +34,17 @@ enum Command { #[arg(default_value = "420")] timer: u64, }, + /// Shows the best entries of the scoreboard for this map. + #[clap(alias = "top5")] + Top { + /// Name of the map, default: current + map: Option<String>, + }, + #[clap(alias = "mapinfo")] + Info { + /// Name of the map, default: current + map: Option<String>, + }, /// Abort the current game End, /// Download recipe/map's source declaration @@ -57,6 +68,8 @@ enum Command { /// Reload the current map #[clap(alias = "r")] Reload, + /// Shows the recipe book + Book, } #[derive(ValueEnum, Clone)] @@ -66,7 +79,12 @@ enum DownloadType { } impl Server { - pub async fn handle_command_parse(&mut self, player: PlayerID, command: &str) -> Result<()> { + pub async fn handle_command_parse( + &mut self, + player: PlayerID, + command: &str, + ) -> Result<Vec<PacketC>> { + let mut replies = Vec::new(); for line in command.split("\n") { self.handle_command( player, @@ -75,12 +93,18 @@ impl Server { .ok_or(anyhow!("quoting invalid"))? .into_iter(), )?, + &mut replies, ) .await?; } - Ok(()) + Ok(replies) } - async fn handle_command(&mut self, player: PlayerID, command: Command) -> Result<()> { + async fn handle_command( + &mut self, + player: PlayerID, + command: Command, + replies: &mut Vec<PacketC>, + ) -> Result<()> { match command { Command::Start { spec, timer } => { let data = self.index.generate(&spec).await?; @@ -113,6 +137,7 @@ impl Server { Command::ReloadIndex => { self.index.reload().await?; } + Command::Book => replies.push(PacketC::Menu(Menu::Book)), Command::Download { r#type, name } => { let source = match r#type { DownloadType::Map => self.index.read_map(&name).await, @@ -162,6 +187,36 @@ impl Server { algo, ))); } + Command::Top { map } => { + let mapname = map.as_ref().unwrap_or(&self.game.data.current_map); + if let Some(board) = self.scoreboard.get(mapname) { + let mut o = format!("Scoreboard for {}:\n", mapname); + for (i, entry) in board.best.iter().take(5).enumerate() { + writeln!( + o, + " {i}. {} points by {}", + entry.score.points, + entry.players.clone().join(", ") + ) + .unwrap(); + } + bail!("{o}"); + } + } + Command::Info { map } => { + let mapname = map.as_ref().unwrap_or(&self.game.data.current_map); + let info = self + .index + .maps + .get(mapname) + .ok_or(anyhow!("no information available"))?; + bail!( + "{:?}\nRecommended player count: {}\nDifficulty: {}", + info.name, + info.difficulty, + info.players + ) + } } Ok(()) } diff --git a/server/src/entity/book.rs b/server/src/entity/book.rs new file mode 100644 index 00000000..3787ffaa --- /dev/null +++ b/server/src/entity/book.rs @@ -0,0 +1,21 @@ +use super::{Entity, EntityContext}; +use anyhow::Result; +use hurrycurry_protocol::{glam::IVec2, Menu, PacketC, PlayerID}; + +#[derive(Debug, Clone)] +pub struct Book(pub IVec2); + +impl Entity for Book { + fn interact( + &mut self, + c: EntityContext<'_>, + pos: Option<IVec2>, + _player: PlayerID, + ) -> Result<bool> { + if pos == Some(self.0) { + c.packet_out.push_back(PacketC::Menu(Menu::Book)); + return Ok(true); + } + Ok(false) + } +} diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index c8424934..30a5da55 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -15,6 +15,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +pub mod book; pub mod bot; pub mod campaign; pub mod conveyor; @@ -25,6 +26,7 @@ pub mod player_portal; use crate::{data::ItemTileRegistry, scoreboard::ScoreboardStore}; use anyhow::{anyhow, Result}; +use book::Book; use campaign::{Gate, GateCondition, Map}; use conveyor::Conveyor; use customers::Customers; @@ -53,7 +55,9 @@ pub struct EntityContext<'a> { } pub trait Entity { - fn tick(&mut self, c: EntityContext<'_>) -> Result<()>; + fn tick(&mut self, _c: EntityContext<'_>) -> Result<()> { + Ok(()) + } fn destructor(&mut self, _c: EntityContext<'_>) {} fn interact( &mut self, @@ -117,6 +121,7 @@ pub enum EntityDecl { location: Option<IVec2>, condition: GateCondition, }, + Book, } pub fn construct_entity( @@ -125,6 +130,7 @@ pub fn construct_entity( reg: &ItemTileRegistry, ) -> Result<DynEntity> { Ok(match decl.to_owned() { + EntityDecl::Book => Box::new(Book(pos.ok_or(anyhow!("book is tile entity"))?)), EntityDecl::ItemPortal { from, to } => Box::new(ItemPortal { from: from .or(pos) diff --git a/server/src/state.rs b/server/src/state.rs index 5e0302b2..e368317c 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -55,7 +55,7 @@ impl Server { player, } if let Some(command) = text.strip_prefix("/") => { match self.handle_command_parse(*player, command).await { - Ok(()) => return Ok(vec![]), + Ok(packets) => return Ok(packets), Err(e) => { return Ok(vec![PacketC::ServerMessage { text: format!("{e}"), diff --git a/test-client/main.ts b/test-client/main.ts index c2843192..6ed3c6e1 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -248,7 +248,7 @@ function packet(p: PacketC) { case "menu": switch (p.menu) { case "book": open("https://s.metamuffin.org/static/hurrycurry/book.pdf"); break - case "score": alert("Your score: " + JSON.stringify(p.data)); break + case "score": global_message = { timeout: { initial: 5, remaining: 5 }, inner: { text: `Score: ${JSON.stringify(p.data, null, 4)}` }, anim_position: { x: 0, y: 0 }, anim_size: 0 }; break default: console.warn("unknown menu"); } break; @@ -310,13 +310,6 @@ export function get_interact_target(): V2 | undefined { function set_interact(edge: boolean) { if (edge) interacting = get_interact_target() - if (edge && interacting) { - const kind = tiles.get([interacting.x, interacting.y].toString())?.kind; - if (kind && data.tile_names[kind] == "book") { - open("https://s.metamuffin.org/static/hurrycurry-1.2/book.pdf") - return - } - } if (interacting) send({ player: my_id, type: "interact", pos: edge ? [interacting.x, interacting.y] : undefined }) if (!edge) interacting = undefined } |