big screen setup display
This commit is contained in:
parent
c0838c276c
commit
0889acca6a
|
|
@ -0,0 +1,47 @@
|
||||||
|
use quote::{ToTokens, quote};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
pub struct All {
|
||||||
|
name: syn::Ident,
|
||||||
|
variants: Box<[syn::Ident]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl All {
|
||||||
|
pub fn parse(input: syn::DeriveInput) -> Result<Self, syn::Error> {
|
||||||
|
let data = match input.data {
|
||||||
|
syn::Data::Enum(enu) => enu,
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"All can only be used on enums",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let variants = data
|
||||||
|
.variants
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| match &v.fields {
|
||||||
|
syn::Fields::Named(_) | syn::Fields::Unnamed(_) => Err(syn::Error::new(
|
||||||
|
v.ident.span(),
|
||||||
|
"All can only be used on enums with only unit fields",
|
||||||
|
)),
|
||||||
|
syn::Fields::Unit => Ok(v.ident),
|
||||||
|
})
|
||||||
|
.collect::<Result<Box<[_]>, _>>()?;
|
||||||
|
let name = input.ident;
|
||||||
|
Ok(Self { name, variants })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for All {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
let name = &self.name;
|
||||||
|
let variants = &self.variants;
|
||||||
|
let count = self.variants.len();
|
||||||
|
tokens.extend(quote! {
|
||||||
|
impl #name {
|
||||||
|
pub const ALL: [#name; #count] = [#(#name::#variants),*];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ use proc_macro2::Span;
|
||||||
use quote::{ToTokens, quote};
|
use quote::{ToTokens, quote};
|
||||||
use syn::{parse::Parse, parse_macro_input};
|
use syn::{parse::Parse, parse_macro_input};
|
||||||
|
|
||||||
|
mod all;
|
||||||
mod checks;
|
mod checks;
|
||||||
pub(crate) mod hashlist;
|
pub(crate) mod hashlist;
|
||||||
mod targets;
|
mod targets;
|
||||||
|
|
@ -163,7 +164,8 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(file_name) = item.file_name().to_str()
|
if let Some(file_name) = item.file_name().to_str()
|
||||||
&& include_in_rerun(file_name) {
|
&& include_in_rerun(file_name)
|
||||||
|
{
|
||||||
out.push(FileWithPath::from_path(item.path(), origin_path)?);
|
out.push(FileWithPath::from_path(item.path(), origin_path)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -542,3 +544,10 @@ pub fn extract(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
|
||||||
quote! {#checks_as}.into()
|
quote! {#checks_as}.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(All)]
|
||||||
|
pub fn all(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let all = all::All::parse(parse_macro_input!(input)).unwrap();
|
||||||
|
|
||||||
|
quote! {#all}.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
settings::{Category, GameSettings, OrRandom, SetupRole, SetupSlot, SlotId},
|
settings::{Category, GameSettings, OrRandom, SetupRole, SetupRoleTitle, SetupSlot, SlotId},
|
||||||
village::Village,
|
village::Village,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,11 +209,11 @@ impl GameSettings {
|
||||||
.filter(|r| Into::<RoleTitle>::into(r.role.clone()).is_mentor())
|
.filter(|r| Into::<RoleTitle>::into(r.role.clone()).is_mentor())
|
||||||
.count();
|
.count();
|
||||||
self.roles.iter().try_for_each(|s| match &s.role {
|
self.roles.iter().try_for_each(|s| match &s.role {
|
||||||
SetupRole::Apprentice { specifically: None } => (mentor_count > 0)
|
SetupRole::Apprentice { to: None } => (mentor_count > 0)
|
||||||
.then_some(())
|
.then_some(())
|
||||||
.ok_or(GameError::NoApprenticeMentor),
|
.ok_or(GameError::NoApprenticeMentor),
|
||||||
SetupRole::Apprentice {
|
SetupRole::Apprentice {
|
||||||
specifically: Some(role),
|
to: Some(role),
|
||||||
} => role
|
} => role
|
||||||
.is_mentor()
|
.is_mentor()
|
||||||
.then_some(())
|
.then_some(())
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use core::{
|
||||||
use rand::distr::{Distribution, StandardUniform};
|
use rand::distr::{Distribution, StandardUniform};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use werewolves_macros::ChecksAs;
|
use werewolves_macros::{All, ChecksAs, Titles};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GameError,
|
error::GameError,
|
||||||
|
|
@ -40,7 +40,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ChecksAs)]
|
#[derive(
|
||||||
|
Debug, PartialOrd, Ord, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ChecksAs, All,
|
||||||
|
)]
|
||||||
pub enum Category {
|
pub enum Category {
|
||||||
#[checks]
|
#[checks]
|
||||||
Wolves,
|
Wolves,
|
||||||
|
|
@ -50,8 +52,30 @@ pub enum Category {
|
||||||
Offensive,
|
Offensive,
|
||||||
StartsAsVillager,
|
StartsAsVillager,
|
||||||
}
|
}
|
||||||
|
impl Category {
|
||||||
|
pub fn entire_category(&self) -> Box<[SetupRoleTitle]> {
|
||||||
|
SetupRoleTitle::ALL
|
||||||
|
.iter()
|
||||||
|
.filter(|r| r.category() == *self)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChecksAs)]
|
impl Display for Category {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Category::Wolves => "Wolves",
|
||||||
|
Category::Villager => "Villager",
|
||||||
|
Category::Intel => "Intel",
|
||||||
|
Category::Defensive => "Defensive",
|
||||||
|
Category::Offensive => "Offensive",
|
||||||
|
Category::StartsAsVillager => "Starts As Villager",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChecksAs, Titles)]
|
||||||
pub enum SetupRole {
|
pub enum SetupRole {
|
||||||
#[checks(Category::Villager)]
|
#[checks(Category::Villager)]
|
||||||
Villager,
|
Villager,
|
||||||
|
|
@ -74,7 +98,7 @@ pub enum SetupRole {
|
||||||
#[checks(Category::Defensive)]
|
#[checks(Category::Defensive)]
|
||||||
Protector,
|
Protector,
|
||||||
#[checks(Category::StartsAsVillager)]
|
#[checks(Category::StartsAsVillager)]
|
||||||
Apprentice { specifically: Option<RoleTitle> },
|
Apprentice { to: Option<RoleTitle> },
|
||||||
#[checks(Category::StartsAsVillager)]
|
#[checks(Category::StartsAsVillager)]
|
||||||
Elder { knows_on_night: NonZeroU8 },
|
Elder { knows_on_night: NonZeroU8 },
|
||||||
|
|
||||||
|
|
@ -88,6 +112,39 @@ pub enum SetupRole {
|
||||||
Shapeshifter,
|
Shapeshifter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SetupRoleTitle {
|
||||||
|
pub const fn into_role(self) -> Role {
|
||||||
|
match self {
|
||||||
|
SetupRoleTitle::Villager => Role::Villager,
|
||||||
|
SetupRoleTitle::Scapegoat => Role::Scapegoat { redeemed: false },
|
||||||
|
SetupRoleTitle::Seer => Role::Seer,
|
||||||
|
SetupRoleTitle::Arcanist => Role::Arcanist,
|
||||||
|
SetupRoleTitle::Gravedigger => Role::Gravedigger,
|
||||||
|
SetupRoleTitle::Hunter => Role::Hunter { target: None },
|
||||||
|
SetupRoleTitle::Militia => Role::Militia { targeted: None },
|
||||||
|
SetupRoleTitle::MapleWolf => Role::MapleWolf {
|
||||||
|
last_kill_on_night: 0,
|
||||||
|
},
|
||||||
|
SetupRoleTitle::Guardian => Role::Guardian {
|
||||||
|
last_protected: None,
|
||||||
|
},
|
||||||
|
SetupRoleTitle::Protector => Role::Protector {
|
||||||
|
last_protected: None,
|
||||||
|
},
|
||||||
|
SetupRoleTitle::Apprentice => Role::Apprentice(RoleTitle::Arcanist),
|
||||||
|
SetupRoleTitle::Elder => Role::Elder {
|
||||||
|
woken_for_reveal: false,
|
||||||
|
lost_protection_night: None,
|
||||||
|
knows_on_night: NonZeroU8::new(1).unwrap(),
|
||||||
|
},
|
||||||
|
SetupRoleTitle::Werewolf => Role::Werewolf,
|
||||||
|
SetupRoleTitle::AlphaWolf => Role::AlphaWolf { killed: None },
|
||||||
|
SetupRoleTitle::DireWolf => Role::DireWolf,
|
||||||
|
SetupRoleTitle::Shapeshifter => Role::Shapeshifter { shifted_into: None },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for SetupRole {
|
impl Display for SetupRole {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
|
|
@ -132,10 +189,8 @@ impl SetupRole {
|
||||||
SetupRole::Protector => Role::Protector {
|
SetupRole::Protector => Role::Protector {
|
||||||
last_protected: None,
|
last_protected: None,
|
||||||
},
|
},
|
||||||
SetupRole::Apprentice {
|
SetupRole::Apprentice { to: Some(role) } => Role::Apprentice(role),
|
||||||
specifically: Some(role),
|
SetupRole::Apprentice { to: None } => {
|
||||||
} => Role::Apprentice(role),
|
|
||||||
SetupRole::Apprentice { specifically: None } => {
|
|
||||||
let mentors = roles_in_game
|
let mentors = roles_in_game
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.is_mentor())
|
.filter(|r| r.is_mentor())
|
||||||
|
|
@ -197,7 +252,7 @@ impl From<RoleTitle> for SetupRole {
|
||||||
RoleTitle::MapleWolf => SetupRole::MapleWolf,
|
RoleTitle::MapleWolf => SetupRole::MapleWolf,
|
||||||
RoleTitle::Guardian => SetupRole::Guardian,
|
RoleTitle::Guardian => SetupRole::Guardian,
|
||||||
RoleTitle::Protector => SetupRole::Protector,
|
RoleTitle::Protector => SetupRole::Protector,
|
||||||
RoleTitle::Apprentice => SetupRole::Apprentice { specifically: None },
|
RoleTitle::Apprentice => SetupRole::Apprentice { to: None },
|
||||||
RoleTitle::Elder => SetupRole::Elder {
|
RoleTitle::Elder => SetupRole::Elder {
|
||||||
knows_on_night: NonZeroU8::new(3).unwrap(),
|
knows_on_night: NonZeroU8::new(3).unwrap(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -99,13 +99,8 @@ impl Role {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wakes(&self, village: &Village) -> bool {
|
pub const fn wakes_night_zero(&self) -> bool {
|
||||||
let night_zero = match village.date_time() {
|
match self {
|
||||||
DateTime::Day { number: _ } => return false,
|
|
||||||
DateTime::Night { number } => number == 0,
|
|
||||||
};
|
|
||||||
if night_zero {
|
|
||||||
return match self {
|
|
||||||
Role::DireWolf | Role::Arcanist | Role::Seer => true,
|
Role::DireWolf | Role::Arcanist | Role::Seer => true,
|
||||||
|
|
||||||
Role::Shapeshifter { .. }
|
Role::Shapeshifter { .. }
|
||||||
|
|
@ -121,7 +116,16 @@ impl Role {
|
||||||
| Role::Villager
|
| Role::Villager
|
||||||
| Role::Scapegoat { .. }
|
| Role::Scapegoat { .. }
|
||||||
| Role::Protector { .. } => false,
|
| Role::Protector { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wakes(&self, village: &Village) -> bool {
|
||||||
|
let night_zero = match village.date_time() {
|
||||||
|
DateTime::Day { number: _ } => return false,
|
||||||
|
DateTime::Night { number } => number == 0,
|
||||||
};
|
};
|
||||||
|
if night_zero {
|
||||||
|
return self.wakes_night_zero();
|
||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
Role::AlphaWolf { killed: Some(_) }
|
Role::AlphaWolf { killed: Some(_) }
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ impl Client {
|
||||||
self.handle_message(msg).await
|
self.handle_message(msg).await
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("[{}] recv error: {err}", self.connection_id.player_id());
|
log::debug!("[{}] recv error: {err}", self.connection_id.player_id());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,12 +86,13 @@ impl Lobby {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next(&mut self) -> Option<GameRunner> {
|
pub async fn next(&mut self) -> Option<GameRunner> {
|
||||||
let msg = self
|
let msg = match self.comms().unwrap().next_message().await {
|
||||||
.comms()
|
Ok(msg) => msg,
|
||||||
.unwrap()
|
Err(err) => {
|
||||||
.next_message()
|
log::error!("get next message: {err}");
|
||||||
.await
|
return None;
|
||||||
.expect("get next message"); // TODO: keeps happening
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match self.next_inner(msg.clone()).await.map_err(|err| (msg, err)) {
|
match self.next_inner(msg.clone()).await.map_err(|err| (msg, err)) {
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
|
|
@ -162,7 +163,14 @@ impl Lobby {
|
||||||
))
|
))
|
||||||
.log_warn(),
|
.log_warn(),
|
||||||
Message::Host(HostMessage::Lobby(HostLobbyMessage::GetState))
|
Message::Host(HostMessage::Lobby(HostLobbyMessage::GetState))
|
||||||
| Message::Host(HostMessage::GetState) => self.send_lobby_info_to_host().await?,
|
| Message::Host(HostMessage::GetState) => {
|
||||||
|
self.send_lobby_info_to_host().await?;
|
||||||
|
let settings = self.settings.clone();
|
||||||
|
self.comms()?
|
||||||
|
.host()
|
||||||
|
.send(ServerToHostMessage::GameSettings(settings))
|
||||||
|
.log_warn();
|
||||||
|
}
|
||||||
Message::Host(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) => {
|
Message::Host(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) => {
|
||||||
let msg = ServerToHostMessage::GameSettings(self.settings.clone());
|
let msg = ServerToHostMessage::GameSettings(self.settings.clone());
|
||||||
let _ = self.comms().unwrap().host().send(msg);
|
let _ = self.comms().unwrap().host().send(msg);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="52.665638mm"
|
||||||
|
height="52.665649mm"
|
||||||
|
viewBox="0 0 52.665638 52.665649"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="true">
|
||||||
|
<inkscape:page
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="52.665638"
|
||||||
|
height="52.665649"
|
||||||
|
id="page2"
|
||||||
|
margin="0"
|
||||||
|
bleed="0" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-53.30057,-132.77008)">
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#d45500;stroke-width:2.348"
|
||||||
|
id="path4-9"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="51.531715"
|
||||||
|
sodipodi:cy="30.475746"
|
||||||
|
sodipodi:r1="37.207153"
|
||||||
|
sodipodi:r2="18.603577"
|
||||||
|
sodipodi:arg1="-1.5707963"
|
||||||
|
sodipodi:arg2="-0.52359875"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 51.531716,-6.7314072 32.222339,55.8107312 -64.44468,-2e-6 z"
|
||||||
|
inkscape:transform-center-x="-0.11920648"
|
||||||
|
inkscape:transform-center-y="-9.0503731"
|
||||||
|
transform="matrix(0.2916535,-0.2916535,0.70710678,0.70710678,43.030992,152.55931)" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#ffccaa;fill-opacity:1;stroke-width:2.348"
|
||||||
|
id="path4-1-2"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="51.531715"
|
||||||
|
sodipodi:cy="30.475746"
|
||||||
|
sodipodi:r1="37.207153"
|
||||||
|
sodipodi:r2="18.603577"
|
||||||
|
sodipodi:arg1="-1.5707963"
|
||||||
|
sodipodi:arg2="-0.52359875"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 51.531716,-6.7314072 32.222339,55.8107312 -64.44468,-2e-6 z"
|
||||||
|
inkscape:transform-center-x="-0.11920648"
|
||||||
|
inkscape:transform-center-y="-9.0503731"
|
||||||
|
transform="matrix(0.14582674,-0.14582674,0.35355339,0.35355339,67.897856,162.39677)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffccaa;fill-opacity:1;stroke-width:2.28741"
|
||||||
|
id="rect4-2"
|
||||||
|
width="34.461983"
|
||||||
|
height="3.9066164"
|
||||||
|
x="-73.424423"
|
||||||
|
y="183.5461"
|
||||||
|
transform="rotate(-45)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffccaa;fill-opacity:1;stroke-width:1.79377"
|
||||||
|
id="rect5-8"
|
||||||
|
width="6.5479999"
|
||||||
|
height="15.395733"
|
||||||
|
x="-59.46743"
|
||||||
|
y="187.38255"
|
||||||
|
transform="rotate(-45)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="52.348007mm"
|
||||||
|
height="52.348mm"
|
||||||
|
viewBox="0 0 52.348007 52.348"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||||
|
sodipodi:docname="icons.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="1.4142136"
|
||||||
|
inkscape:cx="346.12877"
|
||||||
|
inkscape:cy="548.36131"
|
||||||
|
inkscape:window-width="1918"
|
||||||
|
inkscape:window-height="1042"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="17"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:page
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="52.348007"
|
||||||
|
height="52.348"
|
||||||
|
id="page2"
|
||||||
|
margin="0"
|
||||||
|
bleed="0" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer2"
|
||||||
|
inkscape:label="Layer 2"
|
||||||
|
transform="translate(-119.56806,-115.26503)">
|
||||||
|
<circle
|
||||||
|
style="fill:#54ffff;fill-opacity:0.146665;stroke:#00adc1;stroke-width:2.348;stroke-opacity:1"
|
||||||
|
id="path9"
|
||||||
|
cx="145.74207"
|
||||||
|
cy="141.43904"
|
||||||
|
r="25"
|
||||||
|
inkscape:export-filename="../../src/werewolves/werewolves/img/powerful.svg"
|
||||||
|
inkscape:export-xdpi="900.08"
|
||||||
|
inkscape:export-ydpi="900.08" />
|
||||||
|
<path
|
||||||
|
style="fill:#00adc1;fill-opacity:1;stroke:#00adc1;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 146.7354,116.77348 -12.81832,29.07761 11.35727,-0.99768 -9.76266,20.13334 23.61744,-27.67112 -15.11625,2.07403 12.86097,-20.49482 z"
|
||||||
|
id="path10"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="52.348mm"
|
||||||
|
height="52.348mm"
|
||||||
|
viewBox="0 0 52.348 52.348"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||||
|
sodipodi:docname="icons.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="71.5"
|
||||||
|
inkscape:cy="601.5"
|
||||||
|
inkscape:window-width="1918"
|
||||||
|
inkscape:window-height="1042"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="17"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer3"><inkscape:page
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="52.348"
|
||||||
|
height="52.348"
|
||||||
|
id="page2"
|
||||||
|
margin="0"
|
||||||
|
bleed="0" /></sodipodi:namedview><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="Layer 4"
|
||||||
|
transform="translate(-31.749998,-194.46876)"><circle
|
||||||
|
style="fill:#0000ff;fill-opacity:0.15;stroke:#0f07ff;stroke-width:2.348;stroke-opacity:1"
|
||||||
|
id="path12-8"
|
||||||
|
cx="57.924"
|
||||||
|
cy="220.64275"
|
||||||
|
r="25"
|
||||||
|
inkscape:export-filename="../../src/werewolves/werewolves/img/wolf.svg"
|
||||||
|
inkscape:export-xdpi="900.08"
|
||||||
|
inkscape:export-ydpi="900.08" /><path
|
||||||
|
id="path52"
|
||||||
|
style="fill:#0f07ff;fill-opacity:1;stroke:#0f07ff;stroke-width:0.646547;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
inkscape:transform-center-x="0.38117126"
|
||||||
|
inkscape:transform-center-y="-3.2844551"
|
||||||
|
d="m 57.107952,201.21922 c -1.713539,0.0921 -3.402067,0.64565 -4.275191,1.61799 -1.746251,1.94469 -1.477016,6.95297 0.467671,8.69922 0.577742,0.51878 1.869738,0.91451 2.822455,1.08642 0.645263,0.48645 0.441435,1.16866 0.235128,1.54824 -2.113413,0.40992 -4.423609,1.57646 -5.591796,3.31071 -3.059556,4.54209 -0.05893,15.93547 0,21.41161 0.01685,1.56554 14.736774,1.63168 14.555185,-0.0661 -0.582408,-5.44541 2.927265,-16.80337 -0.132292,-21.34547 -1.319465,-1.95883 -3.263349,-3.04974 -5.692251,-3.31904 -0.233696,-0.43409 -0.260906,-1.13472 0.150896,-1.67225 1.115403,-0.2586 1.751648,-0.75274 2.352402,-1.42176 1.746251,-1.94469 1.477016,-6.95348 -0.467672,-8.69973 -0.972343,-0.87312 -2.710997,-1.24193 -4.424535,-1.1498 z"
|
||||||
|
sodipodi:nodetypes="sssccssssccssss" /><path
|
||||||
|
id="rect55"
|
||||||
|
style="display:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.7;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 54.680079,205.91192 0.0075,3.95139 h -0.02186 v 1.21435 h 2.712393 v 25.2552 l 0.5302,0.68461 0.58136,-0.68461 v -25.2552 h 2.712392 v -1.21435 h -0.02232 l -0.007,-3.95139 -0.404006,-1.44994 -0.408967,1.44994 0.007,3.95139 h -1.877095 -0.149294 l -0.0075,-3.95139 -0.440632,-1.43824 -0.372342,1.43824 0.007,3.95139 H 57.37808 55.50052 l -0.007,-3.95139 -0.442679,-1.43824 z"
|
||||||
|
sodipodi:nodetypes="ccccccccccccccccccccccccccc" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -1078,3 +1078,94 @@ input {
|
||||||
|
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-screen {
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
position: fixed;
|
||||||
|
left: 10%;
|
||||||
|
top: 10%;
|
||||||
|
|
||||||
|
.setup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5%;
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
width: 30%;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
& .title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .count {
|
||||||
|
left: -30px;
|
||||||
|
position: relative;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list {
|
||||||
|
text-align: left;
|
||||||
|
flex: 1, 1, 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
.attributes {
|
||||||
|
margin-left: 10px;
|
||||||
|
align-self: flex-end;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: contrast(120%) brightness(120%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
filter: grayscale(100%) brightness(30%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
width: 100%;
|
||||||
|
filter: saturate(40%);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
&.wakes {
|
||||||
|
border: 2px solid yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use crate::{
|
||||||
components::{
|
components::{
|
||||||
Button, CoverOfDarkness, Lobby, LobbyPlayerAction, RoleReveal, Settings,
|
Button, CoverOfDarkness, Lobby, LobbyPlayerAction, RoleReveal, Settings,
|
||||||
action::{ActionResultView, Prompt},
|
action::{ActionResultView, Prompt},
|
||||||
host::DaytimePlayerList,
|
host::{DaytimePlayerList, Setup},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -302,76 +302,10 @@ impl Component for Host {
|
||||||
</div>
|
</div>
|
||||||
},
|
},
|
||||||
HostState::Lobby { players, settings } => {
|
HostState::Lobby { players, settings } => {
|
||||||
let on_error = self.error_callback.clone();
|
if self.big_screen {
|
||||||
|
self.lobby_big_screen_show_setup(players, settings)
|
||||||
let settings = self.big_screen.not().then(|| {
|
} else {
|
||||||
let send = self.send.clone();
|
self.lobby_setup(players, settings)
|
||||||
let on_changed = Callback::from(move |s| {
|
|
||||||
let send = send.clone();
|
|
||||||
yew::platform::spawn_local(async move {
|
|
||||||
let mut send = send.clone();
|
|
||||||
if let Err(err) = send
|
|
||||||
.send(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(s)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
log::error!("sending game settings update: {err}");
|
|
||||||
}
|
|
||||||
if let Err(err) = send
|
|
||||||
.send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
log::error!("sending game settings get: {err}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let send = self.send.clone();
|
|
||||||
let on_start = Callback::from(move |_| {
|
|
||||||
let send = send.clone();
|
|
||||||
let on_error = on_error.clone();
|
|
||||||
yew::platform::spawn_local(async move {
|
|
||||||
let mut send = send.clone();
|
|
||||||
if let Err(err) =
|
|
||||||
send.send(HostMessage::Lobby(HostLobbyMessage::Start)).await
|
|
||||||
{
|
|
||||||
on_error.emit(Some(err.into()))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<Settings
|
|
||||||
settings={settings}
|
|
||||||
on_start={on_start}
|
|
||||||
on_update={on_changed}
|
|
||||||
players_in_lobby={players.clone()}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let on_action = self.big_screen.not().then(|| {
|
|
||||||
let on_error = self.error_callback.clone();
|
|
||||||
let send = self.send.clone();
|
|
||||||
Callback::from(move |(player_id, act): (PlayerId, LobbyPlayerAction)| {
|
|
||||||
let msg = match act {
|
|
||||||
LobbyPlayerAction::Kick => {
|
|
||||||
HostMessage::Lobby(HostLobbyMessage::Kick(player_id))
|
|
||||||
}
|
|
||||||
LobbyPlayerAction::SetNumber(num) => HostMessage::Lobby(
|
|
||||||
HostLobbyMessage::SetPlayerNumber(player_id, num),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let mut send = send.clone();
|
|
||||||
let on_error = on_error.clone();
|
|
||||||
yew::platform::spawn_local(async move {
|
|
||||||
if let Err(err) = send.send(msg).await {
|
|
||||||
on_error.emit(Some(err.into()))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div class="column-list">
|
|
||||||
{settings}
|
|
||||||
<Lobby players={players} on_action={on_action}/>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HostState::Day {
|
HostState::Day {
|
||||||
|
|
@ -644,3 +578,83 @@ impl Component for Host {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Host {
|
||||||
|
fn lobby_big_screen_show_setup(&self, _: Rc<[PlayerState]>, settings: GameSettings) -> Html {
|
||||||
|
html! {
|
||||||
|
<Setup settings={settings}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lobby_setup(&self, players: Rc<[PlayerState]>, settings: GameSettings) -> Html {
|
||||||
|
let on_error = self.error_callback.clone();
|
||||||
|
|
||||||
|
let settings = self.big_screen.not().then(|| {
|
||||||
|
let send = self.send.clone();
|
||||||
|
let on_changed = Callback::from(move |s| {
|
||||||
|
let send = send.clone();
|
||||||
|
yew::platform::spawn_local(async move {
|
||||||
|
let mut send = send.clone();
|
||||||
|
if let Err(err) = send
|
||||||
|
.send(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(s)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
log::error!("sending game settings update: {err}");
|
||||||
|
}
|
||||||
|
if let Err(err) = send
|
||||||
|
.send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
log::error!("sending game settings get: {err}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let send = self.send.clone();
|
||||||
|
let on_start = Callback::from(move |_| {
|
||||||
|
let send = send.clone();
|
||||||
|
let on_error = on_error.clone();
|
||||||
|
yew::platform::spawn_local(async move {
|
||||||
|
let mut send = send.clone();
|
||||||
|
if let Err(err) = send.send(HostMessage::Lobby(HostLobbyMessage::Start)).await {
|
||||||
|
on_error.emit(Some(err.into()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<Settings
|
||||||
|
settings={settings}
|
||||||
|
on_start={on_start}
|
||||||
|
on_update={on_changed}
|
||||||
|
players_in_lobby={players.clone()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let on_action = self.big_screen.not().then(|| {
|
||||||
|
let on_error = self.error_callback.clone();
|
||||||
|
let send = self.send.clone();
|
||||||
|
Callback::from(move |(player_id, act): (PlayerId, LobbyPlayerAction)| {
|
||||||
|
let msg = match act {
|
||||||
|
LobbyPlayerAction::Kick => {
|
||||||
|
HostMessage::Lobby(HostLobbyMessage::Kick(player_id))
|
||||||
|
}
|
||||||
|
LobbyPlayerAction::SetNumber(num) => {
|
||||||
|
HostMessage::Lobby(HostLobbyMessage::SetPlayerNumber(player_id, num))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut send = send.clone();
|
||||||
|
let on_error = on_error.clone();
|
||||||
|
yew::platform::spawn_local(async move {
|
||||||
|
if let Err(err) = send.send(msg).await {
|
||||||
|
on_error.emit(Some(err.into()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<div class="column-list">
|
||||||
|
{settings}
|
||||||
|
<Lobby players={players} on_action={on_action}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
use core::ops::Not;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use werewolves_proto::{
|
||||||
|
game::{Category, GameSettings, SetupRole, SetupRoleTitle},
|
||||||
|
role::Alignment,
|
||||||
|
};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
|
pub struct SetupProps {
|
||||||
|
pub settings: GameSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Setup(SetupProps { settings }: &SetupProps) -> Html {
|
||||||
|
let mut by_category: HashMap<Category, Vec<SetupRole>> =
|
||||||
|
Category::ALL.into_iter().map(|c| (c, Vec::new())).collect();
|
||||||
|
for slot in settings.slots() {
|
||||||
|
by_category
|
||||||
|
.get_mut(&slot.role.category())
|
||||||
|
.unwrap()
|
||||||
|
.push(slot.role.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut categories = by_category.into_iter().collect::<Box<[_]>>();
|
||||||
|
categories.sort_by_key(|(c, _)| match c {
|
||||||
|
Category::Wolves => 1u8,
|
||||||
|
Category::Intel => 2,
|
||||||
|
Category::Villager => 4,
|
||||||
|
Category::Defensive => 3,
|
||||||
|
Category::Offensive => 5,
|
||||||
|
Category::StartsAsVillager => 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
let categories = categories
|
||||||
|
.into_iter()
|
||||||
|
.map(|(cat, members)| {
|
||||||
|
let hide = match cat {
|
||||||
|
Category::Wolves => CategoryMode::ShowExactRoleCount,
|
||||||
|
Category::Villager => CategoryMode::ShowExactRoleCount,
|
||||||
|
Category::Intel
|
||||||
|
| Category::Defensive
|
||||||
|
| Category::Offensive
|
||||||
|
| Category::StartsAsVillager => CategoryMode::HideAllInfo,
|
||||||
|
};
|
||||||
|
html! {
|
||||||
|
<SetupCategory
|
||||||
|
category={cat}
|
||||||
|
roles={members.into_boxed_slice()}
|
||||||
|
mode={hide}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Html>();
|
||||||
|
|
||||||
|
let power_roles_count = settings
|
||||||
|
.slots()
|
||||||
|
.iter()
|
||||||
|
.filter(|r| !matches!(r.role.category(), Category::Villager | Category::Wolves))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="setup-screen">
|
||||||
|
<div class="setup">
|
||||||
|
{categories}
|
||||||
|
</div>
|
||||||
|
<div class="category village">
|
||||||
|
<span class="count">{power_roles_count}</span>
|
||||||
|
<span class="title">{"Power roles from..."}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub enum CategoryMode {
|
||||||
|
#[default]
|
||||||
|
HideAllInfo,
|
||||||
|
ShowTotalCount,
|
||||||
|
ShowExactRoleCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||||
|
pub struct SetupCategoryProps {
|
||||||
|
pub category: Category,
|
||||||
|
pub roles: Box<[SetupRole]>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub mode: CategoryMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn SetupCategory(
|
||||||
|
SetupCategoryProps {
|
||||||
|
category,
|
||||||
|
roles,
|
||||||
|
mode,
|
||||||
|
}: &SetupCategoryProps,
|
||||||
|
) -> Html {
|
||||||
|
let roles_count = match mode {
|
||||||
|
CategoryMode::HideAllInfo => None,
|
||||||
|
CategoryMode::ShowTotalCount | CategoryMode::ShowExactRoleCount => Some(html! {
|
||||||
|
<span class="count">{roles.len().to_string()}</span>
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let mut roles_in_category = SetupRoleTitle::ALL
|
||||||
|
.into_iter()
|
||||||
|
.filter(|r| r.category() == *category)
|
||||||
|
.collect::<Box<[_]>>();
|
||||||
|
roles_in_category.sort_by_key(|l| l.into_role().wakes_night_zero());
|
||||||
|
|
||||||
|
let all_roles = roles_in_category
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| (r, roles.iter().filter(|sr| sr.title() == r).count()))
|
||||||
|
.filter(|(_, count)| !(matches!(mode, CategoryMode::ShowExactRoleCount) && *count == 0))
|
||||||
|
.map(|(r, count)| {
|
||||||
|
let as_role = r.into_role();
|
||||||
|
let wakes = as_role.wakes_night_zero().then_some("wakes");
|
||||||
|
let count = matches!(mode, CategoryMode::ShowExactRoleCount).then(|| {
|
||||||
|
html! {
|
||||||
|
<span class="count">{count}</span>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let killer_inactive = as_role.killer().not().then_some("inactive");
|
||||||
|
let powerful_inactive = as_role.powerful().not().then_some("inactive");
|
||||||
|
let alignment = match as_role.alignment() {
|
||||||
|
Alignment::Village => "/img/village.svg",
|
||||||
|
Alignment::Wolves => "/img/wolf.svg",
|
||||||
|
};
|
||||||
|
html! {
|
||||||
|
<div class={classes!("slot")}>
|
||||||
|
<div class={classes!("role", wakes, r.category().class())}>
|
||||||
|
{count}
|
||||||
|
{r.to_string()}
|
||||||
|
</div>
|
||||||
|
<div class="attributes">
|
||||||
|
<div class="alignment">
|
||||||
|
<img class="icon" src={alignment} alt={"alignment"}/>
|
||||||
|
</div>
|
||||||
|
<div class={classes!("killer", killer_inactive)}>
|
||||||
|
<img class="icon" src="/img/killer.svg" alt="killer icon"/>
|
||||||
|
</div>
|
||||||
|
<div class={classes!("poweful", powerful_inactive)}>
|
||||||
|
<img class="icon" src="/img/powerful.svg" alt="powerful icon"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Html>();
|
||||||
|
html! {
|
||||||
|
<div class="category">
|
||||||
|
{roles_count}
|
||||||
|
<div class={classes!("title", category.class())}>{category.to_string()}</div>
|
||||||
|
<div class={classes!("category-list")}>
|
||||||
|
{all_roles}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -212,9 +212,42 @@ pub fn Settings(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let clear_setup = {
|
||||||
|
let update = on_update.clone();
|
||||||
|
let on_click = Callback::from(move |_| {
|
||||||
|
update.emit(GameSettings::empty());
|
||||||
|
});
|
||||||
|
let disabled_reason = settings.slots().is_empty().then_some("no setup to clear");
|
||||||
|
html! {
|
||||||
|
<Button on_click={on_click} disabled_reason={disabled_reason}>
|
||||||
|
{"clear setup"}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let fill_empty_with_villagers = {
|
||||||
|
let disabled_reason =
|
||||||
|
(settings.min_players_needed() >= players.len()).then_some("no empty slots");
|
||||||
|
let update = on_update.clone();
|
||||||
|
let settings = settings.clone();
|
||||||
|
let player_count = players.len();
|
||||||
|
let on_click = Callback::from(move |_| {
|
||||||
|
let mut settings = (*settings).clone();
|
||||||
|
settings.fill_remaining_slots_with_villagers(player_count);
|
||||||
|
update.emit(settings);
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<Button on_click={on_click} disabled_reason={disabled_reason}>
|
||||||
|
{"fill empty slots with villagers"}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
<div class="top-settings">
|
<div class="top-settings">
|
||||||
|
{fill_empty_with_villagers}
|
||||||
|
{clear_setup}
|
||||||
{clear_all_assignments}
|
{clear_all_assignments}
|
||||||
{clear_bad_assigned}
|
{clear_bad_assigned}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -376,7 +409,7 @@ fn setup_options_for_slot(
|
||||||
</>
|
</>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
SetupRole::Apprentice { specifically } => {
|
SetupRole::Apprentice { to: specifically } => {
|
||||||
let options = roles_in_setup
|
let options = roles_in_setup
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.is_mentor())
|
.filter(|r| r.is_mentor())
|
||||||
|
|
@ -398,7 +431,7 @@ fn setup_options_for_slot(
|
||||||
let role_name = option.to_string().to_case(Case::Title);
|
let role_name = option.to_string().to_case(Case::Title);
|
||||||
let mut slot = slot.clone();
|
let mut slot = slot.clone();
|
||||||
match &mut slot.role {
|
match &mut slot.role {
|
||||||
SetupRole::Apprentice { specifically } => {
|
SetupRole::Apprentice { to: specifically } => {
|
||||||
specifically.replace(option);
|
specifically.replace(option);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
@ -421,7 +454,7 @@ fn setup_options_for_slot(
|
||||||
let role_name = "random";
|
let role_name = "random";
|
||||||
let mut slot = slot.clone();
|
let mut slot = slot.clone();
|
||||||
match &mut slot.role {
|
match &mut slot.role {
|
||||||
SetupRole::Apprentice { specifically } => {
|
SetupRole::Apprentice { to: specifically } => {
|
||||||
specifically.take();
|
specifically.take();
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
} else if path.starts_with("/many-client") {
|
} else if path.starts_with("/many-client") {
|
||||||
let clients = document.query_selector("clients").unwrap().unwrap();
|
let clients = document.query_selector("clients").unwrap().unwrap();
|
||||||
for (player_id, name, num, dupe) in (1..=16).map(|num| {
|
for (player_id, name, num, dupe) in (1..=7).map(|num| {
|
||||||
(
|
(
|
||||||
PlayerId::from_u128(num as u128),
|
PlayerId::from_u128(num as u128),
|
||||||
format!("player {num}"),
|
format!("player {num}"),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue