diff --git a/werewolves-proto/src/game/mod.rs b/werewolves-proto/src/game/mod.rs index 11ed4c7..543cd1d 100644 --- a/werewolves-proto/src/game/mod.rs +++ b/werewolves-proto/src/game/mod.rs @@ -39,7 +39,7 @@ use crate::{ }, message::{ CharacterState, ClientDeadChat, Identification, ServerToClientMessage, - dead::DeadChatMessage, + dead::{DeadChatContent, DeadChatMessage}, host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage}, night::ActionResponse, }, @@ -72,6 +72,10 @@ impl Game { }) } + pub const fn started(&self) -> DateTime { + self.started + } + pub const fn village(&self) -> &Village { match &self.state { GameState::Day { village, marked: _ } => village, @@ -91,10 +95,12 @@ impl Game { match message { ClientDeadChat::Send(message) => { let msg = DeadChatMessage { - message, id: Uuid::new_v4(), - from: char.identity(), timestamp: Utc::now(), + message: DeadChatContent::PlayerMessage { + message, + from: char.identity(), + }, }; match &mut self.state { GameState::Day { village, .. } => { diff --git a/werewolves-proto/src/game/village.rs b/werewolves-proto/src/game/village.rs index 1010322..1446117 100644 --- a/werewolves-proto/src/game/village.rs +++ b/werewolves-proto/src/game/village.rs @@ -184,16 +184,17 @@ impl Village { return Ok(Some(game_over)); } self.time = self.time.next(); - self.set_dead_chat_dead(); + self.dead_chat_time_transition(); Ok(None) } - fn set_dead_chat_dead(&mut self) { - self.dead_chat - .set_dead(self.characters.iter().filter_map(|c| { - c.died_to() - .map(|died_to| (died_to.date_time(), c.character_id())) - })); + fn dead_chat_time_transition(&mut self) { + self.dead_chat.set_dead( + self.characters + .iter() + .filter_map(|c| c.died_to().map(|died_to| (died_to.date_time(), c.clone()))), + self.time, + ) } pub fn to_day(&mut self) -> Result { @@ -201,7 +202,7 @@ impl Village { return Err(GameError::AlreadyDaytime); } self.time = self.time.next(); - self.set_dead_chat_dead(); + self.dead_chat_time_transition(); Ok(self.time) } diff --git a/werewolves-proto/src/message/dead.rs b/werewolves-proto/src/message/dead.rs index 0555a15..0a76eaf 100644 --- a/werewolves-proto/src/message/dead.rs +++ b/werewolves-proto/src/message/dead.rs @@ -19,7 +19,13 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{character::CharacterId, error::GameError, game::GameTime, message::CharacterIdentity}; +use crate::{ + character::{Character, CharacterId}, + diedto::DiedTo, + error::GameError, + game::GameTime, + message::CharacterIdentity, +}; type Result = core::result::Result; @@ -31,9 +37,18 @@ pub struct DeadChat { impl DeadChat { pub fn new() -> Self { + let mut messages = HashMap::new(); + messages.insert( + GameTime::Night { number: 0 }, + vec![DeadChatMessage { + id: Uuid::new_v4(), + timestamp: Utc::now(), + message: DeadChatContent::TimeChange(GameTime::Night { number: 0 }), + }], + ); Self { + messages, deaths: Vec::new(), - messages: HashMap::new(), } } @@ -51,7 +66,7 @@ impl DeadChat { if !self .deaths .iter() - .any(|(t, ch)| time >= *t && *ch == msg.from.character_id) + .any(|(t, ch)| time >= *t && msg.message.is_from_character(*ch)) { return Err(GameError::NotDead); } @@ -63,8 +78,57 @@ impl DeadChat { Ok(()) } - pub fn set_dead(&mut self, dead: impl Iterator) { - self.deaths = dead.collect(); + pub fn set_dead( + &mut self, + dead: impl Iterator + Clone, + time: GameTime, + ) { + let newly_dead = dead + .clone() + .filter_map(|(t, c)| (t.next() == time).then_some(c)) + .collect::>(); + self.deaths = dead.clone().map(|(t, c)| (t, c.character_id())).collect(); + let Some(prev) = time.previous() else { + return; + }; + let messages = if let Some(messages) = self.messages.get_mut(&prev) { + messages + } else { + self.messages.insert( + prev, + vec![DeadChatMessage { + id: Uuid::new_v4(), + timestamp: Utc::now(), + message: DeadChatContent::TimeChange(prev), + }], + ); + self.messages.get_mut(&prev).unwrap() + }; + for dead in newly_dead { + let Some(died_to) = dead.died_to() else { + continue; + }; + messages.push(DeadChatMessage { + id: Uuid::new_v4(), + timestamp: Utc::now(), + message: DeadChatContent::Death { + character: dead.identity(), + cause: died_to.clone(), + }, + }); + } + messages.sort_by_key(|c| c.timestamp); + let id = Uuid::new_v4(); + if let Some(existing) = self.messages.insert( + time, + vec![DeadChatMessage { + id, + timestamp: Utc::now(), + message: DeadChatContent::TimeChange(time), + }], + ) { + log::warn!("replaced: {existing:?}"); + } } pub fn get_since(&self, t: DateTime, character: CharacterId) -> Box<[DeadChatMessage]> { @@ -85,11 +149,7 @@ impl DeadChat { .flatten() .cloned() .collect::>(); - #[cfg(debug_assertions)] - let orig_msg = messages.clone(); messages.sort_by_key(|m| m.timestamp); - #[cfg(debug_assertions)] - assert_eq!(orig_msg, messages); messages } @@ -98,7 +158,36 @@ impl DeadChat { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct DeadChatMessage { pub id: Uuid, - pub from: CharacterIdentity, pub timestamp: DateTime, - pub message: String, + pub message: DeadChatContent, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum DeadChatContent { + PlayerMessage { + from: CharacterIdentity, + message: String, + }, + Death { + character: CharacterIdentity, + cause: DiedTo, + }, + TimeChange(GameTime), +} + +impl DeadChatContent { + pub fn is_from_character(&self, character_id: CharacterId) -> bool { + match self { + DeadChatContent::PlayerMessage { from, .. } => from.character_id == character_id, + DeadChatContent::Death { character, .. } => character.character_id == character_id, + DeadChatContent::TimeChange(_) => false, + } + } + + pub const fn message(&self) -> Option<&str> { + match self { + DeadChatContent::PlayerMessage { message, .. } => Some(message.as_str()), + _ => None, + } + } } diff --git a/werewolves-server/src/game.rs b/werewolves-server/src/game.rs index 047b7b9..b1d1dc9 100644 --- a/werewolves-server/src/game.rs +++ b/werewolves-server/src/game.rs @@ -294,7 +294,7 @@ impl GameRunner { return None; } Ok(Message::Client(IdentifiedClientMessage { - identity: Identification { player_id, public }, + identity: Identification { player_id, .. }, update: ClientUpdate::Message(ClientMessage::GetState), })) => { let Some(char) = self.game.village().character_by_player_id(player_id) else { @@ -364,14 +364,18 @@ impl GameRunner { .village() .dead_characters() .into_iter() - .filter_map(|c| { - c.died_to() - .and_then(|d| (d.date_time() == pre_time).then_some(c.player_id())) - }) - .collect::>(); - self.joined_players - .send_to(&newly_dead, ServerToClientMessage::DeadChat(Box::new([]))) - .await; + .filter_map(|c| c.died_to().map(|_| (c.character_id(), c.player_id()))); + + for (char, player) in newly_dead { + let msgs = self + .game + .village() + .dead_chat() + .get_since(self.game.started(), char); + self.joined_players + .send_to(&[player], ServerToClientMessage::DeadChat(msgs)) + .await; + } } None } diff --git a/werewolves/index.scss b/werewolves/index.scss index e97093b..071df58 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -2995,6 +2995,16 @@ dialog { .message-content { flex-grow: 1; font-size: 1.2em; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: baseline; + gap: 1ch; + } + + .time-change { + width: 100%; + text-align: center; } } diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index 416f2dd..2535279 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -178,7 +178,6 @@ async fn worker(mut recv: Receiver, scope: Scope) { Ok(ServerToHostMessage::Error(GameError::AwaitingResponse)) => {} Ok(msg) => { log::debug!("got message: {:?}", msg.title()); - log::trace!("message content: {msg:?}"); scope.send_message::(msg.into()) } Err(err) => { @@ -324,7 +323,6 @@ impl Component for Host { } fn view(&self, _ctx: &Context) -> Html { - log::trace!("state: {:?}", self.state); let content = match self.state.clone() { HostState::ScreenOverrides { .. } => { let send = { diff --git a/werewolves/src/components/chat/deadchat.rs b/werewolves/src/components/chat/deadchat.rs index 78afc83..992ce00 100644 --- a/werewolves/src/components/chat/deadchat.rs +++ b/werewolves/src/components/chat/deadchat.rs @@ -14,12 +14,16 @@ // along with this program. If not, see . use chrono::{DateTime, Utc}; use chrono_humanize::Humanize; -use std::{cell::RefCell, rc::Rc}; use yew::prelude::*; use wasm_bindgen::{JsCast, UnwrapThrowExt}; use web_sys::{HtmlElement, HtmlInputElement}; -use werewolves_proto::message::{PublicIdentity, dead::DeadChatMessage}; +use werewolves_proto::message::{ + PublicIdentity, + dead::{DeadChatContent, DeadChatMessage}, +}; + +use crate::components::{Icon, IconSource, IconType, attributes::DiedToSpan}; #[derive(Debug, Clone, PartialEq, Properties)] pub struct DeadChatProperties { @@ -32,19 +36,8 @@ pub fn DeadChat(DeadChatProperties { on_send, messages }: &DeadChatProperties) - let messages = messages .iter() .map(|m| { - let DeadChatMessage { - from, - timestamp, - message, - .. - } = m; - html! { -
  • - - - {message.clone()} -
  • + } }) .collect::(); @@ -174,3 +167,53 @@ pub fn Timestamp(TimestampProps { timestamp }: &TimestampProps) -> Html { } } + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct ChatMessageProps { + pub message: DeadChatMessage, +} + +#[function_component] +pub fn ChatMessage( + ChatMessageProps { + message: + DeadChatMessage { + id: _, + timestamp, + message, + }, + }: &ChatMessageProps, +) -> Html { + match message { + DeadChatContent::PlayerMessage { from, message } => { + html! { +
  • + + + {message.clone()} +
  • + } + } + DeadChatContent::Death { character, cause } => { + html! { +
  • + + + + + {"died to "} + // + {cause.title().to_string()} + +
  • + } + } + DeadChatContent::TimeChange(time) => { + html! { +
  • + {time.to_string()} +
  • + } + } + } +}