cancellation fixes, aura fixes, etc.

This commit is contained in:
emilis 2026-02-18 01:44:41 +00:00
parent 06294d872e
commit 852973eddf
No known key found for this signature in database
14 changed files with 177 additions and 110 deletions

View File

@ -11,7 +11,7 @@ $village_border: color.change($village_color, $alpha: 1.0);
$wolves_border: color.change($wolves_color, $alpha: 1.0); $wolves_border: color.change($wolves_color, $alpha: 1.0);
$intel_color: color.adjust($village_color, $hue: -30deg); $intel_color: color.adjust($village_color, $hue: -30deg);
$intel_border: color.change($intel_color, $alpha: 1.0); $intel_border: color.change($intel_color, $alpha: 1.0);
$defensive_color: color.adjust($village_color, $hue: -60deg); $defensive_color: rgba(0, 128, 32, 0.9); //color.adjust(rgba(0, 16, 128, 0.9), $hue: -60deg);
$defensive_border: color.change($defensive_color, $alpha: 1.0); $defensive_border: color.change($defensive_color, $alpha: 1.0);
$offensive_color: color.adjust($village_color, $hue: 30deg); $offensive_color: color.adjust($village_color, $hue: 30deg);
$offensive_border: color.change($offensive_color, $alpha: 1.0); $offensive_border: color.change($offensive_color, $alpha: 1.0);
@ -446,6 +446,17 @@ dialog::backdrop {
gap: 0.25ch; gap: 0.25ch;
align-items: flex-start; align-items: flex-start;
.missing {
word-break: normal;
user-select: none;
font-size: 0.75em;
color: rgb(128, 0, 0);
&:hover {
color: rgb(255, 0, 0);
}
}
.setup-slot { .setup-slot {
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
font-size: 1.5em; font-size: 1.5em;

View File

@ -26,6 +26,7 @@ use crate::{
aura::AuraTitle, aura::AuraTitle,
character::{Character, CharacterId}, character::{Character, CharacterId},
error::GameError, error::GameError,
id_impl,
message::Identification, message::Identification,
player::PlayerId, player::PlayerId,
role::{Role, RoleTitle}, role::{Role, RoleTitle},
@ -426,20 +427,7 @@ impl From<RoleTitle> for SetupRole {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] id_impl!(SlotId);
pub struct SlotId(Uuid);
impl SlotId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl Display for SlotId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SetupSlot { pub struct SetupSlot {

View File

@ -24,7 +24,7 @@ mod time;
use crate::{ use crate::{
character::{Character, CharacterId}, character::{Character, CharacterId},
diedto::DiedToTitle, diedto::DiedToTitle,
error::GameError, error::{GameError, ServerError},
game::{Game, GameOver, GameSettings, OrRandom, SetupRole, SetupSlot}, game::{Game, GameOver, GameSettings, OrRandom, SetupRole, SetupSlot},
message::{ message::{
CharacterState, Identification, PublicIdentity, CharacterState, Identification, PublicIdentity,
@ -803,11 +803,12 @@ fn wolfpack_kill_all_targets_valid() {
for (idx, target) in living_villagers.into_iter().enumerate() { for (idx, target) in living_villagers.into_iter().enumerate() {
let mut attempt = game.clone(); let mut attempt = game.clone();
if let ServerToHostMessage::Error(GameError::InvalidTarget) = attempt if let ServerToHostMessage::Error(ServerError::GameError(GameError::InvalidTarget)) =
.process(HostGameMessage::Night(HostNightMessage::ActionResponse( attempt
ActionResponse::MarkTarget(target.character_id), .process(HostGameMessage::Night(HostNightMessage::ActionResponse(
))) ActionResponse::MarkTarget(target.character_id),
.unwrap() )))
.unwrap()
{ {
panic!("invalid target {target:?} at index [{idx}]"); panic!("invalid target {target:?} at index [{idx}]");
} }

View File

@ -66,6 +66,7 @@ pub struct DayCharacter {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Titles)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Titles)]
pub enum ServerToClientMessage { pub enum ServerToClientMessage {
GameCancelled,
Disconnect, Disconnect,
LobbyInfo { LobbyInfo {
joined: bool, joined: bool,

View File

@ -93,6 +93,7 @@ pub enum HostLobbyMessage {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, werewolves_macros::Titles)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, werewolves_macros::Titles)]
pub enum ServerToHostMessage { pub enum ServerToHostMessage {
GameCancelled,
Disconnect, Disconnect,
Daytime { Daytime {
characters: Box<[CharacterState]>, characters: Box<[CharacterState]>,

View File

@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
app::{ app::{
components::Nav, components::{ErrorBox, Nav},
pages::{GamePage, Main, NotFound, Signin, Signup, UserSettings}, pages::{GamePage, Main, NotFound, Signin, Signup, UserSettings},
storage::{ storage::{
Stored, Stored,
@ -96,6 +96,7 @@ pub fn App() -> impl IntoView {
.then_some(auth_store.session().get().is_some()) .then_some(auth_store.session().get().is_some())
}; };
let not_logged_in = move || Some(auth_store.session().get().is_none()); let not_logged_in = move || Some(auth_store.session().get().is_none());
let error = RwSignal::new(None);
view! { view! {
<Stylesheet id="leptos" href="/pkg/werewolves.css" /> <Stylesheet id="leptos" href="/pkg/werewolves.css" />
@ -106,6 +107,7 @@ pub fn App() -> impl IntoView {
<Router> <Router>
<main> <main>
<Nav /> <Nav />
<ErrorBox msg=error />
<Routes fallback=NotFound> <Routes fallback=NotFound>
<Route path=path!("/") view=Main /> <Route path=path!("/") view=Main />
<ProtectedRoute <ProtectedRoute
@ -126,7 +128,10 @@ pub fn App() -> impl IntoView {
condition=is_logged_in condition=is_logged_in
redirect_path=|| "/" redirect_path=|| "/"
/> />
<Route path=path!("/games/:id") view=|| view! { <GamePage /> } /> <Route
path=path!("/games/:id")
view=move || view! { <GamePage error=error.write_only() /> }
/>
</Routes> </Routes>
</main> </main>

View File

@ -6,16 +6,14 @@ use codee::binary::MsgpackSerdeCodec;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_router::hooks; use leptos_router::hooks;
use leptos_use::{ use leptos_use::{
ReconnectLimit, UseWebSocketOptions, ReconnectLimit, UseWebSocketOptions, UseWebSocketReturn, core::ConnectionReadyState,
UseWebSocketReturn, core::ConnectionReadyState, use_websocket_with_options, use_websocket_with_options,
}; };
use reactive_stores::Store; use reactive_stores::Store;
use werewolves_proto::message::{ use werewolves_proto::message::{ClientMessage, host::HostMessage};
ClientMessage,
host::HostMessage,
};
use werewolves_proto::message::{IntoClientResponse, WrappedServerMessage}; use werewolves_proto::message::{IntoClientResponse, WrappedServerMessage};
use crate::app::components::ErrorBox;
use crate::{ use crate::{
ConsoleLogError, ConsoleLogError,
app::{ app::{
@ -27,7 +25,7 @@ use crate::{
}; };
#[component] #[component]
pub fn GamePage() -> impl IntoView { pub fn GamePage(error: WriteSignal<Option<String>>) -> impl IntoView {
move || { move || {
let params = hooks::use_params_map(); let params = hooks::use_params_map();
let auth = expect_context::<Store<AuthContext>>(); let auth = expect_context::<Store<AuthContext>>();
@ -186,8 +184,9 @@ pub fn GamePage() -> impl IntoView {
view! { view! {
{status} {status}
<HostGamePage message=host_message.into() reply=host_reply.write_only() /> <HostGamePage error=error message=host_message.into() reply=host_reply.write_only() />
<PlayerGamePage <PlayerGamePage
error=error
message=player_message.into() message=player_message.into()
reply=player_reply.write_only() reply=player_reply.write_only()
disconnect=disconnect disconnect=disconnect

View File

@ -11,20 +11,28 @@ use werewolves_proto::{
}, },
}; };
#[cfg(feature = "hydrate")]
use crate::ConsoleLogError; use crate::ConsoleLogError;
use crate::app::{Preferences, components::DialogModal}; use crate::app::{Preferences, components::DialogModal};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum HostPage {
#[default]
None,
Settings,
}
#[component] #[component]
pub fn HostGamePage( pub fn HostGamePage(
error: WriteSignal<Option<String>>,
message: Signal<Option<Srv2Host>>, message: Signal<Option<Srv2Host>>,
reply: WriteSignal<Option<HostMessage>>, reply: WriteSignal<Option<HostMessage>>,
) -> impl IntoView { ) -> impl IntoView {
let prefs = expect_context::<(Signal<Preferences>, WriteSignal<Preferences>)>().0; let prefs = expect_context::<(Signal<Preferences>, WriteSignal<Preferences>)>().0;
let page = RwSignal::new(HostPage::default());
let settings = RwSignal::new(GameSettings::default()); let settings = RwSignal::new(GameSettings::default());
let qr_mode = RwSignal::new(false); let qr_mode = RwSignal::new(false);
let players: RwSignal<Box<[PlayerState]>> = RwSignal::new(Box::new([])); let players: RwSignal<Box<[PlayerState]>> = RwSignal::new(Box::new([]));
let dialog_open = RwSignal::new(false); let dialog_open = RwSignal::new(HashMap::new());
let open_categories = RwSignal::new( let open_categories = RwSignal::new(
Category::ALL Category::ALL
.into_iter() .into_iter()
@ -32,21 +40,12 @@ pub fn HostGamePage(
.collect::<HashMap<_, _>>(), .collect::<HashMap<_, _>>(),
); );
Effect::watch(
move || settings.get(),
move |s: &GameSettings, _, _| {
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(
s.clone(),
))));
},
false,
);
Effect::watch( Effect::watch(
move || qr_mode.get(), move || qr_mode.get(),
move |q, _, _| reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetQrMode(*q)))), move |q, _, _| reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetQrMode(*q)))),
false, false,
); );
let content = move || { Effect::new(move || {
if let Some(message) = message.get() { if let Some(message) = message.get() {
match message { match message {
Srv2Host::Lobby { Srv2Host::Lobby {
@ -54,32 +53,50 @@ pub fn HostGamePage(
settings: s, settings: s,
qr_mode: q, qr_mode: q,
} => { } => {
log::info!("setting setties"); page.set(HostPage::Settings);
settings.set(s); settings.set(s);
*qr_mode.write_untracked() = q; *qr_mode.write_untracked() = q;
players.set(p); players.set(p);
{
view! { let mut d = dialog_open.write();
<Settings for slot in settings.read().slots().iter() {
open_categories=open_categories d.entry(slot.slot_id).or_insert(false);
settings=settings }
players=players.read_only()
qr_mode=qr_mode
dialog_open=dialog_open
/>
} }
.into_any()
} }
_ => view! { <h2>{format!("{message:#?}")}</h2> }.into_any(), Srv2Host::GameCancelled => {
error.set(Some("game was cancelled".into()));
gloo::utils::window()
.location()
.replace("/")
.console_log_warn();
}
Srv2Host::Error(err) => {
error.set(Some(err.to_string()));
}
_ => log::error!("{message:#?}"),
} }
} else {
().into_any()
} }
}; });
let cancel = move || { let cancel = move || {
view! { message
<CancelGame reply=reply prefs=prefs /> .get()
.is_some()
.then_some(view! { <CancelGame reply=reply prefs=prefs /> })
};
let content = move || match page.get() {
HostPage::None => ().into_any(),
HostPage::Settings => view! {
<Settings
open_categories=open_categories
settings=settings.read_only()
players=players.read_only()
qr_mode=qr_mode
dialog_open=dialog_open
reply=reply
/>
} }
.into_any(),
}; };
view! { view! {
{cancel} {cancel}
@ -96,13 +113,6 @@ fn CancelGame(
let cancel = move |_| { let cancel = move |_| {
open.set(false); open.set(false);
reply.set(Some(HostMessage::CancelGame)); reply.set(Some(HostMessage::CancelGame));
#[cfg(not(feature = "ssr"))]
{
gloo::utils::window()
.location()
.replace("/")
.console_log_warn();
}
}; };
let derive_hidden = RwSignal::new(false); let derive_hidden = RwSignal::new(false);
Effect::new(move || derive_hidden.set(!prefs.get().show_cancel_game)); Effect::new(move || derive_hidden.set(!prefs.get().show_cancel_game));
@ -123,5 +133,5 @@ fn CancelGame(
</div> </div>
} }
}; };
view! { {content} } move || view! { {content} }
} }

View File

@ -5,8 +5,11 @@ use convert_case::{Case, Casing};
use leptos::{ev::MouseEvent, prelude::*}; use leptos::{ev::MouseEvent, prelude::*};
use werewolves_proto::{ use werewolves_proto::{
aura::AuraTitle, aura::AuraTitle,
game::{Category, GameSettings, SetupSlot}, game::{Category, GameSettings, SetupSlot, SlotId},
message::PlayerState, message::{
PlayerState,
host::{HostLobbyMessage, HostMessage},
},
role::RoleTitle, role::RoleTitle,
}; };
@ -18,11 +21,12 @@ use crate::app::{
#[component] #[component]
pub fn Settings( pub fn Settings(
settings: RwSignal<GameSettings>, settings: ReadSignal<GameSettings>,
players: ReadSignal<Box<[PlayerState]>>, players: ReadSignal<Box<[PlayerState]>>,
qr_mode: RwSignal<bool>, qr_mode: RwSignal<bool>,
dialog_open: RwSignal<bool>, dialog_open: RwSignal<HashMap<SlotId, bool>>,
open_categories: RwSignal<HashMap<Category, bool>>, open_categories: RwSignal<HashMap<Category, bool>>,
reply: WriteSignal<Option<HostMessage>>,
) -> impl IntoView { ) -> impl IntoView {
let slots = move || { let slots = move || {
settings settings
@ -32,11 +36,11 @@ pub fn Settings(
.cloned() .cloned()
.map(move |s| { .map(move |s| {
let signal = RwSignal::new(s); let signal = RwSignal::new(s);
Effect::watch( Effect::new(move || {
move || signal.get(), let mut s = settings.get();
move |slot_update, _, _| settings.write().update_slot(slot_update.clone()), s.update_slot(signal.get());
false, reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetGameSettings(s))));
); });
view! { <SettingsSetupSlot setup_slot=signal players=players dialog_open=dialog_open /> } view! { <SettingsSetupSlot setup_slot=signal players=players dialog_open=dialog_open /> }
}) })
@ -68,7 +72,6 @@ pub fn Settings(
k.sort(); k.sort();
k k
}; };
Effect::new(|| log::debug!("rendering settings"));
let categories = ordered_keys let categories = ordered_keys
.into_iter() .into_iter()
.map(|c| { .map(|c| {
@ -85,7 +88,11 @@ pub fn Settings(
.map(|r| { .map(|r| {
let add_role = move |ev: MouseEvent| { let add_role = move |ev: MouseEvent| {
ev.prevent_default(); ev.prevent_default();
settings.write().new_slot(r); let mut s = settings.get();
s.new_slot(r);
reply.set(Some(HostMessage::Lobby(
HostLobbyMessage::SetGameSettings(s),
)));
}; };
let classes = let classes =
["add-role", r.class(), "faint", "hover", "box"].as_classes(); ["add-role", r.class(), "faint", "hover", "box"].as_classes();
@ -129,7 +136,7 @@ pub fn Settings(
fn SettingsSetupSlot( fn SettingsSetupSlot(
setup_slot: RwSignal<SetupSlot>, setup_slot: RwSignal<SetupSlot>,
players: ReadSignal<Box<[PlayerState]>>, players: ReadSignal<Box<[PlayerState]>>,
dialog_open: RwSignal<bool>, dialog_open: RwSignal<HashMap<SlotId, bool>>,
) -> impl IntoView { ) -> impl IntoView {
let auras = move || { let auras = move || {
let slot = setup_slot.read(); let slot = setup_slot.read();
@ -158,18 +165,42 @@ fn SettingsSetupSlot(
} }
.into_any() .into_any()
} }
None => { None => view! { <span class="missing error">"assigned player not in lobby"</span> }
view! { <span class="missing error">"missing player "{a.to_string()}</span> } .into_any(),
.into_any()
}
} }
}) })
}; };
let open_signal = RwSignal::new(false);
Effect::new(move || {
open_signal.set(
dialog_open
.read()
.get(&setup_slot.read().slot_id)
.copied()
.unwrap_or_default(),
);
});
Effect::new(move || {
let current = dialog_open
.read_untracked()
.get(&setup_slot.read_untracked().slot_id)
.copied()
.unwrap_or_default();
let new = open_signal.get();
if current == new {
return;
}
dialog_open
.write()
.insert(setup_slot.read_untracked().slot_id, new);
});
move || { move || {
view! { view! {
<div class="setup-slot-container"> <div class="setup-slot-container">
<DialogModal <DialogModal
open=dialog_open open=open_signal
mode=DialogMode::Box mode=DialogMode::Box
button_class=[ button_class=[
"setup-slot", "setup-slot",
@ -272,8 +303,10 @@ fn AuraSelection(setup_slot: RwSignal<SetupSlot>) -> impl IntoView {
ev.prevent_default(); ev.prevent_default();
let mut slot = setup_slot.write(); let mut slot = setup_slot.write();
if slot.auras.contains(&aura) { if slot.auras.contains(&aura) {
log::debug!("removing aura {aura}");
slot.auras.retain(|a| aura != *a); slot.auras.retain(|a| aura != *a);
} else { } else {
log::debug!("adding aura {aura}");
slot.auras.push(aura); slot.auras.push(aura);
} }
}; };
@ -291,7 +324,7 @@ fn AuraSelection(setup_slot: RwSignal<SetupSlot>) -> impl IntoView {
.collect_view() .collect_view()
}; };
view! { <div class="toggle-list">{auras}</div> } move || view! { <div class="toggle-list">{auras}</div> }
} }
#[component] #[component]
@ -325,6 +358,15 @@ fn AssignmentSelection(
}) })
.collect_view() .collect_view()
}; };
let unassign = move || {
setup_slot.get().assign_to.map(|_| {
view! {
<button on:click=move |_| {
setup_slot.write().assign_to.take();
}>"unassign"</button>
}
})
};
view! { <div class="toggle-list">{players}</div> } move || view! { <div class="toggle-list">{unassign}{players}</div> }
} }

View File

@ -1,18 +1,13 @@
werewolves_macros::include_path!("werewolves/src/app/pages/game/player"); werewolves_macros::include_path!("werewolves/src/app/pages/game/player");
use core::{ use core::{hash::Hash, num::NonZeroU8, ops::Deref};
hash::Hash,
num::NonZeroU8,
ops::Deref,
};
use std::collections::HashSet; use std::collections::HashSet;
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use leptos::prelude::*; use leptos::prelude::*;
use werewolves_proto::{ use werewolves_proto::{
message::{ message::{
ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, dead::DeadChatMessage,
dead::DeadChatMessage,
}, },
role::RoleTitle, role::RoleTitle,
}; };
@ -35,6 +30,7 @@ enum Page {
#[component] #[component]
pub fn PlayerGamePage( pub fn PlayerGamePage(
error: WriteSignal<Option<String>>,
message: Signal<Option<Srv2Client>>, message: Signal<Option<Srv2Client>>,
reply: WriteSignal<Option<ClientMessage>>, reply: WriteSignal<Option<ClientMessage>>,
disconnect: RwSignal<bool>, disconnect: RwSignal<bool>,
@ -47,6 +43,13 @@ pub fn PlayerGamePage(
return; return;
}; };
match message { match message {
Srv2Client::GameCancelled => {
error.set(Some("game was cancelled".into()));
gloo::utils::window()
.location()
.replace("/")
.console_log_warn();
}
Srv2Client::Disconnect => disconnect.set(true), Srv2Client::Disconnect => disconnect.set(true),
Srv2Client::LobbyInfo { Srv2Client::LobbyInfo {
joined, joined,

View File

@ -39,21 +39,23 @@ pub trait ConsoleLogError {
fn console_log_debug(self); fn console_log_debug(self);
} }
#[cfg(feature = "hydrate")] impl<T> ConsoleLogError for Result<T, leptos::wasm_bindgen::JsValue> {
impl<T> ConsoleLogError for Result<T, wasm_bindgen::JsValue> {
fn console_log_warn(self) { fn console_log_warn(self) {
#[cfg(feature = "hydrate")]
if let Err(err) = self { if let Err(err) = self {
gloo::console::warn!(err); gloo::console::warn!(err);
} }
} }
fn console_log_err(self) { fn console_log_err(self) {
#[cfg(feature = "hydrate")]
if let Err(err) = self { if let Err(err) = self {
gloo::console::error!(err); gloo::console::error!(err);
} }
} }
fn console_log_debug(self) { fn console_log_debug(self) {
#[cfg(feature = "hydrate")]
if let Err(err) = self { if let Err(err) = self {
gloo::console::debug!(err); gloo::console::debug!(err);
} }

View File

@ -64,7 +64,7 @@ impl<'a> Lobby<'a> {
} }
pub const fn settings(&self) -> &GameSettings { pub const fn settings(&self) -> &GameSettings {
&self.settings self.settings
} }
pub async fn send_lobby_info_to_clients(&mut self) -> Result<(), ServerError> { pub async fn send_lobby_info_to_clients(&mut self) -> Result<(), ServerError> {
@ -242,7 +242,7 @@ impl<'a> Lobby<'a> {
))) => { ))) => {
self.db self.db
.game() .game()
.set_player_number(self.game_id, pid.into(), Some(num)) .set_player_number(self.game_id, pid, Some(num))
.await?; .await?;
self.send_lobby_info_to_clients().await.log_debug(loc!()); self.send_lobby_info_to_clients().await.log_debug(loc!());
@ -257,11 +257,7 @@ impl<'a> Lobby<'a> {
}) => { }) => {
self.db self.db
.game() .game()
.join_game( .join_game(self.game_id, identity.player_id, identity.public.number)
self.game_id,
identity.player_id.into(),
identity.public.number,
)
.await?; .await?;
self.send_lobby_info_to_clients().await.log_debug(loc!()); self.send_lobby_info_to_clients().await.log_debug(loc!());
@ -272,10 +268,7 @@ impl<'a> Lobby<'a> {
identity: Identification { player_id, .. }, identity: Identification { player_id, .. },
update: ClientUpdate::Message(ClientMessage::Goodbye), update: ClientUpdate::Message(ClientMessage::Goodbye),
}) => { }) => {
self.db self.db.game().leave_game(self.game_id, player_id).await?;
.game()
.leave_game(self.game_id, player_id.into())
.await?;
self.send_lobby_info_to_host().await?; self.send_lobby_info_to_host().await?;
self.send_lobby_info_to_clients().await.log_debug(loc!()); self.send_lobby_info_to_clients().await.log_debug(loc!());
@ -306,7 +299,7 @@ impl<'a> Lobby<'a> {
}) => { }) => {
self.db self.db
.game() .game()
.set_player_number(self.game_id, player_id.into(), Some(number)) .set_player_number(self.game_id, player_id, Some(number))
.await?; .await?;
self.send_lobby_info_to_clients().await.log_debug(loc!()); self.send_lobby_info_to_clients().await.log_debug(loc!());
self.send_lobby_info_to_host().await.log_warn(loc!()); self.send_lobby_info_to_host().await.log_warn(loc!());

View File

@ -394,4 +394,5 @@ pub async fn delete_game(game: GameId) {
for key in player_keys { for key in player_keys {
players.remove(&key); players.remove(&key);
} }
log::info!("game {game} is cancelled server-side");
} }

View File

@ -18,6 +18,8 @@ use core::num::NonZeroU8;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
use werewolves_proto::game::GameId; use werewolves_proto::game::GameId;
use werewolves_proto::game_record::{GameRecord, GameRecordState}; use werewolves_proto::game_record::{GameRecord, GameRecordState};
use werewolves_proto::message::ServerToClientMessage;
use werewolves_proto::message::host::ServerToHostMessage;
use werewolves_proto::message::{ClientMessage, Identification, host::HostMessage}; use werewolves_proto::message::{ClientMessage, Identification, host::HostMessage};
use werewolves_proto::{LogError, loc}; use werewolves_proto::{LogError, loc};
@ -55,8 +57,8 @@ async fn next_message(
host_msg = host_recv.recv() => { host_msg = host_recv.recv() => {
match host_msg { match host_msg {
Some(HostMessage::CancelGame) => { Some(HostMessage::CancelGame) => {
cancel_game(game_id, db).await;
log::info!("got game cancellation request for {game_id}"); log::info!("got game cancellation request for {game_id}");
cancel_game(game_id, db).await;
None None
} }
Some(msg) => Some(HostOrClientMessage::Host(msg)), Some(msg) => Some(HostOrClientMessage::Host(msg)),
@ -70,6 +72,12 @@ async fn next_message(
} }
async fn cancel_game(game_id: GameId, db: &Database) { async fn cancel_game(game_id: GameId, db: &Database) {
static RUNTIME: std::sync::LazyLock<tokio::runtime::Runtime> = std::sync::LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("building game destroyer runtime")
});
let game = match db.game().get_game(game_id).await { let game = match db.game().get_game(game_id).await {
Ok(g) => g, Ok(g) => g,
Err(err) => { Err(err) => {
@ -77,11 +85,13 @@ async fn cancel_game(game_id: GameId, db: &Database) {
return; return;
} }
}; };
super::send_to_all_players_in_game(game_id, ServerToClientMessage::GameCancelled).await;
super::send_host(game_id, ServerToHostMessage::GameCancelled).await;
if let Err(err) = db.game().cancel_game(game.host, game_id).await { if let Err(err) = db.game().cancel_game(game.host, game_id).await {
log::error!("error cancelling game: {err}"); log::error!("error cancelling game: {err}");
return; return;
} }
tokio::spawn(crate::server::delete_game(game_id)); RUNTIME.spawn(crate::server::delete_game(game_id));
} }
async fn add_dummies(game_id: GameId, db: &Database, dummy_count: usize) { async fn add_dummies(game_id: GameId, db: &Database, dummy_count: usize) {