diff --git a/client/css/editor.css b/client/css/editor.css new file mode 100644 index 0000000..cc3bb88 --- /dev/null +++ b/client/css/editor.css @@ -0,0 +1,21 @@ +canvas { + box-shadow: inset 0 0 1px red; +} + +#sidebar { + background-color: #191919; + position: absolute; + display: flex; + flex-direction: column; + border: 1px solid; + padding: 1rem; +} + +#mapgen { + display: flex; + flex-direction: column; +} + +#export { + margin-top: 1rem; +} diff --git a/client/css/main.css b/client/css/main.css index 87c145c..f8309de 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -70,4 +70,9 @@ input { border: solid 2px #fff; padding: .25rem; margin-bottom: .215rem; + box-sizing:content-box; +} + +p { + padding: .25rem; } diff --git a/client/img/atlas.png b/client/img/atlas.png index 240705b..cb9e802 100644 Binary files a/client/img/atlas.png and b/client/img/atlas.png differ diff --git a/client/index.html b/client/index.html index af15bbd..4d19ec0 100644 --- a/client/index.html +++ b/client/index.html @@ -11,7 +11,7 @@
- +
diff --git a/client/js/main.js b/client/js/main.js index c80f8f3..7e97bcb 100644 --- a/client/js/main.js +++ b/client/js/main.js @@ -17,8 +17,8 @@ lobby.style.display = "none"; join.onsubmit = function (event) { return __awaiter(this, void 0, void 0, function* () { event.preventDefault(); - const room_code = this.elements.room_code.value.trim(); - const player_name = this.elements.name.value.trim(); + const room_code = document.getElementById("room_code").value; + const player_name = document.getElementById("player_name").value; if (room_code == '') { alert('Please enter a room code'); return; diff --git a/client/mapeditor.html b/client/mapeditor.html new file mode 100644 index 0000000..4fca9de --- /dev/null +++ b/client/mapeditor.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+
+ + + +
+
+ + + + diff --git a/client/src/editor.ts b/client/src/editor.ts new file mode 100644 index 0000000..687f076 --- /dev/null +++ b/client/src/editor.ts @@ -0,0 +1,166 @@ +import { genMap } from "./map.js" +import { startGraphicsUpdater } from "./renderer.js" +import { GameState, Vec2, ATLAS_TILE_WIDTH, Tile } from "./types.js" + +const mapgen = document.getElementById("mapgen") +const sidebar = document.getElementById("sidebar") +sidebar.style.display = "none" + +mapgen.onsubmit = async function(event) { + event.preventDefault() + + const width_str = (document.getElementById("width")).value + const height_str = (document.getElementById("height")).value + + const width = parseInt(width_str) + const height = parseInt(height_str) + + if (!width || width < 3 || !height || height < 3) { + alert('Invalid numbers or dimensions too small') + return + } + + mapgen.style.display = "none" + + runMapEditor(width, height) +} + +const startKeyListener = () => { + + let keys = {} + + window.addEventListener("keydown", ev => { + if(ev.repeat) { + return; + } + if (ev.code == "KeyQ") { + if (sidebar.style.display === "none") { + sidebar.style.display = "" + } else { + sidebar.style.display = "none" + } + } + keys[ev.code] = true + }); + + window.addEventListener("keyup", ev => { + if (ev.repeat) { + return + } + keys[ev.code] = false + }) + + return () => { + return keys + } +} + +const trackMouseMovement = () => { + + let pos: Vec2 = {x : 0, y: 0} + + window.addEventListener("mousemove", ev => { + pos = {x: ev.x, y: ev.y} + }) + + return () => { + return pos + } +} + +const getTilePos = (width: number, height: number, mousePos: Vec2): Vec2 => { + + const canvas = document.getElementById("canvas") as HTMLCanvasElement + + const canvasRect = canvas.getBoundingClientRect() + + let posX = mousePos.x - canvasRect.x + let posY = mousePos.y - canvasRect.y + + let percentX = posX / canvasRect.width + let percentY = posY / canvasRect.height + + return { + x: Math.floor(percentX * width), + y: Math.floor(percentY * height) + } + +} + +const checkInputs = (pressed: {[key: string]: boolean}): Tile => { + + if (pressed["KeyW"]) { + return Tile.WALL + } else if (pressed["KeyG"]) { + return Tile.GHOST_WALL + } else if (pressed["KeyF"]) { + return Tile.FOOD + } else if (pressed["Digit1"]) { + return Tile.PLAYER_SPAWN_1 + } else if (pressed["Digit2"]) { + return Tile.PLAYER_SPAWN_2 + } else if (pressed["Digit3"]) { + return Tile.PLAYER_SPAWN_3 + } else if (pressed["Digit4"]) { + return Tile.PLAYER_SPAWN_4 + } else if (pressed["KeyT"]) { + return Tile.THICC_DOT + } else if (pressed["KeyI"]) { + return Tile.INITIAL_DOT + } else if (pressed["KeyC"]) { + return Tile.EMPTY + } + + return undefined +} + +const checkBounds = (tilePos: Vec2, width: number, height: number) => { + if (tilePos.x < 0 || tilePos.x >= width || tilePos.y < 0 || tilePos.y >= height) return false + return true +} + +const runMapEditor = (width: number, height: number) => { + + sidebar.style.display = "" + + let data: number[] = new Array(width * height).fill(0) + + genMap(width, height, data, Tile.EMPTY) + + let state: GameState = { + started: true, + input: {}, + players: {}, + items: {}, + mapId: 0 + } + + let frame = 0 + const updateGraphics = startGraphicsUpdater() + const getInput = startKeyListener() + const getMousePos = trackMouseMovement() + + const loop = () => { + + const mousePos = getMousePos() + const tilePos = getTilePos(width, height, mousePos) + + const pressed = getInput() + const tile = checkInputs(pressed) + + if (tile !== undefined && checkBounds(tilePos, width, height)) { + let current = data[tilePos.y * width + tilePos.x]; + if (current != tile) { + data[tilePos.y * width + tilePos.x] = tile + genMap(width, height, data, 0) + } + } + + updateGraphics(state, frame, true) + + requestAnimationFrame(loop) + } + + requestAnimationFrame(loop) + +} diff --git a/client/src/logic/movement.ts b/client/src/logic/movement.ts index 40cfc3e..f03008b 100644 --- a/client/src/logic/movement.ts +++ b/client/src/logic/movement.ts @@ -1,5 +1,5 @@ import { getMap } from "../map.js" -import { Vec2, Map, Rotation, Key, Player, GameState } from "../types.js" +import { Vec2, Map, Rotation, Key, Player, GameState, Tile } from "../types.js" const MOVE_SPEED = .1 @@ -20,7 +20,7 @@ const getTile = ( ): number => { let x = Math.round(pos.x + ox) let y = Math.round(pos.y + oy) - if (x < 0 || x >= map.width || y < 0 || y >= map.height) return 1 + if (x < 0 || x >= map.width || y < 0 || y >= map.height) return Tile.WALL return map.data[y * map.width + x] } @@ -73,13 +73,11 @@ const incrementPos = ( pos.x -= speed break case Rotation.EAST: - pos.y += speed + pos.x += speed break } } -let i = 0 - const updateMovementForPlayer = ( map: Map, player: Player, @@ -91,7 +89,7 @@ const updateMovementForPlayer = ( let currentPosition = player.pos let turningFrontTile = getTileFrontWithRot(map, currentPosition, inputRot) - if (turningFrontTile == 1 || turningFrontTile == 2) { + if (turningFrontTile == Tile.WALL || turningFrontTile == Tile.GHOST_WALL) { inputRot = Rotation.NOTHING } @@ -109,7 +107,7 @@ const updateMovementForPlayer = ( incrementPos(movePos, moveRot, MOVE_SPEED) let frontTile = getTileFrontWithRot(map, currentPosition, moveRot) - if (frontTile != 1 && frontTile != 2) { + if (frontTile != Tile.WALL && frontTile != Tile.GHOST_WALL) { player.pos = movePos player.moving = true } else { diff --git a/client/src/logic/players.ts b/client/src/logic/players.ts index ebe469f..96779fe 100644 --- a/client/src/logic/players.ts +++ b/client/src/logic/players.ts @@ -33,7 +33,7 @@ export const updatePlayers = (data: GameState, input: Input) => { data.players[added] ||= { pos: {x: 1, y: 1}, inputRotation: Rotation.EAST, - moveRotation: Rotation.EAST, + moveRotation: Rotation.NOTHING, moving: false, }; diff --git a/client/src/main.ts b/client/src/main.ts index a6cc3ba..c8a1758 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -9,9 +9,9 @@ lobby.style.display = "none" join.onsubmit = async function(event) { event.preventDefault() - - const room_code = this.elements.room_code.value.trim() - const player_name = this.elements.name.value.trim() + + const room_code = (document.getElementById("room_code")).value + const player_name = (document.getElementById("player_name")).value if (room_code == '') { alert('Please enter a room code') diff --git a/client/src/map.ts b/client/src/map.ts index e6fab9d..d26c467 100644 --- a/client/src/map.ts +++ b/client/src/map.ts @@ -126,6 +126,24 @@ export const genItems = (map: Map): Items => { let mapData: Maps = {} let id: number = 0 +export const genMap = ( + width: number, + height: number, + data: number[], + mapId: number, +): Map => { + + mapData[mapId] = { + data: structuredClone(data), + walls: genWalls(width, height, data), + width, + height, + id: mapId + } + + return mapData[mapId] +} + export const loadMap = ( width: number, height: number, diff --git a/client/src/net/game.ts b/client/src/net/game.ts index c8e5991..88a36b1 100644 --- a/client/src/net/game.ts +++ b/client/src/net/game.ts @@ -52,7 +52,6 @@ export class Game { * If the frame is ahead of the current latest frame, the game will be run until that frame. */ setInput(frame: number, input: Input) { - console.log('input', frame, input) this.editFrame(frame, (index: number): void => { let past = this.history[index - 1]; if(index === 0) { @@ -66,7 +65,6 @@ export class Game { } setData(frame: number, data: GameState) { - console.log('data', frame, data) this.editFrame(frame, (index: number): void => { this.history[index] = { data, diff --git a/client/src/net/multiplayer.ts b/client/src/net/multiplayer.ts index e9f3057..5dcf86d 100644 --- a/client/src/net/multiplayer.ts +++ b/client/src/net/multiplayer.ts @@ -80,7 +80,7 @@ export function multiplayer( function update(input: PlayerInput, frame: number) { if(input === undefined) { // used to update the game locally if(hasState) { - applyInput({}) + applyInput({frame}) } return; } diff --git a/client/src/renderer.ts b/client/src/renderer.ts index c7bbbc2..8482ca6 100644 --- a/client/src/renderer.ts +++ b/client/src/renderer.ts @@ -1,7 +1,5 @@ import { getMap } from "./map.js"; -import { Items, Players, Rotation, ItemType, Map, Wall, GameState } from "./types.js"; - -const ATLAS_TILE_WIDTH = 32 +import { Items, Players, Rotation, ItemType, Map, Wall, GameState, Tile, ATLAS_TILE_WIDTH } from "./types.js"; const update_style = (width: number, height: number) => { @@ -257,20 +255,82 @@ const draw_map_canvas = ( } +const draw_debug_sprites = ( + ctx: CanvasRenderingContext2D, + atlas: CanvasImageSource, + map: Map +) => { + + for (let y = 0; y < map.height; y++) { + for (let x = 0; x < map.width; x++) { + + let tile_type = map.data[y * map.width + x] + + + let atlas_index: [number, number]; + switch (tile_type) { + case Tile.EMPTY: + case Tile.WALL: + continue + case Tile.GHOST_WALL: + atlas_index = [4, 0] + break + case Tile.FOOD: + atlas_index = [3, 0] + break + case Tile.PLAYER_SPAWN_1: + atlas_index = [3, 1] + break + case Tile.PLAYER_SPAWN_2: + atlas_index = [4, 1] + break + case Tile.PLAYER_SPAWN_3: + atlas_index = [3, 2] + break + case Tile.PLAYER_SPAWN_4: + atlas_index = [4, 2] + break + case Tile.THICC_DOT: + atlas_index = [4, 3] + break + case Tile.INITIAL_DOT: + atlas_index = [3, 3] + break + } + + draw_sprite ( + ctx, + x, + y, + 1, + atlas, + atlas_index, + ATLAS_TILE_WIDTH, + 0 + ) + + } + } +} + let map_canvas = document.createElement("canvas") const draw_map = ( ctx: CanvasRenderingContext2D, atlas: CanvasImageSource, map: Map, - last: number | undefined + last: number | undefined, + editor: boolean ) => { - if (map.id !== last) { + if (map.id !== last || editor) { map_canvas.width = map.width * ATLAS_TILE_WIDTH map_canvas.height = map.height * ATLAS_TILE_WIDTH let map_ctx = map_canvas.getContext("2d") draw_map_canvas(map_ctx, atlas, map) + if (editor) { + draw_debug_sprites(map_ctx, atlas, map) + } } ctx.drawImage ( @@ -292,7 +352,8 @@ export const startGraphicsUpdater = () => { */ return ( data: GameState, - frame: number + frame: number, + editor: boolean = false ) => { let map = getMap(data.mapId) @@ -308,7 +369,7 @@ export const startGraphicsUpdater = () => { let ctx = canvas.getContext("2d") ctx.clearRect(0, 0, canvas.width, canvas.height) - draw_map(ctx, atlas, map, last_map_drawn) + draw_map(ctx, atlas, map, last_map_drawn, editor) draw_items(ctx, atlas, data.items) draw_players(ctx, atlas, data.players, frame) update_style(map.width, map.height) diff --git a/client/src/types.ts b/client/src/types.ts index df0c8b1..02d5adb 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -1,4 +1,20 @@ +export const ATLAS_TILE_WIDTH = 32 + +export enum Tile { + EMPTY, + WALL, + GHOST_WALL, + FOOD, + PLAYER_SPAWN_1, + PLAYER_SPAWN_2, + PLAYER_SPAWN_3, + PLAYER_SPAWN_4, + GHOST_SPAWN, + THICC_DOT, + INITIAL_DOT +} + export enum Wall { EMPTY, WALL_HZ, @@ -63,8 +79,8 @@ export type Player = { pos: Vec2, moveRotation: Rotation, inputRotation: Rotation, - name?: string, - moving: boolean + moving: boolean, + name?: string } export type PlayerInput = {