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 -    } -} | 
