aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-09-18 15:42:19 +0200
committermetamuffin <metamuffin@disroot.org>2024-09-18 15:42:19 +0200
commitfb6ca2a4b9d78dd80dbdf37b2926d9ede4108e8b (patch)
tree76b1a77dcd3ae29595caaa6423a18b5ce29e32db
parente5fdbaadbf01f14290e70c49482e1dce3c9816d8 (diff)
downloadhurrycurry-fb6ca2a4b9d78dd80dbdf37b2926d9ede4108e8b.tar
hurrycurry-fb6ca2a4b9d78dd80dbdf37b2926d9ede4108e8b.tar.bz2
hurrycurry-fb6ca2a4b9d78dd80dbdf37b2926d9ede4108e8b.tar.zst
translate server messages
-rw-r--r--locale/en.ini15
-rw-r--r--pixel-client/src/game.rs5
-rw-r--r--server/bot/src/main.rs7
-rw-r--r--server/protocol/src/lib.rs8
-rw-r--r--server/replaytool/src/main.rs18
-rw-r--r--server/src/commands.rs10
-rw-r--r--server/src/entity/book.rs3
-rw-r--r--server/src/entity/campaign.rs10
-rw-r--r--server/src/entity/mod.rs4
-rw-r--r--server/src/entity/tutorial.rs4
-rw-r--r--server/src/lib.rs39
-rw-r--r--server/src/main.rs13
-rw-r--r--server/src/server.rs38
-rw-r--r--server/src/state.rs24
-rw-r--r--test-client/main.ts13
-rw-r--r--test-client/protocol.ts3
-rw-r--r--test-client/visual.ts38
17 files changed, 156 insertions, 96 deletions
diff --git a/locale/en.ini b/locale/en.ini
index 73510a7c..83beede5 100644
--- a/locale/en.ini
+++ b/locale/en.ini
@@ -179,6 +179,21 @@ s.tutorial.put_away=Put away this item for later
s.tutorial.put_on=Place on %s
s.tutorial.take=Take %s from here
s.tutorial.wait_finish=...
+s.replay.cannot_join=Replays cannot be joined.
+s.campaign.unlock_condition=To unlock: %n%n%s
+s.state.abort_no_players=Game was aborted due to a lack of players.
+s.state.game_aborted=Game was aborted by %s.
+s.state.overflow_resubscribe=Lagging behind. Some clientbound packets were dropped.
+s.error.conn_too_many_players=Players-per-connection limit exceeded.
+s.error.packet_sender_invalid=Packet sent to a player that is not owned by this connection.
+s.error.no_player=Player does not exist.
+s.error.no_tile=Tile does not exist.
+s.error.already_interacting=already interacting
+s.error.interacting_too_far=interacting too far from player
+s.error.self_interact=Interacting with yourself. This is impossible.
+s.error.customer_interact=You shall not interact with customers.
+s.error.packet_not_supported=Packet not supported in this session.
+s.error.map_load=Map failed to load: %s
unknown298=Signature of the Employer:%nMusterfoods Ltd.%nFrank Miller, Head of HR
unknown312=Signature of the Employee:%n%n%n
unknown33=Automatic
diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs
index 780cde42..07c1fc9b 100644
--- a/pixel-client/src/game.rs
+++ b/pixel-client/src/game.rs
@@ -359,7 +359,7 @@ impl Game {
recipe: RecipeIndex(0),
});
}
- PacketC::ServerMessage { text: _ } => {
+ PacketC::ServerMessage { .. } => {
// TODO
}
PacketC::Score(score) => {
@@ -377,9 +377,6 @@ impl Game {
player.message_persist = message.map(|m| (m, timeout));
}
}
- PacketC::Error { message } => {
- warn!("server error: {message:?}")
- }
_ => (),
}
}
diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs
index d4d21d35..0e6aed36 100644
--- a/server/bot/src/main.rs
+++ b/server/bot/src/main.rs
@@ -78,8 +78,11 @@ fn main() -> Result<()> {
.map(|(_, c)| c())
.unwrap_or_else(|| panic!("unknown algo {:?}", args.algo)),
}),
- PacketC::Error { message } => {
- warn!("server error message: {message}");
+ PacketC::ServerMessage {
+ message,
+ error: true,
+ } => {
+ warn!("server error message: {message:?}");
}
_ => (),
}
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs
index b8f30ce7..fb6ad46e 100644
--- a/server/protocol/src/lib.rs
+++ b/server/protocol/src/lib.rs
@@ -27,7 +27,7 @@ pub use glam;
pub mod movement;
-pub const VERSION: (u32, u32) = (6, 0);
+pub const VERSION: (u32, u32) = (7, 0);
pub const BINCODE_CONFIG: Configuration<LittleEndian, Varint, Limit<4096>> =
standard().with_limit();
@@ -220,7 +220,8 @@ pub enum PacketC {
player: PlayerID,
},
ServerMessage {
- text: String,
+ message: Message,
+ error: bool,
},
ServerHint {
#[bincode(with_serde)]
@@ -232,9 +233,6 @@ pub enum PacketC {
state: bool,
lobby: bool,
},
- Error {
- message: String,
- },
Menu(Menu),
MovementSync {
player: PlayerID,
diff --git a/server/replaytool/src/main.rs b/server/replaytool/src/main.rs
index a6ad70fd..530d1a76 100644
--- a/server/replaytool/src/main.rs
+++ b/server/replaytool/src/main.rs
@@ -19,7 +19,7 @@ use anyhow::{anyhow, Context};
use async_compression::tokio::{bufread::ZstdDecoder, write::ZstdEncoder};
use clap::Parser;
use futures_util::{SinkExt, StreamExt};
-use hurrycurry_protocol::{PacketC, PacketS};
+use hurrycurry_protocol::{Message, PacketC, PacketS};
use log::{debug, info, warn, LevelFilter};
use serde::{Deserialize, Serialize};
use std::{
@@ -32,7 +32,7 @@ use tokio::{
net::TcpListener,
time::sleep,
};
-use tokio_tungstenite::tungstenite::Message;
+use tokio_tungstenite::tungstenite;
#[derive(Parser)]
enum Args {
@@ -121,7 +121,7 @@ async fn main() -> anyhow::Result<()> {
.await?;
'outer: while let Some(Ok(message)) = sock.next().await {
match message {
- Message::Text(line) => {
+ tungstenite::Message::Text(line) => {
let packet: PacketS = match serde_json::from_str(&line) {
Ok(p) => p,
Err(e) => {
@@ -135,7 +135,11 @@ async fn main() -> anyhow::Result<()> {
PacketS::Join { .. } => {
sock.send(tokio_tungstenite::tungstenite::Message::Text(
serde_json::to_string(&PacketC::ServerMessage {
- text: "Replays cannot be joined".to_string(),
+ message: Message::Translation {
+ id: "s.replay.cannot_join".to_owned(),
+ params: vec![],
+ },
+ error: true,
})
.unwrap(),
))
@@ -160,7 +164,7 @@ async fn main() -> anyhow::Result<()> {
x => warn!("unhandled client packet: {x:?}"),
}
}
- Message::Close(_) => break,
+ tungstenite::Message::Close(_) => break,
_ => (),
}
}
@@ -182,7 +186,7 @@ pub async fn do_record(output: &Path, url: &str) -> anyhow::Result<()> {
while let Some(Ok(message)) = sock.next().await {
match message {
- Message::Text(line) => {
+ tungstenite::Message::Text(line) => {
let packet: PacketC = serde_json::from_str(&line).context("invalid packet")?;
debug!("<- {packet:?}");
@@ -206,7 +210,7 @@ pub async fn do_record(output: &Path, url: &str) -> anyhow::Result<()> {
break;
}
}
- Message::Close(_) => break,
+ tungstenite::Message::Close(_) => break,
_ => (),
}
}
diff --git a/server/src/commands.rs b/server/src/commands.rs
index b06d9261..8e6f32c7 100644
--- a/server/src/commands.rs
+++ b/server/src/commands.rs
@@ -18,6 +18,7 @@
use crate::{
entity::{bot::BotDriver, tutorial::Tutorial},
server::Server,
+ trm,
};
use anyhow::{anyhow, bail, Result};
use clap::{Parser, ValueEnum};
@@ -131,14 +132,17 @@ impl Server {
Command::End => {
self.tx
.send(PacketC::ServerMessage {
- text: format!(
- "Game was aborted by {}.",
- self.game
+ message: trm!(
+ "s.state.game_aborted",
+ s = self
+ .game
.players
.get(&player)
.ok_or(anyhow!("player missing"))?
.name
+ .clone()
),
+ error: false,
})
.ok();
self.load(self.index.generate("lobby").await?, None);
diff --git a/server/src/entity/book.rs b/server/src/entity/book.rs
index 9c1192da..11ec847b 100644
--- a/server/src/entity/book.rs
+++ b/server/src/entity/book.rs
@@ -16,6 +16,7 @@
*/
use super::{Entity, EntityContext};
+use crate::TrError;
use anyhow::Result;
use hurrycurry_protocol::{glam::IVec2, Menu, PacketC, PlayerID};
@@ -28,7 +29,7 @@ impl Entity for Book {
c: EntityContext<'_>,
pos: Option<IVec2>,
_player: PlayerID,
- ) -> Result<bool> {
+ ) -> Result<bool, TrError> {
if pos == Some(self.0) {
c.packet_out.push_back(PacketC::Menu(Menu::Book));
return Ok(true);
diff --git a/server/src/entity/campaign.rs b/server/src/entity/campaign.rs
index 934f7542..5d669a4d 100644
--- a/server/src/entity/campaign.rs
+++ b/server/src/entity/campaign.rs
@@ -16,7 +16,7 @@
*/
use super::{Entity, EntityContext};
-use crate::{scoreboard::ScoreboardStore, server::GameServerExt};
+use crate::{scoreboard::ScoreboardStore, server::GameServerExt, trm, TrError};
use anyhow::Result;
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
@@ -79,10 +79,14 @@ impl Entity for Gate {
c: EntityContext<'_>,
pos: Option<IVec2>,
_player: PlayerID,
- ) -> Result<bool> {
+ ) -> Result<bool, TrError> {
if !self.unlocked && pos == Some(self.location) {
c.packet_out.push_back(PacketC::ServerMessage {
- text: format!("To unlock: \n\n{}", self.condition.show(c.scoreboard)),
+ message: trm!(
+ "s.campaign.unlock_condition",
+ s = self.condition.show(c.scoreboard) // TODO localize
+ ),
+ error: false,
});
return Ok(true);
}
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
index f87dbb32..532031d5 100644
--- a/server/src/entity/mod.rs
+++ b/server/src/entity/mod.rs
@@ -25,7 +25,7 @@ pub mod item_portal;
pub mod player_portal;
pub mod tutorial;
-use crate::{data::ItemTileRegistry, scoreboard::ScoreboardStore};
+use crate::{data::ItemTileRegistry, scoreboard::ScoreboardStore, TrError};
use anyhow::{anyhow, Result};
use book::Book;
use campaign::{Gate, GateCondition, Map};
@@ -68,7 +68,7 @@ pub trait Entity {
_c: EntityContext<'_>,
_pos: Option<IVec2>,
_player: PlayerID,
- ) -> Result<bool> {
+ ) -> Result<bool, TrError> {
Ok(false)
}
}
diff --git a/server/src/entity/tutorial.rs b/server/src/entity/tutorial.rs
index fba43fd0..9b2146d2 100644
--- a/server/src/entity/tutorial.rs
+++ b/server/src/entity/tutorial.rs
@@ -1,4 +1,4 @@
-use crate::trm;
+use crate::{trm, TrError};
use super::{Entity, EntityContext};
use anyhow::Result;
@@ -70,7 +70,7 @@ impl Entity for Tutorial {
_c: EntityContext<'_>,
_pos: Option<IVec2>,
_player: PlayerID,
- ) -> Result<bool> {
+ ) -> Result<bool, TrError> {
Ok(false)
}
}
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 3969c67c..306ebd40 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -24,7 +24,7 @@ pub mod scoreboard;
pub mod server;
pub mod state;
-use hurrycurry_protocol::glam::Vec2;
+use hurrycurry_protocol::{glam::Vec2, Message};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConnectionID(pub i64);
@@ -66,3 +66,40 @@ macro_rules! trm_param {
hurrycurry_protocol::Message::Tile($x)
};
}
+
+#[derive(Debug)]
+pub struct TrError {
+ pub id: &'static str,
+ pub params: Vec<Message>,
+}
+impl From<TrError> for Message {
+ fn from(value: TrError) -> Self {
+ Self::Translation {
+ id: value.id.to_owned(),
+ params: value.params,
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! tre {
+ ($id:literal $(, $typ:ident = $param:expr)*) => {
+ crate::TrError {
+ id: $id,
+ params: vec![$(crate::tre_param!($typ, $param)),*]
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! tre_param {
+ (s, $x:expr) => {
+ hurrycurry_protocol::Message::Text($x)
+ };
+ (i, $x:expr) => {
+ hurrycurry_protocol::Message::Item($x)
+ };
+ (t, $x:expr) => {
+ hurrycurry_protocol::Message::Tile($x)
+ };
+}
diff --git a/server/src/main.rs b/server/src/main.rs
index de680200..20d9908d 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -22,7 +22,7 @@ use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG, VERSION};
use hurrycurry_server::{
data::DATA_DIR,
server::{GameServerExt, Server},
- ConnectionID,
+ trm, ConnectionID,
};
use log::{debug, info, trace, warn, LevelFilter};
use std::{
@@ -168,8 +168,8 @@ async fn run(addr: SocketAddr) -> anyhow::Result<()> {
rx = rx.resubscribe();
warn!("Client was lagging; resubscribed: {e}");
PacketC::ServerMessage {
- text: "Lagging behind. Some clientbound packets were dropped."
- .to_string(),
+ message: trm!("s.state.overflow_resubscribe"),
+ error: true,
}
}
}),
@@ -226,9 +226,10 @@ async fn run(addr: SocketAddr) -> anyhow::Result<()> {
let packet_out = match state.write().await.packet_in_outer(id, packet).await {
Ok(packets) => packets,
Err(e) => {
- warn!("Client error: {e}");
- vec![PacketC::Error {
- message: format!("{e}"),
+ warn!("Client error: {e:?}");
+ vec![PacketC::ServerMessage {
+ message: e.into(),
+ error: true,
}]
}
};
diff --git a/server/src/server.rs b/server/src/server.rs
index 848e2bb4..c9961412 100644
--- a/server/src/server.rs
+++ b/server/src/server.rs
@@ -20,9 +20,9 @@ use crate::{
entity::{Entities, EntityContext},
interaction::{interact, tick_slot},
scoreboard::ScoreboardStore,
- ConnectionID,
+ tre, ConnectionID, TrError,
};
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{Context, Result};
use hurrycurry_client_lib::{Game, Involvement, Item, Player, Tile};
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
@@ -341,7 +341,11 @@ impl Server {
self.entities = entities;
}
- pub fn packet_in(&mut self, packet: PacketS, replies: &mut Vec<PacketC>) -> Result<()> {
+ pub fn packet_in(
+ &mut self,
+ packet: PacketS,
+ replies: &mut Vec<PacketC>,
+ ) -> Result<(), TrError> {
match packet {
PacketS::Join {
name,
@@ -362,7 +366,7 @@ impl Server {
.game
.players
.remove(&player)
- .ok_or(anyhow!("player does not exist"))?;
+ .ok_or(tre!("s.error.no_player"))?;
self.game.players_spatial_index.remove_entry(player);
@@ -394,7 +398,7 @@ impl Server {
.game
.players
.get_mut(&player)
- .ok_or(anyhow!("player does not exist"))?;
+ .ok_or(tre!("s.error.no_player"))?;
pd.movement.input(direction, boost);
@@ -437,25 +441,25 @@ impl Server {
.game
.players
.get_mut(&pid)
- .ok_or(anyhow!("player does not exist"))?;
+ .ok_or(tre!("s.error.no_player"))?;
let (pos, edge) = match (pos, player.interacting) {
(None, None) => return Ok(()), // this is silent because of auto release
(None, Some(pos)) => (pos, false),
(Some(pos), None) => (pos, true),
- (Some(_), Some(_)) => bail!("already interacting"),
+ (Some(_), Some(_)) => return Err(tre!("s.error.already_interacting")),
};
let entpos = pos.as_vec2() + Vec2::splat(0.5);
if edge && entpos.distance(player.movement.position) > 2. {
- bail!("interacting too far from player");
+ return Err(tre!("s.error.interacting_too_far"));
}
let tile = self
.game
.tiles
.get_mut(&pos)
- .ok_or(anyhow!("tile does not exist"))?;
+ .ok_or(tre!("s.error.no_tile"))?;
// No going back from here on
@@ -476,10 +480,10 @@ impl Server {
.game
.players
.get_many_mut([&pid, &base_pid])
- .ok_or(anyhow!("Interacting with yourself. This is impossible."))?;
+ .ok_or(tre!("s.error.self_interact"))?;
if this.character < 0 || other.character < 0 {
- bail!("You shall not interact with customers.")
+ return Err(tre!("s.error.customer_interact"));
}
interact(
@@ -501,7 +505,7 @@ impl Server {
.game
.players
.get_mut(&pid)
- .ok_or(anyhow!("The player does not exist"))?;
+ .ok_or(tre!("s.error.no_player"))?;
interact(
&self.game.data,
@@ -548,11 +552,7 @@ impl Server {
})
}
PacketS::ReplaceHand { item, player } => {
- let pdata = self
- .game
- .players
- .get_mut(&player)
- .ok_or(anyhow!("The player does not exist"))?;
+ let pdata = self.game.players.get_mut(&player).ok_or(tre!(""))?;
pdata.item = item.map(|i| Item {
kind: i,
active: None,
@@ -568,7 +568,7 @@ impl Server {
self.game.score.points += score.points;
self.score_changed = true;
}
- PacketS::ReplayTick { .. } => bail!("Packet not supported in this session"),
+ PacketS::ReplayTick { .. } => return Err(tre!("s.error.packet_not_supported")),
}
Ok(())
}
@@ -681,7 +681,7 @@ impl Server {
while let Some(p) = self.packet_loopback.pop_front() {
if let Err(e) = self.packet_in(p, &mut vec![]) {
- warn!("Internal packet errored: {e}");
+ warn!("Internal packet errored: {e:?}");
}
}
diff --git a/server/src/state.rs b/server/src/state.rs
index 09e7031e..9086248a 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -15,8 +15,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::{server::Server, ConnectionID};
-use anyhow::{bail, Result};
+use crate::{server::Server, tre, trm, ConnectionID, TrError};
+use anyhow::Result;
use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID};
use log::{debug, trace};
@@ -41,10 +41,10 @@ impl Server {
&mut self,
conn: ConnectionID,
packet: PacketS,
- ) -> Result<Vec<PacketC>> {
+ ) -> Result<Vec<PacketC>, TrError> {
if let Some(p) = get_packet_player(&packet) {
if !self.connections.entry(conn).or_default().contains(&p) {
- bail!("Packet sent to a player that is not owned by this connection.");
+ return Err(tre!("s.state.packet_sender_invalid"));
}
}
let mut replies = Vec::new();
@@ -58,7 +58,8 @@ impl Server {
Ok(packets) => return Ok(packets),
Err(e) => {
return Ok(vec![PacketC::ServerMessage {
- text: format!("{e}"),
+ message: Message::Text(format!("{e}")), // TODO localize
+ error: true,
}]);
}
}
@@ -68,7 +69,7 @@ impl Server {
}
PacketS::Join { .. } => {
if self.connections.entry(conn).or_default().len() > 8 {
- bail!("Players-per-connection limit exceeded.")
+ return Err(tre!("s.state.conn_too_many_players"));
}
}
_ => (),
@@ -84,10 +85,17 @@ impl Server {
if self.count_chefs() == 0 && !self.game.lobby {
self.tx
.send(PacketC::ServerMessage {
- text: "Game was aborted due to a lack of players".to_string(),
+ message: trm!("s.state.abort_no_players"),
+ error: false,
})
.ok();
- self.load(self.index.generate("lobby").await?, None);
+ self.load(
+ self.index
+ .generate("lobby")
+ .await
+ .map_err(|m| tre!("s.error.map_load", s = format!("{m}")))?,
+ None,
+ );
}
Ok(replies)
}
diff --git a/test-client/main.ts b/test-client/main.ts
index 8eab832d..2f828576 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -235,20 +235,19 @@ function packet(p: PacketC) {
score.points = p.points
score.time_remaining = p.time_remaining ?? null
break;
- case "error":
- 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":
- global_message = { inner: { text: p.text }, anim_size: 0., anim_position: { x: 0, y: 0 }, timeout: { initial: 5, remaining: 5 } }
+ // TODO error -> red
+ global_message = { inner: p.message, anim_size: 0., anim_position: { x: 0, y: 0 }, timeout: { initial: 5, remaining: 5 } }
break;
case "set_ingame":
console.log(`ingame ${p.state}`);
is_lobby = p.lobby
break;
- case "movement_sync":
- players.get(my_id)!.position = last_server_sent_position
+ case "movement_sync": {
+ const me = players.get(my_id)
+ if (me) me.position = last_server_sent_position
break;
+ }
case "server_hint":
if (p.message) server_hints.set(p.position + "", { inner: p.message, anim_size: 0., anim_position: p.position ? { x: p.position[0] + 0.5, y: p.position[1] + 0.5 } : players.get(my_id)!.anim_position })
else server_hints.delete(p.position + "")
diff --git a/test-client/protocol.ts b/test-client/protocol.ts
index 3bceb351..b6ef9046 100644
--- a/test-client/protocol.ts
+++ b/test-client/protocol.ts
@@ -59,13 +59,12 @@ export type PacketC =
| { type: "update_map", tile: Vec2, kind: TileIndex | null, neighbors: [TileIndex | null] } // A map tile was changed
| { type: "communicate", player: PlayerID, message?: Message, timeout?: MessageTimeout } // A player wants to communicate something, message is null when cleared
| { type: "effect", player: PlayerID, name: string } // Player sent an effect
- | { type: "server_message", text: string } // Text message from the server
+ | { type: "server_message", message: Message, error: boolean } // Text message from the server
| { type: "server_hint", message?: Message, position?: Vec2 } // Hint message from server with optional position. Message is unset to clear previous message
| { type: "score" } & Score // Supplies information for score OSD
| { type: "menu" } & Menu // Open a menu on the client-side
| { type: "environment", effects: string[] }
| { type: "set_ingame", state: boolean, lobby: boolean } // Set to false when entering the game or switching maps
- | { type: "error", message: string } // Your client did something wrong.
export type Menu =
{ menu: "book" }
diff --git a/test-client/visual.ts b/test-client/visual.ts
index ece31b8b..d63bee03 100644
--- a/test-client/visual.ts
+++ b/test-client/visual.ts
@@ -221,7 +221,7 @@ function message_str(m: Message): string {
return "[unknown message type]"
}
-function draw_message(m: MessageData) {
+function draw_message(m: MessageData, server?: boolean) {
ctx.save()
ctx.translate(m.anim_position.x, m.anim_position.y)
const scale = Math.min(m.anim_size, 1 - nametag_scale_anim);
@@ -251,20 +251,24 @@ function draw_message(m: MessageData) {
ctx.translate(0, -1)
ctx.textAlign = "center"
- ctx.font = "15px sans-serif"
- ctx.scale(2 / camera_scale, 2 / camera_scale)
+ ctx.font = "15px " + (server ? "monospace" : "sans-serif")
+ if (!server) 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
- const text = message_str(m.inner);
- const w = ctx.measureText(text).width + 30
+ if (!server) ctx.translate(0, -lines.length * 15 / 2)
ctx.fillStyle = "#fffa"
ctx.beginPath()
- ctx.roundRect(-w / 2, -15, w, 30, 5)
+ ctx.roundRect(-w / 2, -5, w, lines.length * 15 + 10, 5)
ctx.fill()
ctx.fillStyle = "black"
- ctx.textBaseline = "middle"
- ctx.fillText(text, 0, 0)
+ ctx.textAlign = "left"
+ ctx.textBaseline = "top"
+ for (let i = 0; i < lines.length; i++)
+ ctx.fillText(lines[i], -w / 2 + 5, i * 15)
ctx.translate(0, 1)
}
@@ -275,22 +279,8 @@ 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.scale(2, 2)
+ draw_message(global_message, true)
ctx.restore()
}