diff options
author | metamuffin <metamuffin@disroot.org> | 2024-06-17 17:39:39 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-06-23 19:20:50 +0200 |
commit | 6f0424b9b4cddc0495eb673d314c570e27e61e83 (patch) | |
tree | 3ca2f5c8f1d16020dfa432d8a93fb1f53be93c4b | |
parent | 428fa6fb8dac18c541c0c231f1b640ba172e52b9 (diff) | |
download | hurrycurry-6f0424b9b4cddc0495eb673d314c570e27e61e83.tar hurrycurry-6f0424b9b4cddc0495eb673d314c570e27e61e83.tar.bz2 hurrycurry-6f0424b9b4cddc0495eb673d314c570e27e61e83.tar.zst |
everything indexed
-rw-r--r-- | Cargo.lock | 42 | ||||
-rw-r--r-- | data/recipes.yaml | 98 | ||||
-rw-r--r-- | data/recipes_raw.yaml | 37 | ||||
-rw-r--r-- | server/Cargo.toml | 1 | ||||
-rw-r--r-- | server/src/game.rs | 133 | ||||
-rw-r--r-- | server/src/interaction.rs | 66 | ||||
-rw-r--r-- | server/src/lib.rs | 2 | ||||
-rw-r--r-- | server/src/main.rs | 8 | ||||
-rw-r--r-- | server/src/protocol.rs | 38 | ||||
-rw-r--r-- | server/src/recipes.rs | 73 | ||||
-rw-r--r-- | test-client/main.ts | 19 | ||||
-rw-r--r-- | test-client/protocol.ts | 31 |
12 files changed, 349 insertions, 199 deletions
@@ -206,6 +206,12 @@ dependencies = [ ] [[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -292,6 +298,12 @@ dependencies = [ ] [[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -321,6 +333,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] name = "is_terminal_polyfill" version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -576,6 +598,19 @@ dependencies = [ ] [[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -728,6 +763,7 @@ dependencies = [ "log", "serde", "serde_json", + "serde_yaml", "tokio", "tokio-tungstenite", ] @@ -739,6 +775,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/data/recipes.yaml b/data/recipes.yaml index c35bee30..de8de444 100644 --- a/data/recipes.yaml +++ b/data/recipes.yaml @@ -1,86 +1,78 @@ -# tomato pipeline -- tile: counter +- tile: floor + action: !never # tomato pipeline + +- tile: meat-spawn + action: !instant + outputs: [raw-meat] + + +- tile: table inputs: [tomato] outputs: [sliced-tomato] - action: hold - duration: 3 -- tile: counter - inputs: [sliced-tomato,plate] + action: !active 3 +- tile: table + inputs: [sliced-tomato, plate] outputs: [sliced-tomato-meal] - action: instant - duration: 0 + action: !instant # bread pipeline -# bread pipeline -- tile: counter + +- tile: table inputs: [flour] outputs: [dough] - action: hold - duration: 5 + action: !active 5 - tile: oven inputs: [dough] outputs: [bread] - action: wait - duration: 20 -- tile: counter - inputs: [bread,plate] + action: !passive 20 +- tile: table + inputs: [bread, plate] outputs: [bread-meal] - action: instant - duration: 0 + action: !instant # steak pipeline -# steak pipeline -- tile: oven + +- tile: pan inputs: [raw-steak] outputs: [steak] - action: wait - duration: 15 -- tile: counter - inputs: [steak,plate] + action: !passive 15 +- tile: table + inputs: [steak, plate] outputs: [steak-meal] - action: instant - duration: 0 + action: !instant # combination meals + -# combination meals -- tile: counter - inputs: [steak-meal,bread] +- tile: table + inputs: [steak-meal, bread] outputs: [burger-meal] - action: instant - duration: 0 -- tile: counter - inputs: [steak-meal,tomato] + action: !instant +- tile: table + inputs: [steak-meal, tomato] outputs: [tomatosteak-meal] - action: instant - duration: 0 -- tile: counter - inputs: [burger-meal,tomato] + action: !instant +- tile: table + inputs: [burger-meal, tomato] outputs: [tomatoburger-meal] - action: instant - duration: 0 -- tile: counter - inputs: [tomatosteak-meal,bread] + action: !instant +- tile: table + inputs: [tomatosteak-meal, bread] outputs: [tomatoburger-meal] - action: instant - duration: 0 + action: !instant # cups + -# cups - tile: watercooler inputs: [glass] outputs: [water] - action: wait - duration: 2 + action: !passive 2 - tile: sink inputs: [water] outputs: [glass] - action: instant - duration: 0 + action: !instant # cleaning + -# cleaning - tile: sink inputs: [dirty-glass] outputs: [glass] - action: hold - duration: 5 + action: !active 5 - tile: sink inputs: [dirty-plate] outputs: [plate] - action: hold - duration: 5 + action: !active 5 diff --git a/data/recipes_raw.yaml b/data/recipes_raw.yaml deleted file mode 100644 index 75939c11..00000000 --- a/data/recipes_raw.yaml +++ /dev/null @@ -1,37 +0,0 @@ - -- tile: cutting-plate - inputs: [tomato] - outputs: [sliced-tomato] - duration: 3 - active: true - -- tile: sink - inputs: [empty-bottle] - outputs: [water-bottle] - instant: true - -- tile: dishwasher - inputs: [dirty-dish] - output: [empty-dish] - duration: 10 - -- tile: table - inputs: [steak, herbs] - outputs: [steak-with-herbs] - ordered: true - instant: true - -- tile: table - inputs: [apple-juice-bottle, carbonated-water-bottle] - outputs: [apply-soda-bottle, empty-bottle] - instant: true - -- tile: table - inputs: [empty-cup, <a>-bottle] - outputs: [<a>-cup, empty-bottle] - instant: true - -- tile: flour-bag - outputs: [flour] - instant: true - diff --git a/server/Cargo.toml b/server/Cargo.toml index 27e6f4b7..447a3ff1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,3 +13,4 @@ tokio = { version = "1.38.0", features = ["full"] } serde_json = "1.0.117" tokio-tungstenite = "0.23.1" futures-util = "0.3.30" +serde_yaml = "0.9.34+deprecated" diff --git a/server/src/game.rs b/server/src/game.rs index a9ee8f3d..a7a07881 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -1,60 +1,81 @@ -use crate::protocol::{Item, PacketC, PacketS, Tile, ID}; +use crate::{ + protocol::{ItemID, ItemIndex, PacketC, PacketS, PlayerID, TileIndex}, + recipes::Gamedata, +}; use anyhow::{anyhow, Result}; use glam::IVec2; -use log::info; -use std::collections::{HashMap, VecDeque}; +use std::{ + collections::{HashMap, VecDeque}, + ops::Deref, + sync::Arc, +}; -struct TileData { - kind: Tile, - items: Vec<ID>, +pub struct Tile { + kind: TileIndex, + items: Vec<ItemID>, active: bool, progress: f32, } struct Player { name: String, - hand: Option<ID>, + hand: Option<ItemID>, } -#[derive(Default)] pub struct Game { - item_id_counter: ID, - tiles: HashMap<IVec2, TileData>, - items: HashMap<ID, Item>, - players: HashMap<ID, Player>, + data: Arc<Gamedata>, + item_id_counter: ItemID, + tiles: HashMap<IVec2, Tile>, + items: HashMap<ItemID, ItemIndex>, + players: HashMap<PlayerID, Player>, packet_out: VecDeque<PacketC>, } impl Game { - pub fn new() -> Self { - let mut g = Self::default(); + pub fn new(gamedata: Arc<Gamedata>) -> Self { + let mut g = Self { + data: gamedata.clone(), + item_id_counter: 0, + items: Default::default(), + packet_out: Default::default(), + players: Default::default(), + tiles: Default::default(), + }; for x in -5..5 { for y in -5..5 { g.tiles - .insert(IVec2 { x, y }, Tile("floor".to_string()).into()); + .insert(IVec2 { x, y }, gamedata.get_tile("floor").unwrap().into()); } } for x in -5..5 { - g.tiles - .insert(IVec2 { x, y: -5 }, Tile("table".to_string()).into()); - g.tiles - .insert(IVec2 { x, y: 4 }, Tile("table".to_string()).into()); + g.tiles.insert( + IVec2 { x, y: -5 }, + gamedata.get_tile("table").unwrap().into(), + ); + g.tiles.insert( + IVec2 { x, y: 4 }, + gamedata.get_tile("table").unwrap().into(), + ); } for y in -5..5 { - g.tiles - .insert(IVec2 { x: -5, y }, Tile("table".to_string()).into()); - g.tiles - .insert(IVec2 { x: 4, y }, Tile("table".to_string()).into()); + g.tiles.insert( + IVec2 { x: -5, y }, + gamedata.get_tile("table").unwrap().into(), + ); + g.tiles.insert( + IVec2 { x: 4, y }, + gamedata.get_tile("table").unwrap().into(), + ); } g.tiles.extend( - [([-5, 1], "pan"), ([-5, 2], "pan"), ([4, 3], "flour_bag")].map(|(k, v)| { + [([-5, 1], "pan"), ([-5, 2], "pan"), ([4, 3], "meat-spawn")].map(|(k, v)| { ( IVec2::from_array(k), - TileData { + Tile { active: false, items: vec![], - kind: Tile(v.to_string()).into(), + kind: gamedata.get_tile(v).unwrap().into(), progress: 0., }, ) @@ -68,7 +89,7 @@ impl Game { self.packet_out.pop_front() } - pub fn prime_client(&self, id: ID) -> Vec<PacketC> { + pub fn prime_client(&self, id: PlayerID) -> Vec<PacketC> { let mut out = Vec::new(); for (&id, player) in &self.players { out.push(PacketC::AddPlayer { @@ -90,11 +111,14 @@ impl Game { }) } } - out.push(PacketC::Joined { id }); + out.push(PacketC::Joined { + id, + data: self.data.deref().to_owned(), + }); out } - pub fn packet_in(&mut self, player: ID, packet: PacketS) -> Result<()> { + pub fn packet_in(&mut self, player: PlayerID, packet: PacketS) -> Result<()> { match packet { PacketS::Join { name } => { self.players.insert( @@ -126,50 +150,27 @@ impl Game { .push_back(PacketC::Position { player, pos, rot }); } PacketS::Interact { pos, edge } => { - if !edge { - return Ok(()); - } - let tile = self - .tiles - .get_mut(&pos) - .ok_or(anyhow!("interacting with empty tile"))?; - let player_data = self - .players - .get_mut(&player) - .ok_or(anyhow!("player does not exist"))?; - if tile.kind.0 == "flour_bag" { - info!("new flour"); - let item = Item("flour".to_string()); - self.items.insert(self.item_id_counter, item.clone()); - tile.items.push(self.item_id_counter); - self.packet_out.push_back(PacketC::ProduceItem { - id: self.item_id_counter, - pos, - kind: item, - }); - self.item_id_counter += 1; - } - if let Some(item) = player_data.hand.take() { - info!("put {item}"); - tile.items.push(item); - self.packet_out.push_back(PacketC::PutItem { item, pos }) - } else { - if let Some(item) = tile.items.pop() { - info!("take {item}"); - player_data.hand = Some(item); - self.packet_out - .push_back(PacketC::TakeItem { item, player }) - } - } + // if let Some(item) = player_data.hand.take() { + // info!("put {item}"); + // tile.items.push(item); + // self.packet_out.push_back(PacketC::PutItem { item, pos }) + // } else { + // if let Some(item) = tile.items.pop() { + // info!("take {item}"); + // player_data.hand = Some(item); + // self.packet_out + // .push_back(PacketC::TakeItem { item, player }) + // } + // } } } Ok(()) } } -impl From<Tile> for TileData { - fn from(kind: Tile) -> Self { +impl From<TileIndex> for Tile { + fn from(kind: TileIndex) -> Self { Self { kind, progress: 0., diff --git a/server/src/interaction.rs b/server/src/interaction.rs new file mode 100644 index 00000000..5f8b0097 --- /dev/null +++ b/server/src/interaction.rs @@ -0,0 +1,66 @@ +use crate::{ + protocol::{ItemIndex, TileIndex}, + recipes::{Action, Gamedata}, +}; +use std::collections::BTreeSet; + +pub enum Out { + Take(usize), + Put, + Produce(ItemIndex), + Consume(usize), +} +use Out::*; + +pub fn interact( + data: &Gamedata, + edge: bool, + tile: TileIndex, + items: &[ItemIndex], + hand: &Option<ItemIndex>, + mut out: impl FnMut(Out), +) { + let mut allowed = BTreeSet::new(); + for r in &data.recipes { + if r.tile == tile { + allowed.extend(r.inputs.clone()) + } + } + if !edge { + return; + } + + let mut put_item = None; + if let Some(hand) = hand { + if allowed.contains(hand) { + out(Put); + put_item = Some(*hand); + } + } + + for r in &data.recipes { + let ok = r + .inputs + .iter() + .all(|e| items.contains(e) || put_item == Some(*e)) + && r.inputs.len() == items.len(); + if ok { + match r.action { + Action::Passive(_) => todo!(), + Action::Active(_) => todo!(), + Action::Instant => { + for i in 0..items.len() { + out(Consume(i)) + } + for i in &r.outputs { + out(Produce(*i)); + } + if !r.outputs.is_empty() { + out(Take(r.outputs.len() - 1)); + } + } + Action::Never => (), + } + } + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 4659e440..5bb09e41 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,2 +1,4 @@ pub mod game; pub mod protocol; +pub mod recipes; +pub mod interaction; diff --git a/server/src/main.rs b/server/src/main.rs index 7426e27e..441487e8 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use futures_util::{SinkExt, StreamExt}; use log::{debug, info, warn}; -use std::{sync::Arc, time::Duration}; +use std::{fs::File, sync::Arc, time::Duration}; use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, net::TcpListener, @@ -13,6 +13,7 @@ use tokio_tungstenite::tungstenite::Message; use undercooked::{ game::Game, protocol::{PacketC, PacketS}, + recipes::build_gamedata, }; #[tokio::main] @@ -26,7 +27,10 @@ async fn main() -> Result<()> { ); info!("listening for websockets on {}", ws_listener.local_addr()?); - let game = Arc::new(RwLock::new(Game::new())); + let data = + build_gamedata(serde_yaml::from_reader(File::open("data/recipes.yaml").unwrap()).unwrap()); + + let game = Arc::new(RwLock::new(Game::new(data.into()))); let (tx, rx) = broadcast::channel::<PacketC>(1024); { diff --git a/server/src/protocol.rs b/server/src/protocol.rs index a318a9b2..ae87ffb4 100644 --- a/server/src/protocol.rs +++ b/server/src/protocol.rs @@ -1,15 +1,12 @@ use glam::{IVec2, Vec2}; use serde::{Deserialize, Serialize}; -pub type ID = u32; +use crate::recipes::Gamedata; -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Item(pub String); - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Tile(pub String); +pub type PlayerID = usize; +pub type ItemID = usize; +pub type ItemIndex = usize; +pub type TileIndex = usize; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -24,36 +21,37 @@ pub enum PacketS { #[serde(rename_all = "snake_case")] pub enum PacketC { Joined { - id: ID, + data: Gamedata, + id: PlayerID, }, AddPlayer { - id: ID, + id: PlayerID, name: String, - hand: Option<(ID, Item)>, + hand: Option<(ItemID, ItemIndex)>, }, RemovePlayer { - id: ID, + id: PlayerID, }, Position { - player: ID, + player: PlayerID, pos: Vec2, rot: f32, }, TakeItem { - item: ID, - player: ID, + item: ItemID, + player: PlayerID, }, PutItem { - item: ID, + item: ItemID, pos: IVec2, }, ProduceItem { - id: ID, + id: ItemID, pos: IVec2, - kind: Item, + kind: ItemIndex, }, ConsumeItem { - id: ID, + id: ItemID, pos: IVec2, }, SetActive { @@ -62,6 +60,6 @@ pub enum PacketC { }, UpdateMap { pos: IVec2, - tile: Tile, + tile: TileIndex, }, } diff --git a/server/src/recipes.rs b/server/src/recipes.rs new file mode 100644 index 00000000..25a4be98 --- /dev/null +++ b/server/src/recipes.rs @@ -0,0 +1,73 @@ +use crate::protocol::{ItemIndex, TileIndex}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum Action { + Passive(f32), + Active(f32), + Instant, + Never, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Recipe<T = TileIndex, I = ItemIndex> { + pub tile: T, + #[serde(default)] + pub inputs: Vec<I>, + #[serde(default)] + pub outputs: Vec<I>, + pub action: Action, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Gamedata { + pub recipes: Vec<Recipe>, + pub item_names: Vec<String>, + pub tile_names: Vec<String>, +} +pub fn build_gamedata(recipes_in: Vec<Recipe<String, String>>) -> Gamedata { + let mut item_names = Vec::new(); + let mut tile_names = Vec::new(); + let mut recipes = Vec::new(); + + for r in recipes_in { + recipes.push(Recipe { + action: r.action, + tile: register(&mut tile_names, r.tile.clone()), + inputs: r + .inputs + .clone() + .into_iter() + .map(|e| register(&mut item_names, e)) + .collect(), + outputs: r + .outputs + .clone() + .into_iter() + .map(|e| register(&mut item_names, e)) + .collect(), + }) + } + + Gamedata { + recipes, + item_names, + tile_names, + } +} +fn register(db: &mut Vec<String>, name: String) -> usize { + if let Some(index) = db.iter().position(|e| e == &name) { + index + } else { + let index = db.len(); + db.push(name); + index + } +} + +impl Gamedata { + pub fn get_tile(&self, name: &str) -> Option<TileIndex> { + self.tile_names.iter().position(|t| t == name) + } +} diff --git a/test-client/main.ts b/test-client/main.ts index 3e04dc05..5f4475f2 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -1,6 +1,6 @@ /// <reference lib="dom" /> -import { ID, Item, PacketC, PacketS, Tile } from "./protocol.ts"; +import { Gamedata, ItemID, ItemIndex, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts"; import { FALLBACK_TILE, TILES } from "./tiles.ts"; import { V2, add_v2, ceil_v2, floor_v2, length, lerp_exp_v2_mut, normalize } from "./util.ts"; @@ -32,14 +32,16 @@ document.addEventListener("DOMContentLoaded", () => { setInterval(tick_update, 1000 / 25); }) -interface PlayerData { x: number; y: number, name: string, rot: number, hand?: ID, facing: V2 } -const players = new Map<number, PlayerData>() -interface ItemData { kind: Item, tile?: V2, player?: ID, tracking_player: boolean, x: number, y: number } -const items = new Map<number, ItemData>() -interface TileData { x: number; y: number, kind: Tile, items: ID[], active: boolean } +interface PlayerData { x: number; y: number, name: string, rot: number, hand?: ItemID, facing: V2 } +const players = new Map<PlayerID, PlayerData>() +interface ItemData { kind: ItemIndex, tile?: V2, player?: PlayerID, tracking_player: boolean, x: number, y: number } +const items = new Map<ItemID, ItemData>() +interface TileData { x: number; y: number, kind: TileIndex, items: ItemID[], active: boolean } const tiles = new Map<string, TileData>() -let my_id: number = -1 +let data: Gamedata = { item_names: [], tile_names: [] } + +let my_id: PlayerID = -1 const camera: V2 = { x: 0, y: 0 } const interact_target_anim: V2 = { x: 0, y: 0 } let scale = 0 @@ -49,6 +51,7 @@ function packet(p: PacketC) { if (!("position" in p)) console.log(p); if ("joined" in p) { my_id = p.joined.id + data = p.joined.data } else if ("add_player" in p) { if (p.add_player.hand) items.set(p.add_player.hand[0], { kind: p.add_player.hand[1], player: p.add_player.id, tracking_player: true, x: 0, y: 0 }) players.set(p.add_player.id, { x: 0, y: 0, name: p.add_player.name, rot: 0, hand: p.add_player.hand?.[0], facing: { x: 0, y: 1 } }) @@ -188,7 +191,7 @@ function draw_ingame() { for (const [_, tile] of tiles) { ctx.save() ctx.translate(tile.x, tile.y) - const comps = TILES[tile.kind] ?? FALLBACK_TILE + const comps = TILES[data.tile_names[tile.kind]] ?? FALLBACK_TILE for (const c of comps) { c(ctx) } diff --git a/test-client/protocol.ts b/test-client/protocol.ts index 707c09a3..d5cb2034 100644 --- a/test-client/protocol.ts +++ b/test-client/protocol.ts @@ -1,7 +1,13 @@ -export type ID = number; export type Vec2 = [number, number] -export type Item = string -export type Tile = string +export type PlayerID = number +export type ItemID = number +export type ItemIndex = number +export type TileIndex = number + +export interface Gamedata { + item_names: string[], + tile_names: string[], +} export type PacketS = { join: { name: string } } @@ -9,16 +15,15 @@ export type PacketS = | { position: { pos: Vec2, rot: number } } | { interact: { pos: Vec2, edge: boolean } } - export type PacketC = - { joined: { id: ID } } - | { add_player: { id: ID, name: string, hand?: [number, Item] } } - | { remove_player: { id: ID } } - | { position: { player: ID, pos: Vec2, rot: number } } - | { take_item: { item: ID, player: ID } } - | { put_item: { item: ID, pos: Vec2 } } - | { produce_item: { id: ID, pos: Vec2, kind: Item } } - | { consume_item: { id: ID, pos: Vec2 } } + { joined: { id: PlayerID, data: Gamedata } } + | { add_player: { id: PlayerID, name: string, hand?: [ItemID, ItemIndex] } } + | { remove_player: { id: PlayerID } } + | { position: { player: PlayerID, pos: Vec2, rot: number } } + | { take_item: { item: ItemID, player: PlayerID } } + | { put_item: { item: ItemID, pos: Vec2 } } + | { produce_item: { id: ItemID, pos: Vec2, kind: ItemIndex } } + | { consume_item: { id: ItemID, pos: Vec2 } } | { set_active: { tile: Vec2 } } - | { update_map: { pos: Vec2, tile: Tile } } + | { update_map: { pos: Vec2, tile: TileIndex } } |