add player for host

This commit is contained in:
emilis 2025-10-10 18:34:59 +01:00
parent 08db7f9bfc
commit 11bc54f996
No known key found for this signature in database
5 changed files with 114 additions and 40 deletions

View File

@ -13,7 +13,7 @@ use crate::{
player::PlayerId, player::PlayerId,
}; };
use super::{CharacterState, PlayerState}; use super::{CharacterState, PlayerState, PublicIdentity};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum HostMessage { pub enum HostMessage {
@ -55,6 +55,7 @@ impl From<HostDayMessage> for HostGameMessage {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum HostLobbyMessage { pub enum HostLobbyMessage {
GetState, GetState,
ManufacturePlayer(PublicIdentity),
Kick(PlayerId), Kick(PlayerId),
SetPlayerNumber(PlayerId, NonZeroU8), SetPlayerNumber(PlayerId, NonZeroU8),
GetGameSettings, GetGameSettings,

View File

@ -155,6 +155,18 @@ impl Lobby {
async fn next_inner(&mut self, msg: Message) -> Result<Option<GameRunner>, GameError> { async fn next_inner(&mut self, msg: Message) -> Result<Option<GameRunner>, GameError> {
match msg { match msg {
Message::Host(HostMessage::Lobby(HostLobbyMessage::ManufacturePlayer(public))) => {
log::info!("adding player {public:?} by host request");
self.players_in_lobby.push((
Identification {
player_id: PlayerId::new(),
public,
},
None,
));
self.send_lobby_info_to_clients().await;
self.send_lobby_info_to_host().await.log_debug();
}
Message::Host(HostMessage::Lobby(HostLobbyMessage::SetQrMode(mode))) => { Message::Host(HostMessage::Lobby(HostLobbyMessage::SetQrMode(mode))) => {
self.qr_mode = mode; self.qr_mode = mode;
self.send_lobby_info_to_host().await.log_debug(); self.send_lobby_info_to_host().await.log_debug();
@ -235,7 +247,7 @@ impl Lobby {
return Ok(None); return Ok(None);
} }
if let Some(sender) = self.joined_players.get_sender(identity.player_id).await { if let Some(sender) = self.joined_players.get_sender(identity.player_id).await {
self.players_in_lobby.push((identity, sender.clone())); self.players_in_lobby.push((identity, Some(sender.clone())));
self.send_lobby_info_to_clients().await; self.send_lobby_info_to_clients().await;
self.send_lobby_info_to_host().await?; self.send_lobby_info_to_host().await?;
} }
@ -321,10 +333,10 @@ impl Lobby {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct LobbyPlayers(Vec<(Identification, Sender<ServerMessage>)>); pub struct LobbyPlayers(Vec<(Identification, Option<Sender<ServerMessage>>)>);
impl Deref for LobbyPlayers { impl Deref for LobbyPlayers {
type Target = Vec<(Identification, Sender<ServerMessage>)>; type Target = Vec<(Identification, Option<Sender<ServerMessage>>)>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@ -339,8 +351,6 @@ impl DerefMut for LobbyPlayers {
impl LobbyPlayers { impl LobbyPlayers {
pub fn with_dummies(dummy_count: NonZeroU8) -> Self { pub fn with_dummies(dummy_count: NonZeroU8) -> Self {
let (send, mut recv) = broadcast::channel(100);
tokio::spawn(async move { while recv.recv().await.is_ok() {} });
Self( Self(
(0..dummy_count.get()) (0..dummy_count.get())
.map(|p| { .map(|p| {
@ -353,7 +363,7 @@ impl LobbyPlayers {
number: NonZeroU8::new(p + 1), number: NonZeroU8::new(p + 1),
}, },
}, },
send.clone(), None,
) )
}) })
.collect(), .collect(),
@ -361,6 +371,7 @@ impl LobbyPlayers {
} }
pub fn find(&self, player_id: PlayerId) -> Option<&Sender<ServerMessage>> { pub fn find(&self, player_id: PlayerId) -> Option<&Sender<ServerMessage>> {
self.iter() self.iter()
.filter_map(|(id, s)| s.as_ref().map(|s| (id, s)))
.find_map(|(id, s)| (id.player_id == player_id).then_some(s)) .find_map(|(id, s)| (id.player_id == player_id).then_some(s))
} }

View File

@ -201,8 +201,7 @@ nav.debug-nav {
} }
.submenu { .submenu {
background-color: black; // border: 1px solid rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.7);
padding: 10px; padding: 10px;
position: relative; position: relative;
// position: fixed; // position: fixed;
@ -829,25 +828,6 @@ input {
margin: 10px; margin: 10px;
} }
.signin {
@extend .row-list;
justify-content: center;
text-align: center;
& label {
font-size: 1.5rem;
}
& input {
height: 2rem;
text-align: center;
&#number {
font-size: 2rem;
}
}
}
.info-update { .info-update {
font-size: 2rem; font-size: 2rem;
align-content: stretch; align-content: stretch;
@ -997,11 +977,17 @@ input {
align-items: center; align-items: center;
align-content: center; align-content: center;
&>p { &>label {
height: 100%; // height: 100%;
width: 100%; // width: 100%;
flex-grow: 3;
} }
&>button {
width: max-content;
font-size: 3rem;
flex-grow: 1;
}
} }
.setup-slot { .setup-slot {
@ -1013,15 +999,23 @@ input {
} }
&>.submenu { &>.submenu {
min-width: 5cm; min-width: 30vw;
.assign-list { .assign-list {
min-width: 5cm; // min-width: 5cm;
gap: 10px;
& .submenu button { & .submenu button {
width: inherit; width: 5cm;
} }
} }
.assignees {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 5px;
}
} }
} }
@ -1324,3 +1318,31 @@ input {
& .next {} & .next {}
} }
.signin {
@extend .row-list;
justify-content: center;
text-align: center;
& label {
font-size: 1.5rem;
}
& input {
height: 2rem;
text-align: center;
&#number {
font-size: 2rem;
}
}
}
.submenu:has(.signin) {
position: absolute;
width: max-content;
& input {
font-size: 1rem !important;
}
}

View File

@ -13,7 +13,7 @@ use werewolves_proto::{
error::GameError, error::GameError,
game::{GameOver, GameSettings}, game::{GameOver, GameSettings},
message::{ message::{
CharacterIdentity, CharacterState, PlayerState, CharacterIdentity, CharacterState, PlayerState, PublicIdentity,
host::{ host::{
HostDayMessage, HostGameMessage, HostLobbyMessage, HostMessage, HostNightMessage, HostDayMessage, HostGameMessage, HostLobbyMessage, HostMessage, HostNightMessage,
ServerToHostMessage, ServerToHostMessage,
@ -662,12 +662,17 @@ impl Host {
} }
}); });
}); });
let on_add_player = crate::callback::send_fn(
|msg: PublicIdentity| HostMessage::Lobby(HostLobbyMessage::ManufacturePlayer(msg)),
self.send.clone(),
);
html! { html! {
<Settings <Settings
settings={settings} settings={settings}
on_start={on_start} on_start={on_start}
on_update={on_changed} on_update={on_changed}
players_in_lobby={players.clone()} players_in_lobby={players.clone()}
on_add_player={on_add_player}
qr_mode_button={qr_mode_toggle} qr_mode_button={qr_mode_toggle}
/> />
} }

View File

@ -5,12 +5,12 @@ use convert_case::{Case, Casing};
use werewolves_proto::{ use werewolves_proto::{
error::GameError, error::GameError,
game::{GameSettings, OrRandom, SetupRole, SetupSlot, SlotId}, game::{GameSettings, OrRandom, SetupRole, SetupSlot, SlotId},
message::{Identification, PlayerState}, message::{Identification, PlayerState, PublicIdentity},
role::RoleTitle, role::RoleTitle,
}; };
use yew::prelude::*; use yew::prelude::*;
use crate::components::{Button, ClickableField, Identity}; use crate::components::{Button, ClickableField, Identity, client::Signin};
#[derive(Debug, PartialEq, Properties)] #[derive(Debug, PartialEq, Properties)]
pub struct SettingsProps { pub struct SettingsProps {
@ -18,6 +18,7 @@ pub struct SettingsProps {
pub players_in_lobby: Rc<[PlayerState]>, pub players_in_lobby: Rc<[PlayerState]>,
pub on_update: Callback<GameSettings>, pub on_update: Callback<GameSettings>,
pub on_start: Callback<()>, pub on_start: Callback<()>,
pub on_add_player: Callback<PublicIdentity>,
pub qr_mode_button: Html, pub qr_mode_button: Html,
} }
@ -28,6 +29,7 @@ pub fn Settings(
players_in_lobby, players_in_lobby,
on_update, on_update,
on_start, on_start,
on_add_player,
qr_mode_button, qr_mode_button,
}: &SettingsProps, }: &SettingsProps,
) -> Html { ) -> Html {
@ -72,6 +74,7 @@ pub fn Settings(
.map(|slot| { .map(|slot| {
html! { html! {
<SettingsSlot <SettingsSlot
all_players={players.clone()}
players_for_assign={players_for_assign.clone()} players_for_assign={players_for_assign.clone()}
roles_in_setup={roles_in_setup.clone()} roles_in_setup={roles_in_setup.clone()}
slot={slot.clone()} slot={slot.clone()}
@ -248,6 +251,11 @@ pub fn Settings(
} }
}; };
let add_player_open = use_state(|| false);
let add_player_opts = html! {
<Signin callback={on_add_player.clone()}/>
};
html! { html! {
<div class="settings"> <div class="settings">
<div class="top-settings"> <div class="top-settings">
@ -257,6 +265,12 @@ pub fn Settings(
{clear_all_assignments} {clear_all_assignments}
{clear_bad_assigned} {clear_bad_assigned}
</div> </div>
<ClickableField
options={add_player_opts}
state={add_player_open}
>
{"add player"}
</ClickableField>
<p>{format!("min roles for setup: {}", settings.min_players_needed())}</p> <p>{format!("min roles for setup: {}", settings.min_players_needed())}</p>
<p>{format!("current role count: {}", settings.slots().len())}</p> <p>{format!("current role count: {}", settings.slots().len())}</p>
<div class="roles-add-list"> <div class="roles-add-list">
@ -291,6 +305,7 @@ pub struct SettingsSlotProps {
pub roles_in_setup: Rc<[RoleTitle]>, pub roles_in_setup: Rc<[RoleTitle]>,
pub slot: SetupSlot, pub slot: SetupSlot,
pub update: Callback<SettingSlotAction>, pub update: Callback<SettingSlotAction>,
pub all_players: Rc<[Identification]>,
} }
#[function_component] #[function_component]
@ -300,6 +315,7 @@ pub fn SettingsSlot(
roles_in_setup, roles_in_setup,
slot, slot,
update, update,
all_players,
}: &SettingsSlotProps, }: &SettingsSlotProps,
) -> Html { ) -> Html {
let open = use_state(|| false); let open = use_state(|| false);
@ -321,6 +337,19 @@ pub fn SettingsSlot(
let assign_to = assign_to_submenu(players_for_assign, slot, &update, &open.setter()); let assign_to = assign_to_submenu(players_for_assign, slot, &update, &open.setter());
let options = let options =
setup_options_for_slot(slot, &update, roles_in_setup, apprentice_open, open.clone()); setup_options_for_slot(slot, &update, roles_in_setup, apprentice_open, open.clone());
let assign_text = slot
.assign_to
.as_ref()
.and_then(|assign_to| all_players.iter().find(|p| p.player_id == *assign_to))
.map(|assign_to| {
html! {
<>
<span>{"assigned to"}</span>
<Identity ident={assign_to.public.clone()} />
</>
}
})
.unwrap_or_else(|| html! {{"assign"}});
html! { html! {
<> <>
<Button on_click={on_kick}> <Button on_click={on_kick}>
@ -331,7 +360,7 @@ pub fn SettingsSlot(
state={assign_open} state={assign_open}
class={classes!("assign-list")} class={classes!("assign-list")}
> >
{"assign"} {assign_text}
</ClickableField> </ClickableField>
{options} {options}
</> </>
@ -357,7 +386,7 @@ fn assign_to_submenu(
update: &Callback<SettingSlotAction>, update: &Callback<SettingSlotAction>,
assign_setter: &UseStateSetter<bool>, assign_setter: &UseStateSetter<bool>,
) -> Html { ) -> Html {
players let buttons = players
.iter() .iter()
.map(|p| { .map(|p| {
let slot = slot.clone(); let slot = slot.clone();
@ -376,7 +405,13 @@ fn assign_to_submenu(
</Button> </Button>
} }
}) })
.collect() .collect::<Html>();
html! {
<div class="assignees">
{buttons}
</div>
}
} }
fn setup_options_for_slot( fn setup_options_for_slot(