dead chat: send fixes and more message types
This commit is contained in:
parent
f126cc8d09
commit
241420757e
|
|
@ -39,7 +39,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
message::{
|
message::{
|
||||||
CharacterState, ClientDeadChat, Identification, ServerToClientMessage,
|
CharacterState, ClientDeadChat, Identification, ServerToClientMessage,
|
||||||
dead::DeadChatMessage,
|
dead::{DeadChatContent, DeadChatMessage},
|
||||||
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
host::{HostDayMessage, HostGameMessage, HostNightMessage, ServerToHostMessage},
|
||||||
night::ActionResponse,
|
night::ActionResponse,
|
||||||
},
|
},
|
||||||
|
|
@ -72,6 +72,10 @@ impl Game {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn started(&self) -> DateTime<Utc> {
|
||||||
|
self.started
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn village(&self) -> &Village {
|
pub const fn village(&self) -> &Village {
|
||||||
match &self.state {
|
match &self.state {
|
||||||
GameState::Day { village, marked: _ } => village,
|
GameState::Day { village, marked: _ } => village,
|
||||||
|
|
@ -91,10 +95,12 @@ impl Game {
|
||||||
match message {
|
match message {
|
||||||
ClientDeadChat::Send(message) => {
|
ClientDeadChat::Send(message) => {
|
||||||
let msg = DeadChatMessage {
|
let msg = DeadChatMessage {
|
||||||
message,
|
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
from: char.identity(),
|
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
|
message: DeadChatContent::PlayerMessage {
|
||||||
|
message,
|
||||||
|
from: char.identity(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
GameState::Day { village, .. } => {
|
GameState::Day { village, .. } => {
|
||||||
|
|
|
||||||
|
|
@ -184,16 +184,17 @@ impl Village {
|
||||||
return Ok(Some(game_over));
|
return Ok(Some(game_over));
|
||||||
}
|
}
|
||||||
self.time = self.time.next();
|
self.time = self.time.next();
|
||||||
self.set_dead_chat_dead();
|
self.dead_chat_time_transition();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_dead_chat_dead(&mut self) {
|
fn dead_chat_time_transition(&mut self) {
|
||||||
self.dead_chat
|
self.dead_chat.set_dead(
|
||||||
.set_dead(self.characters.iter().filter_map(|c| {
|
self.characters
|
||||||
c.died_to()
|
.iter()
|
||||||
.map(|died_to| (died_to.date_time(), c.character_id()))
|
.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> {
|
pub fn to_day(&mut self) -> Result<GameTime> {
|
||||||
|
|
@ -201,7 +202,7 @@ impl Village {
|
||||||
return Err(GameError::AlreadyDaytime);
|
return Err(GameError::AlreadyDaytime);
|
||||||
}
|
}
|
||||||
self.time = self.time.next();
|
self.time = self.time.next();
|
||||||
self.set_dead_chat_dead();
|
self.dead_chat_time_transition();
|
||||||
Ok(self.time)
|
Ok(self.time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,13 @@ use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
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>;
|
type Result<T> = core::result::Result<T, GameError>;
|
||||||
|
|
||||||
|
|
@ -31,9 +37,18 @@ pub struct DeadChat {
|
||||||
|
|
||||||
impl DeadChat {
|
impl DeadChat {
|
||||||
pub fn new() -> Self {
|
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 {
|
Self {
|
||||||
|
messages,
|
||||||
deaths: Vec::new(),
|
deaths: Vec::new(),
|
||||||
messages: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +66,7 @@ impl DeadChat {
|
||||||
if !self
|
if !self
|
||||||
.deaths
|
.deaths
|
||||||
.iter()
|
.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);
|
return Err(GameError::NotDead);
|
||||||
}
|
}
|
||||||
|
|
@ -63,8 +78,57 @@ impl DeadChat {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dead(&mut self, dead: impl Iterator<Item = (GameTime, CharacterId)>) {
|
pub fn set_dead(
|
||||||
self.deaths = dead.collect();
|
&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]> {
|
pub fn get_since(&self, t: DateTime<Utc>, character: CharacterId) -> Box<[DeadChatMessage]> {
|
||||||
|
|
@ -85,11 +149,7 @@ impl DeadChat {
|
||||||
.flatten()
|
.flatten()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Box<_>>();
|
.collect::<Box<_>>();
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
let orig_msg = messages.clone();
|
|
||||||
messages.sort_by_key(|m| m.timestamp);
|
messages.sort_by_key(|m| m.timestamp);
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
assert_eq!(orig_msg, messages);
|
|
||||||
|
|
||||||
messages
|
messages
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +158,36 @@ impl DeadChat {
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct DeadChatMessage {
|
pub struct DeadChatMessage {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub from: CharacterIdentity,
|
|
||||||
pub timestamp: DateTime<Utc>,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ impl GameRunner {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Ok(Message::Client(IdentifiedClientMessage {
|
Ok(Message::Client(IdentifiedClientMessage {
|
||||||
identity: Identification { player_id, public },
|
identity: Identification { player_id, .. },
|
||||||
update: ClientUpdate::Message(ClientMessage::GetState),
|
update: ClientUpdate::Message(ClientMessage::GetState),
|
||||||
})) => {
|
})) => {
|
||||||
let Some(char) = self.game.village().character_by_player_id(player_id) else {
|
let Some(char) = self.game.village().character_by_player_id(player_id) else {
|
||||||
|
|
@ -364,14 +364,18 @@ impl GameRunner {
|
||||||
.village()
|
.village()
|
||||||
.dead_characters()
|
.dead_characters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|c| {
|
.filter_map(|c| c.died_to().map(|_| (c.character_id(), c.player_id())));
|
||||||
c.died_to()
|
|
||||||
.and_then(|d| (d.date_time() == pre_time).then_some(c.player_id()))
|
for (char, player) in newly_dead {
|
||||||
})
|
let msgs = self
|
||||||
.collect::<Box<_>>();
|
.game
|
||||||
self.joined_players
|
.village()
|
||||||
.send_to(&newly_dead, ServerToClientMessage::DeadChat(Box::new([])))
|
.dead_chat()
|
||||||
.await;
|
.get_since(self.game.started(), char);
|
||||||
|
self.joined_players
|
||||||
|
.send_to(&[player], ServerToClientMessage::DeadChat(msgs))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2995,6 +2995,16 @@ dialog {
|
||||||
.message-content {
|
.message-content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-change {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,6 @@ async fn worker(mut recv: Receiver<HostMessage>, scope: Scope<Host>) {
|
||||||
Ok(ServerToHostMessage::Error(GameError::AwaitingResponse)) => {}
|
Ok(ServerToHostMessage::Error(GameError::AwaitingResponse)) => {}
|
||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
log::debug!("got message: {:?}", msg.title());
|
log::debug!("got message: {:?}", msg.title());
|
||||||
log::trace!("message content: {msg:?}");
|
|
||||||
scope.send_message::<HostEvent>(msg.into())
|
scope.send_message::<HostEvent>(msg.into())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -324,7 +323,6 @@ impl Component for Host {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
log::trace!("state: {:?}", self.state);
|
|
||||||
let content = match self.state.clone() {
|
let content = match self.state.clone() {
|
||||||
HostState::ScreenOverrides { .. } => {
|
HostState::ScreenOverrides { .. } => {
|
||||||
let send = {
|
let send = {
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,16 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use chrono_humanize::Humanize;
|
use chrono_humanize::Humanize;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||||
use web_sys::{HtmlElement, HtmlInputElement};
|
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)]
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
pub struct DeadChatProperties {
|
pub struct DeadChatProperties {
|
||||||
|
|
@ -32,19 +36,8 @@ pub fn DeadChat(DeadChatProperties { on_send, messages }: &DeadChatProperties) -
|
||||||
let messages = messages
|
let messages = messages
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| {
|
.map(|m| {
|
||||||
let DeadChatMessage {
|
|
||||||
from,
|
|
||||||
timestamp,
|
|
||||||
message,
|
|
||||||
..
|
|
||||||
} = m;
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<li class="message">
|
<ChatMessage message={m.clone()} />
|
||||||
<Timestamp timestamp={*timestamp} />
|
|
||||||
<DeadChatIdent ident={from.clone().into_public()}/>
|
|
||||||
<span class="message-content">{message.clone()}</span>
|
|
||||||
</li>
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Html>();
|
.collect::<Html>();
|
||||||
|
|
@ -174,3 +167,53 @@ pub fn Timestamp(TimestampProps { timestamp }: &TimestampProps) -> Html {
|
||||||
</span>
|
</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>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue