aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-03-22 22:12:40 +0100
committermetamuffin <metamuffin@disroot.org>2026-03-22 22:12:40 +0100
commitfd8525e2307a7b5e356684705e8cdc9c1db1d420 (patch)
tree6946ef81015217ca6629beab48d80e60f485b948
parent9ee90635a0e6b5847c3c39293b1ebaddd344c593 (diff)
downloadhurrycurry-fd8525e2307a7b5e356684705e8cdc9c1db1d420.tar
hurrycurry-fd8525e2307a7b5e356684705e8cdc9c1db1d420.tar.bz2
hurrycurry-fd8525e2307a7b5e356684705e8cdc9c1db1d420.tar.zst
bot spawning as part of game config; /bot now only available with cheats
-rw-r--r--server/data/src/entities.rs3
-rw-r--r--server/data/src/lib.rs5
-rw-r--r--server/protocol/src/lib.rs1
-rw-r--r--server/src/commands.rs61
-rw-r--r--server/src/entity/mod.rs27
-rw-r--r--server/src/scoreboard.rs2
6 files changed, 72 insertions, 27 deletions
diff --git a/server/data/src/entities.rs b/server/data/src/entities.rs
index 7f8be049..28ea7c86 100644
--- a/server/data/src/entities.rs
+++ b/server/data/src/entities.rs
@@ -98,6 +98,9 @@ pub enum EntityDecl {
#[serde(default)]
item_indices: Vec<ItemIndex>,
},
+ Bot {
+ name: String,
+ },
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, ValueEnum)]
diff --git a/server/data/src/lib.rs b/server/data/src/lib.rs
index 3bfb5095..a3c0275d 100644
--- a/server/data/src/lib.rs
+++ b/server/data/src/lib.rs
@@ -232,6 +232,11 @@ pub fn build_gamedata(
}
entities.push(e);
}
+
+ for name in config.bots.clone() {
+ entities.push(EntityDecl::Bot { name });
+ }
+
debug!("{} entities created", entities.len());
filter_demands_and_recipes(&initial_map, &mut demands, &mut recipes);
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs
index d5228fa5..96758c80 100644
--- a/server/protocol/src/lib.rs
+++ b/server/protocol/src/lib.rs
@@ -362,6 +362,7 @@ pub struct GameConfig {
pub map: String,
pub hand_count: Option<usize>,
pub timer: Option<f32>,
+ pub bots: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
diff --git a/server/src/commands.rs b/server/src/commands.rs
index 4ba9fb60..1d260631 100644
--- a/server/src/commands.rs
+++ b/server/src/commands.rs
@@ -16,7 +16,7 @@
*/
use crate::{
- entity::{bot::BotDriver, tutorial::Tutorial},
+ entity::tutorial::Tutorial,
server::{AnnounceState, Server},
};
use anyhow::Result;
@@ -24,8 +24,7 @@ use clap::{Parser, ValueEnum};
use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
use hurrycurry_locale::{TrError, tre, trm};
use hurrycurry_protocol::{
- Character, GameConfig, Hand, ItemLocation, Menu, Message, PacketC, PacketS, PlayerClass,
- PlayerID, VoteSubject,
+ GameConfig, Hand, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, VoteSubject,
};
use std::{fmt::Write, str::FromStr};
@@ -35,7 +34,7 @@ enum Command {
/// Start a new game
#[clap(alias = "s")]
Start {
- /// Gamedata specification
+ /// Map name
#[arg(default_value = "junior")]
map: String,
@@ -44,8 +43,12 @@ enum Command {
hand_count: Option<usize>,
/// Duration in seconds
- #[arg(short = 't', long)]
+ #[arg(short, long)]
timer: Option<f32>,
+
+ /// Add bot by name
+ #[arg(short, long)]
+ bot: Vec<String>,
},
/// Shows the best entries of the scoreboard for this map.
#[clap(alias = "top", alias = "top5")]
@@ -80,8 +83,6 @@ enum Command {
#[arg(short, long)]
unready: bool,
},
- #[clap(alias = "summon", alias = "bot")]
- CreateBot { algo: String, name: Option<String> },
/// Reload the current map
#[clap(alias = "r")]
Reload,
@@ -98,6 +99,10 @@ enum Command {
message_id: String,
arguments: Vec<String>,
},
+ /// Add bots to the current game (CHEAT)
+ #[cfg(feature = "cheats")]
+ #[clap(alias = "summon", alias = "bot")]
+ CreateBot { algo: String, name: Option<String> },
/// Set your players hand item (CHEAT)
#[cfg(feature = "cheats")]
#[clap(alias = "give")]
@@ -146,7 +151,14 @@ impl Server {
map,
hand_count,
timer,
+ bot,
} => {
+ for b in &bot {
+ ALGO_CONSTRUCTORS
+ .iter()
+ .find(|(name, _)| *name == b)
+ .ok_or(tre!("s.error.algo_not_found", s = b.to_string()))?;
+ }
self.packet_in(
replies,
PacketS::InitiateVote {
@@ -156,6 +168,7 @@ impl Server {
hand_count,
map,
timer,
+ bots: bot,
},
},
},
@@ -243,22 +256,6 @@ impl Server {
},
)?;
}
- Command::CreateBot { algo, name } => {
- let (aname, cons) = ALGO_CONSTRUCTORS
- .iter()
- .find(|(name, _)| *name == algo.as_str())
- .ok_or(tre!("s.error.algo_not_found", s = algo))?;
- self.entities.push(Box::new(BotDriver::new(
- format!("{}-bot", name.unwrap_or((*aname).to_owned())),
- Character {
- color: 0,
- hairstyle: 0,
- headwear: 0,
- },
- PlayerClass::Bot,
- cons,
- )));
- }
Command::Scoreboard { map, text } => {
let mapname = map.as_ref().unwrap_or(&self.game.data.current_map);
let mapname_pretty = &self
@@ -380,6 +377,24 @@ impl Server {
});
}
#[cfg(feature = "cheats")]
+ Command::CreateBot { algo, name } => {
+ let (aname, cons) = ALGO_CONSTRUCTORS
+ .iter()
+ .find(|(name, _)| *name == algo.as_str())
+ .ok_or(tre!("s.error.algo_not_found", s = algo))?;
+ self.entities
+ .push(Box::new(crate::entity::bot::BotDriver::new(
+ format!("{}-bot", name.unwrap_or((*aname).to_owned())),
+ hurrycurry_protocol::Character {
+ color: 0,
+ hairstyle: 0,
+ headwear: 0,
+ },
+ hurrycurry_protocol::PlayerClass::Bot,
+ cons,
+ )));
+ }
+ #[cfg(feature = "cheats")]
Command::SetItem { name } => {
use hurrycurry_game_core::Item;
use hurrycurry_protocol::{Hand, ItemLocation};
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
index 709028e8..b17fbfb9 100644
--- a/server/src/entity/mod.rs
+++ b/server/src/entity/mod.rs
@@ -33,8 +33,8 @@ pub mod tutorial;
use crate::{
entity::{
- ctf_minigame::CtfMinigame, demand_sink::DemandSink, pedestrians::Pedestrians,
- player_portal_pair::PlayerPortalPair, tag_minigame::TagMinigame,
+ bot::BotDriver, ctf_minigame::CtfMinigame, demand_sink::DemandSink,
+ pedestrians::Pedestrians, player_portal_pair::PlayerPortalPair, tag_minigame::TagMinigame,
},
scoreboard::ScoreboardStore,
};
@@ -44,10 +44,13 @@ use campaign::{Gate, Map};
use conveyor::Conveyor;
use customers::Customers;
use environment_effect::{EnvironmentController, EnvironmentEffectController};
+use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
use hurrycurry_data::{PrivateGamedata, entities::EntityDecl};
use hurrycurry_game_core::Game;
use hurrycurry_locale::TrError;
-use hurrycurry_protocol::{Character, GameConfig, ItemLocation, PacketC, PacketS, PlayerID};
+use hurrycurry_protocol::{
+ Character, GameConfig, ItemLocation, PacketC, PacketS, PlayerClass, PlayerID,
+};
use item_portal::ItemPortal;
use player_portal::PlayerPortal;
use std::{
@@ -187,5 +190,23 @@ pub fn construct_entity(decl: &EntityDecl) -> DynEntity {
neutral_tile,
out_tile,
} => Box::new(PlayerPortalPair::new(a, b, in_tile, neutral_tile, out_tile)),
+
+ EntityDecl::Bot { name: search_name } => {
+ let (name, cons) = ALGO_CONSTRUCTORS
+ .iter()
+ .find(|(name, _)| *name == search_name.as_str())
+ .unwrap_or(&ALGO_CONSTRUCTORS[0]);
+
+ Box::new(BotDriver::new(
+ format!("{name}-bot"),
+ Character {
+ color: 0,
+ hairstyle: 0,
+ headwear: 0,
+ },
+ PlayerClass::Bot,
+ cons,
+ ))
+ }
}
}
diff --git a/server/src/scoreboard.rs b/server/src/scoreboard.rs
index 293e082c..635e347c 100644
--- a/server/src/scoreboard.rs
+++ b/server/src/scoreboard.rs
@@ -83,6 +83,6 @@ pub trait GameConfigScoreboardCheckExt {
}
impl GameConfigScoreboardCheckExt for GameConfig {
fn is_valid_for_scoreboard(&self) -> bool {
- self.hand_count.is_none() && self.timer.is_none()
+ self.hand_count.is_none() && self.timer.is_none() && self.bots.is_empty()
}
}