player self seat number update
This commit is contained in:
parent
6fb105ca82
commit
ab1cb29952
|
|
@ -592,6 +592,7 @@ dialog .tab-content {
|
|||
}
|
||||
|
||||
.player-lobby {
|
||||
user-select: none;
|
||||
margin: 5vh 15vw 5vh 15vw;
|
||||
padding: 3ch;
|
||||
font-size: 1.5em;
|
||||
|
|
@ -604,6 +605,74 @@ dialog .tab-content {
|
|||
}
|
||||
}
|
||||
|
||||
.player-additional {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.number-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid rgb(0, 64, 0);
|
||||
background-color: rgba(0, 64, 0, 0.3);
|
||||
gap: 0.25ch;
|
||||
width: fit-content;
|
||||
padding: 3ch;
|
||||
min-width: 40vw;
|
||||
align-items: center;
|
||||
|
||||
&.unset {
|
||||
border: 1px solid rgb(128, 0, 0);
|
||||
background-color: rgba(64, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
#player-number {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.number-text {
|
||||
font-size: 1.5em;
|
||||
user-select: none;
|
||||
|
||||
.number {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
&.unset {
|
||||
font-size: 1.25em;
|
||||
color: rgb(192, 0, 0);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.number-update:not([hidden]) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.25ch;
|
||||
margin-top: 3ch;
|
||||
font-size: 1.5em;
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
@extend button;
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use werewolves_macros::Titles;
|
|||
|
||||
use crate::{character::CharacterId, game::GameTime};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Titles)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Titles)]
|
||||
pub enum DiedTo {
|
||||
Execution {
|
||||
day: NonZeroU8,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ pub enum ServerToClientMessage {
|
|||
LobbyInfo {
|
||||
joined: bool,
|
||||
players: Box<[PublicIdentity]>,
|
||||
current_number: Option<NonZeroU8>,
|
||||
},
|
||||
GameInProgress,
|
||||
GameStart {
|
||||
|
|
|
|||
|
|
@ -197,14 +197,14 @@ impl DeadChat {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DeadChatMessage {
|
||||
pub id: Uuid,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub message: DeadChatContent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DeadChatContent {
|
||||
PlayerMessage {
|
||||
from: CharacterIdentity,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ pub struct PublicIdentity {
|
|||
pub number: Option<NonZeroU8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CharacterIdentity {
|
||||
pub character_id: CharacterId,
|
||||
pub name: String,
|
||||
|
|
|
|||
|
|
@ -6,24 +6,20 @@ use leptos_router::hooks::use_url;
|
|||
use reactive_stores::Store;
|
||||
|
||||
use crate::app::{
|
||||
components::LinkButton,
|
||||
components::{DialogModal, LinkButton},
|
||||
storage::user::{AuthContext, AuthContextStoreFields},
|
||||
};
|
||||
|
||||
#[server]
|
||||
pub async fn get_active_game(token: TokenString) -> Result<Option<GameId>, ServerError> {
|
||||
let db = use_context::<api::state::AppState>()
|
||||
.expect("no app state")
|
||||
.db;
|
||||
let db = expect_context::<api::state::AppState>().db;
|
||||
let user = db.user().check_token(&token).await?;
|
||||
Ok(db.game().get_joined_active_game(user.id).await?)
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn new_game(token: TokenString) -> Result<GameId, ServerError> {
|
||||
let db = use_context::<api::state::AppState>()
|
||||
.expect("no app state")
|
||||
.db;
|
||||
let db = expect_context::<api::state::AppState>().db;
|
||||
let user = db.user().check_token(&token).await?;
|
||||
Ok(db.game().new_game(user.id).await?.id)
|
||||
}
|
||||
|
|
@ -100,6 +96,7 @@ pub fn Nav() -> impl IntoView {
|
|||
}
|
||||
})
|
||||
};
|
||||
|
||||
view! {
|
||||
{home_button}
|
||||
<span class="display-name">{session.name().to_string()}</span>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
werewolves_macros::include_path!("werewolves/src/app/pages/game/player");
|
||||
|
||||
use core::{
|
||||
hash::Hash,
|
||||
num::{self, NonZeroU8},
|
||||
ops::{Deref, Not},
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use leptos::{ev::MouseEvent, prelude::*};
|
||||
use leptos::{
|
||||
ev::{Event, MouseEvent, SubmitEvent, Targeted},
|
||||
prelude::*,
|
||||
};
|
||||
use werewolves_proto::{
|
||||
message::{
|
||||
ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, dead::DeadChatMessage,
|
||||
ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, UpdateSelf,
|
||||
dead::DeadChatMessage,
|
||||
},
|
||||
role::RoleTitle,
|
||||
};
|
||||
|
|
@ -13,8 +24,13 @@ use crate::{ConsoleLogError, app::components::ErrorBox};
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Page {
|
||||
Lobby { joined: bool },
|
||||
RoleReveal { role: RoleTitle },
|
||||
Lobby {
|
||||
joined: bool,
|
||||
current_number: Option<NonZeroU8>,
|
||||
},
|
||||
RoleReveal {
|
||||
role: RoleTitle,
|
||||
},
|
||||
GameInProgress,
|
||||
Sleep,
|
||||
DeadChat,
|
||||
|
|
@ -27,7 +43,7 @@ pub fn PlayerGamePage(
|
|||
disconnect: RwSignal<bool>,
|
||||
) -> impl IntoView {
|
||||
let error = RwSignal::new(None);
|
||||
let dead_chat: RwSignal<Option<Vec<DeadChatMessage>>> = RwSignal::new(None);
|
||||
let dead_chat: RwSignal<Option<HashSet<HashedDeadChatMessage>>> = RwSignal::new(None);
|
||||
let page: RwSignal<Option<Page>> = RwSignal::new(None);
|
||||
Effect::new(move || {
|
||||
let Some(message) = message.get() else {
|
||||
|
|
@ -35,18 +51,30 @@ pub fn PlayerGamePage(
|
|||
};
|
||||
match message {
|
||||
Srv2Client::Disconnect => disconnect.set(true),
|
||||
Srv2Client::LobbyInfo { joined, .. } => page.set(Some(Page::Lobby { joined })),
|
||||
Srv2Client::LobbyInfo {
|
||||
joined,
|
||||
current_number,
|
||||
..
|
||||
} => page.set(Some(Page::Lobby {
|
||||
joined,
|
||||
current_number,
|
||||
})),
|
||||
Srv2Client::GameInProgress => page.set(Some(Page::GameInProgress)),
|
||||
Srv2Client::GameStart { role } => page.set(Some(Page::RoleReveal { role })),
|
||||
Srv2Client::Story(game_story) => todo!("{game_story:#?}"),
|
||||
Srv2Client::Update(_) => {}
|
||||
Srv2Client::DeadChat(dead_chat_messages) => {
|
||||
dead_chat.set(Some(dead_chat_messages.to_vec()));
|
||||
dead_chat.set(Some(
|
||||
dead_chat_messages
|
||||
.into_iter()
|
||||
.map(Into::<HashedDeadChatMessage>::into)
|
||||
.collect(),
|
||||
));
|
||||
page.set(Some(Page::DeadChat));
|
||||
}
|
||||
Srv2Client::DeadChatMessage(dead_chat_message) => {
|
||||
if let Some(chat) = dead_chat.write().as_mut() {
|
||||
chat.push(dead_chat_message);
|
||||
chat.insert(dead_chat_message.into());
|
||||
page.set(Some(Page::DeadChat));
|
||||
} else {
|
||||
reply.set(Some(ClientMessage::DeadChat(ClientDeadChat::GetHistory)));
|
||||
|
|
@ -61,6 +89,8 @@ pub fn PlayerGamePage(
|
|||
}
|
||||
// match message {}
|
||||
});
|
||||
let joined = RwSignal::new(false);
|
||||
let current_number = RwSignal::new(None);
|
||||
let content = move || {
|
||||
let Some(page) = page.get() else {
|
||||
return ().into_any();
|
||||
|
|
@ -68,32 +98,19 @@ pub fn PlayerGamePage(
|
|||
match page {
|
||||
Page::DeadChat => todo!("dead chat"),
|
||||
Page::Sleep => view! { <h1>"go to sleep"</h1> }.into_any(),
|
||||
Page::Lobby { joined } => {
|
||||
let click = move |ev: MouseEvent| {
|
||||
ev.prevent_default();
|
||||
reply.set(Some(if joined {
|
||||
ClientMessage::Goodbye
|
||||
} else {
|
||||
ClientMessage::Hello
|
||||
}));
|
||||
};
|
||||
let text = match joined {
|
||||
true => view! {
|
||||
<h2>"you are in the lobby"</h2>
|
||||
<p>"you're all good c:"</p>
|
||||
}
|
||||
.into_any(),
|
||||
false => view! {
|
||||
<h2>"you are not in the lobby"</h2>
|
||||
<p>"join if you want to play"</p>
|
||||
}
|
||||
.into_any(),
|
||||
};
|
||||
Page::Lobby {
|
||||
joined: j,
|
||||
current_number: c,
|
||||
} => {
|
||||
joined.set(j);
|
||||
current_number.set(c);
|
||||
view! {
|
||||
<div class="player-lobby" class:joined=joined>
|
||||
{text}
|
||||
<button on:click=click>{if joined { "leave" } else { "join" }}</button>
|
||||
</div>
|
||||
<PlayerLobby
|
||||
reply=reply
|
||||
joined=joined.read_only()
|
||||
current_number=current_number.read_only()
|
||||
error=error
|
||||
/>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
|
@ -123,3 +140,39 @@ pub fn PlayerGamePage(
|
|||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HashedDeadChatMessage(DeadChatMessage);
|
||||
impl Ord for HashedDeadChatMessage {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.timestamp.cmp(&other.timestamp)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for HashedDeadChatMessage {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl Hash for HashedDeadChatMessage {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
impl From<DeadChatMessage> for HashedDeadChatMessage {
|
||||
fn from(value: DeadChatMessage) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<HashedDeadChatMessage> for DeadChatMessage {
|
||||
fn from(value: HashedDeadChatMessage) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl Deref for HashedDeadChatMessage {
|
||||
type Target = DeadChatMessage;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
use core::num::NonZeroU8;
|
||||
|
||||
use leptos::{
|
||||
ev::{Event, MouseEvent, SubmitEvent, Targeted},
|
||||
prelude::*,
|
||||
};
|
||||
use werewolves_proto::message::{ClientMessage, UpdateSelf};
|
||||
|
||||
#[component]
|
||||
pub fn PlayerLobby(
|
||||
reply: WriteSignal<Option<ClientMessage>>,
|
||||
joined: ReadSignal<bool>,
|
||||
current_number: ReadSignal<Option<NonZeroU8>>,
|
||||
error: RwSignal<Option<String>>,
|
||||
) -> impl IntoView {
|
||||
let click = move |ev: MouseEvent| {
|
||||
ev.prevent_default();
|
||||
reply.set(Some(if joined.get() {
|
||||
ClientMessage::Goodbye
|
||||
} else {
|
||||
ClientMessage::Hello
|
||||
}));
|
||||
};
|
||||
let text = move || match joined.get() {
|
||||
true => view! {
|
||||
<h2>"you are in the lobby"</h2>
|
||||
<p>"you're all good c:"</p>
|
||||
}
|
||||
.into_any(),
|
||||
false => view! {
|
||||
<h2>"you are not in the lobby"</h2>
|
||||
<p>"join if you want to play"</p>
|
||||
}
|
||||
.into_any(),
|
||||
};
|
||||
let number: RwSignal<Option<NonZeroU8>> = RwSignal::new(None);
|
||||
let number = move || {
|
||||
let form_hidden = RwSignal::new(true);
|
||||
let number_update = {
|
||||
let update = move |e: Targeted<Event, leptos::web_sys::HtmlInputElement>| {
|
||||
let value = e.target().value();
|
||||
if value.trim().is_empty() {
|
||||
number.set(None);
|
||||
return;
|
||||
}
|
||||
let current = number.get_untracked();
|
||||
let default = current.map(|c| c.get().to_string()).unwrap_or_default();
|
||||
let value_u8 = match e.target().value().trim().parse::<u8>() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
e.target().set_value(default.as_str());
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Some(nz) = NonZeroU8::new(value_u8) {
|
||||
number.set(Some(nz));
|
||||
} else {
|
||||
e.target().set_value(default.as_str());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let submit = move |ev: SubmitEvent| {
|
||||
ev.prevent_default();
|
||||
let Some(num) = number.get() else {
|
||||
error.set(Some("please set a number".into()));
|
||||
return;
|
||||
};
|
||||
reply.set(Some(ClientMessage::UpdateSelf(UpdateSelf::Number(num))));
|
||||
form_hidden.set(true);
|
||||
};
|
||||
view! {
|
||||
<form class="number-update" on:submit=submit hidden=move || form_hidden.get()>
|
||||
<label for="player-number">"change seat number"</label>
|
||||
<input
|
||||
id="player-number"
|
||||
type="number"
|
||||
autocomplete="off"
|
||||
on:input:target=update
|
||||
value=move || {
|
||||
number.get().map(|n| n.get().to_string()).unwrap_or_default()
|
||||
}
|
||||
/>
|
||||
<input value="submit" type="submit" />
|
||||
</form>
|
||||
}
|
||||
.into_any()
|
||||
};
|
||||
match current_number.get() {
|
||||
Some(num) => {
|
||||
form_hidden.set(true);
|
||||
view! {
|
||||
<span class="number-text">
|
||||
"you are in seat "<span class="number">{num.get()}</span>
|
||||
</span>
|
||||
<button
|
||||
on:click=move |_| form_hidden.set(false)
|
||||
hidden=move || !form_hidden.get()
|
||||
style="width: fit-content; font-size: 1.5em;"
|
||||
>
|
||||
"change seat"
|
||||
</button>
|
||||
{number_update}
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
None => {
|
||||
form_hidden.set(false);
|
||||
view! {
|
||||
<span class="number-text unset">"you don't have a seat number :c"</span>
|
||||
<span class="number-text">"please put your seat number in below"</span>
|
||||
{number_update}
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
};
|
||||
view! {
|
||||
<div class="player-lobby" class:joined=joined>
|
||||
{text}
|
||||
<button on:click=click>{move || if joined.get() { "leave" } else { "join" }}</button>
|
||||
</div>
|
||||
<div class="player-additional">
|
||||
<div class="number-info" class:unset=move || current_number.get().is_none()>
|
||||
{number}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
@ -24,9 +24,7 @@ pub fn UserSettings() -> impl IntoView {
|
|||
match tut_read.read().enabled {
|
||||
true => view! { <button on:click=move |_| tut_write.write().enabled = false>"disable tutorials"</button> }
|
||||
.into_any(),
|
||||
false => view! {
|
||||
<button on:click=move |_| tut_write.write().enabled = true>"enable tutorials"</button>
|
||||
}.into_any(),
|
||||
false => view! { <button on:click=move |_| tut_write.write().enabled = true>"enable tutorials"</button> }.into_any(),
|
||||
}
|
||||
};
|
||||
view! {
|
||||
|
|
|
|||
|
|
@ -69,19 +69,20 @@ impl<'a> Lobby<'a> {
|
|||
}
|
||||
|
||||
pub async fn send_lobby_info_to_clients(&mut self) -> Result<(), ServerError> {
|
||||
let (players, identities) = self
|
||||
.db
|
||||
.game()
|
||||
.get_joined_players(self.game_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|p| (p.player_id, p.public))
|
||||
let joined = self.db.game().get_joined_players(self.game_id).await?;
|
||||
let (players, identities) = joined
|
||||
.iter()
|
||||
.map(|p| (p.player_id, p.public.clone()))
|
||||
.collect::<(Vec<_>, Vec<_>)>();
|
||||
let identities = identities.into_boxed_slice();
|
||||
super::send_to_all_players_in_game_filtered(self.game_id, move |pid| {
|
||||
Some(ServerToClientMessage::LobbyInfo {
|
||||
joined: players.contains(&pid),
|
||||
players: identities.clone(),
|
||||
current_number: joined
|
||||
.iter()
|
||||
.find_map(|j| (j.player_id == pid).then_some(j.public.number))
|
||||
.flatten(),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
|
@ -257,11 +258,16 @@ impl<'a> Lobby<'a> {
|
|||
self.send_lobby_info_to_clients().await.log_debug(loc!());
|
||||
}
|
||||
HostOrClientMessage::Client(IdentifiedClientMessage {
|
||||
identity: Identification { player_id, .. },
|
||||
identity:
|
||||
Identification {
|
||||
player_id,
|
||||
public: PublicIdentity { number, .. },
|
||||
},
|
||||
update: ClientUpdate::Message(ClientMessage::GetState),
|
||||
}) => {
|
||||
let joined = self.db.game().get_joined_players(self.game_id).await?;
|
||||
let msg = ServerToClientMessage::LobbyInfo {
|
||||
current_number: number,
|
||||
joined: joined.iter().any(|p| p.player_id == player_id),
|
||||
players: joined.into_iter().map(|p| p.public).collect(),
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue