summaryrefslogtreecommitdiff
path: root/server/src/customer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/customer.rs')
-rw-r--r--server/src/customer.rs210
1 files changed, 210 insertions, 0 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()
+}