aboutsummaryrefslogtreecommitdiff
path: root/test-client
diff options
context:
space:
mode:
Diffstat (limited to 'test-client')
-rw-r--r--test-client/main.ts161
-rw-r--r--test-client/protocol.ts13
-rw-r--r--test-client/tiles.ts25
-rw-r--r--test-client/util.ts13
4 files changed, 152 insertions, 60 deletions
diff --git a/test-client/main.ts b/test-client/main.ts
index e0cc9b3c..4c755e55 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -1,9 +1,11 @@
/// <reference lib="dom" />
-import { Gamedata, ItemID, ItemIndex, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts";
+import { Gamedata, ItemIndex, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts";
import { FALLBACK_ITEM } from "./tiles.ts";
import { FALLBACK_TILE, ITEMS, TILES } from "./tiles.ts";
-import { V2, add_v2, ceil_v2, floor_v2, length, lerp_exp_v2_mut, normalize } from "./util.ts";
+import { V2, add_v2, ceil_v2, floor_v2, length, lerp_exp_v2_mut, normalize, aabb_circle_distance } from "./util.ts";
+
+const PLAYER_SIZE = 0.4;
let ctx: CanvasRenderingContext2D;
let canvas: HTMLCanvasElement;
@@ -33,14 +35,31 @@ document.addEventListener("DOMContentLoaded", () => {
setInterval(tick_update, 1000 / 25);
})
-interface PlayerData { x: number; y: number, name: string, rot: number, hand?: ItemID, facing: V2 }
+interface ItemData {
+ kind: ItemIndex,
+ x: number,
+ y: number,
+ progress?: number
+}
+interface PlayerData {
+ x: number,
+ y: number,
+ name: string,
+ rot: number,
+ item?: ItemData,
+ facing: V2,
+ vel: { x: number, y: number }
+}
+interface TileData {
+ x: number
+ y: number
+ kind: TileIndex
+ item?: ItemData
+}
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_progress?: number }
const tiles = new Map<string, TileData>()
-let data: Gamedata = { item_names: [], tile_names: [] }
+let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0] }
let my_id: PlayerID = -1
const camera: V2 = { x: 0, y: 0 }
@@ -55,10 +74,12 @@ function packet(p: PacketC) {
my_id = p.id
data = p.data
break;
- case "add_player":
- if (p.hand) items.set(p.hand[0], { kind: p.hand[1], player: p.id, tracking_player: true, x: 0, y: 0 })
- players.set(p.id, { x: 0, y: 0, name: p.name, rot: 0, hand: p.hand?.[0], facing: { x: 0, y: 1 } })
+ case "add_player": {
+ let item = undefined
+ if (p.item) item = { kind: p.item, x: 0, y: 0 };
+ players.set(p.id, { x: data.spawn[0], y: data.spawn[1], name: p.name, rot: 0, item, facing: { x: 0, y: 1 }, vel: { x: 0, y: 0 } })
break;
+ }
case "remove_player":
players.delete(p.id)
break;
@@ -71,33 +92,34 @@ function packet(p: PacketC) {
break;
}
case "take_item": {
- const item = items.get(p.item)!
- item.tracking_player = true
- item.player = p.player
+ const player = players.get(p.player)!
+ const tile = tiles.get(p.tile.toString())!
+ player.item = tile.item;
+ tile.item = undefined
break;
}
case "put_item": {
- const item = items.get(p.item)!
- item.tracking_player = false
- item.tile = { x: p.pos[0], y: p.pos[1] }
+ const player = players.get(p.player)!
+ const tile = tiles.get(p.tile.toString())!
+ tile.item = player.item;
+ player.item = undefined
break;
}
- case "produce_item":
- items.set(p.id, { kind: p.kind, x: p.pos[0] + 0.5, y: p.pos[1] + 0.5, tracking_player: false, tile: { x: p.pos[0], y: p.pos[1] } })
- tiles.get(p.pos.toString())!.items.push(p.id)
+ case "produce_item": {
+ const item = { kind: p.item, x: p.tile[0] + 0.5, y: p.tile[1] + 0.5 }
+ tiles.get(p.tile.toString())!.item = item
break;
+ }
case "consume_item": {
- const t = tiles.get(p.pos.toString())!
- t.items.splice(t.items.indexOf(p.id))
- items.delete(p.id)
+ tiles.get(p.tile.toString())!.item = undefined
break;
}
case "set_active": {
- tiles.get(p.tile.toString())!.active_progress = p.progress
+ tiles.get(p.tile.toString())!.item!.progress = p.progress
break;
}
case "update_map":
- tiles.set(p.pos.toString(), { x: p.pos[0], y: p.pos[1], kind: p.tile, items: [] })
+ tiles.set(p.pos.toString(), { x: p.pos[0], y: p.pos[1], kind: p.tile })
break;
default:
console.warn("unknown packet", p);
@@ -136,6 +158,7 @@ function tick_update() {
send({ type: "position", pos: [p.x, p.y], rot: p.rot })
}
+
function frame_update(dt: number) {
const p = players.get(my_id)
if (!p) return
@@ -147,26 +170,37 @@ function frame_update(dt: number) {
if (length(input) > 0.1) lerp_exp_v2_mut(p.facing, input, dt * 10.)
p.rot = Math.atan2(p.facing.x, p.facing.y)
- p.x += input.x * dt * 5
- p.y += input.y * dt * 5
+ p.vel.x += input.x * dt * 0.5
+ p.vel.y += input.y * dt * 0.5
+ p.x += p.vel.x
+ p.y += p.vel.y
+ collide_player(p)
+ lerp_exp_v2_mut(p.vel, { x: 0, y: 0 }, dt * 5.)
+
- for (const [_, i] of items) {
- lerp_exp_v2_mut(i, i.tracking_player ? players.get(i.player!)! : add_v2(i.tile!, 0.5), dt * 10.)
+ const update_item = (item: ItemData, parent: V2) => {
+ lerp_exp_v2_mut(item, parent, dt * 10.)
+ }
+ for (const [_, player] of players) {
+ if (player.item) update_item(player.item, player)
}
+ for (const [_, tile] of tiles) {
+ if (tile.item) update_item(tile.item, add_v2(tile, 0.5))
+ }
+
lerp_exp_v2_mut(interact_target_anim, get_interact_target() ?? { x: 0, y: 0 }, dt * 15.)
lerp_exp_v2_mut(camera, p, dt * 10.)
}
-
function resize() {
canvas.width = globalThis.innerWidth
canvas.height = globalThis.innerHeight
}
-let last_frame = Date.now()
+let last_frame = performance.now()
function draw() {
- const now = Date.now()
+ const now = performance.now()
frame_update((now - last_frame) / 1000)
last_frame = now;
if (ws.readyState == ws.CONNECTING) draw_wait("Connecting...")
@@ -226,29 +260,26 @@ function draw_ingame() {
ctx.save()
ctx.translate(player.x, player.y)
ctx.rotate(-player.rot)
+
ctx.fillStyle = "rgb(226, 176, 26)"
- const psize = 0.6;
- ctx.fillRect(-psize / 2, -psize / 2, psize, psize)
- ctx.restore()
- }
+ ctx.beginPath()
+ ctx.arc(0, 0, PLAYER_SIZE, 0, Math.PI * 2)
+ ctx.fill()
+
+ ctx.fillStyle = "rgb(103, 79, 7)"
+ ctx.beginPath()
+ ctx.arc(0, -0.2, PLAYER_SIZE, 0, Math.PI * 2)
+ ctx.fill()
- for (const [_, item] of items) {
- ctx.save()
- ctx.translate(item.x, item.y)
- const comps = ITEMS[data.item_names[item.kind]] ?? FALLBACK_ITEM
- for (const c of comps) {
- c(ctx)
- }
ctx.restore()
+
+ if (player.item) draw_item(player.item)
}
for (const [_, tile] of tiles) {
ctx.save()
ctx.translate(tile.x, tile.y)
- if (tile.active_progress !== null && tile.active_progress !== undefined) {
- ctx.fillStyle = "rgba(115, 230, 58, 0.66)"
- ctx.fillRect(0, 0, 1, tile.active_progress)
- }
+ if (tile.item) draw_item(tile.item)
ctx.restore()
}
@@ -257,6 +288,20 @@ function draw_ingame() {
ctx.restore()
}
+function draw_item(item: ItemData) {
+ ctx.save()
+ ctx.translate(item.x, item.y)
+ const comps = ITEMS[data.item_names[item.kind]] ?? FALLBACK_ITEM
+ for (const c of comps) {
+ c(ctx)
+ }
+ if (item.progress !== null && item.progress !== undefined) {
+ ctx.fillStyle = "rgba(115, 230, 58, 0.66)"
+ ctx.fillRect(0, 0, 1, item.progress)
+ }
+ ctx.restore()
+}
+
function draw_interact_target() {
ctx.save()
ctx.translate(interact_target_anim.x, interact_target_anim.y)
@@ -286,3 +331,27 @@ function draw_grid() {
}
ctx.stroke()
}
+
+function collide_player(p: PlayerData) {
+ const tiles_ignored = ["floor", "door", "chair"].map(t => data.tile_names.indexOf(t))
+ for (const [_, tile] of tiles) {
+ if (tiles_ignored.includes(tile.kind)) continue
+ const d = aabb_circle_distance(tile.x, tile.y, tile.x + 1, tile.y + 1, p.x, p.y)
+ if (d > PLAYER_SIZE) continue
+
+ const h = 0.01
+ const d_sample_x = aabb_circle_distance(tile.x, tile.y, tile.x + 1, tile.y + 1, p.x + h, p.y)
+ const d_sample_y = aabb_circle_distance(tile.x, tile.y, tile.x + 1, tile.y + 1, p.x, p.y + h)
+ const grad_x = (d_sample_x - d) / h
+ const grad_y = (d_sample_y - d) / h
+
+ p.x += (PLAYER_SIZE - d) * grad_x
+ p.y += (PLAYER_SIZE - d) * grad_y
+
+ const vdotn = (grad_x * p.vel.x) + (grad_y * p.vel.y)
+ p.vel.x -= grad_x * vdotn
+ p.vel.y -= grad_y * vdotn
+
+
+ }
+}
diff --git a/test-client/protocol.ts b/test-client/protocol.ts
index 96657db7..98fcbf39 100644
--- a/test-client/protocol.ts
+++ b/test-client/protocol.ts
@@ -1,12 +1,12 @@
export type Vec2 = [number, number] // x, y
export type PlayerID = number
-export type ItemID = number
export type ItemIndex = number
export type TileIndex = number
export interface Gamedata {
item_names: string[], // Look-up table for ItemIndex
tile_names: string[], // Look-up table for TileIndex
+ spawn: Vec2, // Where players spawn when they join.
}
export type PacketS =
@@ -17,13 +17,12 @@ export type PacketS =
export type PacketC =
{ type: "init", id: PlayerID, data: Gamedata } // You joined
- | { type: "add_player", id: PlayerID, name: string, hand?: [ItemID, ItemIndex] } // Somebody else joined (or was already in the game)
+ | { type: "add_player", id: PlayerID, name: string, item?: ItemIndex } // Somebody else joined (or was already in the game)
| { type: "remove_player", id: PlayerID } // Somebody left
| { type: "position", player: PlayerID, pos: Vec2, rot: number } // Update the position of a players (your own position is included here)
- | { type: "take_item", item: ItemID, player: PlayerID } // An item was taken from a tile
- | { type: "put_item", item: ItemID, pos: Vec2 } // An item was put on a tile
- | { type: "produce_item", id: ItemID, pos: Vec2, kind: ItemIndex } // A tile generated a new item
- | { type: "consume_item", id: ItemID, pos: Vec2 } // A tile removed an item
+ | { type: "take_item", tile: Vec2, player: PlayerID } // An item was taken from a tile
+ | { type: "put_item", tile: Vec2, player: PlayerID } // An item was put on a tile
+ | { type: "produce_item", tile: Vec2, item: ItemIndex } // A tile generated a new item
+ | { type: "consume_item", tile: Vec2 } // A tile removed an item
| { type: "set_active", tile: Vec2, progress?: number } // A tile is doing something. progress goes from 0 to 1, then null when finished
| { type: "update_map", pos: Vec2, tile: TileIndex } // A map tile was changed
-
diff --git a/test-client/tiles.ts b/test-client/tiles.ts
index 57d96c0f..5eee2b32 100644
--- a/test-client/tiles.ts
+++ b/test-client/tiles.ts
@@ -55,6 +55,11 @@ function arrange_items(...items: string[]): Component[] {
})
}
+const door: Component = c => {
+ c.fillStyle = "#ff9843"
+ c.fillRect(-0.5, -0.1, 1, 0.2)
+}
+
const plate = [circle(0.4, "#b6b6b6", "#f7f7f7", 0.02)];
export const FALLBACK_ITEM: Component[] = [circle(0.3, "#f0f")];
@@ -76,19 +81,25 @@ export const ITEMS: { [key: string]: Component[] } = {
const table = [base("rgb(133, 76, 38)")];
const floor = [base("#333", "#222", 0.05)];
-const spawn = (i: string) => [base("#60701e", "#b9da37", 0.05), ...ITEMS[i]];
+const counter = [base("rgb(182, 172, 164)")];
+const crate = (i: string) => [base("#60701e", "#b9da37", 0.05), ...ITEMS[i]];
export const FALLBACK_TILE: Component[] = [base("#f0f")];
export const TILES: { [key: string]: Component[] } = {
"floor": floor,
"table": table,
- "counter": [base("rgb(182, 172, 164)")],
+ "counter": counter,
+ "door": [...floor, door],
+ "chair": [...floor, circle(0.45, "rgb(136, 83, 41)")],
+ "wall": [base("rgb(0, 14, 56)")],
+ "window": [base("rgb(233, 233, 233)")],
+ "watercooler": [...floor, circle(0.4, "rgb(64, 226, 207)")],
"trash": [...floor, circle(0.4, "rgb(20, 20, 20)"), cross(0.3, "rgb(90, 36, 36)")],
"sink": [base("rgb(131, 129, 161)", "rgb(177, 174, 226)", 0.2)],
"oven": [base("rgb(241, 97, 61)", "rgb(109, 84, 84)", 0.3)],
- "pan": [...table, circle(0.4, "#444", "#999")],
- "flour-spawn": spawn("flour"),
- "dirty-plate-spawn": spawn("dirty-plate"),
- "raw-steak-spawn": spawn("raw-steak"),
- "tomato-spawn": spawn("tomato"),
+ "pan": [...counter, circle(0.4, "#444", "#999")],
+ "flour-crate": crate("flour"),
+ "dirty-plate-crate": crate("dirty-plate"),
+ "raw-steak-crate": crate("raw-steak"),
+ "tomato-crate": crate("tomato"),
}
diff --git a/test-client/util.ts b/test-client/util.ts
index baebc61e..23679974 100644
--- a/test-client/util.ts
+++ b/test-client/util.ts
@@ -21,3 +21,16 @@ export function add_v2(p: V2, o: V2 | number) {
if (typeof o == "number") return { x: p.x + o, y: p.y + o }
else return { x: p.x + o.x, y: p.y + o.y }
}
+
+export function aabb_circle_distance(
+ min_x: number,
+ min_y: number,
+ max_x: number,
+ max_y: number,
+ px: number,
+ py: number
+): number {
+ const dx = px - Math.max(min_x, Math.min(max_x, px))
+ const dy = py - Math.max(min_y, Math.min(max_y, py))
+ return Math.sqrt(dx * dx + dy * dy)
+}