protocol, console, server: receive chat messages (#479)

Implements support for receiving chat messages from the server, the
first step towards #63 chat support. Long way to go, but is a start.

* server: handle ServerMessage_* packets, on_servermessage

* format: get the with arguments of translations
* format: hardcode chat types
* format: support bare strings, used in chat.type.text
* format: use extra text if present, fixes missing username on 1.16.5

* console/main/server: activate on receiving chat messages
This commit is contained in:
iceiix 2021-01-24 17:41:16 -08:00 committed by GitHub
parent 6b961622aa
commit 3e3bcdc5da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 4 deletions

View File

@ -44,10 +44,42 @@ impl Component {
} else if v.get("text").is_some() {
Component::Text(TextComponent::from_value(v, modifier))
} else if v.get("translate").is_some() {
// TODO: translations
Component::Text(TextComponent::new(
v.get("translate").unwrap().as_str().unwrap(),
))
let translate_key = v.get("translate").unwrap().as_str().unwrap();
if let Some(serde_json::Value::Array(args)) = v.get("with") {
// TODO: recursively build components, avoid throwing away all but "text"
let text_args: Vec<&str> = args
.iter()
.map(|v| {
if let serde_json::Value::Object(obj) = v {
// Usernames might be in "extra":["text":"foo"] and "text":"" empty for
// some reason; use extra instead if present TODO: use both
if let Some(serde_json::Value::Array(extra)) = obj.get("extra") {
if let Some(item) = extra.get(0) {
if let Some(text) = item.get("text") {
return text.as_str().unwrap();
}
}
}
obj.get("text").unwrap().as_str().unwrap()
} else {
v.as_str().unwrap()
}
})
.collect();
// TODO: translations, https://wiki.vg/Chat#Translation_component
Component::Text(TextComponent::new(
match translate_key {
"chat.type.text" => format!("<{}> {}", text_args[0], text_args[1]),
"chat.type.announcement" => format!("[{}] {}", text_args[0], text_args[1]),
_ => format!("unhandled: {}", translate_key),
}
.as_str(),
))
} else {
// TODO
Component::Text(TextComponent::new(translate_key))
}
} else {
modifier.color = Some(Color::RGB(255, 0, 0));
Component::Text(TextComponent {

View File

@ -290,6 +290,10 @@ impl Console {
self.active = !self.active;
}
pub fn activate(&mut self) {
self.active = true;
}
pub fn tick(
&mut self,
ui_container: &mut ui::Container,

View File

@ -559,6 +559,12 @@ fn tick_all(
game.screen_sys
.tick(delta, &mut game.renderer, &mut ui_container);
if let Some(received_chat_at) = game.server.received_chat_at {
if Instant::now().duration_since(received_chat_at).as_secs() < 5 {
game.console.lock().unwrap().activate()
// TODO: automatically deactivate the console after inactivity
}
}
game.console
.lock()
.unwrap()

View File

@ -25,6 +25,7 @@ use crate::types::Gamemode;
use crate::world;
use crate::world::block;
use cgmath::prelude::*;
use instant::Instant;
use log::{debug, error, info, warn};
use rand::{self, Rng};
use std::collections::HashMap;
@ -74,6 +75,7 @@ pub struct Server {
tick_timer: f64,
entity_tick_timer: f64,
pub received_chat_at: Option<Instant>,
sun_model: Option<sun::SunModel>,
target_info: target::Info,
@ -491,6 +493,7 @@ impl Server {
tick_timer: 0.0,
entity_tick_timer: 0.0,
received_chat_at: None,
sun_model: None,
target_info: target::Info::new(),
@ -620,6 +623,9 @@ impl Server {
UpdateSign_u16 => on_sign_update_u16,
PlayerInfo => on_player_info,
PlayerInfo_String => on_player_info_string,
ServerMessage_NoPosition => on_servermessage_noposition,
ServerMessage_Position => on_servermessage_position,
ServerMessage_Sender => on_servermessage_sender,
Disconnect => on_disconnect,
// Entities
EntityDestroy => on_entity_destroy,
@ -1851,6 +1857,31 @@ impl Server {
}
}
fn on_servermessage_noposition(
&mut self,
m: packet::play::clientbound::ServerMessage_NoPosition,
) {
self.on_servermessage(&m.message, None, None);
}
fn on_servermessage_position(&mut self, m: packet::play::clientbound::ServerMessage_Position) {
self.on_servermessage(&m.message, Some(m.position), None);
}
fn on_servermessage_sender(&mut self, m: packet::play::clientbound::ServerMessage_Sender) {
self.on_servermessage(&m.message, Some(m.position), Some(m.sender));
}
fn on_servermessage(
&mut self,
message: &format::Component,
_position: Option<u8>,
_sender: Option<protocol::UUID>,
) {
info!("Received chat message: {}", message);
self.received_chat_at = Some(Instant::now());
}
fn load_block_entities(&mut self, block_entities: Vec<Option<crate::nbt::NamedTag>>) {
for optional_block_entity in block_entities {
if let Some(block_entity) = optional_block_entity {