aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--server/src/game.rs89
-rw-r--r--server/src/main.rs26
-rw-r--r--server/src/protocol.rs71
-rw-r--r--test-client/main.ts44
-rw-r--r--test-client/protocol.ts2
-rw-r--r--test-client/tiles.ts33
7 files changed, 221 insertions, 45 deletions
diff --git a/Cargo.toml b/Cargo.toml
index cae9660f..156820ec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,3 @@
[workspace]
members = ["server"]
+resolver = "2"
diff --git a/server/src/game.rs b/server/src/game.rs
index 458a796b..fbbab0a4 100644
--- a/server/src/game.rs
+++ b/server/src/game.rs
@@ -1,9 +1,10 @@
-use crate::protocol::{Item, PacketC, PacketS, ID};
+use crate::protocol::{Item, PacketC, PacketS, Tile, ID};
use anyhow::{anyhow, Result};
-use glam::UVec2;
+use glam::IVec2;
use std::collections::{HashMap, VecDeque};
struct TileData {
+ kind: Tile,
items: Vec<ID>,
active: bool,
}
@@ -16,7 +17,7 @@ struct Player {
#[derive(Default)]
pub struct Game {
item_id_counter: ID,
- tiles: HashMap<UVec2, TileData>,
+ tiles: HashMap<IVec2, TileData>,
items: HashMap<ID, Item>,
players: HashMap<ID, Player>,
packet_out: VecDeque<PacketC>,
@@ -24,13 +25,72 @@ pub struct Game {
impl Game {
pub fn new() -> Self {
- Self::default()
+ let mut g = Self::default();
+ for x in -5..5 {
+ for y in -5..5 {
+ g.tiles.insert(IVec2 { x, y }, Tile::Floor.into());
+ }
+ }
+ for x in -5..5 {
+ g.tiles.insert(IVec2 { x, y: -5 }, Tile::Table.into());
+ g.tiles.insert(IVec2 { x, y: 4 }, Tile::Table.into());
+ }
+ for y in -5..5 {
+ g.tiles.insert(IVec2 { x: -5, y }, Tile::Table.into());
+ g.tiles.insert(IVec2 { x: 4, y }, Tile::Table.into());
+ }
+
+ g.tiles.extend(
+ [
+ ([-5, 1], Tile::Pan),
+ ([-5, 2], Tile::Pan),
+ ([4, 3], Tile::Pan),
+ ]
+ .map(|(k, v)| {
+ (
+ IVec2::from_array(k),
+ TileData {
+ active: false,
+ items: vec![],
+ kind: v,
+ },
+ )
+ }),
+ );
+
+ g
}
pub fn packet_out(&mut self) -> Option<PacketC> {
self.packet_out.pop_front()
}
+ pub fn prime_client(&self, id: ID) -> Vec<PacketC> {
+ let mut out = Vec::new();
+ for (&id, player) in &self.players {
+ out.push(PacketC::AddPlayer {
+ id,
+ name: player.name.clone(),
+ hand: player.hand.map(|i| (i, self.items[&i])),
+ })
+ }
+ for (&pos, tdata) in &self.tiles {
+ out.push(PacketC::UpdateMap {
+ pos,
+ tile: tdata.kind,
+ });
+ for &id in &tdata.items {
+ out.push(PacketC::ProduceItem {
+ id,
+ pos,
+ kind: self.items[&id],
+ })
+ }
+ }
+ out.push(PacketC::Joined { id });
+ out
+ }
+
pub fn packet_in(&mut self, player: ID, packet: PacketS) -> Result<()> {
match packet {
PacketS::Join { name } => {
@@ -41,16 +101,19 @@ impl Game {
name: name.clone(),
},
);
- self.packet_out
- .push_back(PacketC::AddPlayer { id: player, name });
+ self.packet_out.push_back(PacketC::AddPlayer {
+ id: player,
+ name,
+ hand: None,
+ });
}
PacketS::Leave => {
let p = self
.players
.remove(&player)
.ok_or(anyhow!("player does not exist"))?;
- if let Some(_i) = p.hand {
- // TODO what now?
+ if let Some(id) = p.hand {
+ self.items.remove(&id).expect("hand item lost");
}
self.packet_out
.push_back(PacketC::RemovePlayer { id: player })
@@ -84,3 +147,13 @@ impl Game {
Ok(())
}
}
+
+impl From<Tile> for TileData {
+ fn from(kind: Tile) -> Self {
+ Self {
+ kind,
+ active: false,
+ items: vec![],
+ }
+ }
+}
diff --git a/server/src/main.rs b/server/src/main.rs
index 477595d0..7426e27e 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -53,11 +53,14 @@ async fn main() -> Result<()> {
let game = game.clone();
let mut rx = rx.resubscribe();
info!("{addr} connected");
+ let init = game.write().await.prime_client(id);
spawn(async move {
- write
- .write_all(serde_json::to_string(&PacketC::Joined { id }).unwrap().as_bytes())
- .await?;
- write.write_all(b"\n").await?;
+ for p in init {
+ write
+ .write_all(serde_json::to_string(&p).unwrap().as_bytes())
+ .await?;
+ write.write_all(b"\n").await?;
+ }
while let Ok(packet) = rx.recv().await {
write
.write_all(serde_json::to_string(&packet).unwrap().as_bytes())
@@ -78,6 +81,7 @@ async fn main() -> Result<()> {
warn!("client error: {e}");
}
}
+ let _ = game.write().await.packet_in(id, PacketS::Leave);
});
}
r = ws_listener.accept() => {
@@ -87,12 +91,15 @@ async fn main() -> Result<()> {
let game = game.clone();
let mut rx = rx.resubscribe();
info!("{addr} connected via ws");
+ let init = game.write().await.prime_client(id);
spawn(async move {
- if let Err(e) = write.send(tokio_tungstenite::tungstenite::Message::Text(
- serde_json::to_string(&PacketC::Joined { id }).unwrap(),
- )).await {
- warn!("ws error on init: {e}");
- return;
+ for p in init {
+ if let Err(e) = write.send(tokio_tungstenite::tungstenite::Message::Text(
+ serde_json::to_string(&p).unwrap(),
+ )).await {
+ warn!("ws error on init: {e}");
+ return;
+ }
}
while let Ok(packet) = rx.recv().await {
if let Err(e) = write.send(tokio_tungstenite::tungstenite::Message::Text(
@@ -120,6 +127,7 @@ async fn main() -> Result<()> {
_ => (),
}
}
+ let _ = game.write().await.packet_in(id, PacketS::Leave);
});
}
}
diff --git a/server/src/protocol.rs b/server/src/protocol.rs
index dc2af7d6..a8fd9979 100644
--- a/server/src/protocol.rs
+++ b/server/src/protocol.rs
@@ -1,13 +1,22 @@
-use glam::{UVec2, Vec2};
+use glam::{IVec2, Vec2};
use serde::{Deserialize, Serialize};
pub type ID = u32;
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum Item {}
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum Item {
+ Dough,
+ Pancake,
+}
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum Tile {}
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum Tile {
+ Floor,
+ Table,
+ Pan,
+}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -15,20 +24,50 @@ pub enum PacketS {
Join { name: String },
Leave,
Position { pos: Vec2, rot: f32 },
- Interact { pos: UVec2 },
+ Interact { pos: IVec2 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PacketC {
- Joined { id: ID },
- AddPlayer { id: ID, name: String },
- RemovePlayer { id: ID },
- Position { player: ID, pos: Vec2, rot: f32 },
- TakeItem { item: ID, player: ID },
- PutItem { item: ID, pos: UVec2 },
- ProduceItem { id: ID, pos: UVec2, kind: Item },
- ConsumeItem { id: ID, pos: UVec2 },
- SetActive { tile: UVec2 },
- UpdateMap { pos: UVec2, tile: Tile },
+ Joined {
+ id: ID,
+ },
+ AddPlayer {
+ id: ID,
+ name: String,
+ hand: Option<(ID, Item)>,
+ },
+ RemovePlayer {
+ id: ID,
+ },
+ Position {
+ player: ID,
+ pos: Vec2,
+ rot: f32,
+ },
+ TakeItem {
+ item: ID,
+ player: ID,
+ },
+ PutItem {
+ item: ID,
+ pos: IVec2,
+ },
+ ProduceItem {
+ id: ID,
+ pos: IVec2,
+ kind: Item,
+ },
+ ConsumeItem {
+ id: ID,
+ pos: IVec2,
+ },
+ SetActive {
+ tile: IVec2,
+ },
+ UpdateMap {
+ pos: IVec2,
+ tile: Tile,
+ },
}
diff --git a/test-client/main.ts b/test-client/main.ts
index 1780b5d7..3c1285a4 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -1,6 +1,7 @@
/// <reference lib="dom" />
-import { PacketC, PacketS } from "./protocol.ts";
+import { ID, Item, PacketC, PacketS, Tile } from "./protocol.ts";
+import { FALLBACK_TILE, TILES } from "./tiles.ts";
import { V2, ceil_v2, floor_v2, lerp_v2_mut, normalize } from "./util.ts";
let ctx: CanvasRenderingContext2D;
@@ -27,15 +28,16 @@ document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("keydown", ev => keyboard(ev, true))
document.addEventListener("keyup", ev => keyboard(ev, false))
+ document.addEventListener("contextmenu", ev => ev.preventDefault())
setInterval(tick_update, 1000 / 25);
})
-interface Player { x: number; y: number, name: string, rot: number }
-const players = new Map<number, Player>()
-interface Item { x: number; y: number }
-const items = new Map<number, Item>()
-interface Tile { x: number; y: number }
-const tiles = new Map<string, Tile>()
+interface PlayerData { x: number; y: number, name: string, rot: number, hand?: ID }
+const players = new Map<number, PlayerData>()
+interface ItemData { x: number; y: number, kind: Item }
+const items = new Map<number, ItemData>()
+interface TileData { x: number; y: number, kind: Tile }
+const tiles = new Map<string, TileData>()
let my_id: number = -1
const camera: V2 = { x: 0, y: 0 }
@@ -43,24 +45,32 @@ let scale = 0
function send(p: PacketS) { ws.send(JSON.stringify(p)) }
function packet(p: PacketC) {
- console.log(p);
+ if (!("position" in p)) console.log(p);
if ("joined" in p) {
my_id = p.joined.id
} else if ("add_player" in p) {
- players.set(p.add_player.id, { x: 0, y: 0, name: p.add_player.name, rot: 0 })
+ if (p.add_player.hand) items.set(p.add_player.hand[0], { kind: p.add_player.hand[1], 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] })
} else if ("remove_player" in p) {
players.delete(p.remove_player.id)
} else if ("position" in p) {
+ if (p.position.player == my_id) return; // we know better where we are
const pl = players.get(p.position.player)!
pl.x = p.position.pos[0]
pl.y = p.position.pos[1]
pl.rot = p.position.rot
} else if ("take_item" in p) {
+ // TODO
} else if ("put_item" in p) {
+ // TODO
} else if ("produce_item" in p) {
+ // TODO
} else if ("consume_item" in p) {
+ // TODO
} else if ("set_active" in p) {
+ // TODO
} else if ("update_map" in p) {
+ tiles.set(p.update_map.pos.toString(), { x: p.update_map.pos[0], y: p.update_map.pos[1], kind: p.update_map.tile })
} else console.warn("unknown packet", p);
}
@@ -80,6 +90,8 @@ function interact() {
function tick_update() {
const p = players.get(my_id)
if (!p) return
+
+ send({ position: { pos: [p.x, p.y], rot: p.rot } })
}
function frame_update(dt: number) {
const p = players.get(my_id)
@@ -114,7 +126,7 @@ function draw() {
requestAnimationFrame(draw)
}
function draw_wait(text: string) {
- ctx.fillStyle = "gray"
+ ctx.fillStyle = "#444"
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "#555"
ctx.font = "50px sans-serif"
@@ -149,6 +161,16 @@ function draw_ingame() {
draw_grid()
+ for (const [_, tile] of tiles) {
+ ctx.save()
+ ctx.translate(tile.x, tile.y)
+ const comps = TILES[tile.kind] ?? FALLBACK_TILE
+ for (const c of comps) {
+ c(ctx)
+ }
+ ctx.restore()
+ }
+
for (const [_, player] of players) {
ctx.save()
ctx.translate(player.x, player.y)
@@ -177,4 +199,4 @@ function draw_grid() {
ctx.lineTo(max.x, y)
}
ctx.stroke()
-} \ No newline at end of file
+}
diff --git a/test-client/protocol.ts b/test-client/protocol.ts
index 3b61ce94..717383d4 100644
--- a/test-client/protocol.ts
+++ b/test-client/protocol.ts
@@ -12,7 +12,7 @@ export type PacketS =
export type PacketC =
{ joined: { id: ID } }
- | { add_player: { id: ID, name: string } }
+ | { 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 } }
diff --git a/test-client/tiles.ts b/test-client/tiles.ts
new file mode 100644
index 00000000..3b15385c
--- /dev/null
+++ b/test-client/tiles.ts
@@ -0,0 +1,33 @@
+
+type Component = (ctx: CanvasRenderingContext2D) => void
+function base(fill: string, stroke?: string, stroke_width?: number): Component {
+ return c => {
+ c.fillStyle = fill;
+ c.strokeStyle = stroke ?? "black";
+ c.lineWidth = stroke_width ?? 0.05
+ c.lineJoin = "miter"
+ c.lineCap = "square"
+ c.fillRect(0, 0, 1, 1)
+ if (stroke) c.strokeRect(c.lineWidth / 2, c.lineWidth / 2, 1 - c.lineWidth, 1 - c.lineWidth)
+ }
+}
+function circle(radius: number, fill: string, stroke?: string, stroke_width?: number): Component {
+ return c => {
+ c.fillStyle = fill;
+ c.strokeStyle = stroke ?? "black";
+ c.lineWidth = stroke_width ?? 0.05
+ c.beginPath()
+ c.arc(0.5, 0.5, radius, 0, Math.PI * 2)
+ if (stroke) c.stroke()
+ c.fill()
+ }
+}
+
+const table = [base("rgb(133, 76, 38)")];
+
+export const FALLBACK_TILE: Component[] = [base("#f0f")];
+export const TILES: { [key: string]: Component[] } = {
+ "floor": [base("#333", "#222", 0.05)],
+ "table": table,
+ "pan": [...table, circle(0.4, "#444", "#999")],
+}