diff options
-rw-r--r-- | server/bot/src/algos/customer.rs | 59 | ||||
-rw-r--r-- | server/bot/src/algos/frank.rs | 1 | ||||
-rw-r--r-- | server/protocol/src/lib.rs | 2 | ||||
-rw-r--r-- | server/src/commands.rs | 7 | ||||
-rw-r--r-- | server/src/interaction.rs | 3 | ||||
-rw-r--r-- | server/src/server.rs | 4 | ||||
-rw-r--r-- | server/src/state.rs | 1 | ||||
-rw-r--r-- | test-client/main.ts | 27 | ||||
-rw-r--r-- | test-client/protocol.ts | 5 | ||||
-rw-r--r-- | test-client/visual.ts | 55 |
10 files changed, 117 insertions, 47 deletions
diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs index ec8f2283..e602addc 100644 --- a/server/bot/src/algos/customer.rs +++ b/server/bot/src/algos/customer.rs @@ -39,6 +39,8 @@ pub enum Customer { chair: IVec2, timeout: f32, origin: IVec2, + check: u8, + pinned: bool, }, Eating { demand: DemandIndex, @@ -104,12 +106,15 @@ impl BotAlgo for Customer { timeout, demand, origin: *origin, + check: 0, + pinned: false, }; BotInput { extra: vec![PacketS::Communicate { message: Some(Message::Item(game.data.demands[demand.0].input)), timeout: Some(timeout), player: me, + pin: Some(false), }], ..Default::default() } @@ -144,19 +149,23 @@ impl BotAlgo for Customer { demand, timeout, origin, + check, + pinned, } => { *timeout -= dt; + *check += 1; if *timeout <= 0. { let path = find_path(&game.walkable, pos.as_ivec2(), *origin) .expect("no path to exit"); info!("{me:?} -> exiting"); *self = Customer::Exiting { path }; - BotInput { + return BotInput { extra: vec![ PacketS::Communicate { message: None, timeout: Some(0.), player: me, + pin: Some(false), }, PacketS::ApplyScore(Score { points: -1, @@ -169,9 +178,32 @@ impl BotAlgo for Customer { }, ], ..Default::default() - } - } else { + }; + } else if *check > 10 { let demand_data = &game.data.demands[demand.0]; + *check = 0; + + if !*pinned { + let mut pin = false; + game.players_spatial_index.query(pos, 3., |pid, _| { + if game.players.get(&pid).map_or(false, |p| p.character >= 0) { + pin = true + } + }); + if pin { + *pinned = true; + return BotInput { + extra: vec![PacketS::Communicate { + player: me, + message: Some(Message::Item(demand_data.input)), + timeout: Some(*timeout), + pin: Some(true), + }], + ..Default::default() + }; + } + } + let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] .into_iter() .find_map(|off| { @@ -201,12 +233,19 @@ impl BotAlgo for Customer { chair: *chair, origin: *origin, }; - BotInput { + return BotInput { extra: vec![ PacketS::Communicate { message: None, timeout: Some(0.), player: me, + pin: Some(false), + }, + PacketS::Communicate { + message: None, + timeout: Some(0.), + player: me, + pin: Some(true), }, PacketS::Effect { name: "satisfied".to_string(), @@ -222,15 +261,15 @@ impl BotAlgo for Customer { }, ], ..Default::default() - } - } else { - BotInput { - direction: (chair.as_vec2() + 0.5) - pos, - ..Default::default() - } + }; } } + BotInput { + direction: (chair.as_vec2() + 0.5) - pos, + ..Default::default() + } } + Customer::Eating { demand, target, diff --git a/server/bot/src/algos/frank.rs b/server/bot/src/algos/frank.rs index 95718d4c..854c73bb 100644 --- a/server/bot/src/algos/frank.rs +++ b/server/bot/src/algos/frank.rs @@ -84,6 +84,7 @@ impl BotAlgo for Frank { player: me, message: Some(Message::Text(message)), timeout: Some(3.), + pin: Some(false), }], ..Default::default() }; diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs index b826edae..0203107d 100644 --- a/server/protocol/src/lib.rs +++ b/server/protocol/src/lib.rs @@ -118,6 +118,7 @@ pub enum PacketS { player: PlayerID, message: Option<Message>, timeout: Option<f32>, + pin: Option<bool>, }, /// For use in replay sessions only @@ -266,6 +267,7 @@ pub enum Menu { pub struct MessageTimeout { pub remaining: f32, pub initial: f32, + pub pinned: bool, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] diff --git a/server/src/commands.rs b/server/src/commands.rs index c03c8bcd..6fd95eac 100644 --- a/server/src/commands.rs +++ b/server/src/commands.rs @@ -23,7 +23,7 @@ use crate::{ use anyhow::{anyhow, bail, Result}; use clap::{Parser, ValueEnum}; use hurrycurry_bot::algos::ALGO_CONSTRUCTORS; -use hurrycurry_protocol::{Menu, Message, MessageTimeout, PacketC, PlayerID}; +use hurrycurry_protocol::{Menu, Message, PacketC, PlayerID}; use std::{fmt::Write, time::Duration}; #[derive(Parser)] @@ -294,10 +294,7 @@ impl Server { id: message_id, params: arguments.into_iter().map(|c| Message::Text(c)).collect(), }), - timeout: Some(MessageTimeout { - initial: 5., - remaining: 5., - }), + timeout: None, }); } } diff --git a/server/src/interaction.rs b/server/src/interaction.rs index 7a8c2e9d..74d31cd8 100644 --- a/server/src/interaction.rs +++ b/server/src/interaction.rs @@ -15,13 +15,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +use crate::data::index::GamedataIndex; use hurrycurry_client_lib::{Involvement, Item}; use hurrycurry_protocol::{Gamedata, ItemLocation, PacketC, PlayerID, Recipe, Score, TileIndex}; use log::info; use std::collections::VecDeque; -use crate::data::index::GamedataIndex; - #[allow(clippy::too_many_arguments)] pub fn interact( data: &Gamedata, diff --git a/server/src/server.rs b/server/src/server.rs index 06b6e5fd..0c3df634 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -528,8 +528,10 @@ impl Server { message, timeout, player, + pin, } => { info!("{player:?} message {message:?}"); + let pin = pin.unwrap_or(false); if let Some(timeout) = timeout { if let Some(player) = self.game.players.get_mut(&player) { player.communicate_persist = message.clone().map(|m| { @@ -538,6 +540,7 @@ impl Server { MessageTimeout { initial: timeout, remaining: timeout, + pinned: pin, }, ) }); @@ -549,6 +552,7 @@ impl Server { timeout: timeout.map(|t| MessageTimeout { initial: t, remaining: t, + pinned: pin, }), }) } diff --git a/server/src/state.rs b/server/src/state.rs index 9009d4a1..65e6dfd2 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -53,6 +53,7 @@ impl Server { message: Some(Message::Text(text)), timeout: None, player, + .. } if let Some(command) = text.strip_prefix("/") => { match self.handle_command_parse(*player, command).await { Ok(packets) => return Ok(packets), diff --git a/test-client/main.ts b/test-client/main.ts index a3bf0105..5d3a6d11 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -88,6 +88,7 @@ export interface PlayerData extends MovementBase { character: number, anim_position: V2, message_persist?: MessageData, + message_pinned?: MessageData, message?: MessageData, } @@ -97,8 +98,9 @@ export interface TileData { kind: TileIndex item?: ItemData } +export type MessageStyle = "hint" | "normal" | "error" | "pinned" export interface MessageData { - style: "hint" | "normal" | "error" + style: MessageStyle inner: Message anim_position: V2, anim_size: number, @@ -120,7 +122,7 @@ export let camera_scale = 0.05; export const interact_target_anim: V2 = { x: 0, y: 0 } export let interact_possible_anim: number = 0 export let interact_active_anim: number = 0 -export let nametag_scale_anim: number = 0 +export let overlay_vis_anim: number = 0 export let is_lobby = false let interacting: V2 | undefined; let last_server_sent_position: V2 = { x: 0, y: 0 } @@ -226,11 +228,18 @@ function packet(p: PacketC) { inner: p.message, anim_size: 0., anim_position: player.anim_position, - timeout: p.timeout ?? { initial: 5, remaining: 5 }, + timeout: p.timeout ?? { initial: 5, remaining: 5, pinned: false }, style: "normal" as const }; if (p.timeout === undefined) player.message = message else player.message_persist = message + if (p.timeout?.pinned) player.message_pinned = { + inner: p.message, + anim_size: 0, + anim_position: { x: 20, y: 0 }, + style: "pinned", + timeout: p.timeout + } } else if (p.timeout !== undefined) { delete player.message_persist } @@ -248,7 +257,7 @@ function packet(p: PacketC) { style: p.error ? "error" : "normal", anim_size: 0., anim_position: { x: 0, y: 0 }, - timeout: { initial: 5, remaining: 5 } + timeout: { initial: 5, remaining: 5, pinned: false } } break; case "set_ingame": @@ -286,7 +295,7 @@ function packet(p: PacketC) { case "book": open("https://s.metamuffin.org/static/hurrycurry/book.pdf"); break case "score": global_message = { - timeout: { initial: 5, remaining: 5 }, + timeout: { initial: 5, remaining: 5, pinned: false }, inner: { text: `Score: ${JSON.stringify(p.data, null, 4)}` }, anim_position: { x: 0, y: 0 }, anim_size: 0, @@ -391,16 +400,18 @@ function frame_update(dt: number) { if (item.active) item.active.position += item.active.speed * dt } + let pin_xo = 0 for (const [pid, player] of players) { 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 && tick_message(player.message, dt)) delete player.message if (player.message_persist && tick_message(player.message_persist, dt)) delete player.message_persist + if (player.message_pinned && tick_message(player.message_pinned, dt)) delete player.message_pinned + if (player.message_pinned) lerp_exp_v2_mut(player.message_pinned.anim_position, { x: pin_xo++, y: 0 }, dt * 5) } - for (const [_, tile] of tiles) { + for (const [_, tile] of tiles) if (tile.item !== undefined && tile.item !== null) update_item(tile.item) - } const remove: ItemData[] = [] for (const item of items_removed) { @@ -426,7 +437,7 @@ function frame_update(dt: number) { const zoom_target = Math.min(canvas.width, canvas.height) * (keys_down.has("KeyL") ? 0.05 : 0.1) camera_scale = lerp_exp(camera_scale, zoom_target, dt * 5) - nametag_scale_anim = lerp_exp(nametag_scale_anim, +keys_down.has("KeyL"), dt * 10) + overlay_vis_anim = lerp_exp(overlay_vis_anim, +keys_down.has("KeyL"), dt * 10) tick_particles(dt) } diff --git a/test-client/protocol.ts b/test-client/protocol.ts index c8c59ed2..64d740c3 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, timeout?: number } // Sends a message + | { type: "communicate", player: PlayerID, message?: Message, timeout?: number, pin?: boolean } // Sends a message | { type: "effect", player: PlayerID, name: string } // Sends an effect | { type: "replay_tick", dt: number } // Steps forward in replay. @@ -73,7 +73,8 @@ export type Menu = export interface MessageTimeout { initial: number, - remaining: number + remaining: number, + pinned: boolean, } export interface Score { diff --git a/test-client/visual.ts b/test-client/visual.ts index 9a267da0..784a36a3 100644 --- a/test-client/visual.ts +++ b/test-client/visual.ts @@ -16,7 +16,7 @@ */ import { tr } from "./locale.ts"; -import { ItemData, MessageData, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, is_lobby, items_removed, keys_down, my_id, nametag_scale_anim, players, score, server_hints, tiles } from "./main.ts"; +import { ItemData, MessageData, MessageStyle, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, 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 { PLAYER_SIZE } from "./movement.ts"; import { draw_item_sprite, draw_tile_sprite, ItemName, TileName } from "./tiles.ts"; import { V2, ceil_v2, floor_v2 } from "./util.ts"; @@ -67,16 +67,24 @@ export function draw_ingame() { draw_interact_target() for (const [_, player] of players) - if (player.message) draw_message(player.message) + if (player.message) draw_message(player.message, true) for (const [_, player] of players) - if (player.message_persist) draw_message(player.message_persist) + if (player.message_persist) draw_message(player.message_persist, true) for (const [_, player] of players) draw_player_nametag(player) for (const [_, message] of server_hints) - draw_message(message) + draw_message(message, true) + + ctx.restore() + ctx.save() + ctx.translate(50, 50) + ctx.scale(80, 80) + + for (const [_, player] of players) + if (player.message_pinned) draw_message(player.message_pinned, false) ctx.restore() @@ -143,13 +151,13 @@ function draw_player(player: PlayerData) { } function draw_player_nametag(player: PlayerData) { - if (nametag_scale_anim > 0.01) { + if (overlay_vis_anim > 0.01) { ctx.save() ctx.translate(player.anim_position.x, player.anim_position.y) ctx.translate(0, -1) ctx.textAlign = "center" ctx.font = "15px sans-serif" - ctx.scale(nametag_scale_anim / camera_scale, nametag_scale_anim / camera_scale) + ctx.scale(overlay_vis_anim / camera_scale, overlay_vis_anim / camera_scale) const w = ctx.measureText(player.name).width + 20 ctx.fillStyle = "#fffa" ctx.beginPath() @@ -221,20 +229,27 @@ function message_str(m: Message): string { return "[unknown message type]" } -const MESSAGE_BG = { "normal": "#fff", "hint": "#111", "error": "#fff" } -const MESSAGE_FG = { "normal": "#000", "hint": "#fff", "error": "#a00" } -function draw_message(m: MessageData, server?: boolean) { +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) { ctx.save() ctx.translate(m.anim_position.x, m.anim_position.y) - const scale = Math.min(m.anim_size, 1 - nametag_scale_anim); + const scale = Math.min(m.anim_size, 1 - overlay_vis_anim); ctx.scale(scale, scale) if ("item" in m.inner) { - ctx.fillStyle = "#fffa" - ctx.beginPath() - ctx.moveTo(0, -0.3) - ctx.arc(0, -1, 0.5, Math.PI / 4, Math.PI - Math.PI / 4, true) - ctx.closePath() - ctx.fill() + ctx.fillStyle = MESSAGE_BG[m.style] + if (m.style == "pinned") { + ctx.translate(0, 1) + ctx.beginPath() + ctx.arc(0, -1, 0.5, 0, Math.PI * 2) + ctx.fill() + } else { + ctx.beginPath() + ctx.moveTo(0, -0.3) + ctx.arc(0, -1, 0.5, Math.PI / 4, Math.PI - Math.PI / 4, true) + ctx.closePath() + ctx.fill() + } if (m.timeout) { const t = m.timeout.remaining / m.timeout.initial; @@ -253,13 +268,13 @@ function draw_message(m: MessageData, server?: boolean) { ctx.translate(0, -1) ctx.textAlign = "center" - ctx.font = "15px " + (server ? "monospace" : "sans-serif") - if (!server) ctx.scale(2 / camera_scale, 2 / camera_scale) + ctx.font = "15px " + (world ? "sans-serif" : "monospace") + if (world) ctx.scale(2 / camera_scale, 2 / camera_scale) const lines = message_str(m.inner).split("\n") const w = lines.reduce((a, v) => Math.max(a, ctx.measureText(v).width), 0) + 10 - if (!server) ctx.translate(0, -lines.length * 15 / 2) + if (world) ctx.translate(0, -lines.length * 15 / 2) ctx.fillStyle = MESSAGE_BG[m.style] ctx.beginPath() @@ -282,7 +297,7 @@ function draw_global_message() { ctx.save() ctx.translate(canvas.width / 2, canvas.height / 6) ctx.scale(2, 2) - draw_message(global_message, true) + draw_message(global_message, false) ctx.restore() } |