/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin 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 . */ #![feature(map_many_mut)] pub mod network; pub mod spatial_index; use hurrycurry_protocol::{ glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message, PacketC, PlayerID, RecipeIndex, Score, TileIndex, }; use spatial_index::SpatialIndex; use std::{ collections::{HashMap, HashSet}, sync::Arc, time::Instant, }; #[derive(Debug, PartialEq)] pub struct Involvement { pub progress: f32, pub recipe: RecipeIndex, pub working: usize, pub warn: bool, } #[derive(Debug, PartialEq)] pub struct Item { pub kind: ItemIndex, pub active: Option, } pub struct Tile { pub kind: TileIndex, pub item: Option, } pub struct Player { pub name: String, pub character: i32, pub interacting: Option, pub item: Option, pub communicate_persist: Option, pub movement: MovementBase, } pub struct Game { pub data: Arc, pub tiles: HashMap, pub walkable: HashSet, pub players: HashMap, pub players_spatial_index: SpatialIndex, pub end: Option, pub lobby: bool, pub environment_effects: HashSet, pub score: Score, } impl Default for Game { fn default() -> Self { Self { data: Default::default(), tiles: HashMap::new(), walkable: HashSet::new(), players: HashMap::new(), players_spatial_index: SpatialIndex::default(), end: None, lobby: false, environment_effects: HashSet::new(), score: Score::default(), } } } impl Game { pub fn apply_packet(&mut self, packet: PacketC) { match packet { PacketC::Data { data } => { self.data = Arc::new(data); } PacketC::AddPlayer { id, position, character, name, } => { self.players.insert( id, Player { name, character, interacting: None, item: None, communicate_persist: None, movement: MovementBase::new(position), }, ); } PacketC::RemovePlayer { id } => { self.players.remove(&id); } PacketC::Movement { player, pos, rot, boost, dir, } => { if let Some(p) = self.players.get_mut(&player) { p.movement.input(dir, boost); p.movement.position = pos; p.movement.rotation = rot; } } PacketC::MoveItem { from, to } => { *self.get_item(to) = self.get_item(from).take(); } PacketC::SetItem { location, item } => { *self.get_item(location) = item.map(|kind| Item { kind, active: None }); } PacketC::SetProgress { item, progress, warn, } => { self.get_item(item).as_mut().unwrap().active = progress.map(|progress| Involvement { working: 1, warn, progress, recipe: RecipeIndex(0), }); } PacketC::UpdateMap { tile, kind, neighbors: _, } => { if let Some(kind) = kind { self.tiles.insert(tile, Tile { kind, item: None }); if self.data.tile_collide[kind.0] { self.walkable.remove(&tile); } else { self.walkable.insert(tile); } } else { self.tiles.remove(&tile); self.walkable.remove(&tile); } } PacketC::Communicate { player, message, persist, } => { if persist { if let Some(player) = self.players.get_mut(&player) { player.communicate_persist = message; } } } PacketC::Score(score) => { self.score = score; } PacketC::SetIngame { state: _, lobby } => { self.lobby = lobby; } PacketC::Environment { effects } => { self.environment_effects = effects; } _ => (), } } pub fn tick(&mut self, dt: f32) { self.score.time_remaining -= dt as f64; self.score.time_remaining -= self.score.time_remaining.max(0.); for (&pid, player) in &mut self.players { player.movement.update(&self.walkable, dt); self.players_spatial_index .update_entry(pid, player.movement.position); } self.players_spatial_index.all(|p1, pos1| { self.players_spatial_index.query(pos1, 2., |p2, _pos2| { if let Some([a, b]) = self.players.get_many_mut([&p1, &p2]) { a.movement.collide(&mut b.movement, dt) } }) }); } pub fn get_item(&mut self, location: ItemLocation) -> &mut Option { match location { ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item, ItemLocation::Player(pid) => &mut self.players.get_mut(&pid).unwrap().item, } } } impl From for Tile { fn from(kind: TileIndex) -> Self { Self { kind, item: None } } }