summaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/customer/mod.rs205
-rw-r--r--server/src/customer/pathfinding.rs22
-rw-r--r--server/src/game.rs24
-rw-r--r--server/src/main.rs3
-rw-r--r--server/src/protocol.rs4
5 files changed, 100 insertions, 158 deletions
diff --git a/server/src/customer/mod.rs b/server/src/customer/mod.rs
index 7f32b094..e6f999e6 100644
--- a/server/src/customer/mod.rs
+++ b/server/src/customer/mod.rs
@@ -20,39 +20,27 @@ mod pathfinding;
use crate::{
data::Gamedata,
- game::Game,
- protocol::{DemandIndex, ItemIndex, Message, PacketC, PacketS, PlayerID},
- state::State,
+ game::Tile,
+ protocol::{DemandIndex, Message, PacketS, PlayerID},
};
use anyhow::{anyhow, Result};
use fake::{faker, Fake};
use glam::{IVec2, Vec2};
-use log::{debug, error, warn};
+use log::debug;
use movement::MovementBase;
use pathfinding::{find_path, Path};
use rand::{random, thread_rng};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
- time::Duration,
-};
-use tokio::{
- sync::{broadcast, RwLock},
- time::interval,
};
-struct CustomerManager {
- disabled: bool,
+pub struct DemandState {
+ data: Arc<Gamedata>,
walkable: HashSet<IVec2>,
chairs: HashMap<IVec2, bool>,
- items: HashMap<IVec2, ItemIndex>,
- customers: HashMap<PlayerID, Customer>,
customer_id_counter: PlayerID,
- demand: DemandState,
-}
-
-struct DemandState {
- data: Gamedata,
+ customers: HashMap<PlayerID, Customer>,
}
enum CustomerState {
@@ -76,119 +64,41 @@ enum CustomerState {
},
}
-struct Customer {
+pub struct Customer {
movement: MovementBase,
state: CustomerState,
}
-pub async fn customer(gstate: Arc<RwLock<State>>, mut grx: broadcast::Receiver<PacketC>) {
- let mut state = CustomerManager {
- disabled: true,
- customer_id_counter: PlayerID(0),
- walkable: Default::default(),
- chairs: Default::default(),
- items: Default::default(),
- customers: Default::default(),
- demand: DemandState {
- data: Gamedata::default(),
- },
- };
- let initial = gstate.write().await.game.prime_client();
- for packet in initial {
- state.packet(packet);
- }
-
- let mut interval = interval(Duration::from_millis(40));
- let mut packets_out = Vec::new();
- loop {
- tokio::select! {
- packet = grx.recv() => {
- let packet = match packet {
- Ok(p) => p,
- Err(e) => {
- warn!("{e}");
- continue;
- }
- };
- match packet {
- PacketC::PutItem { .. }
- | PacketC::TakeItem { .. }
- | PacketC::SetTileItem { .. } => {
- let g = gstate.read().await;
- update_items(&mut state, &g.game)
- },
- _ => ()
- }
- state.packet(packet);
- }
- _ = interval.tick() => {
- if !state.disabled {
- if let Err(e) = state.tick(&mut packets_out, 0.04) {
- warn!("error caught: {e}")
- }
- for (player,packet) in packets_out.drain(..) {
- if let Err(e) = gstate.write().await.packet_in(player, packet).await {
- error!("customer misbehaved: {e}")
- }
- }
- }
- }
- }
- }
-}
-
-// TODO very inefficient, please do that incrementally
-fn update_items(state: &mut CustomerManager, game: &Game) {
- state.items.clear();
- for (&pos, tile) in game.tiles() {
- if let Some(item) = &tile.item {
- state.items.insert(pos, item.kind);
+impl DemandState {
+ pub fn new(data: Arc<Gamedata>, map: &HashMap<IVec2, Tile>) -> Self {
+ let chair = data.get_tile_by_name("chair");
+ Self {
+ walkable: map
+ .iter()
+ .filter(|(_, v)| !data.is_tile_colliding(v.kind))
+ .map(|(e, _)| *e)
+ .collect(),
+ chairs: map
+ .iter()
+ .filter(|(_, v)| Some(v.kind) == chair)
+ .map(|(e, _)| (*e, true))
+ .collect(),
+ customer_id_counter: PlayerID(0),
+ customers: Default::default(),
+ data,
}
}
}
impl DemandState {
- pub fn target_customer_count(&self) -> usize {
- // TODO insert sofa magic formula
- 5
- }
- pub fn generate_demand(&self) -> DemandIndex {
- // TODO insert sofa magic formula
- DemandIndex(random::<usize>() % self.data.demands.len())
- }
-}
-
-impl CustomerManager {
- pub fn packet(&mut self, packet: PacketC) {
- match packet {
- PacketC::Data { data } => {
- self.disabled = data.demands.is_empty();
- self.demand.data = data;
- }
- PacketC::RemovePlayer { id } => {
- self.customers.remove(&id);
- }
- PacketC::UpdateMap {
- tile: pos, kind, ..
- } => {
- if let Some(kind) = kind {
- let tilename = self.demand.data.tile_name(kind);
- if !self.demand.data.is_tile_colliding(kind) {
- self.walkable.insert(pos);
- }
- if tilename == "chair" {
- self.chairs.insert(pos, true);
- }
- } else {
- self.chairs.remove(&pos);
- self.walkable.remove(&pos);
- }
- }
- _ => (),
- }
- }
- pub fn tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) -> Result<()> {
- if self.customers.len() < self.demand.target_customer_count() {
+ pub fn tick(
+ &mut self,
+ packets_out: &mut Vec<(PlayerID, PacketS)>,
+ tiles: &mut HashMap<IVec2, Tile>,
+ data: &Gamedata,
+ dt: f32,
+ ) -> Result<()> {
+ if self.customers.len() < 5 {
self.customer_id_counter.0 -= 1;
let id = self.customer_id_counter;
packets_out.push((
@@ -198,14 +108,15 @@ impl CustomerManager {
character: -2,
},
));
- let chair = select_chair(&mut self.chairs).ok_or(anyhow!("no free chair found"))?;
- let to = self.demand.data.customer_spawn.as_ivec2();
- let path = find_path(&self.walkable, to, chair).ok_or(anyhow!("no path to {to}"))?;
+ let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?;
+ let from = data.customer_spawn.as_ivec2();
+ let path = find_path(&self.walkable, from, chair)
+ .ok_or(anyhow!("no path from {from} to {chair}"))?;
self.customers.insert(
id,
Customer {
movement: MovementBase {
- position: self.demand.data.customer_spawn,
+ position: data.customer_spawn,
facing: Vec2::X,
vel: Vec2::ZERO,
},
@@ -220,11 +131,11 @@ impl CustomerManager {
debug!("{id:?} entering");
packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt)));
if path.is_done() {
- let demand = self.demand.generate_demand();
+ let demand = DemandIndex(random::<usize>() % self.data.demands.len());
packets_out.push((
id,
PacketS::Communicate {
- message: Some(Message::Item(self.demand.data.demand(demand).from)),
+ message: Some(Message::Item(data.demand(demand).from)),
},
));
p.state = CustomerState::Waiting {
@@ -246,18 +157,27 @@ impl CustomerManager {
let path = find_path(
&self.walkable,
p.movement.position.as_ivec2(),
- self.demand.data.customer_spawn.as_ivec2(),
+ data.customer_spawn.as_ivec2(),
)
.expect("no path to exit");
*self.chairs.get_mut(&chair).unwrap() = true;
p.state = CustomerState::Exiting { path }
} else {
- let demand_data = &self.demand.data.demand(*demand);
+ let demand_data = &data.demand(*demand);
let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
.into_iter()
.find_map(|off| {
let pos = *chair + off;
- if self.items.get(&pos) == Some(&demand_data.from) {
+ if 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
@@ -284,7 +204,7 @@ impl CustomerManager {
chair,
} => {
debug!("{id:?} eating");
- let demand = self.demand.data.demand(*demand);
+ let demand = data.demand(*demand);
*progress += dt / demand.duration;
if *progress >= 1. {
packets_out.push((
@@ -299,7 +219,7 @@ impl CustomerManager {
let path = find_path(
&self.walkable,
p.movement.position.as_ivec2(),
- self.demand.data.customer_spawn.as_ivec2(),
+ data.customer_spawn.as_ivec2(),
)
.ok_or(anyhow!("no path to exit"))?;
*self.chairs.get_mut(&chair).unwrap() = true;
@@ -321,14 +241,15 @@ impl CustomerManager {
}
Ok(())
}
-}
-pub fn select_chair(chairs: &mut HashMap<IVec2, bool>) -> Option<IVec2> {
- use rand::seq::IteratorRandom;
- let (chosen, free) = chairs
- .iter_mut()
- .filter(|(_p, free)| **free)
- .choose(&mut thread_rng())?;
- *free = false;
- Some(*chosen)
+ pub fn select_chair(&mut self) -> Option<IVec2> {
+ use rand::seq::IteratorRandom;
+ let (chosen, free) = self
+ .chairs
+ .iter_mut()
+ .filter(|(_p, free)| **free)
+ .choose(&mut thread_rng())?;
+ *free = false;
+ Some(*chosen)
+ }
}
diff --git a/server/src/customer/pathfinding.rs b/server/src/customer/pathfinding.rs
index 5354407f..5056975e 100644
--- a/server/src/customer/pathfinding.rs
+++ b/server/src/customer/pathfinding.rs
@@ -1,19 +1,19 @@
/*
Undercooked - 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 super::movement::MovementBase;
use crate::protocol::PacketS;
@@ -29,22 +29,22 @@ pub struct Path(Vec<Vec2>);
impl Path {
pub fn execute_tick(
&mut self,
- customer: &mut MovementBase,
+ player: &mut MovementBase,
walkable: &HashSet<IVec2>,
dt: f32,
) -> PacketS {
if let Some(next) = self.0.last().copied() {
debug!("next {next}");
- if next.distance(customer.position) < if self.0.len() == 1 { 0.1 } else { 0.6 } {
+ if next.distance(player.position) < if self.0.len() == 1 { 0.1 } else { 0.6 } {
self.0.pop();
}
- customer.update(
+ player.update(
&walkable,
- (next - customer.position).normalize_or_zero() * 0.5,
+ (next - player.position).normalize_or_zero() * 0.5,
dt,
)
} else {
- customer.update(&walkable, Vec2::ZERO, dt)
+ player.update(&walkable, Vec2::ZERO, dt)
}
}
pub fn is_done(&self) -> bool {
@@ -52,7 +52,7 @@ impl Path {
}
}
-pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> {
+pub fn find_path(walkable: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> {
#[derive(Debug, PartialEq, Eq)]
struct Open(i32, IVec2, IVec2);
impl PartialOrd for Open {
@@ -84,7 +84,7 @@ pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> {
}
for d in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] {
let n = p + d;
- if map.contains(&n) {
+ if walkable.contains(&n) {
open.push(Open(-d.distance_squared(to), n, p));
}
}
diff --git a/server/src/game.rs b/server/src/game.rs
index defaf7bf..595816e1 100644
--- a/server/src/game.rs
+++ b/server/src/game.rs
@@ -16,14 +16,14 @@
*/
use crate::{
- customer::movement::PLAYER_SPEED_LIMIT,
+ customer::{movement::PLAYER_SPEED_LIMIT, DemandState},
data::Gamedata,
interaction::{interact, tick_tile, InteractEffect, TickEffect},
protocol::{ItemIndex, Message, PacketC, PacketS, PlayerID, RecipeIndex, TileIndex},
};
use anyhow::{anyhow, bail, Result};
use glam::{IVec2, Vec2};
-use log::info;
+use log::{info, warn};
use std::{
collections::{HashMap, VecDeque},
ops::Deref,
@@ -64,6 +64,7 @@ pub struct Game {
tiles: HashMap<IVec2, Tile>,
players: HashMap<PlayerID, Player>,
packet_out: VecDeque<PacketC>,
+ demand: Option<DemandState>,
}
impl Game {
@@ -73,6 +74,7 @@ impl Game {
packet_out: Default::default(),
players: Default::default(),
tiles: Default::default(),
+ demand: None,
}
}
@@ -87,6 +89,7 @@ impl Game {
neighbors: [None, None, None, None],
})
}
+ self.demand = None;
}
pub fn load(&mut self, gamedata: Gamedata) {
let players = self
@@ -99,6 +102,7 @@ impl Game {
self.unload();
self.data = gamedata.into();
+
for (&p, (tile, item)) in &self.data.initial_map {
self.tiles.insert(
p,
@@ -126,6 +130,10 @@ impl Game {
);
}
+ if !self.data.demands.is_empty() {
+ self.demand = Some(DemandState::new(self.data.clone(), &self.tiles))
+ }
+
self.packet_out.extend(self.prime_client());
}
@@ -375,6 +383,18 @@ impl Game {
}
pub fn tick(&mut self, dt: f32) {
+ if let Some(demand) = &mut self.demand {
+ let mut packet_out = Vec::new();
+ if let Err(err) = demand.tick(&mut packet_out, &mut self.tiles, &self.data, dt) {
+ warn!("demand tick {err}");
+ }
+ for (player, packet) in packet_out {
+ if let Err(err) = self.packet_in(player, packet) {
+ warn!("demand packet {err}");
+ }
+ }
+ }
+
for (&pos, tile) in &mut self.tiles {
if let Some(effect) = tick_tile(dt, &self.data, tile) {
match effect {
diff --git a/server/src/main.rs b/server/src/main.rs
index b3e6334d..c294478e 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -27,7 +27,6 @@ use tokio::{
};
use tokio_tungstenite::tungstenite::Message;
use undercooked::{
- customer::customer,
protocol::{PacketC, PacketS, PlayerID},
state::State,
};
@@ -53,8 +52,6 @@ async fn main() -> Result<()> {
});
}
- spawn(customer(state.clone(), rx.resubscribe()));
-
for id in (1..).map(PlayerID) {
let (sock, addr) = ws_listener.accept().await?;
let Ok(sock) = tokio_tungstenite::accept_async(sock).await else {
diff --git a/server/src/protocol.rs b/server/src/protocol.rs
index 262fad8d..daa5c8af 100644
--- a/server/src/protocol.rs
+++ b/server/src/protocol.rs
@@ -130,6 +130,10 @@ pub enum PacketC {
player: PlayerID,
message: Option<Message>,
},
+ Score {
+ demands_failed: usize,
+ demands_complete: usize,
+ },
Error {
message: String,
},