aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-10-15 16:30:20 +0200
committermetamuffin <metamuffin@disroot.org>2025-10-15 16:30:20 +0200
commita47a61ec1ffb0d597783870a053cbe0760544301 (patch)
treed2516f94445eb1f27614bf5618cbc5e4298eee7b /server
parentf9be1a87bbdfdfbeb316cc335b7a2dbbf42b06f2 (diff)
downloadhurrycurry-a47a61ec1ffb0d597783870a053cbe0760544301.tar
hurrycurry-a47a61ec1ffb0d597783870a053cbe0760544301.tar.bz2
hurrycurry-a47a61ec1ffb0d597783870a053cbe0760544301.tar.zst
Add tag minigame (hidden map + entity)
Diffstat (limited to 'server')
-rw-r--r--server/data/src/entities.rs4
-rw-r--r--server/data/src/lib.rs10
-rw-r--r--server/data/src/registry.rs6
-rw-r--r--server/src/entity/mod.rs7
-rw-r--r--server/src/entity/tag_minigame.rs96
5 files changed, 119 insertions, 4 deletions
diff --git a/server/data/src/entities.rs b/server/data/src/entities.rs
index 04d4e1c5..fdd451a0 100644
--- a/server/data/src/entities.rs
+++ b/server/data/src/entities.rs
@@ -70,6 +70,10 @@ pub enum EntityDecl {
speed: Option<f32>,
points: Vec<Vec2>,
},
+ TagMinigame {
+ #[serde(default = "default_item")]
+ tag_item: ItemIndex,
+ },
}
#[derive(Debug, Clone, Deserialize, Serialize)]
diff --git a/server/data/src/lib.rs b/server/data/src/lib.rs
index 78ec21b3..445e59b9 100644
--- a/server/data/src/lib.rs
+++ b/server/data/src/lib.rs
@@ -214,8 +214,14 @@ fn build_data(
}
for mut e in map_in.entities.clone() {
- if let EntityDecl::Customers { unknown_order, .. } = &mut e {
- *unknown_order = reg.register_item("unknown-order".to_owned())
+ match &mut e {
+ EntityDecl::Customers { unknown_order, .. } => {
+ *unknown_order = reg.register_item("unknown-order".to_owned())
+ }
+ EntityDecl::TagMinigame { tag_item } => {
+ *tag_item = reg.register_item("lettuce".to_owned())
+ }
+ _ => (),
}
entities.push(e);
}
diff --git a/server/data/src/registry.rs b/server/data/src/registry.rs
index e7dc7f77..9d513c90 100644
--- a/server/data/src/registry.rs
+++ b/server/data/src/registry.rs
@@ -80,8 +80,12 @@ pub(crate) fn filter_unused_tiles_and_items(data: &mut Gamedata, serverdata: &mu
}
for e in &serverdata.entity_decls {
match e {
- EntityDecl::Gate { blocker_tile: blocker, .. } => used_tiles.insert(*blocker),
+ EntityDecl::Gate {
+ blocker_tile: blocker,
+ ..
+ } => used_tiles.insert(*blocker),
EntityDecl::Customers { unknown_order, .. } => used_items.insert(*unknown_order),
+ EntityDecl::TagMinigame { tag_item, .. } => used_items.insert(*tag_item),
_ => false,
};
}
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
index 6418d0c7..01cb9462 100644
--- a/server/src/entity/mod.rs
+++ b/server/src/entity/mod.rs
@@ -24,10 +24,14 @@ pub mod environment_effect;
pub mod item_portal;
pub mod pedestrians;
pub mod player_portal;
+pub mod tag_minigame;
pub mod tram;
pub mod tutorial;
-use crate::{entity::pedestrians::Pedestrians, scoreboard::ScoreboardStore};
+use crate::{
+ entity::{pedestrians::Pedestrians, tag_minigame::TagMinigame},
+ scoreboard::ScoreboardStore,
+};
use anyhow::Result;
use book::Book;
use campaign::{Gate, Map};
@@ -82,6 +86,7 @@ pub trait Entity: Any {
pub fn construct_entity(decl: &EntityDecl) -> DynEntity {
match decl.to_owned() {
+ EntityDecl::TagMinigame { tag_item } => Box::new(TagMinigame::new(tag_item)),
EntityDecl::Book { pos } => Box::new(Book(pos)),
EntityDecl::ItemPortal { from, to } => Box::new(ItemPortal { from, to }),
EntityDecl::PlayerPortal { from, to } => Box::new(PlayerPortal { from, to }),
diff --git a/server/src/entity/tag_minigame.rs b/server/src/entity/tag_minigame.rs
new file mode 100644
index 00000000..02c3726f
--- /dev/null
+++ b/server/src/entity/tag_minigame.rs
@@ -0,0 +1,96 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, version 3 of the License only.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+*/
+use super::{Entity, EntityContext};
+use anyhow::Result;
+use hurrycurry_client_lib::Item;
+use hurrycurry_protocol::{Hand, ItemIndex, ItemLocation, Message, PacketC, PlayerID};
+use std::{collections::HashMap, fmt::Write};
+
+#[derive(Debug, Clone)]
+pub struct TagMinigame {
+ scores: HashMap<PlayerID, f32>,
+ report_cooldown: f32,
+ init_done: bool,
+ tag_item: ItemIndex,
+}
+impl TagMinigame {
+ pub fn new(tag_item: ItemIndex) -> Self {
+ Self {
+ init_done: false,
+ report_cooldown: 0.,
+ scores: HashMap::new(),
+ tag_item,
+ }
+ }
+}
+impl Entity for TagMinigame {
+ fn tick(&mut self, c: EntityContext) -> Result<()> {
+ if !self.init_done {
+ self.init_done = true;
+ // Hand out item every but one player (not random yet)
+ for (id, player) in c.game.players.iter_mut().skip(1) {
+ if let Some(slot) = player.items.get_mut(0) {
+ *slot = Some(Item {
+ active: None,
+ kind: self.tag_item,
+ });
+ c.packet_out.push_back(PacketC::SetItem {
+ location: ItemLocation::Player(*id, Hand(0)),
+ item: Some(self.tag_item),
+ });
+ }
+ }
+ }
+
+ // Award points to players with the item
+ for (&id, player) in &c.game.players {
+ if let Some(slot) = player.items.get(0)
+ && let Some(item) = slot
+ && item.kind == self.tag_item
+ {
+ *self.scores.entry(id).or_default() += c.dt
+ }
+ }
+
+ // Send out a scoreboard message every 0.1s
+ self.report_cooldown -= c.dt;
+ if self.report_cooldown < 0. {
+ self.report_cooldown = 0.1;
+ let mut scores = self
+ .scores
+ .iter()
+ .map(|(x, y)| ((y * 10.) as i64, *x))
+ .collect::<Vec<_>>();
+ scores.sort_by_key(|(s, _)| -s);
+
+ let mut o = String::new();
+ writeln!(o, "Tag Minigame — Try to not have your lettuce stolen!").unwrap();
+ for ((score, player), rank) in scores.into_iter().zip(1..) {
+ let name = c.game.players.get(&player).map_or("unknown", |p| &p.name);
+ writeln!(o, "{rank:>2} {score:>4} {name}").unwrap();
+ }
+ o.pop(); // newline
+ c.packet_out.push_back(PacketC::ServerMessage {
+ error: false,
+ message: Message::Text(o),
+ });
+ }
+
+ Ok(())
+ }
+}