import { ItemData, PLAYER_SIZE, camera, canvas, ctx, data, get_interact_target, interact_possible_anim, 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 = item.progress_warn ? "rgba(230, 58, 58, 0.66)" : "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) * interact_possible_anim ctx.strokeStyle = `hsla(225, ${interact_possible_anim * 100}%, 62.70%, ${interact_possible_anim * 0.7 + 0.3})` 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() if (character > 0) { 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, } }