diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-03-13 18:04:26 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-03-13 18:04:26 +0100 |
| commit | 7667a7503b033491bdd04d803667f15478a179b7 (patch) | |
| tree | a4d65f3b89966c839dbb90b84bde9dae108618e4 | |
| parent | 0b1fbcf11a0e019d152726e0a40b18b95a97e601 (diff) | |
| download | hurrycurry-7667a7503b033491bdd04d803667f15478a179b7.tar hurrycurry-7667a7503b033491bdd04d803667f15478a179b7.tar.bz2 hurrycurry-7667a7503b033491bdd04d803667f15478a179b7.tar.zst | |
test-client: voting support
| -rw-r--r-- | test-client/locale.ts | 10 | ||||
| -rw-r--r-- | test-client/main.ts | 12 | ||||
| -rw-r--r-- | test-client/system.ts | 25 | ||||
| -rw-r--r-- | test-client/visual.ts | 18 | ||||
| -rw-r--r-- | test-client/vote.ts | 112 |
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 + } + } +} |