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 syn::{parse::Parse, parse_macro_input};
|
||||
|
||||
mod all;
|
||||
mod checks;
|
||||
pub(crate) mod hashlist;
|
||||
mod targets;
|
||||
|
|
@ -163,9 +164,10 @@ where
|
|||
continue;
|
||||
}
|
||||
if let Some(file_name) = item.file_name().to_str()
|
||||
&& include_in_rerun(file_name) {
|
||||
out.push(FileWithPath::from_path(item.path(), origin_path)?);
|
||||
}
|
||||
&& include_in_rerun(file_name)
|
||||
{
|
||||
out.push(FileWithPath::from_path(item.path(), origin_path)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -542,3 +544,10 @@ pub fn extract(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|||
|
||||
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 {
|
||||
settings::{Category, GameSettings, OrRandom, SetupRole, SetupSlot, SlotId},
|
||||
settings::{Category, GameSettings, OrRandom, SetupRole, SetupRoleTitle, SetupSlot, SlotId},
|
||||
village::Village,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -209,11 +209,11 @@ impl GameSettings {
|
|||
.filter(|r| Into::<RoleTitle>::into(r.role.clone()).is_mentor())
|
||||
.count();
|
||||
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(())
|
||||
.ok_or(GameError::NoApprenticeMentor),
|
||||
SetupRole::Apprentice {
|
||||
specifically: Some(role),
|
||||
to: Some(role),
|
||||
} => role
|
||||
.is_mentor()
|
||||
.then_some(())
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use core::{
|
|||
use rand::distr::{Distribution, StandardUniform};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use werewolves_macros::ChecksAs;
|
||||
use werewolves_macros::{All, ChecksAs, Titles};
|
||||
|
||||
use crate::{
|
||||
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 {
|
||||
#[checks]
|
||||
Wolves,
|
||||
|
|
@ -50,8 +52,30 @@ pub enum Category {
|
|||
Offensive,
|
||||
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 {
|
||||
#[checks(Category::Villager)]
|
||||
Villager,
|
||||
|
|
@ -74,7 +98,7 @@ pub enum SetupRole {
|
|||
#[checks(Category::Defensive)]
|
||||
Protector,
|
||||
#[checks(Category::StartsAsVillager)]
|
||||
Apprentice { specifically: Option<RoleTitle> },
|
||||
Apprentice { to: Option<RoleTitle> },
|
||||
#[checks(Category::StartsAsVillager)]
|
||||
Elder { knows_on_night: NonZeroU8 },
|
||||
|
||||
|
|
@ -88,6 +112,39 @@ pub enum SetupRole {
|
|||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
|
|
@ -132,10 +189,8 @@ impl SetupRole {
|
|||
SetupRole::Protector => Role::Protector {
|
||||
last_protected: None,
|
||||
},
|
||||
SetupRole::Apprentice {
|
||||
specifically: Some(role),
|
||||
} => Role::Apprentice(role),
|
||||
SetupRole::Apprentice { specifically: None } => {
|
||||
SetupRole::Apprentice { to: Some(role) } => Role::Apprentice(role),
|
||||
SetupRole::Apprentice { to: None } => {
|
||||
let mentors = roles_in_game
|
||||
.iter()
|
||||
.filter(|r| r.is_mentor())
|
||||
|
|
@ -197,7 +252,7 @@ impl From<RoleTitle> for SetupRole {
|
|||
RoleTitle::MapleWolf => SetupRole::MapleWolf,
|
||||
RoleTitle::Guardian => SetupRole::Guardian,
|
||||
RoleTitle::Protector => SetupRole::Protector,
|
||||
RoleTitle::Apprentice => SetupRole::Apprentice { specifically: None },
|
||||
RoleTitle::Apprentice => SetupRole::Apprentice { to: None },
|
||||
RoleTitle::Elder => SetupRole::Elder {
|
||||
knows_on_night: NonZeroU8::new(3).unwrap(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -99,29 +99,33 @@ impl Role {
|
|||
}
|
||||
}
|
||||
|
||||
pub const fn wakes_night_zero(&self) -> bool {
|
||||
match self {
|
||||
Role::DireWolf | Role::Arcanist | Role::Seer => true,
|
||||
|
||||
Role::Shapeshifter { .. }
|
||||
| Role::Werewolf
|
||||
| Role::AlphaWolf { .. }
|
||||
| Role::Elder { .. }
|
||||
| Role::Gravedigger
|
||||
| Role::Hunter { .. }
|
||||
| Role::Militia { .. }
|
||||
| Role::MapleWolf { .. }
|
||||
| Role::Guardian { .. }
|
||||
| Role::Apprentice(_)
|
||||
| Role::Villager
|
||||
| Role::Scapegoat { .. }
|
||||
| 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 match self {
|
||||
Role::DireWolf | Role::Arcanist | Role::Seer => true,
|
||||
|
||||
Role::Shapeshifter { .. }
|
||||
| Role::Werewolf
|
||||
| Role::AlphaWolf { .. }
|
||||
| Role::Elder { .. }
|
||||
| Role::Gravedigger
|
||||
| Role::Hunter { .. }
|
||||
| Role::Militia { .. }
|
||||
| Role::MapleWolf { .. }
|
||||
| Role::Guardian { .. }
|
||||
| Role::Apprentice(_)
|
||||
| Role::Villager
|
||||
| Role::Scapegoat { .. }
|
||||
| Role::Protector { .. } => false,
|
||||
};
|
||||
return self.wakes_night_zero();
|
||||
}
|
||||
match self {
|
||||
Role::AlphaWolf { killed: Some(_) }
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ impl Client {
|
|||
self.handle_message(msg).await
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("[{}] recv error: {err}", self.connection_id.player_id());
|
||||
log::debug!("[{}] recv error: {err}", self.connection_id.player_id());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,12 +86,13 @@ impl Lobby {
|
|||
}
|
||||
|
||||
pub async fn next(&mut self) -> Option<GameRunner> {
|
||||
let msg = self
|
||||
.comms()
|
||||
.unwrap()
|
||||
.next_message()
|
||||
.await
|
||||
.expect("get next message"); // TODO: keeps happening
|
||||
let msg = match self.comms().unwrap().next_message().await {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => {
|
||||
log::error!("get next message: {err}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match self.next_inner(msg.clone()).await.map_err(|err| (msg, err)) {
|
||||
Ok(None) => {}
|
||||
|
|
@ -162,7 +163,14 @@ impl Lobby {
|
|||
))
|
||||
.log_warn(),
|
||||
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)) => {
|
||||
let msg = ServerToHostMessage::GameSettings(self.settings.clone());
|
||||
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;
|
||||
}
|
||||
|
||||
.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::{
|
||||
Button, CoverOfDarkness, Lobby, LobbyPlayerAction, RoleReveal, Settings,
|
||||
action::{ActionResultView, Prompt},
|
||||
host::DaytimePlayerList,
|
||||
host::{DaytimePlayerList, Setup},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -302,76 +302,10 @@ impl Component for Host {
|
|||
</div>
|
||||
},
|
||||
HostState::Lobby { players, settings } => {
|
||||
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>
|
||||
if self.big_screen {
|
||||
self.lobby_big_screen_show_setup(players, settings)
|
||||
} else {
|
||||
self.lobby_setup(players, settings)
|
||||
}
|
||||
}
|
||||
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! {
|
||||
<div class="settings">
|
||||
<div class="top-settings">
|
||||
{fill_empty_with_villagers}
|
||||
{clear_setup}
|
||||
{clear_all_assignments}
|
||||
{clear_bad_assigned}
|
||||
</div>
|
||||
|
|
@ -376,7 +409,7 @@ fn setup_options_for_slot(
|
|||
</>
|
||||
})
|
||||
}
|
||||
SetupRole::Apprentice { specifically } => {
|
||||
SetupRole::Apprentice { to: specifically } => {
|
||||
let options = roles_in_setup
|
||||
.iter()
|
||||
.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 mut slot = slot.clone();
|
||||
match &mut slot.role {
|
||||
SetupRole::Apprentice { specifically } => {
|
||||
SetupRole::Apprentice { to: specifically } => {
|
||||
specifically.replace(option);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
|
@ -421,7 +454,7 @@ fn setup_options_for_slot(
|
|||
let role_name = "random";
|
||||
let mut slot = slot.clone();
|
||||
match &mut slot.role {
|
||||
SetupRole::Apprentice { specifically } => {
|
||||
SetupRole::Apprentice { to: specifically } => {
|
||||
specifically.take();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ fn main() {
|
|||
}
|
||||
} else if path.starts_with("/many-client") {
|
||||
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),
|
||||
format!("player {num}"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue