diff options
| author | metamuffin <metamuffin@disroot.org> | 2024-06-19 23:21:45 +0200 | 
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2024-06-23 19:21:49 +0200 | 
| commit | 7be7848b65bd16139569389961541fcc52c905d2 (patch) | |
| tree | b2b5a26f9e98d82bc4b54fdf7f76d9dfd583aa0f /server/src | |
| parent | 6ca76cc0568f3d60b280f11ae07a34303c317f34 (diff) | |
| download | hurrycurry-7be7848b65bd16139569389961541fcc52c905d2.tar hurrycurry-7be7848b65bd16139569389961541fcc52c905d2.tar.bz2 hurrycurry-7be7848b65bd16139569389961541fcc52c905d2.tar.zst | |
split up customer code. customer can enter, order and leave but not take meal
Diffstat (limited to 'server/src')
| -rw-r--r-- | server/src/customer.rs | 243 | ||||
| -rw-r--r-- | server/src/customer/mod.rs | 201 | ||||
| -rw-r--r-- | server/src/customer/movement.rs | 56 | ||||
| -rw-r--r-- | server/src/customer/pathfinding.rs | 83 | ||||
| -rw-r--r-- | server/src/data.rs | 3 | 
5 files changed, 343 insertions, 243 deletions
| diff --git a/server/src/customer.rs b/server/src/customer.rs deleted file mode 100644 index 87b67f0e..00000000 --- a/server/src/customer.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::{ -    data::Gamedata, -    game::Game, -    protocol::{Message, PacketC, PacketS, PlayerID}, -}; -use glam::{IVec2, Vec2}; -use log::{debug, error}; -use rand::thread_rng; -use std::{ -    cmp::Ordering, -    collections::{BinaryHeap, HashMap, HashSet}, -    sync::Arc, -    time::Duration, -}; -use tokio::{ -    sync::{broadcast, RwLock}, -    time::interval, -}; - -struct DemandState { -    data: Gamedata, -    walkable: HashSet<IVec2>, -    chairs: HashMap<IVec2, bool>, -    customers: Vec<Customer>, -} - -enum CustomerState { -    WalkingToChair { path: Vec<Vec2>, chair: IVec2 }, -    Waiting { chair: IVec2 }, -} - -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" || tilename == "chair" { -                    state.walkable.insert(pos); -                } -                if tilename == "chair" { -                    state.chairs.insert(pos, true); -                } -            } -            _ => (), -        } -    } - -    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 { -    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, -                }, -            )); -            let chair = select_chair(&mut self.chairs); -            let path = find_path(&self.walkable, self.data.customer_spawn.as_ivec2(), chair) -                .expect("no path"); -            self.customers.push(Customer { -                id: -1, -                position: self.data.customer_spawn, -                facing: Vec2::X, -                vel: Vec2::ZERO, -                state: CustomerState::WalkingToChair { path, chair }, -            }); -        } - -        for p in &mut self.customers { -            match &mut p.state { -                CustomerState::WalkingToChair { path, chair } => { -                    if let Some(next) = path.last().copied() { -                        debug!("next {next}"); -                        if next.distance(p.position) < if path.len() == 1 { 0.1 } else { 0.6 } { -                            path.pop(); -                        } -                        packets_out -                            .push((p.id, move_player(p, &self.walkable, next - p.position, dt))); -                    } else { -                        packets_out.push(( -                            p.id, -                            PacketS::Communicate { -                                message: Some(Message::Item(4)), -                            }, -                        )); -                        p.state = CustomerState::Waiting { chair: *chair }; -                    } -                } -                CustomerState::Waiting { chair } => { -                    debug!("waiting") -                } -            } -        } -    } -} - -pub fn select_chair(chairs: &mut HashMap<IVec2, bool>) -> IVec2 { -    use rand::seq::IteratorRandom; -    let (chosen, free) = chairs -        .iter_mut() -        .filter(|(_p, free)| **free) -        .choose(&mut thread_rng()) -        .unwrap(); -    *free = false; -    *chosen -} - -pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Vec<Vec2>> { -    #[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)); - -    loop { -        let Some(Open(_, p, f)) = open.pop() else { -            eprintln!("{visited:?}"); -            return None; -        }; -        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] { -            let n = p + d; -            if map.contains(&n) { -                open.push(Open(-d.distance_squared(to), n, p)); -            } -        } -    } - -    let mut path = Vec::new(); -    let mut c = to; -    loop { -        path.push(c.as_vec2() + 0.5); -        let cn = visited[&c]; -        if cn == c { -            break; -        } -        c = cn -    } -    Some(path) -} - -fn move_player(p: &mut Customer, map: &HashSet<IVec2>, direction: Vec2, dt: f32) -> PacketS { -    let direction = direction.normalize_or_zero(); -    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/customer/mod.rs b/server/src/customer/mod.rs new file mode 100644 index 00000000..ab3accdd --- /dev/null +++ b/server/src/customer/mod.rs @@ -0,0 +1,201 @@ +pub mod movement; +mod pathfinding; + +use crate::{ +    data::Gamedata, +    game::Game, +    protocol::{ItemIndex, Message, PacketC, PacketS, PlayerID}, +}; +use glam::{IVec2, Vec2}; +use log::{debug, error}; +use movement::MovementBase; +use pathfinding::{find_path, Path}; +use rand::thread_rng; +use std::{ +    collections::{HashMap, HashSet}, +    sync::Arc, +    time::Duration, +}; +use tokio::{ +    sync::{broadcast, RwLock}, +    time::interval, +}; + +struct DemandState { +    data: Gamedata, +    walkable: HashSet<IVec2>, +    chairs: HashMap<IVec2, bool>, +    items: HashMap<IVec2, ItemIndex>, +    customers: HashMap<PlayerID, Customer>, +} + +enum CustomerState { +    WalkingToChair { path: Path, chair: IVec2 }, +    Waiting { chair: IVec2, demand: ItemIndex }, +    Exiting { path: Path }, +} + +struct Customer { +    movement: MovementBase, +    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(), +        items: 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" || tilename == "chair" { +                    state.walkable.insert(pos); +                } +                if tilename == "chair" { +                    state.chairs.insert(pos, true); +                } +            } +            _ => (), +        } +    } + +    let mut interval = interval(Duration::from_millis(40)); +    let mut packets_out = Vec::new(); +    loop { +        tokio::select! { +            packet = grx.recv() => { +                match packet.unwrap() { +                    PacketC::PutItem { .. } +                    | PacketC::TakeItem { .. } +                    | PacketC::ProduceItem { .. } +                    | PacketC::ConsumeItem { .. } => { +                        let g = game.read().await; +                        update_items(&mut state, &g) +                    }, +                    _ => () +                } +            } +            _ = 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}") +                    } +                } +            } +        } +    } +} + +fn update_items(state: &mut DemandState, 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 tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) { +        if self.customers.is_empty() { +            let id = -1; +            packets_out.push(( +                id, +                PacketS::Join { +                    name: "George".to_string(), +                    character: 0, +                }, +            )); +            let chair = select_chair(&mut self.chairs); +            let path = find_path(&self.walkable, self.data.customer_spawn.as_ivec2(), chair) +                .expect("no path"); +            self.customers.insert( +                id, +                Customer { +                    movement: MovementBase { +                        position: self.data.customer_spawn, +                        facing: Vec2::X, +                        vel: Vec2::ZERO, +                    }, +                    state: CustomerState::WalkingToChair { path, chair }, +                }, +            ); +        } +        let mut customers_to_remove = Vec::new(); +        for (&id, p) in &mut self.customers { +            match &mut p.state { +                CustomerState::WalkingToChair { path, chair } => { +                    packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt))); +                    if path.is_done() { +                        let demand = self.data.get_item("tomato").unwrap(); +                        packets_out.push(( +                            id, +                            PacketS::Communicate { +                                message: Some(Message::Item(demand)), +                            }, +                        )); +                        p.state = CustomerState::Waiting { +                            chair: *chair, +                            demand, +                        }; +                    } +                } +                CustomerState::Waiting { chair, 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) { +                                Some(pos) +                            } else { +                                None +                            } +                        }); +                    if let Some(pos) = demand_pos { +                        if self.items.get(&pos) == Some(demand) { +                            packets_out.push((id, PacketS::Communicate { message: None })); +                            let path = find_path( +                                &self.walkable, +                                p.movement.position.as_ivec2(), +                                self.data.customer_spawn.as_ivec2(), +                            ) +                            .expect("no path to exit"); +                            p.state = CustomerState::Exiting { path } +                        } +                    } +                    debug!("waiting") +                } +                CustomerState::Exiting { path } => { +                    packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt))); +                    if path.is_done() { +                        packets_out.push((id, PacketS::Leave)); +                        customers_to_remove.push(id); +                    } +                } +            } +        } +        for c in customers_to_remove { +            self.customers.remove(&c).unwrap(); +        } +    } +} + +pub fn select_chair(chairs: &mut HashMap<IVec2, bool>) -> IVec2 { +    use rand::seq::IteratorRandom; +    let (chosen, free) = chairs +        .iter_mut() +        .filter(|(_p, free)| **free) +        .choose(&mut thread_rng()) +        .unwrap(); +    *free = false; +    *chosen +} diff --git a/server/src/customer/movement.rs b/server/src/customer/movement.rs new file mode 100644 index 00000000..0ddabd0b --- /dev/null +++ b/server/src/customer/movement.rs @@ -0,0 +1,56 @@ +use crate::protocol::PacketS; +use glam::{IVec2, Vec2}; +use std::collections::HashSet; + +pub struct MovementBase { +    pub position: Vec2, +    pub facing: Vec2, +    pub vel: Vec2, +} + +impl MovementBase { +    pub fn update(&mut self, map: &HashSet<IVec2>, direction: Vec2, dt: f32) -> PacketS { +        let direction = direction.normalize_or_zero(); +        if direction.length() > 0.1 { +            self.facing = direction + (self.facing - direction) * (-dt * 10.).exp(); +        } +        let rot = self.facing.x.atan2(self.facing.y); +        self.vel += direction * dt * 0.5; +        self.position += self.vel; +        self.vel = self.vel * (-dt * 5.).exp(); +        collide_player(self, map); +        PacketS::Position { +            pos: self.position, +            rot, +        } +    } +} + +const PLAYER_SIZE: f32 = 0.4; +pub fn collide_player(p: &mut MovementBase, 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); +        } +    } +} +pub fn aabb_circle_distance(min: Vec2, max: Vec2, p: Vec2) -> f32 { +    (p - p.clamp(min, max)).length() +} diff --git a/server/src/customer/pathfinding.rs b/server/src/customer/pathfinding.rs new file mode 100644 index 00000000..d25c6913 --- /dev/null +++ b/server/src/customer/pathfinding.rs @@ -0,0 +1,83 @@ +use super::movement::MovementBase; +use crate::protocol::PacketS; +use glam::{IVec2, Vec2}; +use log::debug; +use std::{ +    cmp::Ordering, +    collections::{BinaryHeap, HashMap, HashSet}, +}; + +pub struct Path(Vec<Vec2>); + +impl Path { +    pub fn execute_tick( +        &mut self, +        customer: &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 } { +                self.0.pop(); +            } +            customer.update(&walkable, next - customer.position, dt) +        } else { +            customer.update(&walkable, Vec2::ZERO, dt) +        } +    } +    pub fn is_done(&self) -> bool { +        self.0.is_empty() +    } +} + +pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> { +    #[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)); + +    loop { +        let Some(Open(_, p, f)) = open.pop() else { +            eprintln!("{visited:?}"); +            return None; +        }; +        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] { +            let n = p + d; +            if map.contains(&n) { +                open.push(Open(-d.distance_squared(to), n, p)); +            } +        } +    } + +    let mut path = Vec::new(); +    let mut c = to; +    loop { +        path.push(c.as_vec2() + 0.5); +        let cn = visited[&c]; +        if cn == c { +            break; +        } +        c = cn +    } +    Some(Path(path)) +} diff --git a/server/src/data.rs b/server/src/data.rs index 9f03b1ea..d138f360 100644 --- a/server/src/data.rs +++ b/server/src/data.rs @@ -123,6 +123,9 @@ impl Gamedata {      pub fn get_tile(&self, name: &str) -> Option<TileIndex> {          self.tile_names.iter().position(|t| t == name)      } +    pub fn get_item(&self, name: &str) -> Option<TileIndex> { +        self.item_names.iter().position(|t| t == name) +    }  }  impl Action {      pub fn duration(&self) -> f32 { | 
