diff options
| author | metamuffin <metamuffin@disroot.org> | 2024-06-19 22:52:37 +0200 | 
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2024-06-23 19:21:49 +0200 | 
| commit | 6ca76cc0568f3d60b280f11ae07a34303c317f34 (patch) | |
| tree | ac867e47b3fb4b6ed99189cb302b2741b107d272 /test-client | |
| parent | 3dc8cc2abb4e6a7be8237b86dab6ebed75fa43cb (diff) | |
| download | hurrycurry-6ca76cc0568f3d60b280f11ae07a34303c317f34.tar hurrycurry-6ca76cc0568f3d60b280f11ae07a34303c317f34.tar.bz2 hurrycurry-6ca76cc0568f3d60b280f11ae07a34303c317f34.tar.zst | |
implement customer communication
Diffstat (limited to 'test-client')
| -rw-r--r-- | test-client/main.ts | 187 | ||||
| -rw-r--r-- | test-client/protocol.ts | 7 | ||||
| -rw-r--r-- | test-client/tiles.ts | 37 | ||||
| -rw-r--r-- | test-client/visual.ts | 172 | 
4 files changed, 239 insertions, 164 deletions
| diff --git a/test-client/main.ts b/test-client/main.ts index 807567c8..b34220df 100644 --- a/test-client/main.ts +++ b/test-client/main.ts @@ -1,14 +1,13 @@  /// <reference lib="dom" /> -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, aabb_circle_distance } from "./util.ts"; +import { Gamedata, ItemIndex, Message, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts"; +import { V2, add_v2, length, lerp_exp_v2_mut, normalize, aabb_circle_distance } from "./util.ts"; +import { draw_ingame, draw_wait } from "./visual.ts"; -const PLAYER_SIZE = 0.4; +export const PLAYER_SIZE = 0.4; -let ctx: CanvasRenderingContext2D; -let canvas: HTMLCanvasElement; +export let ctx: CanvasRenderingContext2D; +export let canvas: HTMLCanvasElement;  let ws: WebSocket;  document.addEventListener("DOMContentLoaded", () => {      const ws_uri = window.location.protocol.endsWith("s:") @@ -38,7 +37,7 @@ document.addEventListener("DOMContentLoaded", () => {      setInterval(tick_update, 1000 / 25);  }) -interface ItemData { +export interface ItemData {      kind: ItemIndex,      x: number,      y: number, @@ -46,7 +45,7 @@ interface ItemData {      progress?: number      remove_anim?: number  } -interface PlayerData { +export interface PlayerData {      x: number,      y: number,      name: string, @@ -54,30 +53,31 @@ interface PlayerData {      item?: ItemData,      facing: V2,      character: number, -    vel: { x: number, y: number } +    vel: { x: number, y: number }, +    message?: Message,  } -interface TileData { +export interface TileData {      x: number      y: number      kind: TileIndex      item?: ItemData  } -const players = new Map<PlayerID, PlayerData>() -const tiles = new Map<string, TileData>() -const items_removed = new Set<ItemData>() -let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0] } +export const players = new Map<PlayerID, PlayerData>() +export const tiles = new Map<string, TileData>() +export const items_removed = new Set<ItemData>() + +export let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0] } +  let my_id: PlayerID = -1 -const camera: V2 = { x: 0, y: 0 } -let camera_zoom = 0.1 -const interact_target_anim: V2 = { x: 0, y: 0 } +export const camera: V2 = { x: 0, y: 0 } +export const interact_target_anim: V2 = { x: 0, y: 0 }  let interacting: V2 | undefined; -let scale = 0  function send(p: PacketS) { ws.send(JSON.stringify(p)) }  function packet(p: PacketC) { -    if (!["position", "set_active"].includes(p.type)) +    if (!["position", "set_active", "update_map"].includes(p.type))          console.log(p);      switch (p.type) {          case "init": @@ -144,12 +144,17 @@ function packet(p: PacketC) {          case "update_map":              tiles.set(p.pos.toString(), { x: p.pos[0], y: p.pos[1], kind: p.tile })              break; +        case "communicate": { +            const player = players.get(p.player)! +            player.message = p.message +            break; +        }          default:              console.warn("unknown packet", p);      }  } -const keys_down = new Set(); +export const keys_down = new Set();  const HANDLED_KEYS = ["KeyW", "KeyA", "KeyS", "KeyD", "Space"]  function keyboard(ev: KeyboardEvent, down: boolean) {      if (HANDLED_KEYS.includes(ev.code)) ev.preventDefault() @@ -157,7 +162,7 @@ function keyboard(ev: KeyboardEvent, down: boolean) {      else keys_down.delete(ev.code)  } -function get_interact_target(): V2 | undefined { +export function get_interact_target(): V2 | undefined {      const me = players.get(my_id)      if (!me) return      return { @@ -244,144 +249,6 @@ function draw() {      else throw new Error(`ws state invalid`);      requestAnimationFrame(draw)  } -function draw_wait(text: string) { -    ctx.fillStyle = "#444" -    ctx.fillRect(0, 0, canvas.width, canvas.height) -    ctx.fillStyle = "#555" -    ctx.font = "50px sans-serif" -    ctx.strokeStyle = "black" -    ctx.fillStyle = "white" -    ctx.lineWidth = 10 -    ctx.textAlign = "center" -    ctx.textBaseline = "middle" -    ctx.lineJoin = "round" -    ctx.lineCap = "round" -    ctx.strokeText(text, canvas.width / 2, canvas.height / 2) -    ctx.fillText(text, canvas.width / 2, canvas.height / 2) -} - - -function map_screen_to_world(screen: V2): V2 { -    return { -        x: ((screen.x - canvas.width / 2) / scale) + camera.x, -        y: ((screen.y - canvas.height / 2) / scale) + camera.y, -    } -} - -function draw_ingame() { -    ctx.fillStyle = "#111" -    ctx.fillRect(0, 0, canvas.width, canvas.height) - -    scale = Math.min(canvas.width, canvas.height) * camera_zoom; -    ctx.save() -    ctx.translate(canvas.width / 2, canvas.height / 2) -    ctx.scale(scale, scale) -    ctx.translate(-camera.x, -camera.y) - -    draw_grid() - -    for (const [_, tile] of tiles) { -        ctx.save() -        ctx.translate(tile.x + 0.5, tile.y + 0.5) -        const comps = TILES[data.tile_names[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) -        ctx.rotate(-player.rot) - -        ctx.fillStyle = `hsl(${player.character}rad, 50%, 50%)` -        ctx.beginPath() -        ctx.arc(0, 0, PLAYER_SIZE, 0, Math.PI * 2) -        ctx.fill() - -        ctx.fillStyle = `hsl(${player.character}rad, 80%, 10%)` -        ctx.beginPath() -        ctx.arc(0, -0.2, PLAYER_SIZE, 0, Math.PI * 2) -        ctx.fill() - -        ctx.fillStyle = `hsl(${player.character}rad, 80%, 70%)` -        ctx.beginPath() -        ctx.moveTo(-0.04, 0.25) -        ctx.lineTo(0.04, 0.25) -        ctx.lineTo(0, 0.4) -        ctx.fill() - -        ctx.restore() - -        if (player.item) draw_item(player.item) -    } - -    for (const item of items_removed) { -        draw_item(item) -    } -    for (const [_, tile] of tiles) { -        if (tile.item) draw_item(tile.item) -    } - -    draw_interact_target() - -    ctx.restore() - -    if (keys_down.has("KeyP")) { -        camera_zoom = 0.05 -        ctx.fillStyle = "white" -        ctx.textAlign = "left" -        ctx.textBaseline = "bottom" -        ctx.font = "20px sans-serif" -        ctx.fillText(`interact = ${JSON.stringify(get_interact_target())}`, 10, 30) -    } else { camera_zoom = 0.1 } -} - -function draw_item(item: ItemData) { -    ctx.save() -    ctx.translate(item.x, item.y) -    if (item.remove_anim) ctx.scale(1 - item.remove_anim, 1 - item.remove_anim) -    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.5, -0.5, 1, item.progress) -    } -    ctx.restore() -} - -function draw_interact_target() { -    ctx.save() -    ctx.translate(interact_target_anim.x, interact_target_anim.y) - -    ctx.lineCap = "round" -    ctx.lineJoin = "round" -    ctx.lineWidth = 0.06 + 0.03 * Math.sin(Date.now() / 100) -    ctx.strokeStyle = "rgb(84, 122, 236)" -    ctx.strokeRect(0, 0, 1, 1) - -    ctx.restore() -} - -function draw_grid() { -    ctx.strokeStyle = "#333" -    ctx.lineWidth = 0.01 -    ctx.beginPath() -    const min = floor_v2(map_screen_to_world({ x: 0, y: 0 })) -    const max = ceil_v2(map_screen_to_world({ x: canvas.width, y: canvas.height })) -    for (let x = min.x; x < max.x; x++) { -        ctx.moveTo(x, min.y) -        ctx.lineTo(x, max.y) -    } -    for (let y = min.y; y < max.y; y++) { -        ctx.moveTo(min.x, y) -        ctx.lineTo(max.x, y) -    } -    ctx.stroke() -}  function collide_player(p: PlayerData) {      const tiles_ignored = ["floor", "door", "chair"].map(t => data.tile_names.indexOf(t)) diff --git a/test-client/protocol.ts b/test-client/protocol.ts index aa3aa063..c20588ce 100644 --- a/test-client/protocol.ts +++ b/test-client/protocol.ts @@ -25,4 +25,9 @@ export type PacketC =      | { 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, neighbours: [TileIndex | null] } // A map tile was changed +    | { type: "update_map", pos: Vec2, tile: TileIndex, neighbors: [TileIndex | null] } // A map tile was changed +    | { type: "communicate", player: PlayerID, message?: Message } // A map tile was changed + +export type Message = +    { item: number } +    | { text: string } diff --git a/test-client/tiles.ts b/test-client/tiles.ts index 5eee2b32..5a6769ed 100644 --- a/test-client/tiles.ts +++ b/test-client/tiles.ts @@ -7,8 +7,39 @@ function base(fill: string, stroke?: string, stroke_width?: number): Component {          c.lineWidth = stroke_width ?? 0.05          c.lineJoin = "miter"          c.lineCap = "square" -        c.fillRect(-0.5, -0.5, 1, 1) -        if (stroke) c.strokeRect(-0.5 + c.lineWidth / 2, -0.5 + c.lineWidth / 2, 1 - c.lineWidth, 1 - c.lineWidth) +        c.fillRect( +            -0.5, +            -0.5, +            1, +            1 +        ) +        if (stroke) c.strokeRect( +            -0.5 + c.lineWidth / 2, +            -0.5 + c.lineWidth / 2, +            1 - c.lineWidth, +            1 - c.lineWidth +        ) +    } +} +function rect(inset: 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.lineJoin = "round" +        c.lineCap = "round" +        c.fillRect( +            -0.5 + inset, +            -0.5 + inset, +            1 - inset * 2, +            1 - inset * 2 +        ) +        if (stroke) c.strokeRect( +            -0.5 + inset, +            -0.5 + inset, +            1 - inset * 2, +            1 - inset * 2 +        )      }  }  function circle(radius: number, fill: string, stroke?: string, stroke_width?: number): Component { @@ -93,7 +124,7 @@ export const TILES: { [key: string]: Component[] } = {      "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)")], +    "cuttingboard": [...counter, rect(0.3, "rgb(158, 236, 68)", "rgb(158, 236, 68)", 0.2)],      "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)], diff --git a/test-client/visual.ts b/test-client/visual.ts new file mode 100644 index 00000000..4bbfac6c --- /dev/null +++ b/test-client/visual.ts @@ -0,0 +1,172 @@ +import { ItemData, PLAYER_SIZE, camera, canvas, ctx, data, get_interact_target, interact_target_anim, items_removed, keys_down, players, tiles } from "./main.ts"; +import { Message } from "./protocol.ts"; +import { FALLBACK_TILE, ITEMS, TILES, FALLBACK_ITEM } from "./tiles.ts"; +import { V2, ceil_v2, floor_v2 } from "./util.ts"; + +let camera_zoom = 0.1 +let scale = 0 + +export function draw_wait(text: string) { +    ctx.fillStyle = "#444" +    ctx.fillRect(0, 0, canvas.width, canvas.height) +    ctx.fillStyle = "#555" +    ctx.font = "50px sans-serif" +    ctx.strokeStyle = "black" +    ctx.fillStyle = "white" +    ctx.lineWidth = 10 +    ctx.textAlign = "center" +    ctx.textBaseline = "middle" +    ctx.lineJoin = "round" +    ctx.lineCap = "round" +    ctx.strokeText(text, canvas.width / 2, canvas.height / 2) +    ctx.fillText(text, canvas.width / 2, canvas.height / 2) +} + + +export function draw_ingame() { +    ctx.fillStyle = "#111" +    ctx.fillRect(0, 0, canvas.width, canvas.height) + +    scale = Math.min(canvas.width, canvas.height) * camera_zoom; +    ctx.save() +    ctx.translate(canvas.width / 2, canvas.height / 2) +    ctx.scale(scale, scale) +    ctx.translate(-camera.x, -camera.y) + +    draw_grid() + +    for (const [_, tile] of tiles) { +        ctx.save() +        ctx.translate(tile.x + 0.5, tile.y + 0.5) +        const comps = TILES[data.tile_names[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) +            { +                ctx.save() +                ctx.rotate(-player.rot) +                draw_character(player.character) +                ctx.restore() +            } +            if (player.message) draw_message(player.message) +            ctx.restore() +        } +        if (player.item) draw_item(player.item) +    } + +    for (const item of items_removed) { +        draw_item(item) +    } +    for (const [_, tile] of tiles) { +        if (tile.item) draw_item(tile.item) +    } + +    draw_interact_target() + +    ctx.restore() + +    if (keys_down.has("KeyP")) { +        camera_zoom = 0.05 +        ctx.fillStyle = "white" +        ctx.textAlign = "left" +        ctx.textBaseline = "bottom" +        ctx.font = "20px sans-serif" +        ctx.fillText(`interact = ${JSON.stringify(get_interact_target())}`, 10, 30) +    } else { camera_zoom = 0.1 } +} + +function draw_item(item: ItemData) { +    ctx.save() +    ctx.translate(item.x, item.y) +    if (item.remove_anim) ctx.scale(1 - item.remove_anim, 1 - item.remove_anim) +    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.5, -0.5, 1, item.progress) +    } +    ctx.restore() +} + +function draw_interact_target() { +    ctx.save() +    ctx.translate(interact_target_anim.x, interact_target_anim.y) + +    ctx.lineCap = "round" +    ctx.lineJoin = "round" +    ctx.lineWidth = 0.06 + 0.03 * Math.sin(Date.now() / 100) +    ctx.strokeStyle = "rgb(84, 122, 236)" +    ctx.strokeRect(0, 0, 1, 1) + +    ctx.restore() +} + +function draw_grid() { +    ctx.strokeStyle = "#333" +    ctx.lineWidth = 0.01 +    ctx.beginPath() +    const min = floor_v2(map_screen_to_world({ x: 0, y: 0 })) +    const max = ceil_v2(map_screen_to_world({ x: canvas.width, y: canvas.height })) +    for (let x = min.x; x < max.x; x++) { +        ctx.moveTo(x, min.y) +        ctx.lineTo(x, max.y) +    } +    for (let y = min.y; y < max.y; y++) { +        ctx.moveTo(min.x, y) +        ctx.lineTo(max.x, y) +    } +    ctx.stroke() +} + +function draw_character(character: number) { +    ctx.fillStyle = `hsl(${character}rad, 50%, 50%)` +    ctx.beginPath() +    ctx.arc(0, 0, PLAYER_SIZE, 0, Math.PI * 2) +    ctx.fill() + +    ctx.fillStyle = `hsl(${character}rad, 80%, 10%)` +    ctx.beginPath() +    ctx.arc(0, -0.2, PLAYER_SIZE, 0, Math.PI * 2) +    ctx.fill() + +    ctx.fillStyle = `hsl(${character}rad, 80%, 70%)` +    ctx.beginPath() +    ctx.moveTo(-0.04, 0.25) +    ctx.lineTo(0.04, 0.25) +    ctx.lineTo(0, 0.4) +    ctx.fill() +} + +function draw_message(m: Message) { +    ctx.save() +    ctx.translate(0, -1) +    if ("item" in m) { +        ctx.fillStyle = "#fffa" +        ctx.beginPath() +        ctx.moveTo(0, 0.7) +        ctx.arc(0, 0, 0.5, Math.PI / 4, Math.PI - Math.PI / 4, true) +        ctx.closePath() +        ctx.fill() + +        const comps = ITEMS[data.item_names[m.item]] ?? FALLBACK_ITEM +        for (const c of comps) c(ctx) +    } +    ctx.restore() +} + +function map_screen_to_world(screen: V2): V2 { +    return { +        x: ((screen.x - canvas.width / 2) / scale) + camera.x, +        y: ((screen.y - canvas.height / 2) / scale) + camera.y, +    } +} + | 
