diff options
Diffstat (limited to 'server')
-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 |
4 files changed, 89 insertions, 7 deletions
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}"), |