diff options
author | metamuffin <metamuffin@disroot.org> | 2024-09-02 14:17:55 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-09-02 14:18:02 +0200 |
commit | 2c2052cf38757e3c77e1e7a6af18af3eb9f3a2eb (patch) | |
tree | f985f1ca2e294aada0cc95ef6761a70ff1fbf20d /server/src | |
parent | e9e1692fb34b661e7c928c9b4b440dcb655eb062 (diff) | |
download | hurrycurry-2c2052cf38757e3c77e1e7a6af18af3eb9f3a2eb.tar hurrycurry-2c2052cf38757e3c77e1e7a6af18af3eb9f3a2eb.tar.bz2 hurrycurry-2c2052cf38757e3c77e1e7a6af18af3eb9f3a2eb.tar.zst |
campaign gate entity
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/entity/campaign.rs | 87 | ||||
-rw-r--r-- | server/src/entity/customers.rs | 1 | ||||
-rw-r--r-- | server/src/entity/mod.rs | 33 | ||||
-rw-r--r-- | server/src/scoreboard.rs | 11 | ||||
-rw-r--r-- | server/src/server.rs | 50 |
5 files changed, 173 insertions, 9 deletions
diff --git a/server/src/entity/campaign.rs b/server/src/entity/campaign.rs index c4c8ab52..934f7542 100644 --- a/server/src/entity/campaign.rs +++ b/server/src/entity/campaign.rs @@ -16,8 +16,14 @@ */ use super::{Entity, EntityContext}; +use crate::{scoreboard::ScoreboardStore, server::GameServerExt}; use anyhow::Result; -use hurrycurry_protocol::glam::Vec2; +use hurrycurry_protocol::{ + glam::{IVec2, Vec2}, + PacketC, PlayerID, TileIndex, +}; +use serde::{Deserialize, Serialize}; +use std::fmt::Write; #[derive(Debug, Default, Clone)] pub struct Map { @@ -39,3 +45,82 @@ impl Entity for Map { Ok(()) } } + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum GateCondition { + All(Vec<GateCondition>), + Any(Vec<GateCondition>), + Stars(String, u8), +} + +#[derive(Debug, Clone)] +pub struct Gate { + pub active: bool, + pub unlocked: bool, + pub location: IVec2, + pub blocker_tile: TileIndex, + pub condition: GateCondition, +} +impl Entity for Gate { + fn tick(&mut self, c: EntityContext<'_>) -> Result<()> { + if self.active { + self.active = false; + self.unlocked = self.condition.check(c.scoreboard); + if !self.unlocked { + c.game + .set_tile(self.location, Some(self.blocker_tile), c.packet_out) + } + } + Ok(()) + } + fn interact( + &mut self, + c: EntityContext<'_>, + pos: Option<IVec2>, + _player: PlayerID, + ) -> Result<bool> { + if !self.unlocked && pos == Some(self.location) { + c.packet_out.push_back(PacketC::ServerMessage { + text: format!("To unlock: \n\n{}", self.condition.show(c.scoreboard)), + }); + return Ok(true); + } + Ok(false) + } +} + +impl GateCondition { + fn check(&self, scoreboard: &ScoreboardStore) -> bool { + match self { + GateCondition::All(cs) => cs.iter().all(|c| c.check(scoreboard)), + GateCondition::Any(cs) => cs.iter().any(|c| c.check(scoreboard)), + GateCondition::Stars(map, thres) => scoreboard.get(map).map_or(false, |s| { + s.best.first().map_or(false, |b| b.score.stars >= *thres) + }), + } + } + pub fn show(&self, scoreboard: &ScoreboardStore) -> String { + match self { + GateCondition::All(cs) => { + let mut o = String::new(); + for c in cs { + if !c.check(scoreboard) { + writeln!(o, "- {}", c.show(scoreboard)).unwrap(); + } + } + o + } + GateCondition::Any(cs) => { + let mut o = String::new(); + for c in cs { + writeln!(o, "- {}", c.show(scoreboard)).unwrap(); + } + o + } + GateCondition::Stars(map, thres) => { + format!("Reach at least {thres} stars in {map}") + } + } + } +} diff --git a/server/src/entity/customers.rs b/server/src/entity/customers.rs index d75f5b51..da905bc0 100644 --- a/server/src/entity/customers.rs +++ b/server/src/entity/customers.rs @@ -54,6 +54,7 @@ impl Entity for Customers { packet_in: c.packet_in, score_changed: c.score_changed, dt: c.dt, + scoreboard: c.scoreboard, load_map: c.load_map, })?; } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 8055b50d..c8424934 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -23,16 +23,16 @@ pub mod environment_effect; pub mod item_portal; pub mod player_portal; -use crate::data::ItemTileRegistry; +use crate::{data::ItemTileRegistry, scoreboard::ScoreboardStore}; use anyhow::{anyhow, Result}; -use campaign::Map; +use campaign::{Gate, GateCondition, Map}; use conveyor::Conveyor; use customers::Customers; use environment_effect::{EnvironmentController, EnvironmentEffect, EnvironmentEffectController}; use hurrycurry_client_lib::Game; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, - PacketC, PacketS, + PacketC, PacketS, PlayerID, }; use item_portal::ItemPortal; use player_portal::PlayerPortal; @@ -48,12 +48,21 @@ pub struct EntityContext<'a> { pub packet_in: &'a mut VecDeque<PacketS>, pub score_changed: &'a mut bool, pub load_map: &'a mut Option<String>, + pub scoreboard: &'a ScoreboardStore, pub dt: f32, } pub trait Entity { fn tick(&mut self, c: EntityContext<'_>) -> Result<()>; fn destructor(&mut self, _c: EntityContext<'_>) {} + fn interact( + &mut self, + _c: EntityContext<'_>, + _pos: Option<IVec2>, + _player: PlayerID, + ) -> Result<bool> { + Ok(false) + } } // macro_rules! entities { @@ -104,6 +113,10 @@ pub enum EntityDecl { }, EnvironmentEffect(EnvironmentEffect), Environment(Vec<String>), + Gate { + location: Option<IVec2>, + condition: GateCondition, + }, } pub fn construct_entity( @@ -113,7 +126,9 @@ pub fn construct_entity( ) -> Result<DynEntity> { Ok(match decl.to_owned() { EntityDecl::ItemPortal { from, to } => Box::new(ItemPortal { - from: from.or(pos).ok_or(anyhow!("Item portal start without start"))?, + from: from + .or(pos) + .ok_or(anyhow!("Item portal start without start"))?, to, }), EntityDecl::PlayerPortal { from, to } => Box::new(PlayerPortal { @@ -149,6 +164,16 @@ pub fn construct_entity( .ok_or(anyhow!("no location"))?, name, }), + EntityDecl::Gate { + condition, + location, + } => Box::new(Gate { + condition, + unlocked: false, + location: location.or(pos).ok_or(anyhow!("no location"))?, + blocker_tile: reg.register_tile("fence".to_string()), + active: true, + }), EntityDecl::Customers {} => Box::new(Customers::new()?), EntityDecl::EnvironmentEffect(config) => Box::new(EnvironmentEffectController::new(config)), EntityDecl::Environment(names) => Box::new(EnvironmentController(names)), diff --git a/server/src/scoreboard.rs b/server/src/scoreboard.rs index e546f973..f61b3001 100644 --- a/server/src/scoreboard.rs +++ b/server/src/scoreboard.rs @@ -30,13 +30,13 @@ pub struct ScoreboardStore { } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Scoreboard { - plays: usize, - best: Vec<ScoreboardEntry>, + pub plays: usize, + pub best: Vec<ScoreboardEntry>, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ScoreboardEntry { - players: Vec<String>, - score: Score, + pub players: Vec<String>, + pub score: Score, } impl ScoreboardStore { @@ -62,6 +62,9 @@ impl ScoreboardStore { rename(buffer_path, path).await?; Ok(()) } + pub fn get<'a>(&'a self, map: &str) -> Option<&'a Scoreboard> { + self.maps.get(map) + } pub fn insert(&mut self, map: &str, players: Vec<String>, score: Score) { let b = self.maps.entry(map.to_owned()).or_default(); b.plays += 1; diff --git a/server/src/server.rs b/server/src/server.rs index bee94b0e..63664354 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -72,6 +72,7 @@ pub trait GameServerExt { packet_out: Option<&mut VecDeque<PacketC>>, ); fn prime_client(&self) -> Vec<PacketC>; + fn set_tile(&mut self, p: IVec2, t: Option<TileIndex>, packet_out: &mut VecDeque<PacketC>); } impl GameServerExt for Game { fn unload(&mut self, packet_out: &mut VecDeque<PacketC>) { @@ -229,6 +230,35 @@ impl GameServerExt for Game { }); } } + + fn set_tile( + &mut self, + tile: IVec2, + kind: Option<TileIndex>, + packet_out: &mut VecDeque<PacketC>, + ) { + if let Some(kind) = kind { + self.tiles.insert(tile, Tile::from(kind)); + if self.data.is_tile_colliding(kind) { + self.walkable.remove(&tile); + } else { + self.walkable.insert(tile); + } + } else { + self.tiles.remove(&tile); + self.walkable.remove(&tile); + } + packet_out.push_back(PacketC::UpdateMap { + tile, + kind, + neighbors: [ + self.tiles.get(&(tile + IVec2::NEG_Y)).map(|e| e.kind), + self.tiles.get(&(tile + IVec2::NEG_X)).map(|e| e.kind), + self.tiles.get(&(tile + IVec2::Y)).map(|e| e.kind), + self.tiles.get(&(tile + IVec2::X)).map(|e| e.kind), + ], + }) + } } impl Server { @@ -267,6 +297,7 @@ impl Server { packet_out: &mut self.packet_out, packet_in: &mut self.packet_loopback, score_changed: &mut self.score_changed, + scoreboard: &self.scoreboard, dt: 0., load_map: &mut None, }); @@ -345,6 +376,24 @@ impl Server { } } PacketS::Interact { pos, player } => { + for e in &mut self.entities { + if e.interact( + EntityContext { + game: &mut self.game, + packet_out: &mut self.packet_out, + packet_in: &mut self.packet_loopback, + score_changed: &mut self.score_changed, + load_map: &mut None, + scoreboard: &self.scoreboard, + dt: 0., + }, + pos, + player, + )? { + return Ok(()); + } + } + let pid = player; let player = self .game @@ -620,6 +669,7 @@ impl Server { packet_out: &mut self.packet_out, score_changed: &mut self.score_changed, packet_in: &mut self.packet_loopback, + scoreboard: &self.scoreboard, dt, }) { warn!("Entity tick failed: {e}") |