aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/bot/src/algos/customer.rs22
-rw-r--r--server/game-core/src/interaction.rs10
-rw-r--r--server/protocol/src/lib.rs17
-rw-r--r--server/src/commands.rs14
-rw-r--r--server/src/entity/ctf_minigame.rs13
-rw-r--r--server/src/entity/demand_sink.rs12
-rw-r--r--server/src/lib.rs9
-rw-r--r--server/src/server.rs10
-rw-r--r--server/src/state.rs4
-rw-r--r--test-client/main.ts5
-rw-r--r--test-client/map/effect.ts85
-rw-r--r--test-client/protocol.ts7
12 files changed, 160 insertions, 48 deletions
diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs
index 81a813e7..cc9f9c60 100644
--- a/server/bot/src/algos/customer.rs
+++ b/server/bot/src/algos/customer.rs
@@ -22,7 +22,8 @@ use crate::{
};
use hurrycurry_game_core::Game;
use hurrycurry_protocol::{
- DemandIndex, Hand, ItemLocation, Message, PacketS, PlayerClass, PlayerID, Score, TileIndex,
+ DemandIndex, Effect, Hand, ItemLocation, Message, PacketS, PlayerClass, PlayerID, Score,
+ TileIndex,
glam::{IVec2, Vec2},
};
use log::debug;
@@ -213,14 +214,19 @@ impl CustomerState {
player: me,
pin: Some(false),
});
+ let points = -1;
out.push(PacketS::ApplyScore(Score {
- points: -1,
+ points,
demands_failed: 1,
..Default::default()
}));
out.push(PacketS::Effect {
- name: "angry".to_string(),
- player: me,
+ effect: Effect::Angry,
+ location: ItemLocation::Player(me, Hand(0)),
+ });
+ out.push(PacketS::Effect {
+ effect: Effect::Points { amount: points },
+ location: ItemLocation::Player(me, Hand(0)),
});
*self = CustomerState::Exiting { path };
return;
@@ -309,8 +315,12 @@ impl CustomerState {
pin: Some(true),
});
out.push(PacketS::Effect {
- name: "satisfied".to_string(),
- player: me,
+ effect: Effect::Satisfied,
+ location: ItemLocation::Player(me, Hand(0)),
+ });
+ out.push(PacketS::Effect {
+ effect: Effect::Points { amount: points },
+ location: ItemLocation::Player(me, Hand(0)),
});
out.push(PacketS::Interact {
target: Some(ItemLocation::Tile(table)),
diff --git a/server/game-core/src/interaction.rs b/server/game-core/src/interaction.rs
index 1ca451d4..fd47a7f5 100644
--- a/server/game-core/src/interaction.rs
+++ b/server/game-core/src/interaction.rs
@@ -17,7 +17,7 @@
*/
use crate::{Game, Involvement, Item};
use hurrycurry_locale::{TrError, tre};
-use hurrycurry_protocol::{ItemLocation, PacketC, Recipe};
+use hurrycurry_protocol::{Effect, ItemLocation, PacketC, Recipe};
use log::info;
use std::collections::{BTreeSet, VecDeque};
@@ -224,7 +224,13 @@ impl Game {
}
self.score.points += pd;
self.score.instant_recipes += 1;
- self.events.push_back(PacketC::Score(self.score.clone()));
+ if *pd != 0 {
+ self.events.push_back(PacketC::Effect {
+ effect: Effect::Points { amount: *pd },
+ location: this_loc,
+ });
+ self.events.push_back(PacketC::Score(self.score.clone()));
+ }
produce_events(
&mut self.events,
this_had_item,
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs
index d24b4973..9dd7e2f4 100644
--- a/server/protocol/src/lib.rs
+++ b/server/protocol/src/lib.rs
@@ -183,8 +183,8 @@ pub enum PacketS {
/// For internal use only (customers)
#[serde(skip)]
Effect {
- player: PlayerID,
- name: String,
+ effect: Effect,
+ location: ItemLocation,
},
/// Used internally and only used when built with debug_events
@@ -287,8 +287,9 @@ pub enum PacketC {
message: Option<Message>,
timeout: Option<MessageTimeout>,
},
- Effect2 {
- name: String,
+ Effect {
+ #[serde(flatten)]
+ effect: Effect,
location: ItemLocation,
},
ServerMessage {
@@ -369,6 +370,14 @@ pub struct GameConfig {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case", tag = "effect")]
+pub enum Effect {
+ Satisfied,
+ Angry,
+ Points { amount: i64 },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "menu", content = "data")]
pub enum Menu {
Score(Score),
diff --git a/server/src/commands.rs b/server/src/commands.rs
index 1d260631..05384849 100644
--- a/server/src/commands.rs
+++ b/server/src/commands.rs
@@ -23,9 +23,7 @@ use anyhow::Result;
use clap::{Parser, ValueEnum};
use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
use hurrycurry_locale::{TrError, tre, trm};
-use hurrycurry_protocol::{
- GameConfig, Hand, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, VoteSubject,
-};
+use hurrycurry_protocol::{GameConfig, Menu, Message, PacketC, PacketS, PlayerID, VoteSubject};
use std::{fmt::Write, str::FromStr};
#[derive(Parser)]
@@ -68,8 +66,6 @@ enum Command {
},
/// Abort the current game
End,
- /// Send an effect
- Effect { name: String },
/// Send an item message
Item { name: String },
/// Send a tile message
@@ -216,14 +212,6 @@ impl Server {
Command::Book => {
replies.push(PacketC::Menu(Menu::Book(self.priv_gamedata.book.clone())))
}
- Command::Effect { name } => {
- self.broadcast
- .send(PacketC::Effect2 {
- name,
- location: ItemLocation::Player(player, Hand(0)),
- })
- .ok();
- }
Command::Item { name } => {
let item = self
.game
diff --git a/server/src/entity/ctf_minigame.rs b/server/src/entity/ctf_minigame.rs
index 03261e41..d2b23aee 100644
--- a/server/src/entity/ctf_minigame.rs
+++ b/server/src/entity/ctf_minigame.rs
@@ -18,6 +18,7 @@
use super::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_locale::TrError;
+use hurrycurry_protocol::{Effect, Hand};
use hurrycurry_protocol::{ItemIndex, ItemLocation, Message, PacketC, PlayerID, glam::IVec2};
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
@@ -36,11 +37,11 @@ struct TeamData {
}
impl TeamData {
- fn effect(&self, c: &mut EntityContext, name: &str) -> Result<(), TrError> {
+ fn effect(&self, c: &mut EntityContext, effect: Effect) -> Result<(), TrError> {
for pid in self.players.iter() {
- c.packet_out.push_back(PacketC::Effect2 {
- name: name.to_string(),
- location: ItemLocation::Player(*pid, hurrycurry_protocol::Hand(0)),
+ c.packet_out.push_back(PacketC::Effect {
+ effect: effect.clone(),
+ location: ItemLocation::Player(*pid, Hand(0)),
});
}
Ok(())
@@ -257,14 +258,14 @@ impl Entity for CtfMinigame {
.is_some_and(|a| a == from_team_idx)
{
for i in self.player_flags(&mut c, from)? {
- self.teams.get(&i).unwrap().effect(&mut c, "angry")?;
+ self.teams.get(&i).unwrap().effect(&mut c, Effect::Angry)?;
self.return_flag(&mut c, i)?;
self.teams.get_mut(&from_team_idx).unwrap().score += 10;
}
self.teams
.get(&from_team_idx)
.unwrap()
- .effect(&mut c, "satisfied")?;
+ .effect(&mut c, Effect::Satisfied)?;
self.send_table(&mut c)?;
}
Ok(true)
diff --git a/server/src/entity/demand_sink.rs b/server/src/entity/demand_sink.rs
index 6fcfa643..45244e91 100644
--- a/server/src/entity/demand_sink.rs
+++ b/server/src/entity/demand_sink.rs
@@ -19,7 +19,7 @@
use crate::entity::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_locale::TrError;
-use hurrycurry_protocol::{ItemLocation, PacketC, glam::IVec2};
+use hurrycurry_protocol::{Effect, ItemLocation, PacketC, glam::IVec2};
pub struct DemandSink {
pub pos: IVec2,
@@ -36,8 +36,14 @@ impl Entity for DemandSink {
c.game.score.demands_completed += 1;
c.game.score.points += demand.points;
*c.score_changed = true;
- c.packet_out.push_back(PacketC::Effect2 {
- name: "satisfied".to_string(),
+ c.packet_out.push_back(PacketC::Effect {
+ effect: Effect::Satisfied,
+ location: ItemLocation::Tile(self.pos),
+ });
+ c.packet_out.push_back(PacketC::Effect {
+ effect: Effect::Points {
+ amount: demand.points,
+ },
location: ItemLocation::Tile(self.pos),
});
} else {
diff --git a/server/src/lib.rs b/server/src/lib.rs
index b5dec60d..b0e93d05 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -62,7 +62,7 @@ mod test {
ConnectionID,
server::{Server, ServerConfig},
};
- use hurrycurry_protocol::{Character, GameConfig, PacketS, PlayerClass, PlayerID};
+ use hurrycurry_protocol::{Character, GameConfig, Hand, PacketS, PlayerClass, PlayerID};
use tokio::sync::broadcast;
fn server() -> Server {
@@ -122,12 +122,13 @@ mod test {
let mut s = server();
for (conn, p) in [
- PacketS::Effect {
+ PacketS::Leave {
player: PlayerID(0),
- name: "test".to_owned(),
},
- PacketS::Leave {
+ PacketS::Interact {
+ hand: Hand(0),
player: PlayerID(0),
+ target: None,
},
PacketS::ReplayTick { dt: 1. },
]
diff --git a/server/src/server.rs b/server/src/server.rs
index 9e91ff9c..9066475c 100644
--- a/server/src/server.rs
+++ b/server/src/server.rs
@@ -30,7 +30,7 @@ use hurrycurry_locale::{
tre, trm,
};
use hurrycurry_protocol::{
- Character, GameConfig, Gamedata, Hand, ItemLocation, Menu, MessageTimeout, PacketC, PacketS,
+ Character, GameConfig, Gamedata, ItemLocation, Menu, MessageTimeout, PacketC, PacketS,
PlayerClass, PlayerID, Score, Serverdata, VoteSubject, glam::Vec2, movement::MovementBase,
};
use log::{info, warn};
@@ -353,11 +353,9 @@ impl Server {
self.last_movement_update.remove(&player);
self.vote_state.leave(player);
}
- PacketS::Effect { player, name } => {
- self.packet_out.push_back(PacketC::Effect2 {
- name,
- location: ItemLocation::Player(player, Hand(0)),
- });
+ PacketS::Effect { effect, location } => {
+ self.packet_out
+ .push_back(PacketC::Effect { effect, location });
}
PacketS::Movement {
pos,
diff --git a/server/src/state.rs b/server/src/state.rs
index 12f6f6e4..150214ba 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -331,12 +331,12 @@ fn get_packet_player(packet: &PacketS) -> Option<PlayerID> {
| PacketS::Communicate { player, .. }
| PacketS::ReplaceHand { player, .. }
| PacketS::InitiateVote { player, .. }
- | PacketS::CastVote { player, .. }
- | PacketS::Effect { player, .. } => Some(*player),
+ | PacketS::CastVote { player, .. } => Some(*player),
PacketS::Join { .. }
| PacketS::Idle { .. }
| PacketS::Ready
| PacketS::ApplyScore(_)
+ | PacketS::Effect { .. }
| PacketS::ReplayTick { .. }
| PacketS::Debug(_)
| PacketS::Keepalive => None,
diff --git a/test-client/main.ts b/test-client/main.ts
index 0926ad0e..aea1713a 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -38,6 +38,7 @@ import { GridLayer } from "./map/mod.ts";
import { SConnectScreen } from "./connect_screen.ts";
import { SAnnouncement } from "./announcement.ts";
import { load_sprites, SSprites } from "./map/sprites.ts";
+import { SEffect } from "./map/effect.ts";
let ctx: CanvasRenderingContext2D;
export let canvas: HTMLCanvasElement;
@@ -99,13 +100,14 @@ const pinned_messages = new SPinnedMessages(sprites, players)
const connect_screen = new SConnectScreen()
export const camera = new SCamera(player_controller)
const announcement = new SAnnouncement()
+const effect = new SEffect(map_items)
const systems: System[] = [
vote, chat, debug_events, server_hints, map_tiles,
map_items, quick_commands, players, player_controller,
camera, particles, server_message, score_display,
pinned_messages, connect_screen, players_overlay,
- announcement, sprites,
+ announcement, sprites, effect,
]
const map_layers = [
new GridLayer(),
@@ -116,6 +118,7 @@ const map_layers = [
player_controller,
server_hints,
players_overlay,
+ effect,
debug_events,
]
const ui_layers = [
diff --git a/test-client/map/effect.ts b/test-client/map/effect.ts
new file mode 100644
index 00000000..5724c0ba
--- /dev/null
+++ b/test-client/map/effect.ts
@@ -0,0 +1,85 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 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/>.
+
+*/
+
+import { int, PacketC } from "../protocol.ts";
+import { System } from "../system.ts";
+import { V2 } from "../util.ts";
+import { SMapItems } from "./item.ts";
+import { MapLayer } from "./mod.ts";
+
+
+export class SEffect extends System implements MapLayer {
+ effects: Set<EffectRenderer> = new Set()
+
+ constructor(public is: SMapItems) { super() }
+
+ override packet(p: PacketC): void {
+ if (p.type != "effect") return
+ const pos = { ...this.is.get_item_location_tracking(p.location) }
+ if (p.effect == "points") this.effects.add(new PointsEffect(pos, p.amount))
+ }
+
+ override tick(dt: number): void {
+ const remove = []
+ for (const e of this.effects)
+ if (!e.tick(dt)) remove.push(e)
+ for (const e of remove)
+ this.effects.delete(e)
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const e of this.effects)
+ e.draw(ctx)
+ }
+}
+
+abstract class EffectRenderer {
+ timer = 0
+ constructor(public pos: V2) { }
+ abstract draw(ctx: CanvasRenderingContext2D): void;
+ tick(dt: number): boolean {
+ this.timer += dt
+ return this.timer < 3
+ }
+}
+
+class PointsEffect extends EffectRenderer {
+ constructor(pos: V2, public amount: int) { super(pos) }
+ override draw(ctx: CanvasRenderingContext2D): void {
+ ctx.save()
+ ctx.translate(this.pos.x, this.pos.y)
+
+ ctx.translate(0, this.timer * -0.5)
+
+ ctx.scale(0.03, 0.03)
+ if (this.timer < 0.5) ctx.scale(this.timer * 2, this.timer * 2)
+ ctx.globalAlpha = this.timer > 2 ? 3 - this.timer : 1
+ ctx.font = "10px sans-serif"
+ ctx.fillStyle = this.amount < 0 ? "rgb(255, 106, 106)" : "rgb(157, 255, 124)"
+ ctx.strokeStyle = "black"
+ ctx.lineWidth = 2
+ ctx.lineJoin = "round"
+ ctx.lineCap = "round"
+ const text = this.amount < 0 ? `${this.amount}` : `+${this.amount}`
+ ctx.strokeText(text, 0, 0)
+ ctx.fillText(text, 0, 0)
+
+
+ ctx.restore()
+ }
+} \ No newline at end of file
diff --git a/test-client/protocol.ts b/test-client/protocol.ts
index 465071b6..afa5101f 100644
--- a/test-client/protocol.ts
+++ b/test-client/protocol.ts
@@ -75,7 +75,7 @@ export type PacketC =
| { type: "clear_progress", item: ItemLocation }
| { type: "update_map", changes: [IVec2, TileIndex[]][] } // List of position-tiles-pairs of the map that changed
| { type: "communicate", player: PlayerID, message?: Message, timeout?: MessageTimeout } // A player wants to communicate something, message is null when cleared
- | { type: "effect2", location: ItemLocation, name: string } // Player sent an effect
+ | { type: "effect", location: ItemLocation } & Effect
| { type: "server_message", message: Message, error: boolean } // Text message from the server
| { type: "server_hint", message?: Message, position?: IVec2, player: PlayerID } // Hint message from server with optional position. Message is unset to clear previous message
| { type: "vote_started", initiated_by: PlayerID, subject: VoteSubject, message: Message, timeout: float }
@@ -97,6 +97,11 @@ export interface Character {
hairstyle: int
}
+export type Effect =
+ { effect: "angry" }
+ | { effect: "satisfied" }
+ | { effect: "points", amount: int }
+
export type VoteSubject =
{ action: "start_game", config: GameConfig }
| { action: "end_game" }