dead chat: send fixes and more message types

This commit is contained in:
emilis 2026-02-03 00:19:14 +00:00
parent f126cc8d09
commit 241420757e
No known key found for this signature in database
7 changed files with 198 additions and 47 deletions

View File

@ -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<Utc> {
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, .. } => {

View File

@ -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<GameTime> {
@ -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)
}

View File

@ -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<T> = core::result::Result<T, GameError>;
@ -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<Item = (GameTime, CharacterId)>) {
self.deaths = dead.collect();
pub fn set_dead(
&mut self,
dead: impl Iterator<Item = (GameTime, Character)> + Clone,
time: GameTime,
) {
let newly_dead = dead
.clone()
.filter_map(|(t, c)| (t.next() == time).then_some(c))
.collect::<Box<_>>();
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<Utc>, character: CharacterId) -> Box<[DeadChatMessage]> {
@ -85,11 +149,7 @@ impl DeadChat {
.flatten()
.cloned()
.collect::<Box<_>>();
#[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<Utc>,
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,
}
}
}

View File

@ -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::<Box<_>>();
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
}

View File

@ -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;
}
}

View File

@ -178,7 +178,6 @@ async fn worker(mut recv: Receiver<HostMessage>, scope: Scope<Host>) {
Ok(ServerToHostMessage::Error(GameError::AwaitingResponse)) => {}
Ok(msg) => {
log::debug!("got message: {:?}", msg.title());
log::trace!("message content: {msg:?}");
scope.send_message::<HostEvent>(msg.into())
}
Err(err) => {
@ -324,7 +323,6 @@ impl Component for Host {
}
fn view(&self, _ctx: &Context<Self>) -> Html {
log::trace!("state: {:?}", self.state);
let content = match self.state.clone() {
HostState::ScreenOverrides { .. } => {
let send = {

View File

@ -14,12 +14,16 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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! {
<li class="message">
<Timestamp timestamp={*timestamp} />
<DeadChatIdent ident={from.clone().into_public()}/>
<span class="message-content">{message.clone()}</span>
</li>
<ChatMessage message={m.clone()} />
}
})
.collect::<Html>();
@ -174,3 +167,53 @@ pub fn Timestamp(TimestampProps { timestamp }: &TimestampProps) -> Html {
</span>
}
}
#[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! {
<li class="message">
<Timestamp timestamp={*timestamp} />
<DeadChatIdent ident={from.clone().into_public()}/>
<span class="message-content">{message.clone()}</span>
</li>
}
}
DeadChatContent::Death { character, cause } => {
html! {
<li class="message">
<Timestamp timestamp={*timestamp} />
<Icon source={IconSource::Skull} icon_type={IconType::Fit}/>
<DeadChatIdent ident={character.clone().into_public()}/>
<span class="message-content">
{"died to "}
// <DiedToSpan died_to={cause.title()}/>
{cause.title().to_string()}
</span>
</li>
}
}
DeadChatContent::TimeChange(time) => {
html! {
<li class="message">
<span class="time-change">{time.to_string()}</span>
</li>
}
}
}
}