aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-03-13 18:04:26 +0100
committermetamuffin <metamuffin@disroot.org>2026-03-13 18:04:26 +0100
commit7667a7503b033491bdd04d803667f15478a179b7 (patch)
treea4d65f3b89966c839dbb90b84bde9dae108618e4
parent0b1fbcf11a0e019d152726e0a40b18b95a97e601 (diff)
downloadhurrycurry-7667a7503b033491bdd04d803667f15478a179b7.tar
hurrycurry-7667a7503b033491bdd04d803667f15478a179b7.tar.bz2
hurrycurry-7667a7503b033491bdd04d803667f15478a179b7.tar.zst
test-client: voting support
-rw-r--r--test-client/locale.ts10
-rw-r--r--test-client/main.ts12
-rw-r--r--test-client/system.ts25
-rw-r--r--test-client/visual.ts18
-rw-r--r--test-client/vote.ts112
5 files changed, 158 insertions, 19 deletions
diff --git a/test-client/locale.ts b/test-client/locale.ts
index 69260d5a..01f1b790 100644
--- a/test-client/locale.ts
+++ b/test-client/locale.ts
@@ -1,3 +1,5 @@
+import { data } from "./main.ts";
+import { Message } from "./protocol.ts";
const LANGUAGES = [
"ar", "de", "en", "es", "eu",
@@ -29,3 +31,11 @@ function select_language(): string {
console.warn("fallback language selected");
return "en"
}
+
+export function message_str(m: Message): string {
+ if ("text" in m) return m.text
+ if ("translation" in m) return tr(m.translation.id, ...m.translation.params.map(message_str))
+ if ("tile" in m) return data.tile_names[m.tile]
+ if ("item" in m) return data.item_names[m.item]
+ return "[unknown message type]"
+}
diff --git a/test-client/main.ts b/test-client/main.ts
index d82e3c5e..4208a9ae 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -23,6 +23,7 @@ import { particle_splash, tick_particles } from "./particles.ts";
import { DebugEvent, Gamedata, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PacketS, PlayerClass, PlayerID, Score, TileIndex } from "./protocol.ts";
import { V2, lerp_exp_v2_mut, normalize, lerp_exp } from "./util.ts";
import { draw_ingame, draw_wait } from "./visual.ts";
+import { SVote } from "./vote.ts";
const KEY_INTERACT_LEFT = "KeyJ"
const KEY_INTERACT_RIGHT = "KeyL"
@@ -149,6 +150,8 @@ let disconnect_reason: string | undefined
let interacting: V2 | undefined;
let last_server_sent_position: V2 = { x: 0, y: 0 }
+export const vote = new SVote()
+
function get_item_location(loc: ItemLocation): ItemSlot {
if ("tile" in loc) return tiles.get(loc.tile.toString())!
if ("player" in loc) return players.get(loc.player[0])!.hands[loc.player[1]]
@@ -167,6 +170,7 @@ function send(p: PacketS) {
function packet(p: PacketC) {
if (!["movement", "update_map", "debug"].includes(p.type))
console.log(p);
+ vote.packet(p)
switch (p.type) {
case "version":
console.log(`Protocol version: ${p.major}.${p.minor}`);
@@ -279,11 +283,6 @@ function packet(p: PacketC) {
}
break;
}
- case "vote_started":
- case "vote_updated":
- case "vote_ended":
- console.log(p);
- break;
case "score":
score.demands_completed = p.demands_completed
score.demands_failed = p.demands_failed
@@ -353,8 +352,6 @@ function packet(p: PacketC) {
case "debug":
debug_events.set(p.key, p)
break;
- default:
- console.warn("unknown packet", p);
}
}
@@ -557,6 +554,7 @@ function frame_update(dt: number) {
overlay_vis_anim = lerp_exp(overlay_vis_anim, +keys_down.has(KEY_ZOOM), dt * 10)
tick_particles(dt)
+ vote.tick(dt)
}
function resize() {
diff --git a/test-client/system.ts b/test-client/system.ts
new file mode 100644
index 00000000..fed6db06
--- /dev/null
+++ b/test-client/system.ts
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+
+*/
+
+import { PacketC } from "./protocol.ts";
+
+export abstract class System {
+ tick(_dt: number) { }
+ draw(_ctx: CanvasRenderingContext2D) { }
+ packet(_p: PacketC) { }
+}
diff --git a/test-client/visual.ts b/test-client/visual.ts
index c8317d67..30ff970f 100644
--- a/test-client/visual.ts
+++ b/test-client/visual.ts
@@ -15,12 +15,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { tr } from "./locale.ts";
-import { ItemData, MessageData, MessageStyle, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, debug_events, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, is_lobby, items_removed, keys_down, my_id, overlay_vis_anim, players, score, server_hints, tiles } from "./main.ts";
+import { message_str, tr } from "./locale.ts";
+import { ItemData, MessageData, MessageStyle, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, debug_events, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, is_lobby, items_removed, keys_down, my_id, overlay_vis_anim, players, score, server_hints, tiles, vote } from "./main.ts";
import { PLAYER_SIZE } from "./movement.ts";
import { draw_item_sprite, draw_tile_sprite, ItemName, TileName } from "./sprites.ts";
-import { V2, ceil_v2, floor_v2, length } from "./util.ts";
-import { Message, PlayerClass } from "./protocol.ts";
+import { V2, ceil_v2, floor_v2 } from "./util.ts";
+import { PlayerClass } from "./protocol.ts";
import { draw_particles, particle_count } from "./particles.ts";
export function draw_wait(text: string) {
@@ -95,6 +95,8 @@ export function draw_ingame() {
if (!is_lobby)
draw_score()
+ vote.draw(ctx)
+
if (keys_down.has("KeyP"))
draw_info_overlay()
}
@@ -263,14 +265,6 @@ function draw_character(pclass: PlayerClass, character: number) {
ctx.fill()
}
-function message_str(m: Message): string {
- if ("text" in m) return m.text
- if ("translation" in m) return tr(m.translation.id, ...m.translation.params.map(message_str))
- if ("tile" in m) return data.tile_names[m.tile]
- if ("item" in m) return data.item_names[m.item]
- return "[unknown message type]"
-}
-
const MESSAGE_BG: { [key in MessageStyle]: string } = { normal: "#fff", hint: "#111", error: "#fff", pinned: "rgb(4, 32, 0)" }
const MESSAGE_FG: { [key in MessageStyle]: string } = { normal: "#000", hint: "#fff", error: "#a00", pinned: "#000" }
function draw_message(m: MessageData, world: boolean) {
diff --git a/test-client/vote.ts b/test-client/vote.ts
new file mode 100644
index 00000000..c4081180
--- /dev/null
+++ b/test-client/vote.ts
@@ -0,0 +1,112 @@
+/*
+ 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/>.
+
+*/
+
+import { message_str } from "./locale.ts";
+import { float, int, Message, PacketC, PlayerID, VoteSubject } from "./protocol.ts";
+import { System } from "./system.ts";
+import { lerp_exp } from "./util.ts";
+
+interface ActiveVote {
+ total: int,
+ agree: int,
+ reject: int,
+ initiated_by: PlayerID,
+ subject: VoteSubject,
+ message: Message,
+ initial_timeout: float,
+ timeout: float,
+
+ anim_box: float,
+ anim_cast: float,
+ done: boolean
+}
+
+export class SVote extends System {
+ active?: ActiveVote
+
+ override tick(dt: number) {
+ if (!this.active) return
+ const a = this.active
+ a.anim_cast *= Math.exp(-dt * 5);
+ a.anim_box = lerp_exp(a.anim_box, a.done ? 1.1 : 0, dt * 5)
+ a.timeout -= dt
+ a.timeout = Math.max(a.timeout, 0)
+ if (a.anim_box > 1) delete this.active
+ }
+ override draw(ctx: CanvasRenderingContext2D) {
+ if (!this.active) return
+ const a = this.active
+ const w = 300
+ const h = 200
+ const p = 20
+
+ ctx.save()
+ ctx.translate(-w * a.anim_box, 0)
+ ctx.translate(p, p)
+ let r = 1, g = 1, b = 1
+ if (a.anim_cast > 0) { r -= a.anim_cast; b -= a.anim_cast }
+ if (a.anim_cast < 0) { g += a.anim_cast; b += a.anim_cast }
+ ctx.fillStyle = `rgba(${r * 255}, ${g * 255}, ${b * 255}, 0.67)`
+ ctx.fillRect(0, 0, 300, 200)
+ ctx.font = "20px sans-serif"
+ ctx.fillStyle = "black"
+ ctx.fillText(message_str(a.message), p, p + 20, w - p * 2)
+
+ ctx.fillStyle = "#0008"
+ ctx.fillRect(0, 0, (1 - a.timeout / a.initial_timeout) * w, 10)
+
+ const bar_height = 50
+ ctx.translate(p, h - bar_height - p)
+ ctx.scale(w - p * 2, bar_height)
+ ctx.fillStyle = "#0008"
+ ctx.fillRect(0, 0, 1, 1)
+ ctx.fillRect(0.49, 0, 0.02, 1)
+ ctx.fillStyle = "#51ff00"
+ ctx.fillRect(0, 0, a.agree / a.total, 1)
+ ctx.fillStyle = "#ff0000"
+ ctx.fillRect(1 - (a.reject / a.total), 0, (a.reject / a.total), 1)
+ ctx.restore()
+
+ }
+ override packet(p: PacketC): void {
+ if (p.type == "vote_started") {
+ this.active = {
+ agree: 0,
+ reject: 0,
+ total: 0,
+ initiated_by: p.initiated_by,
+ message: p.message,
+ subject: p.subject,
+ anim_box: 1,
+ anim_cast: 0,
+ done: false,
+ initial_timeout: p.timeout,
+ timeout: p.timeout
+ }
+ } else if (p.type == "vote_ended" && this.active) {
+ this.active.done = true
+ } else if (p.type == "vote_updated" && this.active) {
+ const a = this.active;
+ if (p.agree > a.agree) a.anim_cast += 1
+ if (p.reject > a.reject) a.anim_cast -= 1
+ a.agree = p.agree
+ a.reject = p.reject
+ a.total = p.total
+ }
+ }
+}