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 | |
| 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')
| -rw-r--r-- | server/src/bin/graph.rs | 2 | ||||
| -rw-r--r-- | server/src/data.rs | 363 | ||||
| -rw-r--r-- | server/src/entity/conveyor.rs | 29 | ||||
| -rw-r--r-- | server/src/entity/customers/mod.rs | 368 | ||||
| -rw-r--r-- | server/src/entity/environment_effect.rs | 38 | ||||
| -rw-r--r-- | server/src/entity/item_portal.rs | 25 | ||||
| -rw-r--r-- | server/src/entity/mod.rs | 16 | ||||
| -rw-r--r-- | server/src/entity/player_portal.rs | 24 | ||||
| -rw-r--r-- | server/src/interaction.rs | 12 | ||||
| -rw-r--r-- | server/src/lib.rs | 2 | ||||
| -rw-r--r-- | server/src/main.rs | 2 | ||||
| -rw-r--r-- | server/src/server.rs (renamed from server/src/game.rs) | 311 | ||||
| -rw-r--r-- | server/src/state.rs | 70 | 
13 files changed, 598 insertions, 664 deletions
| diff --git a/server/src/bin/graph.rs b/server/src/bin/graph.rs index 03a59e37..62dc47a2 100644 --- a/server/src/bin/graph.rs +++ b/server/src/bin/graph.rs @@ -30,7 +30,7 @@ async fn main() -> Result<()> {          .nth(1)          .ok_or(anyhow!("first arg should be recipe set name"))?; -    let data = index.generate(format!("sushibar-{rn}")).await?; +    let (data, _) = index.generate(format!("sushibar-{rn}")).await?;      for i in 0..data.item_names.len() {          println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i))) diff --git a/server/src/data.rs b/server/src/data.rs index 99cbaf9f..f0a85cca 100644 --- a/server/src/data.rs +++ b/server/src/data.rs @@ -20,7 +20,7 @@ use crate::entity::{construct_entity, Entity, EntityDecl};  use anyhow::{anyhow, bail, Result};  use hurrycurry_protocol::{      glam::{IVec2, Vec2}, -    ItemIndex, MapMetadata, Recipe, RecipeIndex, TileIndex, +    Gamedata, ItemIndex, MapMetadata, Recipe, TileIndex,  };  use serde::{Deserialize, Serialize};  use std::{ @@ -99,15 +99,8 @@ pub struct Demand {  #[derive(Debug, Clone, Default)]  #[rustfmt::skip] -pub struct Gamedata { +pub struct Serverdata {      pub spec: String, -    pub map_name: String, -    pub item_names: Vec<String>, -    pub tile_names: Vec<String>, -    pub tile_collide: Vec<bool>, -    pub tile_interact: Vec<bool>, -    pub map: HashMap<String, MapMetadata>, -    pub recipes: Vec<Recipe>,      pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,      pub chef_spawn: Vec2,      pub customer_spawn: Vec2, @@ -151,182 +144,189 @@ impl DataIndex {          Ok(read_to_string(path).await?)      } -    pub async fn generate(&self, spec: String) -> Result<Gamedata> { +    pub async fn generate(&self, spec: String) -> Result<(Gamedata, Serverdata)> {          let (map, recipes) = spec.split_once("-").unwrap_or((spec.as_str(), "default"));          let map_in = serde_yml::from_str(&self.read_map(map).await?)?;          let recipes_in = serde_yml::from_str(&self.read_recipes(recipes).await?)?; -        let mut gd = Gamedata::build(spec.clone(), map.to_string(), map_in, recipes_in)?; -        gd.map = self.maps.clone(); -        Ok(gd) +        Ok(build_data( +            self.maps.clone(), +            spec.clone(), +            map.to_string(), +            map_in, +            recipes_in, +        )?)      }  } -impl Gamedata { -    pub fn build( -        spec: String, -        map_name: String, -        map_in: InitialMap, -        recipes_in: Vec<RecipeDecl>, -    ) -> Result<Self> { -        let reg = ItemTileRegistry::default(); -        let mut recipes = Vec::new(); -        let mut entities = Vec::new(); -        let mut raw_demands = Vec::new(); +pub fn build_data( +    maps: HashMap<String, MapMetadata>, +    spec: String, +    map_name: String, +    map_in: InitialMap, +    recipes_in: Vec<RecipeDecl>, +) -> Result<(Gamedata, Serverdata)> { +    let reg = ItemTileRegistry::default(); +    let mut recipes = Vec::new(); +    let mut entities = Vec::new(); +    let mut raw_demands = Vec::new(); -        for mut r in recipes_in { -            let r2 = r.clone(); -            let mut inputs = r.inputs.into_iter().map(|i| reg.register_item(i)); -            let mut outputs = r.outputs.into_iter().map(|o| reg.register_item(o)); -            let tile = r.tile.map(|t| reg.register_tile(t)); -            match r.action { -                Action::Never => {} -                Action::Passive => recipes.push(Recipe::Passive { -                    duration: r.duration.ok_or(anyhow!("duration for passive missing"))?, -                    warn: r.warn, +    for mut r in recipes_in { +        let r2 = r.clone(); +        let mut inputs = r.inputs.into_iter().map(|i| reg.register_item(i)); +        let mut outputs = r.outputs.into_iter().map(|o| reg.register_item(o)); +        let tile = r.tile.map(|t| reg.register_tile(t)); +        match r.action { +            Action::Never => {} +            Action::Passive => recipes.push(Recipe::Passive { +                duration: r.duration.ok_or(anyhow!("duration for passive missing"))?, +                warn: r.warn, +                tile, +                revert_duration: r.revert_duration, +                input: inputs +                    .next() +                    .ok_or(anyhow!("passive recipe without input"))?, +                output: outputs.next(), +            }), +            Action::Active => recipes.push(Recipe::Active { +                duration: r.duration.ok_or(anyhow!("duration for active missing"))?, +                tile, +                input: inputs +                    .next() +                    .ok_or(anyhow!("active recipe without input"))?, +                outputs: [outputs.next(), outputs.next()], +            }), +            Action::Instant => { +                recipes.push(Recipe::Instant { +                    points: r.points.take().unwrap_or(0),                      tile, -                    revert_duration: r.revert_duration, -                    input: inputs -                        .next() -                        .ok_or(anyhow!("passive recipe without input"))?, -                    output: outputs.next(), -                }), -                Action::Active => recipes.push(Recipe::Active { -                    duration: r.duration.ok_or(anyhow!("duration for active missing"))?, -                    tile, -                    input: inputs -                        .next() -                        .ok_or(anyhow!("active recipe without input"))?, +                    inputs: [inputs.next(), inputs.next()],                      outputs: [outputs.next(), outputs.next()], -                }), -                Action::Instant => { -                    recipes.push(Recipe::Instant { -                        points: r.points.take().unwrap_or(0), -                        tile, -                        inputs: [inputs.next(), inputs.next()], -                        outputs: [outputs.next(), outputs.next()], -                    }); -                } -                Action::Demand => raw_demands.push(( -                    inputs.next().ok_or(anyhow!("demand needs inputs"))?, -                    outputs.next(), -                    r.duration.unwrap_or(10.), -                )), +                });              } -            assert_eq!(inputs.next(), None, "{r2:?} inputs left over"); -            assert_eq!(outputs.next(), None, "{r2:?} outputs left over"); -            assert_eq!(r.points, None, "points specified where not possible") +            Action::Demand => raw_demands.push(( +                inputs.next().ok_or(anyhow!("demand needs inputs"))?, +                outputs.next(), +                r.duration.unwrap_or(10.), +            )),          } +        assert_eq!(inputs.next(), None, "{r2:?} inputs left over"); +        assert_eq!(outputs.next(), None, "{r2:?} outputs left over"); +        assert_eq!(r.points, None, "points specified where not possible") +    } -        // TODO -        // for d in demands_in { -        //     demands.push(Demand { -        //         from: reg.register_item(d.from), -        //         to: d.to.map(|to| reg.register_item(to)), -        //         duration: d.duration, -        //         points: d.points, -        //     }) -        // } - -        let mut chef_spawn = Vec2::new(0., 0.); -        let mut customer_spawn = Vec2::new(0., 0.); -        let mut initial_map = HashMap::new(); -        let mut tiles_used = HashSet::new(); -        let mut items_used = HashSet::new(); -        for (y, line) in map_in.map.iter().enumerate() { -            for (x, tile) in line.chars().enumerate() { -                if tile == ' ' { -                    continue; // space is empty space -                } -                let pos = IVec2::new(x as i32, y as i32); -                if tile == map_in.chef_spawn { -                    chef_spawn = pos.as_vec2() + Vec2::splat(0.5); -                } -                if tile == map_in.customer_spawn { -                    customer_spawn = pos.as_vec2() + Vec2::splat(0.5); -                } -                let tilename = map_in -                    .tiles -                    .get(&tile) -                    .ok_or(anyhow!("tile {tile} is undefined"))? -                    .clone(); +    // TODO +    // for d in demands_in { +    //     demands.push(Demand { +    //         from: reg.register_item(d.from), +    //         to: d.to.map(|to| reg.register_item(to)), +    //         duration: d.duration, +    //         points: d.points, +    //     }) +    // } -                let itemname = map_in.items.get(&tile).cloned(); -                let tile = reg.register_tile(tilename); -                let item = itemname.map(|i| reg.register_item(i)); -                tiles_used.insert(tile); -                if let Some(i) = item { -                    items_used.insert(i); -                }; -                initial_map.insert(pos, (tile, item)); +    let mut chef_spawn = Vec2::new(0., 0.); +    let mut customer_spawn = Vec2::new(0., 0.); +    let mut initial_map = HashMap::new(); +    let mut tiles_used = HashSet::new(); +    let mut items_used = HashSet::new(); +    for (y, line) in map_in.map.iter().enumerate() { +        for (x, tile) in line.chars().enumerate() { +            if tile == ' ' { +                continue; // space is empty space +            } +            let pos = IVec2::new(x as i32, y as i32); +            if tile == map_in.chef_spawn { +                chef_spawn = pos.as_vec2() + Vec2::splat(0.5);              } +            if tile == map_in.customer_spawn { +                customer_spawn = pos.as_vec2() + Vec2::splat(0.5); +            } +            let tilename = map_in +                .tiles +                .get(&tile) +                .ok_or(anyhow!("tile {tile} is undefined"))? +                .clone(); + +            let itemname = map_in.items.get(&tile).cloned(); +            let tile = reg.register_tile(tilename); +            let item = itemname.map(|i| reg.register_item(i)); +            tiles_used.insert(tile); +            if let Some(i) = item { +                items_used.insert(i); +            }; +            initial_map.insert(pos, (tile, item));          } +    } -        for (y, line) in map_in.map.iter().enumerate() { -            for (x, tile) in line.trim().chars().enumerate() { -                let pos = IVec2::new(x as i32, y as i32); -                if let Some(ent) = map_in.tile_entities.get(&tile) { -                    entities.push(construct_entity( -                        Some(pos), -                        ent, -                        ®, -                        &tiles_used, -                        &items_used, -                        &raw_demands, -                        &recipes, -                        &initial_map, -                    )?); -                } +    for (y, line) in map_in.map.iter().enumerate() { +        for (x, tile) in line.trim().chars().enumerate() { +            let pos = IVec2::new(x as i32, y as i32); +            if let Some(ent) = map_in.tile_entities.get(&tile) { +                entities.push(construct_entity( +                    Some(pos), +                    ent, +                    ®, +                    &tiles_used, +                    &items_used, +                    &raw_demands, +                    &recipes, +                    &initial_map, +                )?);              }          } +    } -        entities.extend( -            map_in -                .entities -                .iter() -                .map(|decl| { -                    construct_entity( -                        None, -                        decl, -                        ®, -                        &tiles_used, -                        &items_used, -                        &raw_demands, -                        &recipes, -                        &initial_map, -                    ) -                }) -                .try_collect::<Vec<_>>()?, -        ); - -        let item_names = reg.items.into_inner().unwrap(); -        let tile_names = reg.tiles.into_inner().unwrap(); -        let tile_collide = tile_names -            .iter() -            .map(|i| !map_in.walkable.contains(i)) -            .collect(); -        let tile_interact = tile_names +    entities.extend( +        map_in +            .entities              .iter() -            .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i)) -            .collect(); +            .map(|decl| { +                construct_entity( +                    None, +                    decl, +                    ®, +                    &tiles_used, +                    &items_used, +                    &raw_demands, +                    &recipes, +                    &initial_map, +                ) +            }) +            .try_collect::<Vec<_>>()?, +    ); -        Ok(Gamedata { -            spec, +    let item_names = reg.items.into_inner().unwrap(); +    let tile_names = reg.tiles.into_inner().unwrap(); +    let tile_collide = tile_names +        .iter() +        .map(|i| !map_in.walkable.contains(i)) +        .collect(); +    let tile_interact = tile_names +        .iter() +        .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i)) +        .collect(); + +    Ok(( +        Gamedata { +            current_map: map_name, +            maps,              tile_collide, -            map_name,              tile_interact,              recipes, -            score_baseline: map_in.score_baseline, -            map: HashMap::new(), -            initial_map,              item_names, -            entities,              tile_names, +        }, +        Serverdata { +            spec, +            initial_map,              chef_spawn,              customer_spawn, -        }) -    } +            score_baseline: map_in.score_baseline, +            entities, +        }, +    ))  }  #[derive(Default)] @@ -353,56 +353,3 @@ impl ItemTileRegistry {          }      }  } - -impl Gamedata { -    pub fn tile_name(&self, index: TileIndex) -> &String { -        &self.tile_names[index.0] -    } -    pub fn is_tile_colliding(&self, index: TileIndex) -> bool { -        self.tile_collide[index.0] -    } -    pub fn is_tile_interactable(&self, index: TileIndex) -> bool { -        self.tile_interact[index.0] -    } -    pub fn item_name(&self, index: ItemIndex) -> &String { -        &self.item_names[index.0] -    } -    pub fn recipe(&self, index: RecipeIndex) -> &Recipe { -        &self.recipes[index.0] -    } -    pub fn get_tile_by_name(&self, name: &str) -> Option<TileIndex> { -        self.tile_names -            .iter() -            .position(|t| t == name) -            .map(TileIndex) -    } -    pub fn get_item_by_name(&self, name: &str) -> Option<ItemIndex> { -        self.item_names -            .iter() -            .position(|t| t == name) -            .map(ItemIndex) -    } -    pub fn recipes(&self) -> impl Iterator<Item = (RecipeIndex, &Recipe)> { -        self.recipes -            .iter() -            .enumerate() -            .map(|(i, e)| (RecipeIndex(i), e)) -    } -} -/* -    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/>. - -*/ diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs index b370356c..f7f091c7 100644 --- a/server/src/entity/conveyor.rs +++ b/server/src/entity/conveyor.rs @@ -1,5 +1,3 @@ -use std::collections::VecDeque; -  /*      Hurry Curry! - a game about cooking      Copyright 2024 metamuffin @@ -17,10 +15,10 @@ use std::collections::VecDeque;      along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ -use super::EntityT; -use crate::game::{interact_effect, Game}; +use super::{EntityContext, EntityT}; +use crate::server::interact_effect;  use anyhow::{anyhow, Result}; -use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation, PacketC}; +use hurrycurry_protocol::{glam::IVec2, ItemIndex, ItemLocation};  #[derive(Debug, Clone)]  pub struct Conveyor { @@ -33,15 +31,17 @@ pub struct Conveyor {  }  impl EntityT for Conveyor { -    fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> { -        let from = game +    fn tick(&mut self, c: EntityContext) -> Result<()> { +        let from = c +            .game              .tiles              .get(&self.from)              .ok_or(anyhow!("conveyor from missing"))?;          if let Some(from_item) = from.item.as_ref() {              let filter = if let Some(t) = &self.filter_tile { -                let filter_tile = game +                let filter_tile = c +                    .game                      .tiles                      .get(t)                      .ok_or(anyhow!("conveyor filter missing"))?; @@ -56,28 +56,29 @@ impl EntityT for Conveyor {                  }              } -            self.cooldown += dt; +            self.cooldown += c.dt;              if self.cooldown < self.max_cooldown {                  return Ok(());              }              self.cooldown = 0.; -            let [from, to] = game +            let [from, to] = c +                .game                  .tiles                  .get_many_mut([&self.from, &self.to])                  .ok_or(anyhow!("conveyor does ends in itself"))?;              interact_effect( -                &game.data, +                &c.game.data,                  true,                  &mut to.item,                  ItemLocation::Tile(self.to),                  &mut from.item,                  ItemLocation::Tile(self.from),                  Some(to.kind), -                packet_out, -                &mut game.score, -                &mut game.score_changed, +                c.packet_out, +                &mut c.game.score, +                c.score_changed,                  true,              );          } diff --git a/server/src/entity/customers/mod.rs b/server/src/entity/customers/mod.rs index 221351a5..85da2c07 100644 --- a/server/src/entity/customers/mod.rs +++ b/server/src/entity/customers/mod.rs @@ -18,8 +18,8 @@  pub mod demands;  mod pathfinding; -use super::EntityT; -use crate::{data::Demand, game::Game}; +use super::{EntityContext, EntityT}; +use crate::{data::Demand, server::Server};  use anyhow::{anyhow, bail, Result};  use fake::{faker, Fake};  use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketC, PacketS, PlayerID}; @@ -75,190 +75,190 @@ impl Customers {  }  impl EntityT for Customers { -    fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> { -        self.spawn_cooldown -= dt; -        self.spawn_cooldown = self.spawn_cooldown.max(0.); -        if self.customers.len() < 5 && self.spawn_cooldown <= 0. { -            self.spawn_cooldown = 10. + random::<f32>() * 10.; -            let id = game.join_player( -                faker::name::fr_fr::Name().fake(), -                -1 - (random::<u16>() as i32), -                packet_out, -            ); +    fn tick(&mut self, c: EntityContext) -> Result<()> { +        // self.spawn_cooldown -= dt; +        // self.spawn_cooldown = self.spawn_cooldown.max(0.); +        // if self.customers.len() < 5 && self.spawn_cooldown <= 0. { +        //     self.spawn_cooldown = 10. + random::<f32>() * 10.; +        //     let id = game.join_player( +        //         faker::name::fr_fr::Name().fake(), +        //         -1 - (random::<u16>() as i32), +        //         packet_out, +        //     ); -            let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?; -            let from = game.data.customer_spawn.as_ivec2(); -            let path = find_path(&game.walkable, from, chair) -                .ok_or(anyhow!("no path from {from} to {chair}"))?; -            info!("{id:?} -> entering"); -            self.customers -                .insert(id, CustomerState::Entering { path, chair }); -        } -        let mut customers_to_remove = Vec::new(); -        for (&player, state) in &mut self.customers { -            let Some(playerdata) = game.players.get_mut(&player) else { -                continue; -            }; +        //     let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?; +        //     let from = game.data.customer_spawn.as_ivec2(); +        //     let path = find_path(&game.walkable, from, chair) +        //         .ok_or(anyhow!("no path from {from} to {chair}"))?; +        //     info!("{id:?} -> entering"); +        //     self.customers +        //         .insert(id, CustomerState::Entering { path, chair }); +        // } +        // let mut customers_to_remove = Vec::new(); +        // for (&player, state) in &mut self.customers { +        //     let Some(playerdata) = game.players.get_mut(&player) else { +        //         continue; +        //     }; -            match state { -                CustomerState::Entering { path, chair } => { -                    playerdata -                        .movement -                        .input(path.next_direction(playerdata.position()), false); -                    if path.is_done() { -                        let demand = DemandIndex(random::<u32>() as usize % self.demands.len()); -                        self.cpackets.push_back(PacketS::Communicate { -                            message: Some(Message::Item(self.demands[demand.0].from)), -                            persist: true, -                            player, -                        }); -                        info!("{player:?} -> waiting"); -                        *state = CustomerState::Waiting { -                            chair: *chair, -                            timeout: 90. + random::<f32>() * 60., -                            demand, -                        }; -                    } -                } -                CustomerState::Waiting { -                    chair, -                    demand, -                    timeout, -                } => { -                    playerdata -                        .movement -                        .input((chair.as_vec2() + 0.5) - playerdata.position(), false); -                    *timeout -= dt; -                    if *timeout <= 0. { -                        self.cpackets.push_back(PacketS::Communicate { -                            message: None, -                            persist: true, -                            player, -                        }); -                        self.cpackets.push_back(PacketS::Communicate { -                            message: Some(Message::Effect("angry".to_string())), -                            persist: false, -                            player, -                        }); -                        let path = find_path( -                            &game.walkable, -                            playerdata.position().as_ivec2(), -                            game.data.customer_spawn.as_ivec2(), -                        ) -                        .expect("no path to exit"); -                        *self.chairs.get_mut(chair).unwrap() = true; -                        game.score.demands_failed += 1; -                        game.score.points -= 1; -                        game.score_changed = true; -                        info!("{player:?} -> exiting"); -                        *state = CustomerState::Exiting { path } -                    } else { -                        let demand_data = &self.demands[demand.0]; -                        let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] -                            .into_iter() -                            .find_map(|off| { -                                let pos = *chair + off; -                                if game -                                    .tiles -                                    .get(&pos) -                                    .map(|t| { -                                        t.item -                                            .as_ref() -                                            .map(|i| i.kind == demand_data.from) -                                            .unwrap_or_default() -                                    }) -                                    .unwrap_or_default() -                                { -                                    Some(pos) -                                } else { -                                    None -                                } -                            }); -                        if let Some(pos) = demand_pos { -                            self.cpackets.push_back(PacketS::Communicate { -                                persist: true, -                                message: None, -                                player, -                            }); -                            self.cpackets.push_back(PacketS::Communicate { -                                message: Some(Message::Effect("satisfied".to_string())), -                                persist: false, -                                player, -                            }); -                            self.cpackets.push_back(PacketS::Interact { -                                pos: Some(pos), -                                player, -                            }); -                            self.cpackets -                                .push_back(PacketS::Interact { pos: None, player }); -                            info!("{player:?} -> eating"); -                            *state = CustomerState::Eating { -                                demand: *demand, -                                target: pos, -                                progress: 0., -                                chair: *chair, -                            } -                        } -                    } -                } -                CustomerState::Eating { -                    demand, -                    target, -                    progress, -                    chair, -                } => { -                    playerdata -                        .movement -                        .input((chair.as_vec2() + 0.5) - playerdata.position(), false); -                    let demand = &self.demands[demand.0]; -                    *progress += dt / demand.duration; -                    if *progress >= 1. { -                        self.cpackets.push_back(PacketS::ReplaceHand { -                            player, -                            item: demand.to, -                        }); -                        if demand.to.is_some() { -                            self.cpackets.push_back(PacketS::Interact { -                                player, -                                pos: Some(*target), -                            }); -                            self.cpackets -                                .push_back(PacketS::Interact { player, pos: None }); -                        } -                        let path = find_path( -                            &game.walkable, -                            playerdata.position().as_ivec2(), -                            game.data.customer_spawn.as_ivec2(), -                        ) -                        .ok_or(anyhow!("no path to exit"))?; -                        *self.chairs.get_mut(chair).unwrap() = true; -                        game.score.demands_completed += 1; -                        game.score.points += demand.points; -                        game.score_changed = true; -                        info!("{player:?} -> exiting"); -                        *state = CustomerState::Exiting { path } -                    } -                } -                CustomerState::Exiting { path } => { -                    playerdata -                        .movement -                        .input(path.next_direction(playerdata.position()), false); -                    if path.is_done() { -                        info!("{player:?} -> leave"); -                        self.cpackets.push_back(PacketS::Leave { player }); -                        customers_to_remove.push(player); -                    } -                } -            } -        } -        for c in customers_to_remove { -            self.customers.remove(&c).unwrap(); -        } -        for packet in self.cpackets.drain(..) { -            if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) { -                warn!("demand packet {err}"); -            } -        } +        //     match state { +        //         CustomerState::Entering { path, chair } => { +        //             playerdata +        //                 .movement +        //                 .input(path.next_direction(playerdata.position()), false); +        //             if path.is_done() { +        //                 let demand = DemandIndex(random::<u32>() as usize % self.demands.len()); +        //                 self.cpackets.push_back(PacketS::Communicate { +        //                     message: Some(Message::Item(self.demands[demand.0].from)), +        //                     persist: true, +        //                     player, +        //                 }); +        //                 info!("{player:?} -> waiting"); +        //                 *state = CustomerState::Waiting { +        //                     chair: *chair, +        //                     timeout: 90. + random::<f32>() * 60., +        //                     demand, +        //                 }; +        //             } +        //         } +        //         CustomerState::Waiting { +        //             chair, +        //             demand, +        //             timeout, +        //         } => { +        //             playerdata +        //                 .movement +        //                 .input((chair.as_vec2() + 0.5) - playerdata.position(), false); +        //             *timeout -= dt; +        //             if *timeout <= 0. { +        //                 self.cpackets.push_back(PacketS::Communicate { +        //                     message: None, +        //                     persist: true, +        //                     player, +        //                 }); +        //                 self.cpackets.push_back(PacketS::Communicate { +        //                     message: Some(Message::Effect("angry".to_string())), +        //                     persist: false, +        //                     player, +        //                 }); +        //                 let path = find_path( +        //                     &game.walkable, +        //                     playerdata.position().as_ivec2(), +        //                     game.data.customer_spawn.as_ivec2(), +        //                 ) +        //                 .expect("no path to exit"); +        //                 *self.chairs.get_mut(chair).unwrap() = true; +        //                 game.score.demands_failed += 1; +        //                 game.score.points -= 1; +        //                 game.score_changed = true; +        //                 info!("{player:?} -> exiting"); +        //                 *state = CustomerState::Exiting { path } +        //             } else { +        //                 let demand_data = &self.demands[demand.0]; +        //                 let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] +        //                     .into_iter() +        //                     .find_map(|off| { +        //                         let pos = *chair + off; +        //                         if game +        //                             .tiles +        //                             .get(&pos) +        //                             .map(|t| { +        //                                 t.item +        //                                     .as_ref() +        //                                     .map(|i| i.kind == demand_data.from) +        //                                     .unwrap_or_default() +        //                             }) +        //                             .unwrap_or_default() +        //                         { +        //                             Some(pos) +        //                         } else { +        //                             None +        //                         } +        //                     }); +        //                 if let Some(pos) = demand_pos { +        //                     self.cpackets.push_back(PacketS::Communicate { +        //                         persist: true, +        //                         message: None, +        //                         player, +        //                     }); +        //                     self.cpackets.push_back(PacketS::Communicate { +        //                         message: Some(Message::Effect("satisfied".to_string())), +        //                         persist: false, +        //                         player, +        //                     }); +        //                     self.cpackets.push_back(PacketS::Interact { +        //                         pos: Some(pos), +        //                         player, +        //                     }); +        //                     self.cpackets +        //                         .push_back(PacketS::Interact { pos: None, player }); +        //                     info!("{player:?} -> eating"); +        //                     *state = CustomerState::Eating { +        //                         demand: *demand, +        //                         target: pos, +        //                         progress: 0., +        //                         chair: *chair, +        //                     } +        //                 } +        //             } +        //         } +        //         CustomerState::Eating { +        //             demand, +        //             target, +        //             progress, +        //             chair, +        //         } => { +        //             playerdata +        //                 .movement +        //                 .input((chair.as_vec2() + 0.5) - playerdata.position(), false); +        //             let demand = &self.demands[demand.0]; +        //             *progress += dt / demand.duration; +        //             if *progress >= 1. { +        //                 self.cpackets.push_back(PacketS::ReplaceHand { +        //                     player, +        //                     item: demand.to, +        //                 }); +        //                 if demand.to.is_some() { +        //                     self.cpackets.push_back(PacketS::Interact { +        //                         player, +        //                         pos: Some(*target), +        //                     }); +        //                     self.cpackets +        //                         .push_back(PacketS::Interact { player, pos: None }); +        //                 } +        //                 let path = find_path( +        //                     &game.walkable, +        //                     playerdata.position().as_ivec2(), +        //                     game.data.customer_spawn.as_ivec2(), +        //                 ) +        //                 .ok_or(anyhow!("no path to exit"))?; +        //                 *self.chairs.get_mut(chair).unwrap() = true; +        //                 game.score.demands_completed += 1; +        //                 game.score.points += demand.points; +        //                 game.score_changed = true; +        //                 info!("{player:?} -> exiting"); +        //                 *state = CustomerState::Exiting { path } +        //             } +        //         } +        //         CustomerState::Exiting { path } => { +        //             playerdata +        //                 .movement +        //                 .input(path.next_direction(playerdata.position()), false); +        //             if path.is_done() { +        //                 info!("{player:?} -> leave"); +        //                 self.cpackets.push_back(PacketS::Leave { player }); +        //                 customers_to_remove.push(player); +        //             } +        //         } +        //     } +        // } +        // for c in customers_to_remove { +        //     self.customers.remove(&c).unwrap(); +        // } +        // for packet in self.cpackets.drain(..) { +        //     if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) { +        //         warn!("demand packet {err}"); +        //     } +        // }          Ok(())      }  } diff --git a/server/src/entity/environment_effect.rs b/server/src/entity/environment_effect.rs index f369bce4..8f54d29c 100644 --- a/server/src/entity/environment_effect.rs +++ b/server/src/entity/environment_effect.rs @@ -15,15 +15,11 @@      along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ -use super::EntityT; -use crate::game::Game; +use super::{EntityContext, EntityT};  use hurrycurry_protocol::PacketC;  use rand::random;  use serde::{Deserialize, Serialize}; -use std::{ -    collections::VecDeque, -    time::{Duration, Instant}, -}; +use std::time::{Duration, Instant};  #[derive(Clone, Debug, Deserialize, Serialize, Default)]  pub struct EnvironmentEffect { @@ -54,26 +50,21 @@ impl EnvironmentEffectController {      }  }  impl EntityT for EnvironmentEffectController { -    fn tick( -        &mut self, -        game: &mut Game, -        packet_out: &mut VecDeque<PacketC>, -        _dt: f32, -    ) -> anyhow::Result<()> { +    fn tick(&mut self, c: EntityContext) -> anyhow::Result<()> {          if self.next_transition < Instant::now() {              if self.active {                  self.next_transition +=                      Duration::from_secs_f32(self.config.on + (0.5 + random::<f32>()));                  self.active = false; -                game.environment_effects.remove(&self.config.name); +                c.game.environment_effects.remove(&self.config.name);              } else {                  self.next_transition +=                      Duration::from_secs_f32(self.config.off * (0.5 + random::<f32>()));                  self.active = true; -                game.environment_effects.insert(self.config.name.clone()); +                c.game.environment_effects.insert(self.config.name.clone());              } -            packet_out.push_back(PacketC::Environment { -                effects: game.environment_effects.clone(), +            c.packet_out.push_back(PacketC::Environment { +                effects: c.game.environment_effects.clone(),              })          }          Ok(()) @@ -83,16 +74,11 @@ impl EntityT for EnvironmentEffectController {  #[derive(Debug, Clone)]  pub struct EnvironmentController(pub Vec<String>);  impl EntityT for EnvironmentController { -    fn tick( -        &mut self, -        game: &mut Game, -        packet_out: &mut VecDeque<PacketC>, -        _dt: f32, -    ) -> anyhow::Result<()> { -        if game.environment_effects.is_empty() { -            game.environment_effects.extend(self.0.clone()); -            packet_out.push_back(PacketC::Environment { -                effects: game.environment_effects.clone(), +    fn tick(&mut self, c: EntityContext) -> anyhow::Result<()> { +        if c.game.environment_effects.is_empty() { +            c.game.environment_effects.extend(self.0.clone()); +            c.packet_out.push_back(PacketC::Environment { +                effects: c.game.environment_effects.clone(),              })          }          Ok(()) diff --git a/server/src/entity/item_portal.rs b/server/src/entity/item_portal.rs index f63ff274..be6acd06 100644 --- a/server/src/entity/item_portal.rs +++ b/server/src/entity/item_portal.rs @@ -15,11 +15,10 @@      along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ -use super::EntityT; -use crate::game::{interact_effect, Game}; +use super::{EntityContext, EntityT}; +use crate::server::interact_effect;  use anyhow::{anyhow, Result}; -use hurrycurry_protocol::{glam::IVec2, ItemLocation, PacketC}; -use std::collections::VecDeque; +use hurrycurry_protocol::{glam::IVec2, ItemLocation};  #[derive(Debug, Default, Clone)]  pub struct ItemPortal { @@ -28,29 +27,25 @@ pub struct ItemPortal {  }  impl EntityT for ItemPortal { -    fn tick( -        &mut self, -        game: &mut Game, -        packet_out: &mut VecDeque<PacketC>, -        _dt: f32, -    ) -> Result<()> { -        let [from, to] = game +    fn tick(&mut self, c: EntityContext) -> Result<()> { +        let [from, to] = c +            .game              .tiles              .get_many_mut([&self.from, &self.to])              .ok_or(anyhow!("conveyor does ends in itself"))?;          if from.item.is_some() {              interact_effect( -                &game.data, +                &c.game.data,                  true,                  &mut to.item,                  ItemLocation::Tile(self.to),                  &mut from.item,                  ItemLocation::Tile(self.from),                  Some(to.kind), -                packet_out, -                &mut game.score, -                &mut game.score_changed, +                c.packet_out, +                &mut c.game.score, +                c.score_changed,                  true,              );          } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 94718dd1..10d0c155 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -21,11 +21,12 @@ pub mod environment_effect;  pub mod item_portal;  pub mod player_portal; -use crate::{data::ItemTileRegistry, game::Game}; +use crate::data::ItemTileRegistry;  use anyhow::{anyhow, Result};  use conveyor::Conveyor;  use customers::{demands::generate_demands, Customers};  use environment_effect::{EnvironmentController, EnvironmentEffect, EnvironmentEffectController}; +use hurrycurry_client_lib::Game;  use hurrycurry_protocol::{      glam::{IVec2, Vec2},      ItemIndex, PacketC, Recipe, TileIndex, @@ -35,8 +36,15 @@ use player_portal::PlayerPortal;  use serde::{Deserialize, Serialize};  use std::collections::{HashMap, HashSet, VecDeque}; +pub struct EntityContext<'a> { +    pub game: &'a mut Game, +    pub packet_out: &'a mut VecDeque<PacketC>, +    pub score_changed: &'a mut bool, +    pub dt: f32, +} +  pub trait EntityT: Clone { -    fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()>; +    fn tick(&mut self, c: EntityContext<'_>) -> Result<()>;  }  macro_rules! entities { @@ -44,8 +52,8 @@ macro_rules! entities {          #[derive(Debug, Clone)]          pub enum Entity { $($e($e)),* }          impl EntityT for Entity { -            fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque<PacketC>, dt: f32) -> Result<()> { -                match self { $(Entity::$e(x) => x.tick(game, packet_out, dt)),*, } +            fn tick(&mut self, c: EntityContext<'_>) -> Result<()> { +                match self { $(Entity::$e(x) => x.tick(c)),*, }              }          }      }; diff --git a/server/src/entity/player_portal.rs b/server/src/entity/player_portal.rs index 6ee04f15..11442677 100644 --- a/server/src/entity/player_portal.rs +++ b/server/src/entity/player_portal.rs @@ -15,11 +15,9 @@      along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ -use super::EntityT; -use crate::game::Game; +use super::{EntityContext, EntityT};  use anyhow::{anyhow, Result};  use hurrycurry_protocol::{glam::Vec2, PacketC}; -use std::collections::VecDeque;  #[derive(Debug, Default, Clone)]  pub struct PlayerPortal { @@ -28,19 +26,21 @@ pub struct PlayerPortal {  }  impl EntityT for PlayerPortal { -    fn tick( -        &mut self, -        game: &mut Game, -        _packet_out: &mut VecDeque<PacketC>, -        _dt: f32, -    ) -> Result<()> { +    fn tick(&mut self, c: EntityContext) -> Result<()> {          let mut players = Vec::new(); -        game.players_spatial_index +        c.game +            .players_spatial_index              .query(self.from, 0.5, |pid, _| players.push(pid)); -        for p in players { -            let p = game.players.get_mut(&p).ok_or(anyhow!("player missing"))?; +        for pid in players { +            let p = c +                .game +                .players +                .get_mut(&pid) +                .ok_or(anyhow!("player missing"))?;              p.movement.position = self.to; +            c.packet_out +                .push_back(PacketC::MovementSync { player: pid });          }          Ok(()) diff --git a/server/src/interaction.rs b/server/src/interaction.rs index 4630b536..2f58ed8f 100644 --- a/server/src/interaction.rs +++ b/server/src/interaction.rs @@ -1,3 +1,4 @@ +use hurrycurry_client_lib::{Involvement, Item};  /*      Hurry Curry! - a game about cooking      Copyright 2024 metamuffin @@ -15,11 +16,7 @@      along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ -use crate::{ -    data::Gamedata, -    game::{Involvement, Item}, -}; -use hurrycurry_protocol::{Recipe, Score, TileIndex}; +use hurrycurry_protocol::{Gamedata, Recipe, Score, TileIndex};  use log::info;  pub enum InteractEffect { @@ -80,6 +77,7 @@ pub fn interact(                                      recipe: ri,                                      working: 1,                                      progress: 0., +                                    warn: false,                                  });                              }                          } @@ -96,6 +94,7 @@ pub fn interact(                                          recipe: ri,                                          working: 1,                                          progress: 0., +                                        warn: false,                                      });                                  }                                  *this = Some(item); @@ -180,11 +179,12 @@ pub fn tick_slot(          } else {              for (ri, recipe) in data.recipes() {                  if recipe.supports_tile(tile) { -                    if let Recipe::Passive { input, .. } = recipe { +                    if let Recipe::Passive { input, warn, .. } = recipe {                          if *input == item.kind {                              item.active = Some(Involvement {                                  recipe: ri,                                  progress: 0., +                                warn: *warn,                                  working: 1,                              });                              return Some(TickEffect::Progress(recipe.warn())); diff --git a/server/src/lib.rs b/server/src/lib.rs index c8f7af8c..b87f1cb9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -18,7 +18,7 @@  #![feature(if_let_guard, map_many_mut, let_chains, iterator_try_collect, isqrt)]  pub mod data;  pub mod entity; -pub mod game; +pub mod server;  pub mod interaction;  pub mod spatial_index;  pub mod state; diff --git a/server/src/main.rs b/server/src/main.rs index 0f38aa2b..40ea3433 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -19,7 +19,7 @@ use anyhow::{anyhow, Result};  use clap::Parser;  use futures_util::{SinkExt, StreamExt};  use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG, VERSION}; -use hurrycurry_server::{data::DATA_DIR, state::State, ConnectionID}; +use hurrycurry_server::{data::DATA_DIR, server::GameServerExt, state::State, ConnectionID};  use log::{debug, info, trace, warn, LevelFilter};  use std::{      net::SocketAddr, diff --git a/server/src/game.rs b/server/src/server.rs index 39cd61dc..f4ccbf35 100644 --- a/server/src/game.rs +++ b/server/src/server.rs @@ -16,95 +16,55 @@  */  use crate::{ -    data::Gamedata, -    entity::{Entity, EntityT}, +    data::Serverdata, +    entity::{Entity, EntityContext, EntityT},      interaction::{interact, tick_slot, InteractEffect, TickEffect}, -    spatial_index::SpatialIndex,  };  use anyhow::{anyhow, bail, Result}; +use hurrycurry_client_lib::{Game, Item, Player, Tile};  use hurrycurry_protocol::{      glam::{IVec2, Vec2},      movement::MovementBase, -    ClientGamedata, ItemIndex, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, -    RecipeIndex, Score, TileIndex, +    Gamedata, ItemLocation, Menu, PacketC, PacketS, PlayerID, Score, TileIndex,  };  use log::{info, warn};  use std::{ -    collections::{HashMap, HashSet, VecDeque}, -    sync::{Arc, RwLock}, +    collections::{HashMap, VecDeque}, +    sync::Arc,      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 struct ServerState { +    pub data: Arc<Serverdata>, +    entities: Vec<Entity>,      pub lobby: bool, - -    pub environment_effects: HashSet<String>, +    pub player_id_counter: PlayerID,      pub score_changed: bool, -    pub score: Score, +} -    pub player_id_counter: PlayerID, +pub struct Server<'a> { +    pub game: &'a mut Game, +    pub state: &'a mut ServerState,  } -impl Default for Game { +impl Default for ServerState {      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), -        } -    } - +pub trait GameServerExt { +    fn unload(&mut self, packet_out: &mut VecDeque<PacketC>); +    fn load( +        &mut self, +        gamedata: Gamedata, +        serverdata: &Serverdata, +        timer: Option<Duration>, +        packet_out: &mut VecDeque<PacketC>, +    ); +    fn prime_client(&self) -> Vec<PacketC>; +} +impl GameServerExt for Game {      fn unload(&mut self, packet_out: &mut VecDeque<PacketC>) {          packet_out.push_back(PacketC::SetIngame {              state: false, @@ -125,9 +85,10 @@ impl Game {          self.environment_effects.clear();          self.walkable.clear();      } -    pub fn load( +    fn load(          &mut self,          gamedata: Gamedata, +        serverdata: &Serverdata,          timer: Option<Duration>,          packet_out: &mut VecDeque<PacketC>,      ) { @@ -140,16 +101,15 @@ impl Game {          self.unload(packet_out); -        self.lobby = gamedata.map_name == "lobby"; +        self.lobby = gamedata.current_map == "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 { +        for (&p, (tile, item)) in &serverdata.initial_map {              self.tiles.insert(                  p,                  Tile { @@ -172,9 +132,9 @@ impl Game {                      character,                      movement: MovementBase {                          position: if character < 0 { -                            self.data.customer_spawn +                            serverdata.customer_spawn                          } else { -                            self.data.chef_spawn +                            serverdata.chef_spawn                          },                          input_direction: Vec2::ZERO,                          input_boost: false, @@ -184,7 +144,6 @@ impl Game {                          boosting: false,                          stamina: 0.,                      }, -                    last_position_update: Instant::now(),                      communicate_persist: None,                      interacting: None,                      name: name.clone(), @@ -195,32 +154,10 @@ impl Game {          packet_out.extend(self.prime_client());      } -    pub fn prime_client(&self) -> Vec<PacketC> { +    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(), -            }, +            data: self.data.as_ref().to_owned(),          });          out.push(PacketC::Environment {              effects: self.environment_effects.clone(), @@ -271,6 +208,29 @@ impl Game {          });          out      } +} + +impl ServerState { +    pub fn new() -> Self { +        Self { +            lobby: false, +            data: Serverdata::default().into(), +            entities: vec![], +            player_id_counter: PlayerID(1), +            score_changed: false, +        } +    } +} +impl Server<'_> { +    pub fn load( +        &mut self, +        (gamedata, serverdata): (Gamedata, Serverdata), +        timer: Option<Duration>, +        packet_out: &mut VecDeque<PacketC>, +    ) { +        self.game.load(gamedata, &serverdata, timer, packet_out); +        self.state.data = serverdata.into(); +    }      pub fn join_player(          &mut self, @@ -278,24 +238,20 @@ impl Game {          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 +        let id = self.state.player_id_counter; +        self.state.player_id_counter.0 += 1; +        let position = if character < 0 { +            self.state.data.customer_spawn          } else { -            self.data.chef_spawn +            self.state.data.chef_spawn          }; -        self.players.insert( +        self.game.players.insert(              id,              Player {                  item: None,                  character,                  movement: MovementBase { -                    position: if character < 0 { -                        self.data.customer_spawn -                    } else { -                        self.data.chef_spawn -                    }, +                    position,                      input_direction: Vec2::ZERO,                      input_boost: false,                      facing: Vec2::X, @@ -304,13 +260,13 @@ impl Game {                      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()); +        self.game.score.players = self.game.score.players.max(self.game.players.len());          packet_out.push_back(PacketC::AddPlayer {              id,              name, @@ -333,15 +289,16 @@ impl Game {              }              PacketS::Leave { player } => {                  let p = self +                    .game                      .players                      .remove(&player)                      .ok_or(anyhow!("player does not exist"))?; -                self.players_spatial_index.remove_entry(player); +                self.game.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 let Some(tile) = self.game.tiles.get_mut(&pos) {                          if tile.item.is_none() {                              packet_out.push_back(PacketC::SetItem {                                  location: ItemLocation::Tile(pos), @@ -360,26 +317,30 @@ impl Game {                  player,              } => {                  let pd = self +                    .game                      .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()); +                let _ = pos; // TODO -                    if diff.length() > 1. { -                        replies.push(PacketC::MovementSync { player }); -                    } -                } +                // 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 +                    .game                      .players                      .get_mut(&pid)                      .ok_or(anyhow!("player does not exist"))?; @@ -397,6 +358,7 @@ impl Game {                  }                  let tile = self +                    .game                      .tiles                      .get_mut(&pos)                      .ok_or(anyhow!("tile does not exist"))?; @@ -405,8 +367,9 @@ impl Game {                  player.interacting = if edge { Some(pos) } else { None }; -                let other_pid = if !self.data.is_tile_interactable(tile.kind) { -                    self.players +                let other_pid = if !self.game.data.is_tile_interactable(tile.kind) { +                    self.game +                        .players                          .iter()                          .find(|(id, p)| **id != pid && p.movement.position.distance(entpos) < 0.7)                          .map(|(&id, _)| id) @@ -416,6 +379,7 @@ impl Game {                  if let Some(base_pid) = other_pid {                      let [other, this] = self +                        .game                          .players                          .get_many_mut([&pid, &base_pid])                          .ok_or(anyhow!("interacting with yourself. this is impossible"))?; @@ -425,7 +389,7 @@ impl Game {                      }                      interact_effect( -                        &self.data, +                        &self.game.data,                          edge,                          &mut this.item,                          ItemLocation::Player(base_pid), @@ -433,18 +397,19 @@ impl Game {                          ItemLocation::Player(pid),                          None,                          packet_out, -                        &mut self.score, -                        &mut self.score_changed, +                        &mut self.game.score, +                        &mut self.state.score_changed,                          false,                      )                  } else {                      let player = self +                        .game                          .players                          .get_mut(&pid)                          .ok_or(anyhow!("player does not exist"))?;                      interact_effect( -                        &self.data, +                        &self.game.data,                          edge,                          &mut tile.item,                          ItemLocation::Tile(pos), @@ -452,8 +417,8 @@ impl Game {                          ItemLocation::Player(pid),                          Some(tile.kind),                          packet_out, -                        &mut self.score, -                        &mut self.score_changed, +                        &mut self.game.score, +                        &mut self.state.score_changed,                          false,                      )                  } @@ -465,7 +430,7 @@ impl Game {              } => {                  info!("{player:?} message {message:?}");                  if persist { -                    if let Some(player) = self.players.get_mut(&player) { +                    if let Some(player) = self.game.players.get_mut(&player) {                          player.communicate_persist = message.clone()                      }                  } @@ -477,6 +442,7 @@ impl Game {              }              PacketS::ReplaceHand { item, player } => {                  let pdata = self +                    .game                      .players                      .get_mut(&player)                      .ok_or(anyhow!("player does not exist"))?; @@ -496,18 +462,18 @@ impl Game {      /// 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())); +        if self.state.score_changed { +            self.state.score_changed = false; +            packet_out.push_back(PacketC::Score(self.game.score.clone()));          } -        for (&pos, tile) in &mut self.tiles { +        for (&pos, tile) in &mut self.game.tiles {              if let Some(effect) = tick_slot(                  dt, -                &self.data, +                &self.game.data,                  Some(tile.kind),                  &mut tile.item, -                &mut self.score, +                &mut self.game.score,              ) {                  match effect {                      TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress { @@ -536,22 +502,25 @@ impl Game {              }          } -        for (&pid, player) in &mut self.players { -            player.movement.update(&self.walkable, dt); +        for (&pid, player) in &mut self.game.players { +            player.movement.update(&self.game.walkable, dt); -            self.players_spatial_index +            self.game +                .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) -                } -            }) +        self.game.players_spatial_index.all(|p1, pos1| { +            self.game +                .players_spatial_index +                .query(pos1, 2., |p2, _pos2| { +                    if let Some([a, b]) = self.game.players.get_many_mut([&p1, &p2]) { +                        a.movement.collide(&mut b.movement, dt) +                    } +                })          }); -        for (&pid, player) in &mut self.players { +        for (&pid, player) in &mut self.game.players {              packet_out.push_back(PacketC::Movement {                  player: pid,                  pos: player.movement.position, @@ -560,8 +529,13 @@ impl Game {                  rot: player.movement.rotation,              }); -            if let Some(effect) = tick_slot(dt, &self.data, None, &mut player.item, &mut self.score) -            { +            if let Some(effect) = tick_slot( +                dt, +                &self.game.data, +                None, +                &mut player.item, +                &mut self.game.score, +            ) {                  match effect {                      TickEffect::Progress(warn) => packet_out.push_back(PacketC::SetProgress {                          warn, @@ -590,9 +564,9 @@ impl Game {          }          let mut players_auto_release = Vec::new(); -        for (pid, player) in &mut self.players { +        for (pid, player) in &mut self.game.players {              if let Some(pos) = player.interacting { -                if let Some(tile) = self.tiles.get(&pos) { +                if let Some(tile) = self.game.tiles.get(&pos) {                      if let Some(item) = &tile.item {                          if let Some(involvement) = &item.active {                              if involvement.progress >= 1. { @@ -611,25 +585,31 @@ impl Game {              );          } -        for entity in self.entities.clone().write().unwrap().iter_mut() { -            if let Err(e) = entity.tick(self, packet_out, dt) { +        for entity in self.state.entities.iter_mut() { +            if let Err(e) = entity.tick(EntityContext { +                game: self.game, +                packet_out, +                score_changed: &mut self.state.score_changed, +                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 let Some(end) = self.game.end { +            self.game.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 { +                let relative_score = +                    (self.game.score.points * 100) / self.state.data.score_baseline.max(1); +                self.game.score.stars = match relative_score {                      100.. => 3,                      70.. => 2,                      40.. => 1,                      _ => 0,                  }; -                packet_out.push_back(PacketC::Menu(Menu::Score(self.score.clone()))); +                packet_out.push_back(PacketC::Menu(Menu::Score(self.game.score.clone())));                  true              } else {                  false @@ -640,19 +620,14 @@ impl Game {      }      pub fn count_chefs(&self) -> usize { -        self.players +        self.game +            .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, @@ -729,9 +704,3 @@ pub fn interact_effect(          }      }  } - -impl Player { -    pub fn position(&self) -> Vec2 { -        self.movement.position -    } -} diff --git a/server/src/state.rs b/server/src/state.rs index 526f70aa..ecfa0fcb 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -15,9 +15,14 @@      along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ -use crate::{data::DataIndex, game::Game, ConnectionID}; +use crate::{ +    data::DataIndex, +    server::{Server, ServerState}, +    ConnectionID, +};  use anyhow::{anyhow, bail, Result};  use clap::{Parser, ValueEnum}; +use hurrycurry_client_lib::Game;  use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID};  use log::{debug, trace};  use std::{ @@ -31,6 +36,7 @@ pub struct State {      packet_out: VecDeque<PacketC>,      tx: Sender<PacketC>,      connections: HashMap<ConnectionID, HashSet<PlayerID>>, +    pub server: ServerState,      pub game: Game,  } @@ -81,15 +87,24 @@ impl State {          index.reload()?;          let mut packet_out = VecDeque::new(); -        let mut game = Game::new(); -        game.load( -            index.generate("lobby-none".to_string()).await?, -            None, -            &mut packet_out, -        ); +        let mut game = Game::default(); +        let mut server = ServerState::default(); + +        { +            Server { +                game: &mut game, +                state: &mut server, +            } +            .load( +                index.generate("lobby-none".to_string()).await?, +                None, +                &mut packet_out, +            ); +        }          Ok(Self {              game, +            server,              index,              tx,              packet_out, @@ -98,8 +113,12 @@ impl State {      }      pub async fn tick(&mut self, dt: f32) -> anyhow::Result<()> { -        if self.game.tick(dt, &mut self.packet_out) { -            self.game.load( +        let mut server = Server { +            game: &mut self.game, +            state: &mut self.server, +        }; +        if server.tick(dt, &mut self.packet_out) { +            server.load(                  self.index.generate("lobby-none".to_string()).await?,                  None,                  &mut self.packet_out, @@ -147,8 +166,11 @@ impl State {              }              _ => (),          } -        self.game -            .packet_in(packet, &mut replies, &mut self.packet_out)?; +        let mut server = Server { +            game: &mut self.game, +            state: &mut self.server, +        }; +        server.packet_in(packet, &mut replies, &mut self.packet_out)?;          for p in &replies {              match p { @@ -159,13 +181,13 @@ impl State {              }          } -        if self.game.count_chefs() <= 0 && !self.game.lobby { +        if server.count_chefs() <= 0 && !server.state.lobby {              self.tx                  .send(PacketC::ServerMessage {                      text: "Game was aborted automatically due to a lack of players".to_string(),                  })                  .ok(); -            self.game.load( +            server.load(                  self.index.generate("lobby-none".to_string()).await?,                  None,                  &mut self.packet_out, @@ -197,18 +219,22 @@ impl State {      }      async fn handle_command(&mut self, player: PlayerID, command: Command) -> Result<()> { +        let mut server = Server { +            game: &mut self.game, +            state: &mut self.server, +        };          match command {              Command::Start { spec, timer } => {                  let data = self.index.generate(spec).await?; -                self.game -                    .load(data, Some(Duration::from_secs(timer)), &mut self.packet_out); +                server.load(data, Some(Duration::from_secs(timer)), &mut self.packet_out);              }              Command::End => {                  self.tx                      .send(PacketC::ServerMessage {                          text: format!(                              "Game was aborted by {}.", -                            self.game +                            server +                                .game                                  .players                                  .get(&player)                                  .ok_or(anyhow!("player missing"))? @@ -216,18 +242,20 @@ impl State {                          ),                      })                      .ok(); -                self.game.load( +                server.load(                      self.index.generate("lobby-none".to_string()).await?,                      None,                      &mut self.packet_out,                  );              }              Command::Reload => { -                if self.game.count_chefs() > 1 { +                if server.count_chefs() > 1 {                      bail!("must be at most one player to reload");                  } -                self.game.load( -                    self.index.generate(self.game.data.spec.to_string()).await?, +                server.load( +                    self.index +                        .generate(server.state.data.spec.to_string()) +                        .await?,                      None,                      &mut self.packet_out,                  ); @@ -259,7 +287,7 @@ impl State {                      .ok();              }              Command::Item { name } => { -                let item = self +                let item = server                      .game                      .data                      .get_item_by_name(&name) | 
