diff options
-rw-r--r-- | pixel-client/src/game.rs | 19 | ||||
-rw-r--r-- | server/bot/src/algos/customer.rs | 19 | ||||
-rw-r--r-- | server/bot/src/algos/simple.rs | 2 | ||||
-rw-r--r-- | server/bot/src/algos/test.rs | 2 | ||||
-rw-r--r-- | server/client-lib/src/lib.rs | 18 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 10 | ||||
-rw-r--r-- | server/src/server.rs | 33 | ||||
-rw-r--r-- | server/src/state.rs | 6 | ||||
-rw-r--r-- | test-client/main.ts | 54 | ||||
-rw-r--r-- | test-client/protocol.ts | 9 | ||||
-rw-r--r-- | test-client/visual.ts | 10 |
11 files changed, 116 insertions, 66 deletions
diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs index 318b9a02..59b30ad0 100644 --- a/pixel-client/src/game.rs +++ b/pixel-client/src/game.rs @@ -30,7 +30,8 @@ use hurrycurry_client_lib::{network::sync::Network, spatial_index::SpatialIndex} use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, - Gamedata, ItemIndex, ItemLocation, Message, PacketC, PacketS, PlayerID, Score, TileIndex, + Gamedata, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PacketS, PlayerID, Score, + TileIndex, }; use log::{info, warn}; use sdl2::{ @@ -67,7 +68,7 @@ pub struct Tile { pub struct Player { movement: MovementBase, item: Option<Item>, - message_persist: Option<Message>, + message_persist: Option<(Message, MessageTimeout)>, _name: String, _character: i32, interact_target_anim: Vec2, @@ -183,6 +184,12 @@ impl Game { for (&pid, player) in &mut self.players { player.movement.update(&self.walkable, dt); + if let Some((_, timeout)) = &mut player.message_persist { + timeout.remaining -= dt; + if timeout.remaining < 0. { + player.message_persist = None; + } + } self.players_spatial_index .update_entry(pid, player.movement.position); } @@ -352,11 +359,11 @@ impl Game { PacketC::Communicate { player, message, - persist, + timeout, } => { - if persist { + if let Some(timeout) = timeout { if let Some(player) = self.players.get_mut(&player) { - player.message_persist = message; + player.message_persist = message.map(|m| (m, timeout)); } } } @@ -458,7 +465,7 @@ impl Player { } .at(self.movement.position), ); - if let Some(message) = &self.message_persist { + if let Some((message, _timeout)) = &self.message_persist { match message { Message::Text(_) => (), // TODO Message::Item(item) => { diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs index 9c0ce210..2e0a45b4 100644 --- a/server/bot/src/algos/customer.rs +++ b/server/bot/src/algos/customer.rs @@ -91,16 +91,17 @@ impl BotAlgo for Customer { if path.is_done() { let demand = DemandIndex(random::<u32>() as usize % game.data.demands.len()); info!("{me:?} -> waiting"); + let timeout = 90. + random::<f32>() * 60.; *self = Customer::Waiting { chair: *chair, - timeout: 90. + random::<f32>() * 60., + timeout, demand, origin: *origin, }; BotInput { extra: vec![PacketS::Communicate { message: Some(Message::Item(game.data.demands[demand.0].input)), - persist: true, + timeout: Some(timeout), player: me, }], ..Default::default() @@ -137,13 +138,8 @@ impl BotAlgo for Customer { ..Default::default() }), PacketS::Communicate { - message: None, - persist: true, - player: me, - }, - PacketS::Communicate { message: Some(Message::Effect("angry".to_string())), - persist: false, + timeout: None, player: me, }, ], @@ -183,13 +179,8 @@ impl BotAlgo for Customer { BotInput { extra: vec![ PacketS::Communicate { - persist: true, - message: None, - player: me, - }, - PacketS::Communicate { message: Some(Message::Effect("satisfied".to_string())), - persist: false, + timeout: None, player: me, }, PacketS::Interact { diff --git a/server/bot/src/algos/simple.rs b/server/bot/src/algos/simple.rs index 1be2cddc..27493d74 100644 --- a/server/bot/src/algos/simple.rs +++ b/server/bot/src/algos/simple.rs @@ -123,7 +123,7 @@ impl<S> Context<'_, S> { .players .iter() .find_map(|(_, pl)| match &pl.communicate_persist { - Some(Message::Item(item)) => { + Some((Message::Item(item), _)) => { let pos = pl.movement.position.as_ivec2(); [IVec2::X, IVec2::Y, -IVec2::X, -IVec2::Y] .into_iter() diff --git a/server/bot/src/algos/test.rs b/server/bot/src/algos/test.rs index a47befa9..73368de2 100644 --- a/server/bot/src/algos/test.rs +++ b/server/bot/src/algos/test.rs @@ -59,7 +59,7 @@ fn find_demand(game: &Game) -> Option<(ItemIndex, IVec2)> { game.players .iter() .find_map(|(_, pl)| match &pl.communicate_persist { - Some(Message::Item(item)) => { + Some((Message::Item(item), _)) => { let pos = pl.movement.position.as_ivec2(); let t = pos; Some((*item, t)) diff --git a/server/client-lib/src/lib.rs b/server/client-lib/src/lib.rs index b04f46f7..38f1070e 100644 --- a/server/client-lib/src/lib.rs +++ b/server/client-lib/src/lib.rs @@ -20,8 +20,8 @@ pub mod network; pub mod spatial_index; use hurrycurry_protocol::{ - glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message, PacketC, - PlayerID, RecipeIndex, Score, TileIndex, + glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message, + MessageTimeout, PacketC, PlayerID, RecipeIndex, Score, TileIndex, }; use spatial_index::SpatialIndex; use std::{ @@ -54,7 +54,7 @@ pub struct Player { pub character: i32, pub interacting: Option<IVec2>, pub item: Option<Item>, - pub communicate_persist: Option<Message>, + pub communicate_persist: Option<(Message, MessageTimeout)>, pub movement: MovementBase, } @@ -168,11 +168,11 @@ impl Game { PacketC::Communicate { player, message, - persist, + timeout, } => { - if persist { + if let Some(timeout) = &timeout { if let Some(player) = self.players.get_mut(&player) { - player.communicate_persist = message; + player.communicate_persist = message.to_owned().map(|m| (m, *timeout)); } } } @@ -195,6 +195,12 @@ impl Game { for (&pid, player) in &mut self.players { player.movement.update(&self.walkable, dt); + if let Some((_, timeout)) = &mut player.communicate_persist { + timeout.remaining -= dt; + if timeout.remaining < 0. { + player.communicate_persist = None; + } + } self.players_spatial_index .update_entry(pid, player.movement.position); } diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index 06cc9552..ee9fc0a3 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -116,7 +116,7 @@ pub enum PacketS { Communicate { player: PlayerID, message: Option<Message>, - persist: bool, + timeout: Option<f32>, }, #[serde(skip)] @@ -199,7 +199,7 @@ pub enum PacketC { Communicate { player: PlayerID, message: Option<Message>, - persist: bool, + timeout: Option<MessageTimeout>, }, ServerMessage { text: String, @@ -231,6 +231,12 @@ pub enum Menu { Score(Score), } +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] +pub struct MessageTimeout { + pub remaining: f32, + pub initial: f32, +} + #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] pub struct Score { pub time_remaining: f64, diff --git a/server/src/server.rs b/server/src/server.rs index 41931ef7..2f93246d 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -25,7 +25,7 @@ use hurrycurry_client_lib::{Game, Item, Player, Tile}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, - Gamedata, ItemLocation, Menu, PacketC, PacketS, PlayerID, Score, TileIndex, + Gamedata, ItemLocation, Menu, MessageTimeout, PacketC, PacketS, PlayerID, Score, TileIndex, }; use log::{info, warn}; use std::{ @@ -177,11 +177,11 @@ impl GameServerExt for Game { item: Some(item.kind), }) } - if let Some(c) = &player.communicate_persist { + if let Some((message, timeout)) = &player.communicate_persist { out.push(PacketC::Communicate { player: id, - message: Some(c.to_owned()), - persist: true, + message: Some(message.to_owned()), + timeout: Some(*timeout), }) } } @@ -449,19 +449,30 @@ impl Server<'_> { } PacketS::Communicate { message, - persist, + timeout, player, } => { info!("{player:?} message {message:?}"); - if persist { + if let Some(timeout) = timeout { if let Some(player) = self.game.players.get_mut(&player) { - player.communicate_persist = message.clone() + player.communicate_persist = message.clone().map(|m| { + ( + m, + MessageTimeout { + initial: timeout, + remaining: timeout, + }, + ) + }); } } packet_out.push_back(PacketC::Communicate { player, message, - persist, + timeout: timeout.map(|t| MessageTimeout { + initial: t, + remaining: t, + }), }) } PacketS::ReplaceHand { item, player } => { @@ -595,6 +606,12 @@ impl Server<'_> { let mut players_auto_release = Vec::new(); for (pid, player) in &mut self.game.players { + if let Some((_, timeout)) = &mut player.communicate_persist { + timeout.remaining -= dt; + if timeout.remaining < 0. { + player.communicate_persist = None; + } + } if let Some(pos) = player.interacting { if let Some(tile) = self.game.tiles.get(&pos) { if let Some(item) = &tile.item { diff --git a/server/src/state.rs b/server/src/state.rs index 54beb3f7..f6304db5 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -148,7 +148,7 @@ impl State { match &packet { PacketS::Communicate { message: Some(Message::Text(text)), - persist: false, + timeout: None, player, } if let Some(command) = text.strip_prefix("/") => { match self.handle_command_parse(*player, command).await { @@ -288,7 +288,7 @@ impl State { .send(PacketC::Communicate { player, message: Some(Message::Effect(name)), - persist: false, + timeout: None, }) .ok(); } @@ -302,7 +302,7 @@ impl State { .send(PacketC::Communicate { player, message: Some(Message::Item(item)), - persist: false, + timeout: None, }) .ok(); } diff --git a/test-client/main.ts b/test-client/main.ts index 1679ab42..2990c685 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -18,7 +18,7 @@ /// <reference lib="dom" /> import { MovementBase, collide_player_player, update_movement } from "./movement.ts"; -import { Gamedata, ItemIndex, ItemLocation, Message, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts"; +import { Gamedata, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts"; import { V2, lerp_exp_v2_mut, normalize, lerp_exp } from "./util.ts"; import { draw_ingame, draw_wait } from "./visual.ts"; @@ -79,8 +79,8 @@ export interface PlayerData extends MovementBase { direction: V2, character: number, anim_position: V2, + message_persist?: MessageData, message?: MessageData, - message_clear?: number, } export interface TileData { @@ -93,17 +93,17 @@ export interface MessageData { inner: Message anim_position: V2, anim_size: number, + timeout: MessageTimeout, } export const players = new Map<PlayerID, PlayerData>() export const tiles = new Map<string, TileData>() export const items_removed = new Set<ItemData>() -export let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0], tile_collide: [], tile_interact: [], maps: {} } +export let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0], tile_collide: [], tile_interact: [], maps: [] } export let time_remaining: number | null = null export let global_message: MessageData | undefined = undefined -let global_message_clear: number | undefined = undefined export let my_id: PlayerID = -1 export let points = 0 export let demands_completed = 0 @@ -206,10 +206,13 @@ function packet(p: PacketC) { break; case "communicate": { const player = players.get(p.player)! - if (player.message_clear) clearTimeout(player.message_clear) - if (p.message) player.message = { inner: p.message, anim_size: 0., anim_position: player.anim_position } - if (p.persist && !p.message) player.message = undefined - if (!p.persist) player.message_clear = setTimeout(() => delete player.message, 3000) + if (p.message) { + const message = { inner: p.message, anim_size: 0., anim_position: player.anim_position, timeout: p.timeout ?? { initial: 5, remaining: 5 } }; + if (p.timeout === undefined) player.message = message + else player.message_persist = message + } else if (p.timeout !== undefined) { + delete player.message_persist + } break; } case "score": @@ -219,15 +222,11 @@ function packet(p: PacketC) { time_remaining = p.time_remaining ?? null break; case "error": - if (global_message_clear) clearTimeout(global_message_clear) - global_message = { inner: { text: p.message }, anim_size: 0., anim_position: { x: 0, y: 0 } } - global_message_clear = setTimeout(() => global_message = undefined, 4000) + global_message = { inner: { text: p.message }, anim_size: 0., anim_position: { x: 0, y: 0 }, timeout: { initial: 5, remaining: 5 } } console.warn(p.message) break; case "server_message": - if (global_message_clear) clearTimeout(global_message_clear) - global_message = { inner: { text: p.text }, anim_size: 0., anim_position: { x: 0, y: 0 } } - global_message_clear = setTimeout(() => global_message = undefined, 4000) + global_message = { inner: { text: p.text }, anim_size: 0., anim_position: { x: 0, y: 0 }, timeout: { initial: 5, remaining: 5 } } break; case "set_ingame": console.log(`ingame ${p.state}`); @@ -257,12 +256,12 @@ function keyboard(ev: KeyboardEvent, down: boolean) { if (HANDLED_KEYS.includes(ev.code)) ev.preventDefault() if (!keys_down.has(KEY_INTERACT) && ev.code == KEY_INTERACT && down) set_interact(true) if (keys_down.has(KEY_INTERACT) && ev.code == KEY_INTERACT && !down) set_interact(false) - if (down && ev.code == "Numpad1") send({ player: my_id, type: "communicate", message: { text: "/start junior" }, persist: false }) - if (down && ev.code == "Numpad2") send({ player: my_id, type: "communicate", message: { text: "/start senior" }, persist: false }) - if (down && ev.code == "Numpad3") send({ player: my_id, type: "communicate", message: { text: "/start sophomore" }, persist: false }) - if (down && ev.code == "Numpad4") send({ player: my_id, type: "communicate", message: { text: "/start debug" }, persist: false }) - if (down && ev.code == "Numpad5") send({ player: my_id, type: "communicate", message: { text: "/start bus" }, persist: false }) - if (down && ev.code == "Numpad0") send({ player: my_id, type: "communicate", message: { text: "/end" }, persist: false }) + if (down && ev.code == "Numpad1") send({ player: my_id, type: "communicate", message: { text: "/start junior" } }) + if (down && ev.code == "Numpad2") send({ player: my_id, type: "communicate", message: { text: "/start senior" } }) + if (down && ev.code == "Numpad3") send({ player: my_id, type: "communicate", message: { text: "/start sophomore" } }) + if (down && ev.code == "Numpad4") send({ player: my_id, type: "communicate", message: { text: "/start debug" } }) + if (down && ev.code == "Numpad5") send({ player: my_id, type: "communicate", message: { text: "/start bus" } }) + if (down && ev.code == "Numpad0") send({ player: my_id, type: "communicate", message: { text: "/end" } }) if (down) keys_down.add(ev.code) else keys_down.delete(ev.code) } @@ -275,7 +274,7 @@ function close_chat() { } function toggle_chat() { if (chat) { - if (chat.value.length) send({ player: my_id, type: "communicate", message: { text: chat.value }, persist: false }) + if (chat.value.length) send({ player: my_id, type: "communicate", message: { text: chat.value } }) chat.remove() canvas.focus() chat = null; @@ -342,7 +341,9 @@ function frame_update(dt: number) { if (pid == my_id) player.anim_position.x = player.position.x, player.anim_position.y = player.position.y else lerp_exp_v2_mut(player.anim_position, player.position, dt * 15) if (player.item !== undefined && player.item !== null) update_item(player.item) - if (player.message) player.message.anim_size = lerp_exp(player.message.anim_size, 1, dt * 3) + if (player.message && tick_message(player.message, dt)) delete player.message + if (player.message_persist && tick_message(player.message_persist, dt)) delete player.message_persist + } for (const [_, tile] of tiles) { if (tile.item !== undefined && tile.item !== null) update_item(tile.item) @@ -359,6 +360,8 @@ function frame_update(dt: number) { lerp_exp_v2_mut(camera, p.position, dt * 10.) + if (global_message && tick_message(global_message, dt)) global_message = undefined + const it = get_interact_target() ?? { x: 0, y: 0 }; const possible = data.tile_interact[tiles.get([it.x, it.y].toString())?.kind ?? 0] ?? false lerp_exp_v2_mut(interact_target_anim, it, dt * 15.) @@ -387,3 +390,10 @@ function draw() { else throw new Error(`ws state invalid`); requestAnimationFrame(draw) } + +function tick_message(m: MessageData | undefined, dt: number): boolean { + if (!m) return true + m.anim_size = lerp_exp(m.anim_size, m.timeout.remaining > 0.3 ? 1 : 0, dt * 3) + m.timeout.remaining -= dt; + return m.timeout.remaining <= 0 +} diff --git a/test-client/protocol.ts b/test-client/protocol.ts index d5ce67ff..40532c08 100644 --- a/test-client/protocol.ts +++ b/test-client/protocol.ts @@ -40,7 +40,7 @@ export type PacketS = | { type: "leave", player: PlayerID } // Despawns a character | { type: "movement", player: PlayerID, pos: Vec2, dir: Vec2, boost: boolean } | { type: "interact", player: PlayerID, pos?: Vec2 } // Interact with some tile. pos is a position when pressing and null when releasing interact button - | { type: "communicate", player: PlayerID, message?: Message, persist: boolean } // Send a message + | { type: "communicate", player: PlayerID, message?: Message, timeout?: number } // Send a message | { type: "replay_tick", dt: number } // Steps forward in replay. export type PacketC = @@ -55,7 +55,7 @@ export type PacketC = | { type: "set_item", location: ItemLocation, item?: ItemIndex } // the item contained in a tile or player changed | { type: "set_progress", item: ItemLocation, progress?: number, warn: boolean } // A tile is doing something. progress goes from 0 to 1, then null when finished | { type: "update_map", tile: Vec2, kind: TileIndex | null, neighbors: [TileIndex | null] } // A map tile was changed - | { type: "communicate", player: PlayerID, message?: Message, persist: boolean } // A player wants to communicate something, message is null when cleared + | { type: "communicate", player: PlayerID, message?: Message, timeout?: MessageTimeout } // A player wants to communicate something, message is null when cleared | { type: "server_message", text: string } // Text message from the server | { type: "score" } & Score // Supplies information for score OSD | { type: "menu" } & Menu // Open a menu on the client-side @@ -67,6 +67,11 @@ export type Menu = { menu: "book" } | { menu: "score", data: Score } +export interface MessageTimeout { + initial: number, + remaining: number +} + export interface Score { points: number, demands_failed: number, diff --git a/test-client/visual.ts b/test-client/visual.ts index 07cfde67..59734234 100644 --- a/test-client/visual.ts +++ b/test-client/visual.ts @@ -66,8 +66,10 @@ export function draw_ingame() { if (tile.item) draw_item(tile.item) // Draw player messages - for (const [_, player] of players) + for (const [_, player] of players) { if (player.message) draw_message(player.message) + if (player.message_persist) draw_message(player.message_persist) + } for (const [_, player] of players) draw_player_nametag(player) @@ -219,6 +221,12 @@ function draw_message(m: MessageData) { ctx.closePath() ctx.fill() + ctx.beginPath() + ctx.strokeStyle = "red" + ctx.lineWidth = 0.1 + ctx.arc(0, -1, 0.45, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * (1 - m.timeout.remaining / m.timeout.initial)) + ctx.stroke() + ctx.translate(0, -1) draw_item_sprite(ctx, data.item_names[m.inner.item] as ItemName) ctx.translate(0, 1) |