aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-07-06 12:39:57 +0200
committermetamuffin <metamuffin@disroot.org>2024-07-06 12:39:57 +0200
commitc56431eb8fcb1ec6758a5aa3cdbba3892989085f (patch)
treeef3d7b1e5420b231e85f89cb8437c9581bb81494
parent8360741499e11767aa3cbbec0fae43ddaab706f9 (diff)
downloadhurrycurry-c56431eb8fcb1ec6758a5aa3cdbba3892989085f.tar
hurrycurry-c56431eb8fcb1ec6758a5aa3cdbba3892989085f.tar.bz2
hurrycurry-c56431eb8fcb1ec6758a5aa3cdbba3892989085f.tar.zst
conveyors
-rw-r--r--data/index.yaml1
-rw-r--r--data/maps/test.yaml91
-rw-r--r--server/src/data.rs20
-rw-r--r--server/src/entity/conveyor.rs54
-rw-r--r--server/src/entity/mod.rs42
-rw-r--r--server/src/game.rs27
-rw-r--r--server/src/lib.rs1
-rw-r--r--test-client/main.ts6
8 files changed, 229 insertions, 13 deletions
diff --git a/data/index.yaml b/data/index.yaml
index 535a43ea..d0398327 100644
--- a/data/index.yaml
+++ b/data/index.yaml
@@ -9,6 +9,7 @@ maps:
- tiny
- small
- big
+ - test
recipes:
- none
diff --git a/data/maps/test.yaml b/data/maps/test.yaml
new file mode 100644
index 00000000..09f0b8a7
--- /dev/null
+++ b/data/maps/test.yaml
@@ -0,0 +1,91 @@
+# 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/>.
+#
+map:
+ - "*''''*'''*'''''*'''*'''*'''*''*'"
+ - "'''*''''*'*'**'''*''**''**''*'''"
+ - "''██▒██▒██▒███▒███▒████▒██▒███*'"
+ - "''█ctc.ctc.ctc.ctc.ctc█s....#█**"
+ - "''█.....c.............█s.~..⌷█''"
+ - "'*▒c...c...████www██d██⌷⌷.⌷.⌷█*'"
+ - "*'█tc.ctc..█#.....CC.>>>>.⌷.L█''"
+ - "''▒c...c...w..⌷⌷.....<<<<...R█'*"
+ - "*'█c.......w..⌷⌷⌷⌷⌷⌷⌷⌷█ff.⌷.T█*'"
+ - "'*▒tc......w..........d...⌷.F█''"
+ - "''█c.....ct█⌷SSS⌷f⌷oo⌷█⌷⌷...X█*'"
+ - "*'████dd██████████▒███████▒███'*"
+ - "'''*''__''''''''''''''''''''''*'"
+ - "*'''*'__________________________"
+ - "'*'*''_______________________!__"
+ - "*''*''''''''''''''''''''''''''''"
+
+tiles:
+ "⌷": counter
+ "f": counter
+ "<": counter
+ "#": counter
+ ">": counter
+ "t": table
+ "w": counter-window
+ "s": sink
+ "o": oven
+ "S": stove
+ "C": cuttingboard
+ "R": raw-steak-crate
+ "T": tomato-crate
+ "F": flour-crate
+ "L": leek-crate
+ "X": trash
+
+ "c": chair
+ "~": floor
+ ".": floor
+ "'": grass
+ "*": tree
+ "!": path
+ "_": path
+ "d": door
+ "█": wall
+ "▒": wall-window
+
+items:
+ "S": pot
+ "w": plate
+ "f": foodprocessor
+
+entities:
+ - !conveyor { from: [22, 7], to: [21, 7] }
+ - !conveyor { from: [23, 7], to: [22, 7] }
+ - !conveyor { from: [24, 7], to: [23, 7] }
+ - !conveyor { from: [21, 6], to: [22, 6] }
+ - !conveyor { from: [22, 6], to: [23, 6] }
+ - !conveyor { from: [23, 6], to: [24, 6] }
+ - !conveyor { from: [23, 6], to: [24, 6] }
+
+ - !conveyor { from: [28, 3], to: [12, 6] }
+
+chef_spawn: "~"
+customer_spawn: "!"
+
+walkable:
+ - door
+ - floor
+ - chair
+ - grass
+ - path
+
+collider:
+ - wall
+ - tree
diff --git a/server/src/data.rs b/server/src/data.rs
index 46f7ed28..62a7f5d8 100644
--- a/server/src/data.rs
+++ b/server/src/data.rs
@@ -16,6 +16,7 @@
*/
use crate::{
+ entity::EntityDecl,
interaction::Recipe,
protocol::{DemandIndex, ItemIndex, RecipeIndex, TileIndex},
};
@@ -64,11 +65,14 @@ pub struct RecipeDecl {
pub struct InitialMap {
map: Vec<String>,
tiles: HashMap<char, String>,
+ #[serde(default)]
items: HashMap<char, String>,
collider: Vec<String>,
walkable: Vec<String>,
chef_spawn: char,
customer_spawn: char,
+ #[serde(default)]
+ entities: Vec<EntityDecl>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -88,19 +92,18 @@ pub struct Demand {
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+#[rustfmt::skip]
pub struct Gamedata {
- #[serde(skip)]
- pub recipes: Vec<Recipe>,
- #[serde(skip)]
- pub demands: Vec<Demand>,
pub item_names: Vec<String>,
pub tile_names: Vec<String>,
pub tile_collide: Vec<bool>,
pub tile_interact: Vec<bool>,
- #[serde(skip)]
- pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,
- pub chef_spawn: Vec2,
- pub customer_spawn: Vec2,
+ #[serde(skip)] pub recipes: Vec<Recipe>,
+ #[serde(skip)] pub demands: Vec<Demand>,
+ #[serde(skip)] pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>,
+ #[serde(skip)] pub chef_spawn: Vec2,
+ #[serde(skip)] pub customer_spawn: Vec2,
+ #[serde(skip)] pub entities: Vec<EntityDecl>,
}
#[derive(Debug, Deserialize, Default)]
@@ -258,6 +261,7 @@ impl Gamedata {
recipes,
initial_map,
item_names,
+ entities: map_in.entities,
tile_names,
chef_spawn,
customer_spawn,
diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs
new file mode 100644
index 00000000..b74c03ef
--- /dev/null
+++ b/server/src/entity/conveyor.rs
@@ -0,0 +1,54 @@
+use super::Entity;
+use crate::{
+ data::Gamedata,
+ game::{interact_effect, Tile},
+ protocol::{ItemLocation, PacketC},
+};
+use anyhow::{anyhow, Result};
+use glam::IVec2;
+use std::collections::{HashMap, VecDeque};
+
+#[derive(Debug, Default)]
+pub struct Conveyor {
+ pub(super) from: IVec2,
+ pub(super) to: IVec2,
+ pub(super) cooldown: f32,
+ pub(super) max_cooldown: f32,
+}
+
+impl Entity for Conveyor {
+ fn tick(
+ &mut self,
+ data: &Gamedata,
+ points: &mut i64,
+ packet_out: &mut VecDeque<PacketC>,
+ tiles: &mut HashMap<IVec2, Tile>,
+ dt: f32,
+ ) -> Result<()> {
+ let [from, to] = tiles
+ .get_many_mut([&self.from, &self.to])
+ .ok_or(anyhow!("conveyor does ends in itself"))?;
+
+ if from.item.is_some() {
+ self.cooldown += dt;
+ if self.cooldown < self.max_cooldown {
+ return Ok(());
+ }
+ self.cooldown = 0.;
+
+ interact_effect(
+ data,
+ true,
+ &mut to.item,
+ ItemLocation::Tile(self.to),
+ &mut from.item,
+ ItemLocation::Tile(self.from),
+ Some(to.kind),
+ packet_out,
+ points,
+ );
+ }
+
+ Ok(())
+ }
+}
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
new file mode 100644
index 00000000..089c60a5
--- /dev/null
+++ b/server/src/entity/mod.rs
@@ -0,0 +1,42 @@
+pub mod conveyor;
+
+use crate::{data::Gamedata, game::Tile, protocol::PacketC};
+use anyhow::Result;
+use conveyor::Conveyor;
+use glam::IVec2;
+use serde::{Deserialize, Serialize};
+use std::collections::{HashMap, VecDeque};
+
+pub type DynEntity = Box<dyn Entity + Send + Sync + 'static>;
+
+pub trait Entity {
+ fn tick(
+ &mut self,
+ data: &Gamedata,
+ points: &mut i64,
+ packet_out: &mut VecDeque<PacketC>,
+ tiles: &mut HashMap<IVec2, Tile>,
+ dt: f32,
+ ) -> Result<()>;
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum EntityDecl {
+ Conveyor {
+ from: IVec2,
+ to: IVec2,
+ speed: Option<f32>,
+ },
+}
+
+pub fn construct_entity(decl: &EntityDecl) -> DynEntity {
+ match decl.to_owned() {
+ EntityDecl::Conveyor { from, to, speed } => Box::new(Conveyor {
+ from,
+ to,
+ max_cooldown: 1. / speed.unwrap_or(2.),
+ ..Default::default()
+ }),
+ }
+}
diff --git a/server/src/game.rs b/server/src/game.rs
index 838a71e4..13ee5410 100644
--- a/server/src/game.rs
+++ b/server/src/game.rs
@@ -18,6 +18,7 @@
use crate::{
customer::DemandState,
data::Gamedata,
+ entity::{construct_entity, DynEntity},
interaction::{interact, tick_slot, InteractEffect, TickEffect},
protocol::{
ItemIndex, ItemLocation, Message, PacketC, PacketS, PlayerID, RecipeIndex, TileIndex,
@@ -68,6 +69,7 @@ pub struct Game {
packet_out: VecDeque<PacketC>,
demand: Option<DemandState>,
pub points: i64,
+ entities: Vec<DynEntity>,
end: Option<Instant>,
}
@@ -76,10 +78,11 @@ impl Game {
Self {
data: Gamedata::default().into(),
packet_out: Default::default(),
- players: Default::default(),
- tiles: Default::default(),
+ players: HashMap::new(),
+ tiles: HashMap::new(),
demand: None,
end: None,
+ entities: vec![],
points: 0,
}
}
@@ -112,6 +115,12 @@ impl Game {
self.data = gamedata.into();
self.points = 0;
self.end = timer.map(|dur| Instant::now() + dur);
+ self.entities = self
+ .data
+ .entities
+ .iter()
+ .map(|decl| construct_entity(decl))
+ .collect();
for (&p, (tile, item)) in &self.data.initial_map {
self.tiles.insert(
@@ -476,6 +485,18 @@ impl Game {
}
}
+ for entity in &mut self.entities {
+ if let Err(e) = entity.tick(
+ &self.data,
+ &mut self.points,
+ &mut self.packet_out,
+ &mut self.tiles,
+ dt,
+ ) {
+ warn!("entity tick failed: {e}")
+ }
+ }
+
return self.end.map(|t| t < Instant::now()).unwrap_or_default();
}
}
@@ -486,7 +507,7 @@ impl From<TileIndex> for Tile {
}
}
-fn interact_effect(
+pub fn interact_effect(
data: &Gamedata,
edge: bool,
this: &mut Option<Item>,
diff --git a/server/src/lib.rs b/server/src/lib.rs
index b8929bd6..890e5148 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -22,3 +22,4 @@ pub mod game;
pub mod interaction;
pub mod protocol;
pub mod state;
+pub mod entity;
diff --git a/test-client/main.ts b/test-client/main.ts
index c7d6cf1c..0cd5c4cb 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -240,8 +240,10 @@ function keyboard(ev: KeyboardEvent, down: boolean) {
if (HANDLED_KEYS.includes(ev.code)) ev.preventDefault()
if (!keys_down.has(KEY_INTERACT) && ev.code == KEY_INTERACT && down) set_interact(true)
if (keys_down.has(KEY_INTERACT) && ev.code == KEY_INTERACT && !down) set_interact(false)
- if (down && ev.code == "Numpad1") send({ type: "communicate", message: { text: "/start default-small-default" }, persist: false })
- if (down && ev.code == "Numpad2") send({ type: "communicate", message: { text: "/start default-big-default" }, persist: false })
+ if (down && ev.code == "Numpad1") send({ type: "communicate", message: { text: "/start tiny" }, persist: false })
+ if (down && ev.code == "Numpad2") send({ type: "communicate", message: { text: "/start small" }, persist: false })
+ if (down && ev.code == "Numpad3") send({ type: "communicate", message: { text: "/start big" }, persist: false })
+ if (down && ev.code == "Numpad4") send({ type: "communicate", message: { text: "/start test" }, persist: false })
if (down && ev.code == "Numpad0") send({ type: "communicate", message: { text: "/end" }, persist: false })
if (down) keys_down.add(ev.code)
else keys_down.delete(ev.code)