diff options
author | metamuffin <metamuffin@disroot.org> | 2024-08-13 12:48:31 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-08-13 16:03:38 +0200 |
commit | 16ff78180669411326d42ea32d4a9260c018236c (patch) | |
tree | d7c6a7ab498bb1b4f9a3b3db99d54e8781216e05 /server/src/game.rs | |
parent | 11ff74f034aeec58c06dbe15a3f1ee650ef18c9f (diff) | |
download | hurrycurry-16ff78180669411326d42ea32d4a9260c018236c.tar hurrycurry-16ff78180669411326d42ea32d4a9260c018236c.tar.bz2 hurrycurry-16ff78180669411326d42ea32d4a9260c018236c.tar.zst |
refactor server to use client-lib data model (breaks customers)
Diffstat (limited to 'server/src/game.rs')
-rw-r--r-- | server/src/game.rs | 737 |
1 files changed, 0 insertions, 737 deletions
diff --git a/server/src/game.rs b/server/src/game.rs deleted file mode 100644 index 39cd61dc..00000000 --- a/server/src/game.rs +++ /dev/null @@ -1,737 +0,0 @@ -/* - 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 <https://www.gnu.org/licenses/>. - -*/ -use crate::{ - data::Gamedata, - entity::{Entity, EntityT}, - interaction::{interact, tick_slot, InteractEffect, TickEffect}, - spatial_index::SpatialIndex, -}; -use anyhow::{anyhow, bail, Result}; -use hurrycurry_protocol::{ - glam::{IVec2, Vec2}, - movement::MovementBase, - ClientGamedata, ItemIndex, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, - RecipeIndex, Score, TileIndex, -}; -use log::{info, warn}; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - sync::{Arc, RwLock}, - time::{Duration, Instant}, -}; - -#[derive(Debug, PartialEq)] -pub struct Involvement { - pub recipe: RecipeIndex, - pub progress: f32, - pub working: usize, -} - -#[derive(Debug, PartialEq)] -pub struct Item { - pub kind: ItemIndex, - pub active: Option<Involvement>, -} - -pub struct Tile { - pub kind: TileIndex, - pub item: Option<Item>, -} - -pub struct Player { - pub name: String, - pub character: i32, - pub interacting: Option<IVec2>, - pub item: Option<Item>, - pub communicate_persist: Option<Message>, - - pub movement: MovementBase, - pub last_position_update: Instant, -} - -pub struct Game { - pub data: Arc<Gamedata>, - pub tiles: HashMap<IVec2, Tile>, - pub walkable: HashSet<IVec2>, - pub players: HashMap<PlayerID, Player>, - pub players_spatial_index: SpatialIndex<PlayerID>, - entities: Arc<RwLock<Vec<Entity>>>, - end: Option<Instant>, - pub lobby: bool, - - pub environment_effects: HashSet<String>, - pub score_changed: bool, - pub score: Score, - - pub player_id_counter: PlayerID, -} - -impl Default for Game { - fn default() -> Self { - Self::new() - } -} - -impl Game { - pub fn new() -> Self { - Self { - lobby: false, - data: Gamedata::default().into(), - players: HashMap::new(), - tiles: HashMap::new(), - walkable: HashSet::new(), - end: None, - entities: Arc::new(RwLock::new(vec![])), - players_spatial_index: SpatialIndex::default(), - score: Score::default(), - environment_effects: HashSet::default(), - score_changed: false, - player_id_counter: PlayerID(1), - } - } - - fn unload(&mut self, packet_out: &mut VecDeque<PacketC>) { - packet_out.push_back(PacketC::SetIngame { - state: false, - lobby: false, - }); - for (id, _) in self.players.drain() { - packet_out.push_back(PacketC::RemovePlayer { id }) - } - for (pos, _) in self.tiles.drain() { - packet_out.push_back(PacketC::UpdateMap { - tile: pos, - kind: None, - neighbors: [None, None, None, None], - }) - } - self.score = Score::default(); - self.end = None; - self.environment_effects.clear(); - self.walkable.clear(); - } - pub fn load( - &mut self, - gamedata: Gamedata, - timer: Option<Duration>, - packet_out: &mut VecDeque<PacketC>, - ) { - let players = self - .players - .iter() - .filter(|(_, p)| p.character >= 0) - .map(|(id, p)| (*id, (p.name.to_owned(), p.character))) - .collect::<HashMap<_, _>>(); - - self.unload(packet_out); - - self.lobby = gamedata.map_name == "lobby"; - self.data = gamedata.into(); - self.score = Score { - time_remaining: timer.map(|dur| dur.as_secs_f64()).unwrap_or(0.), - ..Default::default() - }; - self.end = timer.map(|dur| Instant::now() + dur); - self.entities = Arc::new(RwLock::new(self.data.entities.clone())); - - for (&p, (tile, item)) in &self.data.initial_map { - self.tiles.insert( - p, - Tile { - kind: *tile, - item: item.map(|i| Item { - kind: i, - active: None, - }), - }, - ); - if !self.data.tile_collide[tile.0] { - self.walkable.insert(p); - } - } - for (id, (name, character)) in players { - self.players.insert( - id, - Player { - item: None, - character, - movement: MovementBase { - position: if character < 0 { - self.data.customer_spawn - } else { - self.data.chef_spawn - }, - input_direction: Vec2::ZERO, - input_boost: false, - facing: Vec2::X, - rotation: 0., - velocity: Vec2::ZERO, - boosting: false, - stamina: 0., - }, - last_position_update: Instant::now(), - communicate_persist: None, - interacting: None, - name: name.clone(), - }, - ); - } - - packet_out.extend(self.prime_client()); - } - - pub fn prime_client(&self) -> Vec<PacketC> { - let mut out = Vec::new(); - out.push(PacketC::Data { - data: ClientGamedata { - recipes: self.data.recipes.clone(), - item_names: self.data.item_names.clone(), - tile_names: self.data.tile_names.clone(), - tile_collide: self.data.tile_collide.clone(), - tile_interact: self.data.tile_interact.clone(), - current_map: self.data.map_name.clone(), - map_names: self - .data - .map - .clone() - .keys() - .filter(|n| n.as_str() != "lobby") - .map(|s| s.to_owned()) - .collect(), - maps: self - .data - .map - .clone() - .into_iter() - .filter(|(n, _)| n != "lobby") - .collect(), - }, - }); - out.push(PacketC::Environment { - effects: self.environment_effects.clone(), - }); - for (&id, player) in &self.players { - out.push(PacketC::AddPlayer { - id, - position: player.movement.position, - character: player.character, - name: player.name.clone(), - }); - if let Some(item) = &player.item { - out.push(PacketC::SetItem { - location: ItemLocation::Player(id), - item: Some(item.kind), - }) - } - if let Some(c) = &player.communicate_persist { - out.push(PacketC::Communicate { - player: id, - message: Some(c.to_owned()), - persist: true, - }) - } - } - for (&tile, tdata) in &self.tiles { - out.push(PacketC::UpdateMap { - tile, - neighbors: [ - self.tiles.get(&(tile + IVec2::NEG_Y)).map(|e| e.kind), - self.tiles.get(&(tile + IVec2::NEG_X)).map(|e| e.kind), - self.tiles.get(&(tile + IVec2::Y)).map(|e| e.kind), - self.tiles.get(&(tile + IVec2::X)).map(|e| e.kind), - ], - kind: Some(tdata.kind), - }); - if let Some(item) = &tdata.item { - out.push(PacketC::SetItem { - location: ItemLocation::Tile(tile), - item: Some(item.kind), - }) - } - } - out.push(PacketC::Score(self.score.clone())); - out.push(PacketC::SetIngame { - state: true, - lobby: self.lobby, - }); - out - } - - pub fn join_player( - &mut self, - name: String, - character: i32, - packet_out: &mut VecDeque<PacketC>, - ) -> PlayerID { - let id = self.player_id_counter; - self.player_id_counter.0 += 1; - let position = if id.0 < 0 { - self.data.customer_spawn - } else { - self.data.chef_spawn - }; - self.players.insert( - id, - Player { - item: None, - character, - movement: MovementBase { - position: if character < 0 { - self.data.customer_spawn - } else { - self.data.chef_spawn - }, - input_direction: Vec2::ZERO, - input_boost: false, - facing: Vec2::X, - rotation: 0., - velocity: Vec2::ZERO, - boosting: false, - stamina: 0., - }, - last_position_update: Instant::now(), - communicate_persist: None, - interacting: None, - name: name.clone(), - }, - ); - self.score.players = self.score.players.max(self.players.len()); - packet_out.push_back(PacketC::AddPlayer { - id, - name, - position, - character, - }); - id - } - - pub fn packet_in( - &mut self, - packet: PacketS, - replies: &mut Vec<PacketC>, - packet_out: &mut VecDeque<PacketC>, - ) -> Result<()> { - match packet { - PacketS::Join { name, character } => { - let id = self.join_player(name, character, packet_out); - replies.push(PacketC::Joined { id }) - } - PacketS::Leave { player } => { - let p = self - .players - .remove(&player) - .ok_or(anyhow!("player does not exist"))?; - - self.players_spatial_index.remove_entry(player); - - if let Some(item) = p.item { - let pos = p.movement.position.floor().as_ivec2(); - if let Some(tile) = self.tiles.get_mut(&pos) { - if tile.item.is_none() { - packet_out.push_back(PacketC::SetItem { - location: ItemLocation::Tile(pos), - item: Some(item.kind), - }); - tile.item = Some(item); - } - } - } - packet_out.push_back(PacketC::RemovePlayer { id: player }) - } - PacketS::Movement { - pos, - boost, - dir: direction, - player, - } => { - let pd = self - .players - .get_mut(&player) - .ok_or(anyhow!("player does not exist"))?; - - pd.movement.input(direction, boost); - - if let Some(pos) = pos { - let dt = pd.last_position_update.elapsed(); - pd.last_position_update += dt; - let diff = pos - pd.movement.position; - pd.movement.position += diff.clamp_length_max(dt.as_secs_f32()); - - if diff.length() > 1. { - replies.push(PacketC::MovementSync { player }); - } - } - } - PacketS::Interact { pos, player } => { - let pid = player; - let player = self - .players - .get_mut(&pid) - .ok_or(anyhow!("player does not exist"))?; - - let (pos, edge) = match (pos, player.interacting) { - (None, None) => return Ok(()), // this is silent because of auto release - (None, Some(pos)) => (pos, false), - (Some(pos), None) => (pos, true), - (Some(_), Some(_)) => bail!("already interacting"), - }; - - let entpos = pos.as_vec2() + Vec2::splat(0.5); - if edge && entpos.distance(player.movement.position) > 2. { - bail!("interacting too far from player"); - } - - let tile = self - .tiles - .get_mut(&pos) - .ok_or(anyhow!("tile does not exist"))?; - - // No going back from here on - - player.interacting = if edge { Some(pos) } else { None }; - - let other_pid = if !self.data.is_tile_interactable(tile.kind) { - self.players - .iter() - .find(|(id, p)| **id != pid && p.movement.position.distance(entpos) < 0.7) - .map(|(&id, _)| id) - } else { - None - }; - - if let Some(base_pid) = other_pid { - let [other, this] = self - .players - .get_many_mut([&pid, &base_pid]) - .ok_or(anyhow!("interacting with yourself. this is impossible"))?; - - if this.character < 0 || other.character < 0 { - bail!("You shall not interact with customers.") - } - - interact_effect( - &self.data, - edge, - &mut this.item, - ItemLocation::Player(base_pid), - &mut other.item, - ItemLocation::Player(pid), - None, - packet_out, - &mut self.score, - &mut self.score_changed, - false, - ) - } else { - let player = self - .players - .get_mut(&pid) - .ok_or(anyhow!("player does not exist"))?; - - interact_effect( - &self.data, - edge, - &mut tile.item, - ItemLocation::Tile(pos), - &mut player.item, - ItemLocation::Player(pid), - Some(tile.kind), - packet_out, - &mut self.score, - &mut self.score_changed, - false, - ) - } - } - PacketS::Communicate { - message, - persist, - player, - } => { - info!("{player:?} message {message:?}"); - if persist { - if let Some(player) = self.players.get_mut(&player) { - player.communicate_persist = message.clone() - } - } - packet_out.push_back(PacketC::Communicate { - player, - message, - persist, - }) - } - PacketS::ReplaceHand { item, player } => { - let pdata = self - .players - .get_mut(&player) - .ok_or(anyhow!("player does not exist"))?; - pdata.item = item.map(|i| Item { - kind: i, - active: None, - }); - packet_out.push_back(PacketC::SetItem { - location: ItemLocation::Player(player), - item, - }) - } - PacketS::ReplayTick { .. } => bail!("packet not supported in this session"), - } - Ok(()) - } - - /// Returns true if the game should end - pub fn tick(&mut self, dt: f32, packet_out: &mut VecDeque<PacketC>) -> bool { - if self.score_changed { - self.score_changed = false; - packet_out.push_back(PacketC::Score(self.score.clone())); - } - - for (&pos, tile) in &mut self.tiles { - if let Some(effect) = tick_slot( - dt, - &self.data, - Some(tile.kind), - &mut tile.item, - &mut self.score, - ) { - match effect { - TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress { - warn, - item: ItemLocation::Tile(pos), - progress: tile - .item - .as_ref() - .unwrap() - .active - .as_ref() - .map(|i| i.progress), - }), - TickEffect::Produce => { - packet_out.push_back(PacketC::SetProgress { - warn: false, - item: ItemLocation::Tile(pos), - progress: None, - }); - packet_out.push_back(PacketC::SetItem { - location: ItemLocation::Tile(pos), - item: tile.item.as_ref().map(|i| i.kind), - }); - } - } - } - } - - 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) - } - }) - }); - - for (&pid, player) in &mut self.players { - packet_out.push_back(PacketC::Movement { - player: pid, - pos: player.movement.position, - dir: player.movement.input_direction, - boost: player.movement.boosting, - rot: player.movement.rotation, - }); - - if let Some(effect) = tick_slot(dt, &self.data, None, &mut player.item, &mut self.score) - { - match effect { - TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress { - warn, - item: ItemLocation::Player(pid), - progress: player - .item - .as_ref() - .unwrap() - .active - .as_ref() - .map(|i| i.progress), - }), - TickEffect::Produce => { - packet_out.push_back(PacketC::SetProgress { - warn: false, - item: ItemLocation::Player(pid), - progress: None, - }); - packet_out.push_back(PacketC::SetItem { - location: ItemLocation::Player(pid), - item: player.item.as_ref().map(|i| i.kind), - }); - } - } - } - } - - let mut players_auto_release = Vec::new(); - for (pid, player) in &mut self.players { - if let Some(pos) = player.interacting { - if let Some(tile) = self.tiles.get(&pos) { - if let Some(item) = &tile.item { - if let Some(involvement) = &item.active { - if involvement.progress >= 1. { - players_auto_release.push(*pid); - } - } - } - } - } - } - for player in players_auto_release.drain(..) { - let _ = self.packet_in( - PacketS::Interact { pos: None, player }, - &mut vec![], - packet_out, - ); - } - - for entity in self.entities.clone().write().unwrap().iter_mut() { - if let Err(e) = entity.tick(self, packet_out, dt) { - warn!("entity tick failed: {e}") - } - } - - let now = Instant::now(); - - if let Some(end) = self.end { - self.score.time_remaining = (end - now).as_secs_f64(); - if end < now { - let relative_score = (self.score.points * 100) / self.data.score_baseline.max(1); - self.score.stars = match relative_score { - 100.. => 3, - 70.. => 2, - 40.. => 1, - _ => 0, - }; - packet_out.push_back(PacketC::Menu(Menu::Score(self.score.clone()))); - true - } else { - false - } - } else { - false - } - } - - pub fn count_chefs(&self) -> usize { - self.players - .values() - .map(|p| if p.character >= 0 { 1 } else { 0 }) - .sum() - } -} - -impl From<TileIndex> for Tile { - fn from(kind: TileIndex) -> Self { - Self { kind, item: None } - } -} - -pub fn interact_effect( - data: &Gamedata, - edge: bool, - this: &mut Option<Item>, - this_loc: ItemLocation, - other: &mut Option<Item>, - other_loc: ItemLocation, - this_tile_kind: Option<TileIndex>, - packet_out: &mut VecDeque<PacketC>, - score: &mut Score, - score_changed: &mut bool, - automated: bool, -) { - let this_had_item = this.is_some(); - let other_had_item = other.is_some(); - - if let Some(effect) = interact(data, edge, this_tile_kind, this, other, score, automated) { - match effect { - InteractEffect::Put => { - info!("put {this_loc} <- {other_loc}"); - packet_out.push_back(PacketC::MoveItem { - from: other_loc, - to: this_loc, - }) - } - InteractEffect::Take => { - info!("take {this_loc} -> {other_loc}"); - packet_out.push_back(PacketC::MoveItem { - from: this_loc, - to: other_loc, - }) - } - InteractEffect::Produce => { - info!("produce {this_loc} <~ {other_loc}"); - *score_changed = true; - if this_had_item { - packet_out.push_back(PacketC::SetProgress { - item: this_loc, - progress: None, - warn: false, - }); - packet_out.push_back(PacketC::SetItem { - location: this_loc, - item: None, - }); - } - if other_had_item { - packet_out.push_back(PacketC::MoveItem { - from: other_loc, - to: this_loc, - }); - packet_out.push_back(PacketC::SetItem { - location: this_loc, - item: None, - }); - } - if let Some(i) = &other { - packet_out.push_back(PacketC::SetItem { - location: this_loc, - item: Some(i.kind), - }); - packet_out.push_back(PacketC::MoveItem { - from: this_loc, - to: other_loc, - }) - } - if let Some(i) = &this { - packet_out.push_back(PacketC::SetItem { - location: this_loc, - item: Some(i.kind), - }); - } - } - } - } -} - -impl Player { - pub fn position(&self) -> Vec2 { - self.movement.position - } -} |