host gets dead chat too
This commit is contained in:
parent
27752727a3
commit
78ecb6c164
|
|
@ -83,6 +83,13 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn dead_chats_since(&mut self, since: DateTime<Utc>) -> Vec<DeadChatMessage> {
|
||||
match &mut self.state {
|
||||
GameState::Day { village, .. } => village.dead_chat_mut().host_get_since(since),
|
||||
GameState::Night { night } => night.dead_chat_mut().host_get_since(since),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_dead_chat_request(
|
||||
&mut self,
|
||||
player_id: PlayerId,
|
||||
|
|
@ -106,7 +113,10 @@ impl Game {
|
|||
GameState::Day { village, .. } => {
|
||||
village.send_dead_chat_message(msg.clone())?
|
||||
}
|
||||
GameState::Night { night } => night.send_dead_chat_message(msg.clone())?,
|
||||
GameState::Night { night } => {
|
||||
let time = night.village().time();
|
||||
night.dead_chat_mut().add(time, msg.clone())?
|
||||
}
|
||||
}
|
||||
Ok(ServerToClientMessage::DeadChatMessage(msg))
|
||||
}
|
||||
|
|
@ -138,6 +148,28 @@ impl Game {
|
|||
|
||||
pub fn process(&mut self, message: HostGameMessage) -> Result<ServerToHostMessage> {
|
||||
match (&mut self.state, message) {
|
||||
(_, HostGameMessage::SendChatMessage(msg)) => {
|
||||
let msg = match &mut self.state {
|
||||
GameState::Day { village, .. } => {
|
||||
let time = village.time();
|
||||
village.dead_chat_mut().add_host_message(time, msg)
|
||||
}
|
||||
GameState::Night { night } => {
|
||||
let time = night.village().time();
|
||||
night.dead_chat_mut().add_host_message(time, msg)
|
||||
}
|
||||
};
|
||||
log::info!("host sent dead chat message: {msg:?}");
|
||||
|
||||
Ok(ServerToHostMessage::DeadChatMessage(msg))
|
||||
}
|
||||
(_, HostGameMessage::GetDeadChatSince(since)) => Ok(ServerToHostMessage::DeadChat(
|
||||
match &mut self.state {
|
||||
GameState::Day { village, .. } => village.dead_chat(),
|
||||
GameState::Night { night } => night.dead_chat(),
|
||||
}
|
||||
.host_get_since(since),
|
||||
)),
|
||||
(GameState::Night { night }, HostGameMessage::SeePlayersWithRoles) => {
|
||||
Ok(ServerToHostMessage::PlayerStates(
|
||||
night
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ use crate::{
|
|||
night::changes::{ChangesLookup, NightChange},
|
||||
},
|
||||
message::{
|
||||
dead::DeadChatMessage,
|
||||
dead::{DeadChat, DeadChatMessage},
|
||||
night::{
|
||||
ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult, ActionType, Visits,
|
||||
},
|
||||
|
|
@ -1224,8 +1224,12 @@ impl Night {
|
|||
&self.village
|
||||
}
|
||||
|
||||
pub fn send_dead_chat_message(&mut self, msg: DeadChatMessage) -> Result<()> {
|
||||
self.village.send_dead_chat_message(msg)
|
||||
pub fn dead_chat(&self) -> &DeadChat {
|
||||
self.village.dead_chat()
|
||||
}
|
||||
|
||||
pub fn dead_chat_mut(&mut self) -> &mut DeadChat {
|
||||
self.village.dead_chat_mut()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
mod apply;
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -65,10 +66,22 @@ impl Village {
|
|||
&self.dead_chat
|
||||
}
|
||||
|
||||
pub const fn dead_chat_mut(&mut self) -> &mut DeadChat {
|
||||
&mut self.dead_chat
|
||||
}
|
||||
|
||||
pub fn send_dead_chat_message(&mut self, msg: DeadChatMessage) -> Result<()> {
|
||||
self.dead_chat.add(self.time, msg)
|
||||
}
|
||||
|
||||
pub fn host_send_dead_chat_message(&mut self, msg: String) -> DeadChatMessage {
|
||||
self.dead_chat.add_host_message(self.time, msg)
|
||||
}
|
||||
|
||||
pub fn host_dead_chat_since(&mut self, since: DateTime<Utc>) -> Vec<DeadChatMessage> {
|
||||
self.dead_chat.host_get_since(since)
|
||||
}
|
||||
|
||||
pub fn settings(&self) -> GameSettings {
|
||||
self.settings.clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ type Result<T> = core::result::Result<T, GameError>;
|
|||
pub struct DeadChat {
|
||||
deaths: Vec<(GameTime, CharacterId)>,
|
||||
messages: HashMap<GameTime, Vec<DeadChatMessage>>,
|
||||
new_messages_from: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl DeadChat {
|
||||
|
|
@ -49,6 +50,7 @@ impl DeadChat {
|
|||
Self {
|
||||
messages,
|
||||
deaths: Vec::new(),
|
||||
new_messages_from: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +65,10 @@ impl DeadChat {
|
|||
}
|
||||
|
||||
pub fn add(&mut self, time: GameTime, msg: DeadChatMessage) -> Result<()> {
|
||||
let start = msg
|
||||
.timestamp
|
||||
.checked_sub_signed(TimeDelta::nanoseconds(1))
|
||||
.unwrap_or_default();
|
||||
if !self
|
||||
.deaths
|
||||
.iter()
|
||||
|
|
@ -75,9 +81,28 @@ impl DeadChat {
|
|||
} else {
|
||||
self.messages.insert(time, vec![msg]);
|
||||
}
|
||||
self.new_messages_from.get_or_insert(start);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_host_message(&mut self, time: GameTime, msg: String) -> DeadChatMessage {
|
||||
let start = Utc::now();
|
||||
let msg = DeadChatMessage {
|
||||
id: Uuid::new_v4(),
|
||||
message: DeadChatContent::HostMessage(msg),
|
||||
timestamp: start
|
||||
.checked_add_signed(TimeDelta::nanoseconds(1))
|
||||
.unwrap_or_else(Utc::now),
|
||||
};
|
||||
if let Some(msgs) = self.messages.get_mut(&time) {
|
||||
msgs.push(msg.clone());
|
||||
} else {
|
||||
self.messages.insert(time, vec![msg.clone()]);
|
||||
}
|
||||
self.new_messages_from.get_or_insert(start);
|
||||
msg
|
||||
}
|
||||
|
||||
pub fn set_dead(
|
||||
&mut self,
|
||||
dead: impl Iterator<Item = (GameTime, Character)> + Clone,
|
||||
|
|
@ -129,6 +154,23 @@ impl DeadChat {
|
|||
) {
|
||||
log::warn!("replaced: {existing:?}");
|
||||
}
|
||||
self.new_messages_from.get_or_insert(Utc::now());
|
||||
}
|
||||
|
||||
pub fn new_messages_since(&mut self) -> Option<DateTime<Utc>> {
|
||||
self.new_messages_from.take()
|
||||
}
|
||||
|
||||
pub fn host_get_since(&self, t: DateTime<Utc>) -> Vec<DeadChatMessage> {
|
||||
let mut messages = self
|
||||
.messages
|
||||
.clone()
|
||||
.into_iter()
|
||||
.flat_map(|(_, msgs)| msgs.into_iter())
|
||||
.filter(|m| m.timestamp >= t)
|
||||
.collect::<Vec<_>>();
|
||||
messages.sort_by_key(|m| m.timestamp);
|
||||
messages
|
||||
}
|
||||
|
||||
pub fn get_since(&self, t: DateTime<Utc>, character: CharacterId) -> Box<[DeadChatMessage]> {
|
||||
|
|
@ -173,6 +215,7 @@ pub enum DeadChatContent {
|
|||
cause: DiedTo,
|
||||
},
|
||||
TimeChange(GameTime),
|
||||
HostMessage(String),
|
||||
}
|
||||
|
||||
impl DeadChatContent {
|
||||
|
|
@ -180,13 +223,14 @@ impl DeadChatContent {
|
|||
match self {
|
||||
DeadChatContent::PlayerMessage { from, .. } => from.character_id == character_id,
|
||||
DeadChatContent::Death { character, .. } => character.character_id == character_id,
|
||||
DeadChatContent::TimeChange(_) => false,
|
||||
DeadChatContent::TimeChange(_) | DeadChatContent::HostMessage(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn message(&self) -> Option<&str> {
|
||||
match self {
|
||||
DeadChatContent::PlayerMessage { message, .. } => Some(message.as_str()),
|
||||
DeadChatContent::HostMessage(message)
|
||||
| DeadChatContent::PlayerMessage { message, .. } => Some(message.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -22,6 +23,7 @@ use crate::{
|
|||
game::{GameOver, GameSettings, story::GameStory},
|
||||
message::{
|
||||
CharacterIdentity,
|
||||
dead::DeadChatMessage,
|
||||
night::{ActionPrompt, ActionResponse, ActionResult},
|
||||
},
|
||||
player::PlayerId,
|
||||
|
|
@ -48,6 +50,8 @@ pub enum PostGameMessage {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum HostGameMessage {
|
||||
SendChatMessage(String),
|
||||
GetDeadChatSince(DateTime<Utc>),
|
||||
Day(HostDayMessage),
|
||||
Night(HostNightMessage),
|
||||
PreviousState,
|
||||
|
|
@ -113,4 +117,6 @@ pub enum ServerToHostMessage {
|
|||
story: GameStory,
|
||||
page: usize,
|
||||
},
|
||||
DeadChat(Vec<DeadChatMessage>),
|
||||
DeadChatMessage(DeadChatMessage),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use crate::{
|
|||
lobby::{Lobby, LobbyPlayers},
|
||||
runner::{ClientUpdate, IdentifiedClientMessage, Message},
|
||||
};
|
||||
use chrono::Utc;
|
||||
use tokio::time::Instant;
|
||||
use werewolves_proto::{
|
||||
character::Character,
|
||||
|
|
@ -259,6 +260,7 @@ impl GameRunner {
|
|||
}
|
||||
|
||||
pub async fn next(&mut self) -> Option<GameOver> {
|
||||
let start = Utc::now();
|
||||
let msg = match self.comms.message().await {
|
||||
Ok(Message::Client(IdentifiedClientMessage {
|
||||
update: ClientUpdate::ConnectStateUpdate,
|
||||
|
|
@ -343,30 +345,48 @@ impl GameRunner {
|
|||
};
|
||||
|
||||
let pre_time = self.game.village().time();
|
||||
let mut is_host_message = false;
|
||||
match self.host_message(msg) {
|
||||
Ok(ServerToHostMessage::DeadChatMessage(msg)) => {
|
||||
self.comms
|
||||
.host()
|
||||
.send(ServerToHostMessage::DeadChatMessage(msg))
|
||||
.log_err();
|
||||
is_host_message = true;
|
||||
}
|
||||
Ok(resp) => {
|
||||
self.comms.host().send(resp).log_warn();
|
||||
self.comms.host().send(resp).log_err();
|
||||
}
|
||||
Err(err) => {
|
||||
self.comms
|
||||
.host()
|
||||
.send(ServerToHostMessage::Error(err))
|
||||
.log_warn();
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
let messages_for_host = self.game.dead_chats_since(start);
|
||||
let msg_count = messages_for_host.len();
|
||||
if !messages_for_host.is_empty()
|
||||
&& let Err(err) = self
|
||||
.comms
|
||||
.host()
|
||||
.send(ServerToHostMessage::DeadChat(messages_for_host))
|
||||
{
|
||||
log::error!("sending {msg_count} dead chat messages to host channel: {err}");
|
||||
}
|
||||
let post_time = self.game.village().time();
|
||||
if let Some(game_over) = self.game.game_over() {
|
||||
return Some(game_over);
|
||||
}
|
||||
if pre_time != post_time {
|
||||
let newly_dead = self
|
||||
if pre_time != post_time || is_host_message {
|
||||
let dead = self
|
||||
.game
|
||||
.village()
|
||||
.dead_characters()
|
||||
.into_iter()
|
||||
.filter_map(|c| c.died_to().map(|_| (c.character_id(), c.player_id())));
|
||||
|
||||
for (char, player) in newly_dead {
|
||||
for (char, player) in dead {
|
||||
let msgs = self
|
||||
.game
|
||||
.village()
|
||||
|
|
@ -381,6 +401,13 @@ impl GameRunner {
|
|||
}
|
||||
|
||||
pub async fn send_dead_message(&mut self, msg: &DeadChatMessage) -> Result<()> {
|
||||
if let Err(err) = self
|
||||
.comms
|
||||
.host()
|
||||
.send(ServerToHostMessage::DeadChatMessage(msg.clone()))
|
||||
{
|
||||
log::error!("sending message {} to host channel: {err}", msg.id);
|
||||
}
|
||||
let player_ids = self
|
||||
.game
|
||||
.village()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ web-sys = { version = "0.3", features = [
|
|||
"HtmlSelectElement",
|
||||
"HtmlDialogElement",
|
||||
"DomRect",
|
||||
"WheelEvent",
|
||||
] }
|
||||
wasm-bindgen = { version = "=0.2.100" }
|
||||
log = "0.4"
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@ body {
|
|||
user-select: none;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background: black;
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.7) black;
|
||||
}
|
||||
|
||||
app {
|
||||
|
|
@ -128,6 +131,10 @@ $error_shadow_color: hsla(340, 95%, 61%, 0.7);
|
|||
$error_shadow_color_2: hsla(0, 95%, 61%, 0.7);
|
||||
$error_filter: drop-shadow(5px 5px 0 $error_shadow_color) drop-shadow(5px 5px 0 $error_shadow_color_2);
|
||||
|
||||
$host_nav_height: 36px;
|
||||
$host_nav_top_pad: 10px;
|
||||
$host_nav_bottom_pad: 10px;
|
||||
$host_nav_total_height: $host_nav_height + $host_nav_top_pad + $host_nav_bottom_pad;
|
||||
|
||||
nav.host-nav {
|
||||
position: sticky;
|
||||
|
|
@ -140,7 +147,10 @@ nav.host-nav {
|
|||
padding-left: 5vw;
|
||||
padding-right: 5vw;
|
||||
gap: 10px;
|
||||
|
||||
height: $host_nav_height;
|
||||
overflow-x: scroll;
|
||||
scrollbar-width: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2093,7 +2103,6 @@ li.choice {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
max-height: 40%;
|
||||
|
||||
@media only screen and (min-width : 1600px) {
|
||||
width: 100%;
|
||||
|
|
@ -2956,6 +2965,10 @@ dialog {
|
|||
gap: 3px;
|
||||
|
||||
.chat-messages {
|
||||
&:first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
user-select: text;
|
||||
padding-inline-start: 0px;
|
||||
overflow-y: scroll;
|
||||
|
|
@ -2965,10 +2978,7 @@ dialog {
|
|||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
max-height: 95vh;
|
||||
justify-content: flex-end;
|
||||
// scrollbar-width: thin;
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.7) black;
|
||||
|
||||
}
|
||||
|
||||
.message {
|
||||
|
|
@ -3127,3 +3137,8 @@ dialog {
|
|||
flex-wrap: wrap;
|
||||
gap: 1ch;
|
||||
}
|
||||
|
||||
.host-dead-chat {
|
||||
height: calc(100vh - $host_nav_total_height);
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use werewolves_proto::{
|
|||
game::{GameOver, GameSettings, story::GameStory},
|
||||
message::{
|
||||
CharacterIdentity, CharacterState, PlayerState, PublicIdentity,
|
||||
dead::DeadChatMessage,
|
||||
host::{
|
||||
HostDayMessage, HostGameMessage, HostLobbyMessage, HostMessage, HostNightMessage,
|
||||
PostGameMessage, ServerToHostMessage,
|
||||
|
|
@ -44,11 +45,13 @@ use crate::{
|
|||
components::{
|
||||
Button, Footer, Lobby, LobbyPlayerAction, RoleReveal, Victory,
|
||||
action::{ActionResultView, Prompt},
|
||||
chat::DeadChat,
|
||||
host::{CharacterStatesReadOnly, DaytimePlayerList, Setup, VotingMode},
|
||||
settings::Settings,
|
||||
story::Story,
|
||||
},
|
||||
pages::RolePage,
|
||||
scroll::vertical_scroll_to_horizontal,
|
||||
storage::StorageKey,
|
||||
test_util::TestScreens,
|
||||
};
|
||||
|
|
@ -204,6 +207,8 @@ pub enum HostEvent {
|
|||
ToOverrideView,
|
||||
ReturnFromOverride,
|
||||
ExpectEcho(Box<HostEvent>),
|
||||
DeadChat(Vec<DeadChatMessage>),
|
||||
DeadChatMessage(DeadChatMessage),
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Titles)]
|
||||
pub enum HostState {
|
||||
|
|
@ -240,11 +245,16 @@ pub enum HostState {
|
|||
page: usize,
|
||||
},
|
||||
CharacterStates(Box<[CharacterState]>),
|
||||
InChat {
|
||||
previously: Box<HostState>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ServerToHostMessage> for HostEvent {
|
||||
fn from(msg: ServerToHostMessage) -> Self {
|
||||
match msg {
|
||||
ServerToHostMessage::DeadChatMessage(msg) => HostEvent::DeadChatMessage(msg),
|
||||
ServerToHostMessage::DeadChat(msgs) => HostEvent::DeadChat(msgs),
|
||||
ServerToHostMessage::PlayerStates(states) => HostEvent::CharacterList(states),
|
||||
ServerToHostMessage::QrMode(mode) => HostEvent::QrMode(mode),
|
||||
ServerToHostMessage::Disconnect => HostEvent::SetState(HostState::Disconnected),
|
||||
|
|
@ -292,6 +302,7 @@ pub struct Host {
|
|||
qr_mode: bool,
|
||||
debug: bool,
|
||||
expecting_echo: Option<HostEvent>,
|
||||
dead_chat: Option<Vec<DeadChatMessage>>,
|
||||
}
|
||||
|
||||
impl Component for Host {
|
||||
|
|
@ -323,11 +334,67 @@ impl Component for Host {
|
|||
}
|
||||
}),
|
||||
expecting_echo: None,
|
||||
dead_chat: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
if self.dead_chat.is_none()
|
||||
&& matches!(&self.state, HostState::Day { .. } | HostState::Prompt(_, _))
|
||||
{
|
||||
let mut send = self.send.clone();
|
||||
let on_err = self.error_callback.clone();
|
||||
yew::platform::spawn_local(async move {
|
||||
if let Err(err) = send
|
||||
.send(HostMessage::InGame(HostGameMessage::GetDeadChatSince(
|
||||
Default::default(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
on_err.emit(Some(WerewolfError::Send(err)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let content = match self.state.clone() {
|
||||
HostState::InChat { .. } => {
|
||||
let on_send = {
|
||||
let send = self.send.clone();
|
||||
let on_err = self.error_callback.clone();
|
||||
move |msg: String| {
|
||||
let mut send = send.clone();
|
||||
let on_err = on_err.clone();
|
||||
yew::platform::spawn_local(async move {
|
||||
if let Err(err) = send
|
||||
.send(HostMessage::InGame(HostGameMessage::SendChatMessage(msg)))
|
||||
.await
|
||||
{
|
||||
on_err.emit(Some(WerewolfError::Send(err)))
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
let messages = self.dead_chat.clone().unwrap_or_default();
|
||||
if messages.is_empty() {
|
||||
let mut send = self.send.clone();
|
||||
let on_err = self.error_callback.clone();
|
||||
yew::platform::spawn_local(async move {
|
||||
if let Err(err) = send
|
||||
.send(HostMessage::InGame(HostGameMessage::GetDeadChatSince(
|
||||
Default::default(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
on_err.emit(Some(WerewolfError::Send(err)));
|
||||
}
|
||||
});
|
||||
}
|
||||
html! {
|
||||
<div class="host-dead-chat">
|
||||
<DeadChat on_send={on_send} messages={messages}/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
HostState::VotingMode { characters, .. } => {
|
||||
html! {
|
||||
<VotingMode characters={characters.clone()}/>
|
||||
|
|
@ -508,7 +575,21 @@ impl Component for Host {
|
|||
}
|
||||
}
|
||||
};
|
||||
let voting_mode_btn = {
|
||||
let mut nav_buttons = Vec::<Html>::new();
|
||||
let dead_chat_btn = {
|
||||
let on_dead_chat = {
|
||||
let scope = _ctx.link().clone();
|
||||
let state = self.state.clone();
|
||||
move |_| {
|
||||
scope.send_message(HostEvent::SetState(HostState::InChat {
|
||||
previously: Box::new(state.clone()),
|
||||
}))
|
||||
}
|
||||
};
|
||||
html! {
|
||||
<Button on_click={on_dead_chat}>{"chat"}</Button>
|
||||
}
|
||||
};
|
||||
match &self.state {
|
||||
HostState::Day { characters, .. } => {
|
||||
let on_vote_mode = {
|
||||
|
|
@ -523,41 +604,37 @@ impl Component for Host {
|
|||
}
|
||||
};
|
||||
|
||||
Some(html! {
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={on_vote_mode}>{"voting mode"}</Button>
|
||||
})
|
||||
});
|
||||
|
||||
nav_buttons.push(dead_chat_btn);
|
||||
}
|
||||
HostState::VotingMode { .. } => {
|
||||
let back =
|
||||
crate::callback::send_message(HostMessage::GetState, self.send.clone());
|
||||
Some(html! {
|
||||
let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={back}>{"back"}</Button>
|
||||
})
|
||||
});
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let view_roles_btn = match &self.state {
|
||||
HostState::Prompt(_, _) | HostState::Result(_, _) => {
|
||||
let on_prev_click = callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::PreviousState),
|
||||
self.send.clone(),
|
||||
);
|
||||
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={on_prev_click}>{"previous"}</Button>
|
||||
});
|
||||
|
||||
let on_view_click = crate::callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::SeePlayersWithRoles),
|
||||
self.send.clone(),
|
||||
);
|
||||
|
||||
Some(html! {
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={on_view_click}>{"view players"}</Button>
|
||||
})
|
||||
}
|
||||
HostState::CharacterStates(_) => {
|
||||
let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
|
||||
Some(html! {
|
||||
<Button on_click={back}>{"back"}</Button>
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let override_screens_btn = match &self.state {
|
||||
HostState::Prompt(_, _) | HostState::Result(_, _) => {
|
||||
});
|
||||
|
||||
let overrides_click = {
|
||||
let scope = _ctx.link().clone();
|
||||
Callback::from(move |_| {
|
||||
|
|
@ -565,39 +642,12 @@ impl Component for Host {
|
|||
})
|
||||
};
|
||||
|
||||
Some(html! {
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={overrides_click}>{"overrides"}</Button>
|
||||
})
|
||||
}
|
||||
HostState::ScreenOverrides { .. } => {
|
||||
let return_click = {
|
||||
let scope = _ctx.link().clone();
|
||||
Callback::from(move |_| {
|
||||
scope.send_message(HostEvent::ReturnFromOverride);
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
Some(html! {
|
||||
<Button on_click={return_click}>{"back"}</Button>
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let previous_btn = match &self.state {
|
||||
HostState::Prompt(_, _) | HostState::Result(_, _) => {
|
||||
let on_prev_click = callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::PreviousState),
|
||||
self.send.clone(),
|
||||
);
|
||||
nav_buttons.push(dead_chat_btn);
|
||||
|
||||
Some(html! {
|
||||
<Button on_click={on_prev_click}>{"previous"}</Button>
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let skip_btn = match &self.state {
|
||||
HostState::Prompt(_, _) | HostState::Result(_, _) => {
|
||||
let on_skip_click = callback::send_message(
|
||||
HostMessage::InGame(HostGameMessage::Night(HostNightMessage::SkipAction)),
|
||||
self.send.clone(),
|
||||
|
|
@ -609,7 +659,7 @@ impl Component for Host {
|
|||
})
|
||||
};
|
||||
|
||||
Some(html! {
|
||||
nav_buttons.push(html! {
|
||||
<crate::components::modal::Dialog
|
||||
id={"skip-button"}
|
||||
button={html!{{"skip"}}}
|
||||
|
|
@ -619,18 +669,47 @@ impl Component for Host {
|
|||
<p>{"if this is the final prompt of the night, you may not be able to go back"}</p>
|
||||
<Button on_click={on_skip_click}>{"skip prompt"}</Button>
|
||||
</crate::components::modal::Dialog>
|
||||
})
|
||||
});
|
||||
}
|
||||
_ => None,
|
||||
HostState::CharacterStates(_) => {
|
||||
let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={back}>{"back"}</Button>
|
||||
});
|
||||
}
|
||||
HostState::ScreenOverrides { .. } => {
|
||||
let return_click = {
|
||||
let scope = _ctx.link().clone();
|
||||
Callback::from(move |_| {
|
||||
scope.send_message(HostEvent::ReturnFromOverride);
|
||||
})
|
||||
};
|
||||
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={return_click}>{"back"}</Button>
|
||||
});
|
||||
}
|
||||
HostState::InChat { previously } => {
|
||||
let return_click = {
|
||||
let scope = _ctx.link().clone();
|
||||
let previously = (**previously).clone();
|
||||
Callback::from(move |_| {
|
||||
scope.send_message(HostEvent::SetState(previously.clone()));
|
||||
})
|
||||
};
|
||||
|
||||
nav_buttons.push(html! {
|
||||
<Button on_click={return_click}>{"back"}</Button>
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let on_wheel = vertical_scroll_to_horizontal(".host-nav");
|
||||
|
||||
let nav = self.big_screen.not().then(|| {
|
||||
html! {
|
||||
<nav class="host-nav" style="z-index: 3;">
|
||||
{previous_btn}
|
||||
{view_roles_btn}
|
||||
{override_screens_btn}
|
||||
{skip_btn}
|
||||
{voting_mode_btn}
|
||||
<nav class="host-nav" style="z-index: 3;" onwheel={on_wheel}>
|
||||
{nav_buttons}
|
||||
</nav>
|
||||
}
|
||||
});
|
||||
|
|
@ -736,8 +815,41 @@ impl Component for Host {
|
|||
}
|
||||
|
||||
impl Host {
|
||||
fn message_to_new_state(&self, msg: HostEvent) -> (Option<HostState>, bool) {
|
||||
fn message_to_new_state(&mut self, msg: HostEvent) -> (Option<HostState>, bool) {
|
||||
match msg {
|
||||
HostEvent::DeadChatMessage(msg) => {
|
||||
match self.dead_chat.as_mut() {
|
||||
Some(dc) => {
|
||||
dc.push(msg);
|
||||
dc.sort_by_key(|d| d.timestamp);
|
||||
}
|
||||
None => {
|
||||
self.dead_chat.replace(vec![msg]);
|
||||
}
|
||||
}
|
||||
if let HostState::InChat { .. } = &self.state {
|
||||
(None, true)
|
||||
} else {
|
||||
(None, false)
|
||||
}
|
||||
}
|
||||
HostEvent::DeadChat(mut msgs) => {
|
||||
match self.dead_chat.as_mut() {
|
||||
Some(chat) => {
|
||||
chat.append(&mut msgs);
|
||||
chat.sort_by_key(|k| k.timestamp);
|
||||
chat.dedup_by_key(|k| k.id);
|
||||
}
|
||||
None => {
|
||||
self.dead_chat.replace(msgs);
|
||||
}
|
||||
}
|
||||
if let HostState::InChat { .. } = &self.state {
|
||||
(None, true)
|
||||
} else {
|
||||
(None, false)
|
||||
}
|
||||
}
|
||||
HostEvent::ExpectEcho(_) => (None, false),
|
||||
HostEvent::ReturnFromOverride => {
|
||||
if let HostState::ScreenOverrides { return_to } = &self.state {
|
||||
|
|
@ -776,6 +888,7 @@ impl Host {
|
|||
.unwrap_or(LAST)
|
||||
.cmp(&r.identification.public.number.unwrap_or(LAST))
|
||||
});
|
||||
self.dead_chat = None;
|
||||
(
|
||||
Some(HostState::Lobby {
|
||||
settings,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use werewolves_proto::message::{
|
|||
dead::{DeadChatContent, DeadChatMessage},
|
||||
};
|
||||
|
||||
use crate::components::{Icon, IconSource, IconType, attributes::DiedToSpan};
|
||||
use crate::components::{Icon, IconSource, IconType};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct DeadChatProperties {
|
||||
|
|
@ -185,6 +185,18 @@ pub fn ChatMessage(
|
|||
}: &ChatMessageProps,
|
||||
) -> Html {
|
||||
match message {
|
||||
DeadChatContent::HostMessage(message) => {
|
||||
html! {
|
||||
<li class="message">
|
||||
<Timestamp timestamp={*timestamp} />
|
||||
<span class="red">
|
||||
// <Icon source={IconSource::Adjudicator} icon_type={IconType::Fit}/>
|
||||
<strong>{"Host"}</strong>
|
||||
</span>
|
||||
<span class="message-content">{message.clone()}</span>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
DeadChatContent::PlayerMessage { from, message } => {
|
||||
html! {
|
||||
<li class="message">
|
||||
|
|
@ -202,7 +214,6 @@ pub fn ChatMessage(
|
|||
<DeadChatIdent ident={character.clone().into_public()}/>
|
||||
<span class="message-content">
|
||||
{"died to "}
|
||||
// <DiedToSpan died_to={cause.title()}/>
|
||||
{cause.title().to_string()}
|
||||
</span>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
mod assets;
|
||||
mod class;
|
||||
mod clients;
|
||||
mod scroll;
|
||||
mod storage;
|
||||
mod test_util;
|
||||
mod components {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
use yew::WheelEvent;
|
||||
|
||||
pub fn vertical_scroll_to_horizontal(selector: &str) -> impl Fn(WheelEvent) {
|
||||
let selector = selector.to_string();
|
||||
move |ev: WheelEvent| {
|
||||
let scroll_y = ev.delta_y();
|
||||
if scroll_y == 0.0 {
|
||||
return;
|
||||
}
|
||||
let Some(target) = gloo::utils::document()
|
||||
.query_selector(&selector)
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
target.set_scroll_left(target.scroll_left() + ((scroll_y + ev.delta_x()) as i32));
|
||||
ev.prevent_default();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue