diff options
-rw-r--r-- | server/src/customer/mod.rs | 17 | ||||
-rw-r--r-- | server/src/game.rs | 24 | ||||
-rw-r--r-- | server/src/protocol.rs | 3 | ||||
-rw-r--r-- | server/src/state.rs | 32 | ||||
-rw-r--r-- | test-client/main.ts | 22 | ||||
-rw-r--r-- | test-client/protocol.ts | 6 | ||||
-rw-r--r-- | test-client/visual.ts | 46 |
7 files changed, 121 insertions, 29 deletions
diff --git a/server/src/customer/mod.rs b/server/src/customer/mod.rs index d1f49655..92e6e94c 100644 --- a/server/src/customer/mod.rs +++ b/server/src/customer/mod.rs @@ -140,6 +140,7 @@ impl DemandState { id, PacketS::Communicate { message: Some(Message::Item(data.demand(demand).from)), + persist: true, }, )); p.state = CustomerState::Waiting { @@ -157,7 +158,13 @@ impl DemandState { debug!("{id:?} waiting"); *timeout -= dt; if *timeout <= 0. { - packets_out.push((id, PacketS::Communicate { message: None })); + packets_out.push(( + id, + PacketS::Communicate { + message: None, + persist: true, + }, + )); let path = find_path( &self.walkable, p.movement.position.as_ivec2(), @@ -191,7 +198,13 @@ impl DemandState { } }); if let Some(pos) = demand_pos { - packets_out.push((id, PacketS::Communicate { message: None })); + packets_out.push(( + id, + PacketS::Communicate { + persist: true, + message: None, + }, + )); for edge in [true, false] { packets_out.push((id, PacketS::Interact { pos, edge })) } diff --git a/server/src/game.rs b/server/src/game.rs index 72f653c4..4f609608 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -56,7 +56,7 @@ pub struct Player { pub last_position_ts: Instant, pub interacting: Option<IVec2>, pub item: Option<Item>, - pub communicate: Option<Message>, + pub communicate_persist: Option<Message>, } pub struct Game { @@ -127,7 +127,7 @@ impl Game { last_position_ts: Instant::now(), character, position: self.data.chef_spawn, - communicate: None, + communicate_persist: None, interacting: None, name: name.clone(), }, @@ -167,10 +167,11 @@ impl Game { item: Some(item.kind), }) } - if let Some(c) = &player.communicate { + if let Some(c) = &player.communicate_persist { out.push(PacketC::Communicate { player: id, message: Some(c.to_owned()), + persist: true, }) } } @@ -223,7 +224,7 @@ impl Game { last_position_ts: Instant::now(), character, position, - communicate: None, + communicate_persist: None, interacting: None, name: name.clone(), }, @@ -392,13 +393,18 @@ impl Game { player.interacting = if edge { Some(pos) } else { None }; } - PacketS::Communicate { message } => { + PacketS::Communicate { message, persist } => { info!("{player:?} message {message:?}"); - if let Some(player) = self.players.get_mut(&player) { - player.communicate = message.clone() + if persist { + if let Some(player) = self.players.get_mut(&player) { + player.communicate_persist = message.clone() + } } - self.packet_out - .push_back(PacketC::Communicate { player, message }) + self.packet_out.push_back(PacketC::Communicate { + player, + message, + persist, + }) } PacketS::ReplaceHand { item } => { let pdata = self diff --git a/server/src/protocol.rs b/server/src/protocol.rs index 24ac2a11..6ed34a65 100644 --- a/server/src/protocol.rs +++ b/server/src/protocol.rs @@ -58,9 +58,11 @@ pub enum PacketS { }, Communicate { message: Option<Message>, + persist: bool, }, #[serde(skip)] + /// For internal use only ReplaceHand { item: Option<ItemIndex>, }, @@ -130,6 +132,7 @@ pub enum PacketC { Communicate { player: PlayerID, message: Option<Message>, + persist: bool, }, Score { points: i64, diff --git a/server/src/state.rs b/server/src/state.rs index ae3388b6..da05f33a 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -21,6 +21,9 @@ enum Command { #[arg(default_value = "small-default-default")] spec: String, }, + Effect { + name: String, + }, End, } @@ -45,13 +48,17 @@ impl State { pub async fn packet_in(&mut self, player: PlayerID, packet: PacketS) -> Result<()> { match &packet { PacketS::Communicate { - message: Some(Message::Text(message)), - } if let Some(command) = message.strip_prefix("/") => { - self.handle_command(Command::try_parse_from( - shlex::split(command) - .ok_or(anyhow!("quoting invalid"))? - .into_iter(), - )?) + message: Some(Message::Text(text)), + persist: false, + } if let Some(command) = text.strip_prefix("/") => { + self.handle_command( + player, + Command::try_parse_from( + shlex::split(command) + .ok_or(anyhow!("quoting invalid"))? + .into_iter(), + )?, + ) .await?; return Ok(()); } @@ -61,7 +68,7 @@ impl State { Ok(()) } - async fn handle_command(&mut self, command: Command) -> Result<()> { + async fn handle_command(&mut self, player: PlayerID, command: Command) -> Result<()> { match command { Command::Start { spec } => { let data = self.index.generate(spec)?; @@ -71,6 +78,15 @@ impl State { self.game .load(self.index.generate("lobby-none-none".to_string())?); } + Command::Effect { name } => { + self.tx + .send(PacketC::Communicate { + player, + message: Some(Message::Effect(name)), + persist: false, + }) + .ok(); + } } Ok(()) } diff --git a/test-client/main.ts b/test-client/main.ts index 72e88c90..e3185c67 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -78,6 +78,7 @@ export interface PlayerData extends MovementBase { character: number, anim_position: V2, message?: MessageData, + message_clear?: number, } export interface TileData { @@ -98,6 +99,8 @@ export const items_removed = new Set<ItemData>() export let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0], tile_collide: [], tile_interact: [] } +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 @@ -191,8 +194,10 @@ 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 } - else player.message = undefined + if (p.persist && !p.message) player.message = undefined + if (!p.persist) player.message_clear = setTimeout(() => delete player.message, 3000) break; } case "score": @@ -201,8 +206,14 @@ function packet(p: PacketC) { points = p.points 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) console.warn(p.message) break; + case "set_ingame": + console.log(`ingame ${p.state}`); + break; default: console.warn("unknown packet", p); } @@ -218,9 +229,9 @@ 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({ type: "communicate", message: { text: "/start default-small-default" } }) - if (down && ev.code == "Numpad2") send({ type: "communicate", message: { text: "/start default-big-default" } }) - if (down && ev.code == "Numpad0") send({ type: "communicate", message: { text: "/end" } }) + if (down && ev.code == "Numpad1") send({ type: "communicate", message: { text: "/start default-small-default" }, persist: false }) + if (down && ev.code == "Numpad2") send({ type: "communicate", message: { text: "/start default-big-default" }, persist: false }) + if (down && ev.code == "Numpad0") send({ type: "communicate", message: { text: "/end" }, persist: false }) if (down) keys_down.add(ev.code) else keys_down.delete(ev.code) } @@ -233,8 +244,7 @@ function close_chat() { } function toggle_chat() { if (chat) { - if (chat.value.length) send({ type: "communicate", message: { text: chat.value } }) - else send({ type: "communicate" }) + if (chat.value.length) send({ type: "communicate", message: { text: chat.value }, persist: false }) chat.remove() canvas.focus() chat = null; diff --git a/test-client/protocol.ts b/test-client/protocol.ts index 94858387..f9f0dbaf 100644 --- a/test-client/protocol.ts +++ b/test-client/protocol.ts @@ -32,7 +32,7 @@ export type PacketS = { type: "join", name: string, character: number } // You join, sent as first packet. | { type: "position", pos: Vec2, rot: number } // Update your position and rotation in radians (0 is -y) | { type: "interact", pos: Vec2, edge: boolean } // Interact with some tile. edge is true when pressing and false when releasing interact button - | { type: "communicate", message?: Message } // Send a message + | { type: "communicate", message?: Message, persist: boolean } // Send a message | { type: "collide", player: PlayerID, force: Vec2 } // Apply force to another player as a result of a collision export type PacketC = @@ -47,10 +47,10 @@ export type PacketC = | { type: "set_player_item", player: PlayerID, item?: ItemIndex } // A player changed their item | { type: "set_active", tile: Vec2, 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 } // A player wants to communicate something, message is null when cleared + | { type: "communicate", player: PlayerID, message?: Message, persist: boolean } // A player wants to communicate something, message is null when cleared | { type: "score", points: number, demands_failed: number, demands_completed: number, } // Supplies information for score OSD | { type: "set_ingame", state: boolean } // Set to false when entering the game or switching maps - | { type: "error", message?: Message } // Your client did something wrong. + | { type: "error", message: string } // Your client did something wrong. export type Message = { item: number } diff --git a/test-client/visual.ts b/test-client/visual.ts index 19866710..a26bab6c 100644 --- a/test-client/visual.ts +++ b/test-client/visual.ts @@ -15,7 +15,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ItemData, MessageData, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, demands_completed, demands_failed, get_interact_target, interact_active_anim, interact_possible_anim, interact_target_anim, items_removed, keys_down, my_id, players, points, tiles } from "./main.ts"; +import { ItemData, MessageData, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, demands_completed, demands_failed, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, items_removed, keys_down, my_id, players, points, tiles } from "./main.ts"; import { PLAYER_SIZE } from "./movement.ts"; import { FALLBACK_TILE, ITEMS, TILES, FALLBACK_ITEM } from "./tiles.ts"; import { V2, ceil_v2, floor_v2 } from "./util.ts"; @@ -74,6 +74,8 @@ export function draw_ingame() { ctx.restore() + draw_global_message() + ctx.fillStyle = "white" ctx.textAlign = "left" ctx.textBaseline = "bottom" @@ -200,6 +202,48 @@ function draw_message(m: MessageData) { for (const c of comps) c(ctx) ctx.translate(0, 1) } + if ("text" in m.inner) { + ctx.translate(0, -1) + + ctx.textAlign = "center" + ctx.font = "15px sans-serif" + ctx.scale(2 / camera_scale, 2 / camera_scale) + const w = ctx.measureText(m.inner.text).width + 30 + + ctx.fillStyle = "#fffa" + ctx.beginPath() + ctx.roundRect(-w / 2, -15, w, 30, 5) + ctx.fill() + + ctx.fillStyle = "black" + ctx.textBaseline = "middle" + ctx.fillText(m.inner.text, 0, 0) + + ctx.translate(0, 1) + } + ctx.restore() +} + +function draw_global_message() { + if (!global_message) return + ctx.save() + ctx.translate(canvas.width / 2, canvas.height / 6) + if ("text" in global_message.inner) { + ctx.font = "20px monospace" + const lines = global_message.inner.text.split("\n") + const w = lines.reduce((a, v) => Math.max(a, ctx.measureText(v).width), 0) + 20 + + ctx.fillStyle = "#fffa" + ctx.beginPath() + ctx.roundRect(-w / 2, -20, w, lines.length * 25 + 20, 5) + ctx.fill() + + ctx.fillStyle = "black" + ctx.textAlign = "left" + ctx.textBaseline = "middle" + for (let i = 0; i < lines.length; i++) + ctx.fillText(lines[i], -w / 2 + 10, i * 25) + } ctx.restore() } |