/* 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(let_chains)] pub mod network; pub mod spatial_index; use hurrycurry_protocol::{ glam::IVec2, movement::MovementBase, Character, Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PlayerClass, PlayerID, RecipeIndex, Score, TileIndex, }; use spatial_index::SpatialIndex; use std::{ collections::{HashMap, HashSet}, sync::Arc, time::Instant, }; #[derive(Debug, PartialEq)] pub struct Involvement { pub position: f32, pub speed: f32, pub recipe: RecipeIndex, pub player: Option, 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 class: PlayerClass, pub character: Character, pub interacting: Option<(IVec2, Hand)>, pub items: Vec>, pub communicate_persist: Option<(Message, MessageTimeout)>, pub movement: MovementBase, } #[derive(Default)] 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 Game { pub fn apply_packet(&mut self, packet: PacketC) { match packet { PacketC::Data { data } => { self.data = Arc::new(data); } PacketC::AddPlayer { id, position, character, class, name, } => { self.players.insert( id, Player { name, character, class, interacting: None, items: (0..self.data.hand_count).map(|_| None).collect(), 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 } => { if let Some(item) = self.get_item(to).map(|e| e.take()) { if let Some(to) = self.get_item(from) { *to = item; } else { // TODO perhaps restore to original position? } } } PacketC::SetItem { location, item } => { let location = self.get_item(location); if let Some(location) = location { *location = item.map(|kind| Item { kind, active: None }); } } PacketC::ClearProgress { item } => { if let Some(Some(item)) = self.get_item(item) { item.active = None; } } PacketC::SetProgress { item, position, player, speed, warn, } => { if let Some(Some(item)) = self.get_item(item) { item.active = Some(Involvement { player, speed, warn, position, 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, timeout, } => { if let Some(timeout) = &timeout && let Some(player) = self.players.get_mut(&player) { player.communicate_persist = message.to_owned().map(|m| (m, *timeout)); } } 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); if let Some((_, timeout)) = &mut player.communicate_persist { timeout.remaining -= dt; if timeout.remaining < 0. { player.communicate_persist = None; } } self.players_spatial_index .update_entry(pid, player.movement.position); } for player in self.players.values_mut() { for item in player.items.iter_mut().flatten() { if let Some(active) = &mut item.active { active.position += active.speed; } } } for tile in self.tiles.values_mut() { if let Some(item) = &mut tile.item { if let Some(active) = &mut item.active { active.position += active.speed; } } } self.players_spatial_index.all(|p1, pos1| { self.players_spatial_index.query(pos1, 2., |p2, _pos2| { if let [Some(a), Some(b)] = self.players.get_disjoint_mut([&p1, &p2]) { a.movement.collide(&mut b.movement, dt) } }) }); } pub fn get_item(&mut self, location: ItemLocation) -> Option<&mut Option> { match location { ItemLocation::Tile(pos) => Some(&mut self.tiles.get_mut(&pos)?.item), ItemLocation::Player(pid, hand) => { Some(self.players.get_mut(&pid)?.items.get_mut(hand.0)?) } } } } impl From for Tile { fn from(kind: TileIndex) -> Self { Self { kind, item: None } } }