2026-01-09 02:03:48 +00:00
|
|
|
use core::ops::Not;
|
|
|
|
|
|
2026-02-01 22:18:27 +00:00
|
|
|
// Copyright (C) 2025-2026 Emilis Bliūdžius
|
2025-11-05 20:24:51 +00:00
|
|
|
//
|
|
|
|
|
// 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/>.
|
2025-10-12 23:48:52 +01:00
|
|
|
use convert_case::{Case, Casing};
|
2026-01-09 02:03:48 +00:00
|
|
|
use werewolves_proto::{
|
|
|
|
|
character::Character,
|
|
|
|
|
game::{GameTime, SetupRole},
|
|
|
|
|
message::CharacterIdentity,
|
|
|
|
|
};
|
2025-10-12 23:48:52 +01:00
|
|
|
use yew::prelude::*;
|
|
|
|
|
|
2026-01-09 02:03:48 +00:00
|
|
|
use crate::{
|
|
|
|
|
class::Class,
|
|
|
|
|
components::{Icon, IconSource, IconType, PartialAssociatedIcon},
|
|
|
|
|
};
|
2025-10-12 23:48:52 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
|
|
|
|
pub struct CharacterCardProps {
|
|
|
|
|
pub char: Character,
|
2025-10-13 23:29:10 +01:00
|
|
|
#[prop_or_default]
|
|
|
|
|
pub dead: bool,
|
|
|
|
|
#[prop_or_default]
|
|
|
|
|
pub faint: bool,
|
2025-10-12 23:48:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[function_component]
|
2025-10-13 23:29:10 +01:00
|
|
|
pub fn CharacterCard(CharacterCardProps { faint, char, dead }: &CharacterCardProps) -> Html {
|
2025-10-12 23:48:52 +01:00
|
|
|
let class = Into::<SetupRole>::into(char.role_title())
|
|
|
|
|
.category()
|
|
|
|
|
.class();
|
|
|
|
|
let role = char.role_title().to_string().to_case(Case::Title);
|
|
|
|
|
let ident = char.identity();
|
|
|
|
|
let pronouns = ident.pronouns.as_ref().map(|p| {
|
|
|
|
|
html! {
|
|
|
|
|
<span class="pronouns">{"("}{p}{")"}</span>
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
let name = ident.name.clone();
|
|
|
|
|
let source = char.role_title().icon().unwrap_or(if char.is_village() {
|
|
|
|
|
IconSource::Village
|
|
|
|
|
} else {
|
|
|
|
|
IconSource::Wolves
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-13 23:29:10 +01:00
|
|
|
let dead = dead.then(|| {
|
|
|
|
|
html! {
|
|
|
|
|
<Icon source={IconSource::Skull} icon_type={IconType::Small}/>
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
let faint = faint.then_some("faint");
|
|
|
|
|
|
2025-10-12 23:48:52 +01:00
|
|
|
html! {
|
2025-10-13 23:29:10 +01:00
|
|
|
<span class={classes!("character-span", class, faint)}>
|
|
|
|
|
<span class={classes!("role", class, faint)}>
|
2025-10-12 23:48:52 +01:00
|
|
|
<div>
|
2025-10-13 23:29:10 +01:00
|
|
|
{dead}
|
2025-10-12 23:48:52 +01:00
|
|
|
<Icon source={source} icon_type={IconType::Small}/>
|
|
|
|
|
</div>
|
|
|
|
|
{role}
|
|
|
|
|
</span>
|
|
|
|
|
<span class={classes!("number")}><b>{ident.number.get()}</b></span>
|
|
|
|
|
<span class="name">{name}</span>
|
|
|
|
|
{pronouns}
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-13 23:29:10 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
|
|
|
|
pub struct CharacterTargetCardProps {
|
|
|
|
|
pub ident: CharacterIdentity,
|
2025-11-17 20:03:08 +00:00
|
|
|
#[prop_or_default]
|
|
|
|
|
pub classes: Classes,
|
2025-10-13 23:29:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[function_component]
|
2025-11-17 20:03:08 +00:00
|
|
|
pub fn CharacterTargetCard(
|
|
|
|
|
CharacterTargetCardProps { ident, classes }: &CharacterTargetCardProps,
|
|
|
|
|
) -> Html {
|
2025-10-13 23:29:10 +01:00
|
|
|
let name = ident.name.clone();
|
|
|
|
|
|
|
|
|
|
html! {
|
2025-11-17 20:03:08 +00:00
|
|
|
<span class={classes!("character-span", classes.clone())}>
|
2025-10-13 23:29:10 +01:00
|
|
|
<span class={classes!("number")}><b>{ident.number.get()}</b></span>
|
|
|
|
|
<span class="name">{name}</span>
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-09 02:03:48 +00:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Properties)]
|
|
|
|
|
pub struct CharacterHighlightProps {
|
|
|
|
|
pub char: Character,
|
|
|
|
|
pub time: GameTime,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[function_component]
|
|
|
|
|
pub fn CharacterHighlight(
|
|
|
|
|
CharacterHighlightProps { char, time }: &CharacterHighlightProps,
|
|
|
|
|
) -> Html {
|
|
|
|
|
let class = char.class().map(|c| format!("{c}-highlight"));
|
|
|
|
|
let icon = char
|
|
|
|
|
.role_title()
|
|
|
|
|
.icon()
|
|
|
|
|
.map(|source| {
|
|
|
|
|
html! {
|
|
|
|
|
<Icon source={source} icon_type={IconType::Fit}/>
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(html! {
|
|
|
|
|
<div class="icon-spacer"/>
|
|
|
|
|
});
|
|
|
|
|
let dead = char.died_to().and_then(|died_to| {
|
|
|
|
|
let night = died_to.night();
|
|
|
|
|
let day = died_to.day();
|
|
|
|
|
match (time, day, night) {
|
|
|
|
|
(GameTime::Day { number }, Some(day), None) => number.get() > day.get(),
|
|
|
|
|
(GameTime::Night { number }, None, Some(night)) => *number > night,
|
|
|
|
|
(GameTime::Day { number }, None, Some(night)) => number.get() > night,
|
|
|
|
|
(GameTime::Night { number }, Some(day), None) => *number >= day.get(),
|
|
|
|
|
(_, None, None) | (_, Some(_), Some(_)) => true,
|
|
|
|
|
}
|
|
|
|
|
.then_some("dead")
|
|
|
|
|
});
|
|
|
|
|
let role_text = char.role_title().to_string().to_case(Case::Title);
|
|
|
|
|
html! {
|
|
|
|
|
<span class={classes!("highlight-span", class)} role={role_text}>
|
|
|
|
|
{icon}
|
|
|
|
|
<span class={classes!("number")}><b>{char.number().get()}</b></span>
|
|
|
|
|
<span class={classes!("name", dead)}>{char.name()}</span>
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
}
|