/* Hurry Curry! - a game about cooking Copyright (C) 2025 Hurry Curry! Contributors 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, version 3 of the License only. 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 . */ use crate::{message::TrError, server::Server, tre, trm, ConnectionID}; use anyhow::Result; use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID}; use log::{debug, info, trace}; impl Server { pub fn tick_outer(&mut self, dt: f32) -> anyhow::Result<()> { if self.start_pause_timer > 0. { self.start_pause_timer -= dt } let should_pause = self.start_pause_timer > 0. || self.connections.iter().all(|c| c.1 .1); if should_pause != self.paused { info!("Game paused: {should_pause}"); self.paused = should_pause; for p in self.game.players.keys() { self.packet_out .push_back(PacketC::Pause { state: self.paused }); self.packet_out.push_back(PacketC::ServerHint { position: None, message: if self.paused { Some(trm!("s.state.paused")) } else { None }, player: *p, }); } } if !self.paused { let r = self.tick(dt); if let Some((name, timer)) = r { self.scoreboard.save()?; self.load(self.index.generate_with_book(&name)?, timer); } } while let Some(p) = self.packet_out.pop_front() { if matches!(p, PacketC::UpdateMap { .. } | PacketC::Movement { .. }) { trace!("-> {p:?}"); } else { debug!("-> {p:?}"); } self.tx.send(p).unwrap(); } Ok(()) } pub async fn packet_in_outer( &mut self, conn: ConnectionID, packet: PacketS, ) -> Result, TrError> { if let Some(p) = get_packet_player(&packet) { if !self.connections.entry(conn).or_default().0.contains(&p) { return Err(tre!("s.error.packet_sender_invalid")); } } let mut replies = Vec::new(); match &packet { PacketS::Communicate { message: Some(Message::Text(text)), timeout: None, player, .. } if let Some(command) = text.strip_prefix("/") => { match self.handle_command_parse(*player, command) { Ok(packets) => return Ok(packets), Err(e) => { return Ok(vec![PacketC::ServerMessage { message: e.into(), error: true, }]); } } } PacketS::Idle { paused } => self.connections.entry(conn).or_default().1 = *paused, PacketS::Leave { player } => { self.connections.entry(conn).or_default().0.remove(player); } PacketS::Join { .. } => { if self.connections.entry(conn).or_default().0.len() > 8 { return Err(tre!("s.error.conn_too_many_players")); } } _ => (), } self.packet_in(packet, &mut replies)?; for p in &replies { if let PacketC::Joined { id } = p { self.connections.entry(conn).or_default().0.insert(*id); } } if self.count_chefs() == 0 && !self.game.lobby { self.tx .send(PacketC::ServerMessage { message: trm!("s.state.abort_no_players"), error: false, }) .ok(); self.load( self.index .generate_with_book("lobby") .map_err(|m| tre!("s.error.map_load", s = format!("{m}")))?, None, ); } Ok(replies) } pub async fn disconnect(&mut self, conn: ConnectionID) { if let Some((players, _)) = self.connections.get(&conn) { for player in players.clone() { let _ = self.packet_in_outer(conn, PacketS::Leave { player }).await; } } self.connections.remove(&conn); } } fn get_packet_player(packet: &PacketS) -> Option { match packet { PacketS::Join { .. } => None, PacketS::Idle { .. } => None, PacketS::Leave { player } => Some(*player), PacketS::Movement { player, .. } => Some(*player), PacketS::Interact { player, .. } => Some(*player), PacketS::Communicate { player, .. } => Some(*player), PacketS::ReplaceHand { player, .. } => Some(*player), PacketS::Effect { player, .. } => Some(*player), PacketS::ApplyScore(_) => None, PacketS::ReplayTick { .. } => None, } }