removed notorious, added more info to host setup
This commit is contained in:
parent
3d17a2d54b
commit
6dd78aa1b5
|
|
@ -53,8 +53,6 @@ pub enum Aura {
|
|||
/// getting a first result of wolf/killer/powerful if seer/adjudicator/power seer
|
||||
#[checks("assignable")]
|
||||
InevitableScapegoat,
|
||||
#[checks("assignable")]
|
||||
Notorious,
|
||||
}
|
||||
|
||||
impl Display for Aura {
|
||||
|
|
@ -69,7 +67,6 @@ impl Display for Aura {
|
|||
Aura::VindictiveScapegoat => "Vindictive Scapegoat",
|
||||
Aura::SpitefulScapegoat => "Spiteful Scapegoat",
|
||||
Aura::InevitableScapegoat => "Inevitable Scapegoat",
|
||||
Aura::Notorious => "Notorious",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +79,6 @@ impl Aura {
|
|||
| Aura::VindictiveScapegoat
|
||||
| Aura::SpitefulScapegoat
|
||||
| Aura::InevitableScapegoat
|
||||
| Aura::Notorious
|
||||
| Aura::Traitor
|
||||
| Aura::Drunk(_)
|
||||
| Aura::Insane => false,
|
||||
|
|
@ -171,8 +167,7 @@ impl Auras {
|
|||
for aura in self.0.iter() {
|
||||
match aura {
|
||||
Aura::Traitor => return Some(Alignment::Traitor),
|
||||
Aura::Notorious
|
||||
| Aura::RedeemableScapegoat
|
||||
Aura::RedeemableScapegoat
|
||||
| Aura::VindictiveScapegoat
|
||||
| Aura::SpitefulScapegoat
|
||||
| Aura::Scapegoat
|
||||
|
|
@ -212,7 +207,39 @@ impl AuraTitle {
|
|||
AuraTitle::VindictiveScapegoat => Aura::VindictiveScapegoat,
|
||||
AuraTitle::SpitefulScapegoat => Aura::SpitefulScapegoat,
|
||||
AuraTitle::InevitableScapegoat => Aura::InevitableScapegoat,
|
||||
AuraTitle::Notorious => Aura::Notorious,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns a list of auras with the current aura added.
|
||||
///
|
||||
/// **this will remove incompatible auras**
|
||||
pub fn try_add_to_list(&self, list: &[Self]) -> Vec<Self> {
|
||||
list.iter()
|
||||
.filter(|o| *o != self && !self.incompatible_with(**o))
|
||||
.copied()
|
||||
.chain(Some(*self))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// determines whether the `other` [AuraTitle] is incompatible with
|
||||
/// this aura
|
||||
pub fn incompatible_with(&self, other: Self) -> bool {
|
||||
match self {
|
||||
AuraTitle::Bloodlet | AuraTitle::Insane | AuraTitle::Drunk | AuraTitle::Traitor => {
|
||||
false
|
||||
}
|
||||
AuraTitle::VindictiveScapegoat
|
||||
| AuraTitle::RedeemableScapegoat
|
||||
| AuraTitle::SpitefulScapegoat
|
||||
| AuraTitle::InevitableScapegoat
|
||||
| AuraTitle::Scapegoat => matches!(
|
||||
other,
|
||||
AuraTitle::Scapegoat
|
||||
| AuraTitle::RedeemableScapegoat
|
||||
| AuraTitle::SpitefulScapegoat
|
||||
| AuraTitle::VindictiveScapegoat
|
||||
| AuraTitle::InevitableScapegoat
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -643,7 +643,6 @@ impl Night {
|
|||
Aura::RedeemableScapegoat
|
||||
| Aura::VindictiveScapegoat
|
||||
| Aura::SpitefulScapegoat
|
||||
| Aura::Notorious
|
||||
| Aura::Scapegoat
|
||||
| Aura::Traitor
|
||||
| Aura::Bloodlet { .. } => continue,
|
||||
|
|
|
|||
|
|
@ -165,7 +165,6 @@ impl SetupRoleTitle {
|
|||
| AuraTitle::VindictiveScapegoat
|
||||
| AuraTitle::SpitefulScapegoat
|
||||
| AuraTitle::InevitableScapegoat
|
||||
| AuraTitle::Notorious
|
||||
| AuraTitle::Traitor
|
||||
| AuraTitle::Bloodlet
|
||||
| AuraTitle::Insane => false,
|
||||
|
|
@ -212,12 +211,10 @@ impl SetupRoleTitle {
|
|||
| SetupRoleTitle::Insomniac
|
||||
)
|
||||
}
|
||||
AuraTitle::Notorious => {
|
||||
!matches!(self, SetupRoleTitle::Villager | SetupRoleTitle::Scapegoat)
|
||||
}
|
||||
AuraTitle::RedeemableScapegoat => matches!(self, SetupRoleTitle::Villager),
|
||||
AuraTitle::VindictiveScapegoat | AuraTitle::SpitefulScapegoat => true,
|
||||
AuraTitle::Scapegoat => matches!(self, SetupRoleTitle::Villager),
|
||||
AuraTitle::VindictiveScapegoat
|
||||
| AuraTitle::SpitefulScapegoat
|
||||
| AuraTitle::Scapegoat => !matches!(self, SetupRoleTitle::Scapegoat),
|
||||
}
|
||||
}
|
||||
pub fn into_role(self) -> Role {
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ nav.host-nav {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
&>label {
|
||||
&>span {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -473,13 +473,17 @@ button.confirm {
|
|||
}
|
||||
|
||||
.roles-in-setup {
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
padding: 10px;
|
||||
padding: 1rem;
|
||||
border: 4px solid $village_color_faint;
|
||||
background-color: color.change($village_color_faint, $alpha: 0.05);
|
||||
|
||||
zoom: 120%;
|
||||
|
||||
&>h3 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: white;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -862,11 +866,9 @@ clients {
|
|||
}
|
||||
|
||||
&.shown {
|
||||
// visibility: visible;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
// position: absolute;
|
||||
}
|
||||
|
||||
button {
|
||||
|
|
@ -914,6 +916,31 @@ error {
|
|||
}
|
||||
}
|
||||
|
||||
.identity-span {
|
||||
list-style: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
font-size: 1rem;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.number {
|
||||
padding-right: 5px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.binary {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
@ -1133,6 +1160,52 @@ input {
|
|||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.slot-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 3px;
|
||||
font-size: 0.7em;
|
||||
|
||||
.slot-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 3px;
|
||||
align-items: center;
|
||||
|
||||
opacity: 30%;
|
||||
|
||||
&:hover {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slot-auras {
|
||||
padding-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
.slot-aura-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px;
|
||||
|
||||
span::after {
|
||||
content: ', ';
|
||||
}
|
||||
|
||||
span:last-child::after {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.increment-decrement {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -1142,9 +1215,7 @@ input {
|
|||
align-items: center;
|
||||
align-content: center;
|
||||
|
||||
&>label {
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
&>span {
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
|
|
@ -1158,8 +1229,7 @@ input {
|
|||
.setup-slot {
|
||||
text-align: center;
|
||||
|
||||
& button label {
|
||||
color: white;
|
||||
& button span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -1167,6 +1237,7 @@ input {
|
|||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
|
||||
&>.submenu {
|
||||
width: 30vw;
|
||||
|
|
@ -1199,6 +1270,19 @@ input {
|
|||
gap: 10px;
|
||||
font-size: 1.5rem;
|
||||
|
||||
.role-box {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&>* {
|
||||
flex-shrink: 1;
|
||||
width: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -1431,7 +1515,8 @@ input {
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
justify-content: flex-start;
|
||||
gap: 5px;
|
||||
row-gap: 10px;
|
||||
|
||||
font-size: 2em;
|
||||
|
|
@ -1463,71 +1548,106 @@ input {
|
|||
border: 1px solid color.change($defensive_border, $lightness: 40%);
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: 30px;
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.final {
|
||||
margin-top: 1cm;
|
||||
margin-bottom: 1cm;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.category {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.add-list {
|
||||
@media only screen and (max-width : 599px) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& .title {
|
||||
text-shadow: black 3px 2px;
|
||||
margin-bottom: 10px;
|
||||
@media only screen and (min-width : 600px) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
& .count {
|
||||
text-align: right;
|
||||
left: -40px;
|
||||
position: relative;
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
width: auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 1px;
|
||||
|
||||
.scapegoats {
|
||||
color: rgba(255, 0, 255, 0.7);
|
||||
font-size: 2em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&>.title {
|
||||
font-size: 0.5em !important;
|
||||
margin-bottom: 0px !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
text-align: left;
|
||||
flex: 1, 1, 100%;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
gap: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.slot {
|
||||
&.big {
|
||||
margin-bottom: 30px;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&.final {
|
||||
margin-top: 1cm;
|
||||
margin-bottom: 1cm;
|
||||
}
|
||||
|
||||
& .title {
|
||||
text-shadow: black 3px 2px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
& .count {
|
||||
text-align: right;
|
||||
left: -40px;
|
||||
position: relative;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
.scapegoats {
|
||||
color: rgba(255, 0, 255, 0.7);
|
||||
font-size: 2em;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.category-list {
|
||||
text-align: left;
|
||||
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;
|
||||
}
|
||||
|
||||
.attributes {
|
||||
margin-left: 10px;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.role {
|
||||
text-shadow: black 3px 2px;
|
||||
|
||||
.role {
|
||||
text-shadow: black 3px 2px;
|
||||
width: 100%;
|
||||
filter: saturate(40%);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
width: 100%;
|
||||
filter: saturate(40%);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
&.wakes {
|
||||
border: 2px solid yellow;
|
||||
}
|
||||
&.wakes {
|
||||
border: 2px solid yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2521,3 +2641,10 @@ li.choice {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ use yew::{html::Scope, prelude::*};
|
|||
use crate::{
|
||||
callback,
|
||||
components::{
|
||||
Button, Footer, Lobby, LobbyPlayerAction, RoleReveal, Settings, Story, Victory,
|
||||
Button, Footer, Lobby, LobbyPlayerAction, RoleReveal, Story, Victory,
|
||||
action::{ActionResultView, Prompt},
|
||||
host::{CharacterStatesReadOnly, DaytimePlayerList, Setup},
|
||||
settings::Settings,
|
||||
},
|
||||
pages::RolePage,
|
||||
storage::StorageKey,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ impl Class for AuraTitle {
|
|||
AuraTitle::RedeemableScapegoat
|
||||
| AuraTitle::SpitefulScapegoat
|
||||
| AuraTitle::VindictiveScapegoat
|
||||
| AuraTitle::Notorious
|
||||
| AuraTitle::InevitableScapegoat
|
||||
| AuraTitle::Scapegoat => "scapegoat",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ pub fn Setup(SetupProps { settings }: &SetupProps) -> Html {
|
|||
<div class="setup">
|
||||
{categories}
|
||||
</div>
|
||||
<div class="category village final">
|
||||
<div class="category big village final">
|
||||
<span class="count">{power_roles_count}</span>
|
||||
<span class="title">{"Power roles from..."}</span>
|
||||
</div>
|
||||
|
|
@ -208,7 +208,7 @@ pub fn SetupCategory(
|
|||
})
|
||||
.collect::<Html>();
|
||||
html! {
|
||||
<div class="category">
|
||||
<div class="category big">
|
||||
{roles_count}
|
||||
<div class={classes!("title", category.class())}>
|
||||
{category.to_string().to_case(Case::Title)}
|
||||
|
|
|
|||
|
|
@ -241,8 +241,7 @@ impl PartialAssociatedIcon for AuraTitle {
|
|||
| AuraTitle::RedeemableScapegoat
|
||||
| AuraTitle::VindictiveScapegoat
|
||||
| AuraTitle::SpitefulScapegoat
|
||||
| AuraTitle::InevitableScapegoat
|
||||
| AuraTitle::Notorious => Some(IconSource::Scapegoat),
|
||||
| AuraTitle::InevitableScapegoat => Some(IconSource::Scapegoat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,3 +50,33 @@ pub fn Identity(props: &IdentityProps) -> Html {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn IdentitySpan(
|
||||
IdentityProps {
|
||||
ident:
|
||||
PublicIdentity {
|
||||
name,
|
||||
pronouns,
|
||||
number,
|
||||
},
|
||||
class,
|
||||
}: &IdentityProps,
|
||||
) -> Html {
|
||||
let pronouns = pronouns.as_ref().map(|p| {
|
||||
html! {
|
||||
<span class="pronouns">{"("}{p}{")"}</span>
|
||||
}
|
||||
});
|
||||
let not_set = number.is_none().then_some("not-set");
|
||||
let number = number
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or_else(|| String::from("???"));
|
||||
html! {
|
||||
<div class={classes!("identity-span", class.clone())}>
|
||||
<span class={classes!("number", not_set)}><b>{number}</b></span>
|
||||
<span>{name}</span>
|
||||
{pronouns}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (C) 2025 Emilis Bliūdžius
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use convert_case::{Case, Casing};
|
||||
use werewolves_proto::role::RoleTitle;
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::components::Button;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct AddRoleCategoryProps {
|
||||
pub category: werewolves_proto::game::Category,
|
||||
pub roles: Box<[RoleTitle]>,
|
||||
pub add_role: Callback<RoleTitle>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn AddRoleCategory(
|
||||
AddRoleCategoryProps {
|
||||
category,
|
||||
roles,
|
||||
add_role,
|
||||
}: &AddRoleCategoryProps,
|
||||
) -> Html {
|
||||
let class = category.class();
|
||||
let hidden = use_state(|| true);
|
||||
|
||||
let roles = roles
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|title| {
|
||||
let on_click = {
|
||||
let add_role = add_role.clone();
|
||||
let role = title;
|
||||
Callback::from(move |_| add_role.emit(role))
|
||||
};
|
||||
let name = title.to_string().to_case(Case::Title);
|
||||
html! {
|
||||
<div class="slot">
|
||||
<Button on_click={on_click} classes={classes!("role", class)}>
|
||||
{name}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
let on_toggle = {
|
||||
let hidden = hidden.clone();
|
||||
Callback::from(move |_| hidden.set(!*hidden))
|
||||
};
|
||||
let hidden = (*hidden).then_some("hidden");
|
||||
|
||||
html! {
|
||||
<div class="category add-list">
|
||||
<div class={classes!("title", class)} onclick={on_toggle}>
|
||||
{category.to_string().to_case(Case::Sentence)}
|
||||
</div>
|
||||
<div class={classes!("category-list", hidden)}>
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
// Copyright (C) 2025 Emilis Bliūdžius
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use core::{num::NonZeroU8, ops::Not};
|
||||
use std::rc::Rc;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use werewolves_proto::{
|
||||
aura::AuraTitle,
|
||||
error::GameError,
|
||||
game::{Category, GameSettings, OrRandom, SetupRole, SetupSlot, SlotId},
|
||||
message::{Identification, PlayerState, PublicIdentity},
|
||||
role::RoleTitle,
|
||||
};
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::{
|
||||
class::Class,
|
||||
components::{
|
||||
Button, ClickableField, Icon, IconType, Identity, PartialAssociatedIcon,
|
||||
client::Signin,
|
||||
settings::{AddRoleCategory, SettingSlotAction, SettingsSlot},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Properties)]
|
||||
pub struct SettingsProps {
|
||||
pub settings: GameSettings,
|
||||
pub players_in_lobby: Rc<[PlayerState]>,
|
||||
pub on_update: Callback<GameSettings>,
|
||||
pub on_start: Callback<()>,
|
||||
pub on_add_player: Callback<PublicIdentity>,
|
||||
pub qr_mode_button: Html,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Settings(
|
||||
SettingsProps {
|
||||
settings,
|
||||
players_in_lobby,
|
||||
on_update,
|
||||
on_start,
|
||||
on_add_player,
|
||||
qr_mode_button,
|
||||
}: &SettingsProps,
|
||||
) -> Html {
|
||||
let players = players_in_lobby
|
||||
.iter()
|
||||
.map(|p| p.identification.clone())
|
||||
.collect::<Rc<[_]>>();
|
||||
let disabled_reason = settings
|
||||
.check_with_player_list(&players)
|
||||
.err()
|
||||
.map(|err| err.to_string());
|
||||
|
||||
let roles_in_setup: Rc<[RoleTitle]> = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.map(|s| Into::<RoleTitle>::into(s.role.clone()))
|
||||
.collect();
|
||||
let on_update_role = on_update.clone();
|
||||
let settings = Rc::new(settings.clone());
|
||||
let on_update_role_settings = settings.clone();
|
||||
let already_assigned_pids = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.filter_map(|r| r.assign_to)
|
||||
.collect::<Box<[_]>>();
|
||||
let players_for_assign = players
|
||||
.iter()
|
||||
.filter(|p| !already_assigned_pids.contains(&p.player_id))
|
||||
.cloned()
|
||||
.collect::<Rc<[_]>>();
|
||||
let update_role_card = Callback::from(move |act: SettingSlotAction| {
|
||||
let mut new_settings = (*on_update_role_settings).clone();
|
||||
match act {
|
||||
SettingSlotAction::Remove(slot_id) => new_settings.remove_slot(slot_id),
|
||||
SettingSlotAction::Update(slot) => new_settings.update_slot(slot),
|
||||
}
|
||||
on_update_role.emit(new_settings);
|
||||
});
|
||||
let roles = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.map(|slot| {
|
||||
html! {
|
||||
<SettingsSlot
|
||||
all_players={players.clone()}
|
||||
players_for_assign={players_for_assign.clone()}
|
||||
roles_in_setup={roles_in_setup.clone()}
|
||||
slot={slot.clone()}
|
||||
update={update_role_card.clone()}
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
let add_roles_update = on_update.clone();
|
||||
|
||||
let add_role = {
|
||||
let settings = settings.clone();
|
||||
let update = add_roles_update.clone();
|
||||
Callback::from(move |role: RoleTitle| {
|
||||
let mut settings = (*settings).clone();
|
||||
settings.new_slot(role);
|
||||
update.emit(settings);
|
||||
})
|
||||
};
|
||||
|
||||
let add_roles_buttons = Category::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|cat| {
|
||||
let roles = cat
|
||||
.entire_category()
|
||||
.into_iter()
|
||||
.map(|c| c.into_role().title())
|
||||
.collect::<Box<[_]>>();
|
||||
html! {
|
||||
<AddRoleCategory category={cat} roles={roles} add_role={add_role.clone()}/>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
let clear_bad_assigned = matches!(
|
||||
settings.check_with_player_list(&players),
|
||||
Err(GameError::AssignedMultipleTimes(_, _)) | Err(GameError::AssignedPlayerMissing(_))
|
||||
)
|
||||
.then(|| {
|
||||
let clear_settings = settings.clone();
|
||||
let clear_update = on_update.clone();
|
||||
let clear_players = players.clone();
|
||||
let clear_bad_assigned = Callback::from(move |_| {
|
||||
let mut settings = (*clear_settings).clone();
|
||||
settings.remove_assignments_not_in_list(&clear_players);
|
||||
settings.remove_duplicate_assignments();
|
||||
clear_update.emit(settings);
|
||||
});
|
||||
|
||||
html! {
|
||||
<Button on_click={clear_bad_assigned}>
|
||||
{"clear bad assignments"}
|
||||
</Button>
|
||||
}
|
||||
});
|
||||
|
||||
let clear_all_assignments = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.any(|s| s.assign_to.is_some())
|
||||
.then(|| {
|
||||
let settings = settings.clone();
|
||||
let update = on_update.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
let mut settings = (*settings).clone();
|
||||
settings
|
||||
.slots()
|
||||
.iter()
|
||||
.filter(|s| s.assign_to.is_some())
|
||||
.map(|s| {
|
||||
let mut s = s.clone();
|
||||
s.assign_to.take();
|
||||
s
|
||||
})
|
||||
.collect::<Box<[_]>>()
|
||||
.into_iter()
|
||||
.for_each(|s| settings.update_slot(s));
|
||||
update.emit(settings);
|
||||
});
|
||||
html! {
|
||||
<Button on_click={on_click}>{"clear all assignments"}</Button>
|
||||
}
|
||||
});
|
||||
|
||||
let assignments = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.any(|s| s.assign_to.is_some())
|
||||
.then(|| {
|
||||
let assignments =
|
||||
settings
|
||||
.slots()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(|s| {
|
||||
s.assign_to
|
||||
.as_ref()
|
||||
.map(|a| {
|
||||
players
|
||||
.iter()
|
||||
.find(|p| p.player_id == *a)
|
||||
.map(|assign| {
|
||||
let class = s.role.category().class();
|
||||
(html! {
|
||||
<Identity ident={assign.public.clone()}/>
|
||||
}, Some(class))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
(html! {
|
||||
<span>{"[left the lobby]"}</span>
|
||||
}, None)
|
||||
})
|
||||
})
|
||||
.map(|(who, class)| {
|
||||
let assignments_update = on_update.clone();
|
||||
let assignments_settings = settings.clone();
|
||||
let click_slot = s.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
let mut settings = (*assignments_settings).clone();
|
||||
let mut click_slot = click_slot.clone();
|
||||
click_slot.assign_to.take();
|
||||
settings.update_slot(click_slot);
|
||||
assignments_update.emit(settings);
|
||||
});
|
||||
html! {
|
||||
<Button classes={classes!("assignment", class)} on_click={on_click}>
|
||||
<label>{s.role.to_string().to_case(Case::Title)}</label>
|
||||
{who}
|
||||
</Button>
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
html! {
|
||||
<>
|
||||
<label>{"assignments"}</label>
|
||||
<div class="assignments">
|
||||
{assignments}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
}
|
||||
};
|
||||
|
||||
let add_player_open = use_state(|| false);
|
||||
let add_player_opts = html! {
|
||||
<div class="add-player">
|
||||
<Signin callback={on_add_player.clone()}/>
|
||||
</div>
|
||||
};
|
||||
let current_roles = settings.slots().len();
|
||||
let min_roles = settings.min_players_needed();
|
||||
let min_roles_class = (current_roles < min_roles).then_some("red");
|
||||
let player_count = players_in_lobby.len();
|
||||
let player_count_class = (player_count != current_roles).then_some("red");
|
||||
|
||||
html! {
|
||||
<div class="settings">
|
||||
<div class="top-settings">
|
||||
{qr_mode_button.clone()}
|
||||
{fill_empty_with_villagers}
|
||||
{clear_setup}
|
||||
{clear_all_assignments}
|
||||
{clear_bad_assigned}
|
||||
</div>
|
||||
<ClickableField
|
||||
options={add_player_opts}
|
||||
state={add_player_open}
|
||||
>
|
||||
{"add player"}
|
||||
</ClickableField>
|
||||
|
||||
<div class="roles-add-list">
|
||||
{add_roles_buttons}
|
||||
</div>
|
||||
<div class="settings-info">
|
||||
<span class="current-min-role-count">
|
||||
<span class="dimmed">{"current/minimum roles: "}</span>
|
||||
<span class={classes!(min_roles_class)}>
|
||||
<span class="current-roles">
|
||||
{current_roles}
|
||||
</span>
|
||||
{"/"}
|
||||
<span class="min-roles">
|
||||
{min_roles}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="player-role-count">
|
||||
<span class="dimmed">{"players/roles: "}</span>
|
||||
<span class={classes!(player_count_class)}>
|
||||
<span>
|
||||
{player_count}
|
||||
</span>
|
||||
{"/"}
|
||||
<span>
|
||||
{current_roles}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="roles-in-setup">
|
||||
<h3>{"roles in the game"}</h3>
|
||||
<div class="role-list">
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
{assignments}
|
||||
<Button
|
||||
disabled_reason={disabled_reason}
|
||||
classes={classes!("start-game")}
|
||||
on_click={on_start.clone()}
|
||||
>
|
||||
{"start game"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
@ -12,348 +12,27 @@
|
|||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use core::{num::NonZeroU8, ops::Not};
|
||||
use std::rc::Rc;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use werewolves_proto::{
|
||||
aura::AuraTitle,
|
||||
error::GameError,
|
||||
game::{GameSettings, OrRandom, SetupRole, SetupSlot, SlotId},
|
||||
message::{Identification, PlayerState, PublicIdentity},
|
||||
game::{OrRandom, SetupRole, SetupSlot, SlotId},
|
||||
message::Identification,
|
||||
role::RoleTitle,
|
||||
};
|
||||
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::{
|
||||
class::Class,
|
||||
components::{
|
||||
Button, ClickableField, Icon, IconType, Identity, PartialAssociatedIcon, client::Signin,
|
||||
Button, ClickableField, Icon, IconType, Identity, IdentitySpan, PartialAssociatedIcon,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Properties)]
|
||||
pub struct SettingsProps {
|
||||
pub settings: GameSettings,
|
||||
pub players_in_lobby: Rc<[PlayerState]>,
|
||||
pub on_update: Callback<GameSettings>,
|
||||
pub on_start: Callback<()>,
|
||||
pub on_add_player: Callback<PublicIdentity>,
|
||||
pub qr_mode_button: Html,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Settings(
|
||||
SettingsProps {
|
||||
settings,
|
||||
players_in_lobby,
|
||||
on_update,
|
||||
on_start,
|
||||
on_add_player,
|
||||
qr_mode_button,
|
||||
}: &SettingsProps,
|
||||
) -> Html {
|
||||
let players = players_in_lobby
|
||||
.iter()
|
||||
.map(|p| p.identification.clone())
|
||||
.collect::<Rc<[_]>>();
|
||||
let disabled_reason = settings
|
||||
.check_with_player_list(&players)
|
||||
.err()
|
||||
.map(|err| err.to_string());
|
||||
|
||||
let roles_in_setup: Rc<[RoleTitle]> = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.map(|s| Into::<RoleTitle>::into(s.role.clone()))
|
||||
.collect();
|
||||
let on_update_role = on_update.clone();
|
||||
let settings = Rc::new(settings.clone());
|
||||
let on_update_role_settings = settings.clone();
|
||||
let already_assigned_pids = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.filter_map(|r| r.assign_to)
|
||||
.collect::<Box<[_]>>();
|
||||
let players_for_assign = players
|
||||
.iter()
|
||||
.filter(|p| !already_assigned_pids.contains(&p.player_id))
|
||||
.cloned()
|
||||
.collect::<Rc<[_]>>();
|
||||
let update_role_card = Callback::from(move |act: SettingSlotAction| {
|
||||
let mut new_settings = (*on_update_role_settings).clone();
|
||||
match act {
|
||||
SettingSlotAction::Remove(slot_id) => new_settings.remove_slot(slot_id),
|
||||
SettingSlotAction::Update(slot) => new_settings.update_slot(slot),
|
||||
}
|
||||
on_update_role.emit(new_settings);
|
||||
});
|
||||
let roles = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.map(|slot| {
|
||||
html! {
|
||||
<SettingsSlot
|
||||
all_players={players.clone()}
|
||||
players_for_assign={players_for_assign.clone()}
|
||||
roles_in_setup={roles_in_setup.clone()}
|
||||
slot={slot.clone()}
|
||||
update={update_role_card.clone()}
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
let add_roles_update = on_update.clone();
|
||||
let sorted_role_tiles = {
|
||||
let mut v = RoleTitle::ALL.to_vec();
|
||||
v.sort_by_key(|v| Into::<SetupRole>::into(*v).category());
|
||||
v
|
||||
};
|
||||
let add_roles_buttons = sorted_role_tiles
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let update = add_roles_update.clone();
|
||||
let settings = settings.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
let mut settings = (*settings).clone();
|
||||
settings.new_slot(r);
|
||||
update.emit(settings);
|
||||
});
|
||||
let class = Into::<SetupRole>::into(r).category().class();
|
||||
let name = r.to_string().to_case(Case::Title);
|
||||
// let icon = r.icon().map(|icon| {
|
||||
// html! {
|
||||
// <Icon source={icon} icon_type={IconType::Small}/>
|
||||
// }
|
||||
// });
|
||||
html! {
|
||||
<button
|
||||
onclick={on_click}
|
||||
class={classes!(class, "add-role")}
|
||||
>
|
||||
<span>{name}</span>
|
||||
</button>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
let clear_bad_assigned = matches!(
|
||||
settings.check_with_player_list(&players),
|
||||
Err(GameError::AssignedMultipleTimes(_, _)) | Err(GameError::AssignedPlayerMissing(_))
|
||||
)
|
||||
.then(|| {
|
||||
let clear_settings = settings.clone();
|
||||
let clear_update = on_update.clone();
|
||||
let clear_players = players.clone();
|
||||
let clear_bad_assigned = Callback::from(move |_| {
|
||||
let mut settings = (*clear_settings).clone();
|
||||
settings.remove_assignments_not_in_list(&clear_players);
|
||||
settings.remove_duplicate_assignments();
|
||||
clear_update.emit(settings);
|
||||
});
|
||||
|
||||
html! {
|
||||
<Button on_click={clear_bad_assigned}>
|
||||
{"clear bad assignments"}
|
||||
</Button>
|
||||
}
|
||||
});
|
||||
|
||||
let clear_all_assignments = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.any(|s| s.assign_to.is_some())
|
||||
.then(|| {
|
||||
let settings = settings.clone();
|
||||
let update = on_update.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
let mut settings = (*settings).clone();
|
||||
settings
|
||||
.slots()
|
||||
.iter()
|
||||
.filter(|s| s.assign_to.is_some())
|
||||
.map(|s| {
|
||||
let mut s = s.clone();
|
||||
s.assign_to.take();
|
||||
s
|
||||
})
|
||||
.collect::<Box<[_]>>()
|
||||
.into_iter()
|
||||
.for_each(|s| settings.update_slot(s));
|
||||
update.emit(settings);
|
||||
});
|
||||
html! {
|
||||
<Button on_click={on_click}>{"clear all assignments"}</Button>
|
||||
}
|
||||
});
|
||||
|
||||
let assignments = settings
|
||||
.slots()
|
||||
.iter()
|
||||
.any(|s| s.assign_to.is_some())
|
||||
.then(|| {
|
||||
let assignments =
|
||||
settings
|
||||
.slots()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(|s| {
|
||||
s.assign_to
|
||||
.as_ref()
|
||||
.map(|a| {
|
||||
players
|
||||
.iter()
|
||||
.find(|p| p.player_id == *a)
|
||||
.map(|assign| {
|
||||
let class = s.role.category().class();
|
||||
(html! {
|
||||
<Identity ident={assign.public.clone()}/>
|
||||
}, Some(class))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
(html! {
|
||||
<span>{"[left the lobby]"}</span>
|
||||
}, None)
|
||||
})
|
||||
})
|
||||
.map(|(who, class)| {
|
||||
let assignments_update = on_update.clone();
|
||||
let assignments_settings = settings.clone();
|
||||
let click_slot = s.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
let mut settings = (*assignments_settings).clone();
|
||||
let mut click_slot = click_slot.clone();
|
||||
click_slot.assign_to.take();
|
||||
settings.update_slot(click_slot);
|
||||
assignments_update.emit(settings);
|
||||
});
|
||||
html! {
|
||||
<Button classes={classes!("assignment", class)} on_click={on_click}>
|
||||
<label>{s.role.to_string().to_case(Case::Title)}</label>
|
||||
{who}
|
||||
</Button>
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
html! {
|
||||
<>
|
||||
<label>{"assignments"}</label>
|
||||
<div class="assignments">
|
||||
{assignments}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
}
|
||||
};
|
||||
|
||||
let add_player_open = use_state(|| false);
|
||||
let add_player_opts = html! {
|
||||
<div class="add-player">
|
||||
<Signin callback={on_add_player.clone()}/>
|
||||
</div>
|
||||
};
|
||||
let current_roles = settings.slots().len();
|
||||
let min_roles = settings.min_players_needed();
|
||||
let min_roles_class = (current_roles < min_roles).then_some("red");
|
||||
let player_count = players_in_lobby.len();
|
||||
let player_count_class = (player_count != current_roles).then_some("red");
|
||||
|
||||
html! {
|
||||
<div class="settings">
|
||||
<div class="top-settings">
|
||||
{qr_mode_button.clone()}
|
||||
{fill_empty_with_villagers}
|
||||
{clear_setup}
|
||||
{clear_all_assignments}
|
||||
{clear_bad_assigned}
|
||||
</div>
|
||||
<ClickableField
|
||||
options={add_player_opts}
|
||||
state={add_player_open}
|
||||
>
|
||||
{"add player"}
|
||||
</ClickableField>
|
||||
|
||||
<div class="roles-add-list">
|
||||
{add_roles_buttons}
|
||||
</div>
|
||||
<div class="settings-info">
|
||||
<span class="current-min-role-count">
|
||||
<span class="dimmed">{"current/minimum roles: "}</span>
|
||||
<span class={classes!(min_roles_class)}>
|
||||
<span class="current-roles">
|
||||
{current_roles}
|
||||
</span>
|
||||
{"/"}
|
||||
<span class="min-roles">
|
||||
{min_roles}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="player-role-count">
|
||||
<span class="dimmed">{"players/roles: "}</span>
|
||||
<span class={classes!(player_count_class)}>
|
||||
<span>
|
||||
{player_count}
|
||||
</span>
|
||||
{"/"}
|
||||
<span>
|
||||
{current_roles}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="roles-in-setup">
|
||||
<h3>{"roles in the game"}</h3>
|
||||
<div class="role-list">
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
{assignments}
|
||||
<Button
|
||||
disabled_reason={disabled_reason}
|
||||
classes={classes!("start-game")}
|
||||
on_click={on_start.clone()}
|
||||
>
|
||||
{"start game"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SettingSlotAction {
|
||||
Remove(SlotId),
|
||||
Update(SetupSlot),
|
||||
|
|
@ -418,7 +97,7 @@ pub fn SettingsSlot(
|
|||
})
|
||||
.unwrap_or_else(|| html! {{"assign"}});
|
||||
html! {
|
||||
<>
|
||||
<div class="option-menu">
|
||||
<Button on_click={on_kick} classes={classes!("red")}>
|
||||
{"remove"}
|
||||
</Button>
|
||||
|
|
@ -430,20 +109,62 @@ pub fn SettingsSlot(
|
|||
{assign_text}
|
||||
</ClickableField>
|
||||
{options}
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
};
|
||||
let class = slot.role.category().class();
|
||||
|
||||
let assigned_to = slot
|
||||
.assign_to
|
||||
.as_ref()
|
||||
.copied()
|
||||
.and_then(|assigned_to| all_players.iter().find(|p| p.player_id == assigned_to))
|
||||
.map(|ident| {
|
||||
html! {
|
||||
<IdentitySpan ident={ident.public.clone()}/>
|
||||
}
|
||||
});
|
||||
|
||||
let auras = slot.auras.is_empty().not().then_some({
|
||||
let list = slot
|
||||
.auras
|
||||
.iter()
|
||||
.map(|aura| {
|
||||
html! {
|
||||
<span>{aura.to_string()}</span>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
let title = (slot.auras.len() > 1).then_some(html! {
|
||||
<span>{"auras:"}</span>
|
||||
});
|
||||
html! {
|
||||
<div class="slot-auras">
|
||||
{title}
|
||||
<div class="slot-aura-list">
|
||||
{list}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
let other_options = display_options_for_slot(slot);
|
||||
html! {
|
||||
<ClickableField
|
||||
class={classes!("setup-slot")}
|
||||
button_class={classes!(class)}
|
||||
options={submenu}
|
||||
state={open}
|
||||
with_backdrop_exit=true
|
||||
>
|
||||
<label>{role_name}</label>
|
||||
</ClickableField>
|
||||
<div class="slot-container">
|
||||
<ClickableField
|
||||
class={classes!("setup-slot")}
|
||||
button_class={classes!(class, "faint")}
|
||||
options={submenu}
|
||||
state={open}
|
||||
with_backdrop_exit=true
|
||||
>
|
||||
<span>{role_name}</span>
|
||||
</ClickableField>
|
||||
<div class="slot-options">
|
||||
{assigned_to}
|
||||
{auras}
|
||||
{other_options}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -481,6 +202,81 @@ fn assign_to_submenu(
|
|||
}
|
||||
}
|
||||
|
||||
fn display_options_for_slot(slot: &SetupSlot) -> Html {
|
||||
let options = match &slot.role {
|
||||
SetupRole::Seer
|
||||
| SetupRole::Arcanist
|
||||
| SetupRole::Gravedigger
|
||||
| SetupRole::Hunter
|
||||
| SetupRole::Militia
|
||||
| SetupRole::MapleWolf
|
||||
| SetupRole::Guardian
|
||||
| SetupRole::Protector
|
||||
| SetupRole::Werewolf
|
||||
| SetupRole::AlphaWolf
|
||||
| SetupRole::DireWolf
|
||||
| SetupRole::Shapeshifter
|
||||
| SetupRole::LoneWolf
|
||||
| SetupRole::Bloodletter
|
||||
| SetupRole::Adjudicator
|
||||
| SetupRole::Insomniac
|
||||
| SetupRole::PowerSeer
|
||||
| SetupRole::Mortician
|
||||
| SetupRole::Beholder
|
||||
| SetupRole::Empath
|
||||
| SetupRole::Vindicator
|
||||
| SetupRole::Diseased
|
||||
| SetupRole::BlackKnight
|
||||
| SetupRole::Weightlifter
|
||||
| SetupRole::PyreMaster
|
||||
| SetupRole::Villager => return html! {},
|
||||
SetupRole::Elder { knows_on_night } => html! {
|
||||
<span>{"wakes night "}{knows_on_night.get()}</span>
|
||||
},
|
||||
SetupRole::MasonLeader { recruits_available } => html! {
|
||||
<span>{recruits_available.get()}{
|
||||
if recruits_available.get() == 1 {
|
||||
" recruit"
|
||||
} else {
|
||||
" recruits"
|
||||
}
|
||||
}</span>
|
||||
},
|
||||
SetupRole::Apprentice { to: None } => html! {
|
||||
<span>{"random mentor"}</span>
|
||||
},
|
||||
SetupRole::Apprentice { to: Some(mentor) } => {
|
||||
let class = Into::<SetupRole>::into(*mentor).category().class();
|
||||
html! {
|
||||
<span>
|
||||
{"apprentice to "}
|
||||
<span class={classes!(class, "faint")}>{mentor.to_string().to_case(Case::Sentence)}</span>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
SetupRole::Scapegoat {
|
||||
redeemed: OrRandom::Random,
|
||||
} => html! {
|
||||
<span>{"redemption random"}</span>
|
||||
},
|
||||
SetupRole::Scapegoat {
|
||||
redeemed: OrRandom::Determined(false),
|
||||
} => html! {
|
||||
<span>{"not redeemed"}</span>
|
||||
},
|
||||
SetupRole::Scapegoat {
|
||||
redeemed: OrRandom::Determined(true),
|
||||
} => html! {
|
||||
<span>{"redeemed"}</span>
|
||||
},
|
||||
};
|
||||
html! {
|
||||
<div class="slot-options-display">
|
||||
{options}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_options_for_slot(
|
||||
slot: &SetupSlot,
|
||||
update: &Callback<SettingSlotAction>,
|
||||
|
|
@ -506,7 +302,7 @@ fn setup_options_for_slot(
|
|||
if aura_active {
|
||||
slot.auras.retain(|a| *a != aura);
|
||||
} else {
|
||||
slot.auras.push(aura);
|
||||
slot.auras = aura.try_add_to_list(&slot.auras);
|
||||
}
|
||||
update.emit(SettingSlotAction::Update(slot))
|
||||
})
|
||||
|
|
@ -547,7 +343,7 @@ fn setup_options_for_slot(
|
|||
state={open_aura_assign}
|
||||
options={options}
|
||||
>
|
||||
{"auras"}
|
||||
{slot.auras.len()}{" auras"}
|
||||
</ClickableField>
|
||||
}
|
||||
})
|
||||
|
|
@ -597,10 +393,10 @@ fn setup_options_for_slot(
|
|||
let decrement_disabled_reason = prev.is_none().then_some("at minimum");
|
||||
Some(html! {
|
||||
<>
|
||||
<label>{"recruits"}</label>
|
||||
<span>{"recruits"}</span>
|
||||
<div class={classes!("increment-decrement")}>
|
||||
<Button on_click={on_decrement} disabled_reason={decrement_disabled_reason}>{"-"}</Button>
|
||||
<label>{recruits_available.get().to_string()}</label>
|
||||
<span>{recruits_available.get().to_string()}</span>
|
||||
<Button on_click={on_increment}>{"+"}</Button>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -630,9 +426,9 @@ fn setup_options_for_slot(
|
|||
};
|
||||
Some(html! {
|
||||
<>
|
||||
<label>{"redeemed?"}</label>
|
||||
<span>{"redeemed?"}</span>
|
||||
<Button on_click={on_click}>
|
||||
<label>{body}</label>
|
||||
<span>{body}</span>
|
||||
</Button>
|
||||
</>
|
||||
})
|
||||
|
|
@ -751,10 +547,10 @@ fn setup_options_for_slot(
|
|||
});
|
||||
Some(html! {
|
||||
<>
|
||||
<label>{"knows on night"}</label>
|
||||
<span>{"knows on night"}</span>
|
||||
<div class={classes!("increment-decrement")}>
|
||||
<Button on_click={decrement}>{"-"}</Button>
|
||||
<label>{knows_on_night.to_string()}</label>
|
||||
<span>{knows_on_night.to_string()}</span>
|
||||
<Button on_click={increment}>{"+"}</Button>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -31,6 +31,9 @@ mod components {
|
|||
pub mod action {
|
||||
werewolves_macros::include_path!("werewolves/src/components/action");
|
||||
}
|
||||
pub mod settings {
|
||||
werewolves_macros::include_path!("werewolves/src/components/settings");
|
||||
}
|
||||
}
|
||||
mod pages {
|
||||
werewolves_macros::include_path!("werewolves/src/pages");
|
||||
|
|
|
|||
Loading…
Reference in New Issue