aboutsummaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/customer.rs210
-rw-r--r--server/src/data.rs19
-rw-r--r--server/src/game.rs12
-rw-r--r--server/src/lib.rs1
-rw-r--r--server/src/main.rs3
-rw-r--r--server/src/protocol.rs2
6 files changed, 238 insertions, 9 deletions
diff --git a/server/src/customer.rs b/server/src/customer.rs
new file mode 100644
index 00000000..ea63f93a
--- /dev/null
+++ b/server/src/customer.rs
@@ -0,0 +1,210 @@
+use crate::{
+ data::Gamedata,
+ game::Game,
+ protocol::{PacketC, PacketS, PlayerID},
+};
+use glam::{IVec2, Vec2};
+use log::{error, info};
+use std::{
+ cmp::Ordering,
+ collections::{BinaryHeap, HashMap, HashSet, VecDeque},
+ sync::Arc,
+ time::Duration,
+};
+use tokio::{
+ sync::{broadcast, RwLock},
+ time::interval,
+};
+
+struct DemandState {
+ data: Gamedata,
+ walkable: HashSet<IVec2>,
+ chairs: HashSet<IVec2>,
+ customers: Vec<Customer>,
+}
+
+enum CustomerState {
+ WalkingToChair { target: Vec2 },
+ Waiting,
+}
+
+struct Customer {
+ id: PlayerID,
+ position: Vec2,
+ facing: Vec2,
+ vel: Vec2,
+ state: CustomerState,
+}
+
+pub async fn customer(game: Arc<RwLock<Game>>, mut grx: broadcast::Receiver<PacketC>) {
+ let mut state = DemandState {
+ walkable: Default::default(),
+ chairs: Default::default(),
+ customers: Default::default(),
+ data: Gamedata::default(),
+ };
+ let initial = game.write().await.prime_client(-1);
+ for p in initial {
+ match p {
+ PacketC::Init { data, .. } => {
+ state.data = data;
+ }
+ PacketC::UpdateMap { pos, tile } => {
+ let tilename = &state.data.tile_names[tile];
+ if tilename == "floor" || tilename == "door" {
+ state.walkable.insert(pos);
+ }
+ if tilename == "chair" {
+ state.chairs.insert(pos);
+ }
+ }
+ _ => (),
+ }
+ }
+
+ let mut interval = interval(Duration::from_millis(40));
+ let mut packets_out = Vec::new();
+ loop {
+ tokio::select! {
+ packet = grx.recv() => {
+ match packet.unwrap() {
+ // TODO handle map update
+ _ => ()
+ }
+ }
+ _ = interval.tick() => {
+ state.tick(&mut packets_out, 0.04);
+ for (player,packet) in packets_out.drain(..) {
+ if let Err(e) = game.write().await.packet_in(player, packet) {
+ error!("customer misbehaved: {e}")
+ }
+ }
+ }
+ }
+ }
+}
+
+impl DemandState {}
+impl DemandState {
+ pub fn tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) {
+ if self.customers.is_empty() {
+ packets_out.push((
+ -1,
+ PacketS::Join {
+ name: "George".to_string(),
+ character: 0,
+ },
+ ));
+ self.customers.push(Customer {
+ id: -1,
+ position: self.data.customer_spawn,
+ facing: Vec2::X,
+ vel: Vec2::ZERO,
+ state: CustomerState::WalkingToChair {
+ target: Vec2::new(2., 2.),
+ },
+ });
+ }
+
+ for p in &mut self.customers {
+ match p.state {
+ CustomerState::WalkingToChair { target } => {
+ packets_out.push((
+ p.id,
+ move_player(p, &self.walkable, target - p.position, dt),
+ ));
+ if target.distance(p.position) < 0.5 {
+ p.state = CustomerState::Waiting;
+ }
+ }
+ CustomerState::Waiting => (),
+ }
+ }
+ }
+}
+
+pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> VecDeque<IVec2> {
+ #[derive(Debug, PartialEq, Eq)]
+ struct Open(i32, IVec2, IVec2);
+ impl PartialOrd for Open {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+ }
+ impl Ord for Open {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.0.cmp(&other.0)
+ }
+ }
+
+ let mut visited = HashMap::new();
+ let mut open = BinaryHeap::new();
+ open.push(Open(1, from, from));
+
+ while let Some(Open(_, p, f)) = open.pop() {
+ if !visited.contains_key(&p) {
+ continue;
+ }
+ visited.insert(p, f);
+ if p == to {
+ break;
+ }
+ for d in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] {
+ open.push(Open(d.distance_squared(to), p + d, p));
+ }
+ }
+
+ let mut path = VecDeque::new();
+ path.push_back(to);
+
+ // loop {
+ // path.
+ // }
+
+ path
+}
+
+fn move_player(p: &mut Customer, map: &HashSet<IVec2>, direction: Vec2, dt: f32) -> PacketS {
+ let direction = direction.normalize();
+ if direction.length() > 0.1 {
+ p.facing = direction + (p.facing - direction) * (-dt * 10.).exp();
+ }
+ let rot = p.facing.x.atan2(p.facing.y);
+ p.vel += direction * dt * 0.5;
+ p.position += p.vel;
+ p.vel = p.vel * (-dt * 5.).exp();
+ collide_player(p, map);
+ PacketS::Position {
+ pos: p.position,
+ rot,
+ }
+}
+
+const PLAYER_SIZE: f32 = 0.4;
+fn collide_player(p: &mut Customer, map: &HashSet<IVec2>) {
+ for xo in -1..=1 {
+ for yo in -1..=1 {
+ let tile = IVec2::new(xo, yo) + p.position.as_ivec2();
+ if map.contains(&tile) {
+ continue;
+ }
+ let tile = tile.as_vec2();
+ let d = aabb_circle_distance(tile, tile + Vec2::ONE, p.position);
+ if d > PLAYER_SIZE {
+ continue;
+ }
+ let h = 0.01;
+ let d_sample_x =
+ aabb_circle_distance(tile, tile + Vec2::ONE, p.position + Vec2::new(h, 0.));
+ let d_sample_y =
+ aabb_circle_distance(tile, tile + Vec2::ONE, p.position + Vec2::new(0., h));
+ let grad = (Vec2::new(d_sample_x, d_sample_y) - d) / h;
+
+ p.position += (PLAYER_SIZE - d) * grad;
+ p.vel -= grad * grad.dot(p.vel);
+ }
+ }
+}
+fn aabb_circle_distance(min: Vec2, max: Vec2, p: Vec2) -> f32 {
+ (p - p.clamp(min, max)).length()
+}
diff --git a/server/src/data.rs b/server/src/data.rs
index 6affccb5..9f03b1ea 100644
--- a/server/src/data.rs
+++ b/server/src/data.rs
@@ -31,14 +31,15 @@ pub struct InitialMap {
tiles: HashMap<String, String>,
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Gamedata {
pub recipes: Vec<Recipe>,
pub item_names: Vec<String>,
pub tile_names: Vec<String>,
#[serde(skip)]
pub initial_map: HashMap<IVec2, TileIndex>,
- pub spawn: Vec2,
+ pub chef_spawn: Vec2,
+ pub customer_spawn: Vec2,
}
pub fn build_gamedata(recipes_in: Vec<RecipeDecl>, map_in: InitialMap) -> Gamedata {
@@ -77,14 +78,19 @@ pub fn build_gamedata(recipes_in: Vec<RecipeDecl>, map_in: InitialMap) -> Gameda
assert_eq!(outputs.next(), None, "{r2:?}");
}
- let mut spawn = Vec2::new(0., 0.);
+ let mut chef_spawn = Vec2::new(0., 0.);
+ let mut customer_spawn = Vec2::new(0., 0.);
let mut initial_map = HashMap::new();
for (y, line) in map_in.map.iter().enumerate() {
for (x, tile) in line.trim().char_indices() {
let pos = IVec2::new(x as i32, y as i32);
let mut tilename = map_in.tiles[&tile.to_string()].clone();
- if tilename == "spawn" {
- spawn = pos.as_vec2();
+ if tilename == "chef-spawn" {
+ chef_spawn = pos.as_vec2();
+ tilename = "floor".to_owned();
+ }
+ if tilename == "customer-spawn" {
+ customer_spawn = pos.as_vec2();
tilename = "floor".to_owned();
}
let tile = register(&tile_names, tilename);
@@ -97,7 +103,8 @@ pub fn build_gamedata(recipes_in: Vec<RecipeDecl>, map_in: InitialMap) -> Gameda
initial_map,
item_names: item_names.into_inner().unwrap(),
tile_names: tile_names.into_inner().unwrap(),
- spawn,
+ chef_spawn,
+ customer_spawn,
}
}
diff --git a/server/src/game.rs b/server/src/game.rs
index c9881e7c..9c1bdd29 100644
--- a/server/src/game.rs
+++ b/server/src/game.rs
@@ -59,6 +59,10 @@ impl Game {
g
}
+ pub fn tiles(&self) -> &HashMap<IVec2, Tile> {
+ &self.tiles
+ }
+
pub fn packet_out(&mut self) -> Option<PacketC> {
self.packet_out.pop_front()
}
@@ -101,7 +105,11 @@ impl Game {
Player {
item: None,
character,
- position: self.data.spawn,
+ position: if player < 0 {
+ self.data.customer_spawn
+ } else {
+ self.data.chef_spawn
+ },
interacting: None,
name: name.clone(),
},
@@ -109,7 +117,7 @@ impl Game {
self.packet_out.push_back(PacketC::AddPlayer {
id: player,
name,
- position: self.data.spawn,
+ position: self.data.chef_spawn,
character,
item: None,
});
diff --git a/server/src/lib.rs b/server/src/lib.rs
index b40bcdd8..a326190d 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -2,3 +2,4 @@ pub mod game;
pub mod protocol;
pub mod data;
pub mod interaction;
+pub mod customer;
diff --git a/server/src/main.rs b/server/src/main.rs
index 45ee39dd..e505195c 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -11,6 +11,7 @@ use tokio::{
};
use tokio_tungstenite::tungstenite::Message;
use undercooked::{
+ customer::customer,
data::build_gamedata,
game::Game,
protocol::{PacketC, PacketS},
@@ -53,6 +54,8 @@ async fn main() -> Result<()> {
});
}
+ spawn(customer(game.clone(), rx.resubscribe()));
+
for id in 1.. {
tokio::select! {
r = raw_listener.accept() => {
diff --git a/server/src/protocol.rs b/server/src/protocol.rs
index 6fe8ccd8..34442263 100644
--- a/server/src/protocol.rs
+++ b/server/src/protocol.rs
@@ -2,7 +2,7 @@ use crate::data::Gamedata;
use glam::{IVec2, Vec2};
use serde::{Deserialize, Serialize};
-pub type PlayerID = usize;
+pub type PlayerID = i64;
pub type ItemIndex = usize;
pub type TileIndex = usize;
pub type RecipeIndex = usize;