let canvas; let context; let username = localStorage.getItem("username"); let userData = { name: "", coordinates: { x: 0, y: 0 }, inventory: [], position: { x: 0.5, y: 0.5 }, size: { width: 0.03, height: 0.07 }, speed: 0.005, sprite: 'medievalUnit_01.png' }; let lockUserMovement = false; let loadingDelayTimeout; let showLoadingMessage; let otherPlayerCache = {}; let otherPlayers = {}; let roomData = { name: "", walls: [], tiles: [], decals: [] }; let keys = []; let sendMovement = false; let userChanged = false; let atlas = new SpriteAtlas('assets', 'medievalRTS_spritesheet@2.xml'); const apidataurl = `http://jsonapi.eyeofmidas.net/data`; //const websocketurl = `ws://${location.host}/ws`; const websocketurl = `ws://jsonapi.eyeofmidas.net:8080`; let webSocket; function ajaxGet(url) { return new Promise((resolve, reject) => { var r = new XMLHttpRequest(); r.open("GET", url, true); r.onreadystatechange = function (response) { if (r.readyState == 4 && r.status == 200) { resolve(JSON.parse(r.responseText)); } if (r.readyState == 4 && r.status != 200) { reject(JSON.parse(r.response)); } return; } r.onerror = function (response) { console.error("onerror", response, r); reject(r.responseText); } r.setRequestHeader("Content-Type", "application/json"); r.send(); }); } function ajaxPost(url, data) { return new Promise((resolve, reject) => { var r = new XMLHttpRequest(); r.open("POST", url, true); r.onreadystatechange = function (response) { if (r.readyState == 4 && r.status == 200) { resolve(JSON.parse(r.responseText)); } if (r.readyState == 4 && r.status != 200) { reject(r.responseText); } return; } r.onerror = function (response) { reject(r.responseText); } r.setRequestHeader("Content-Type", "application/json"); let jsonData = JSON.stringify(data); r.send(jsonData); }); } function websocketSafeSend(message) { //console.log("%c" + message, "color: #22FF22"); try { webSocket.send(message); } catch (error) { console.error("websocket", error); } } function handleMessage(message) { //console.log("%c" + message, "color: #FF2222"); splitMessage = message.split(" "); switch (splitMessage[0]) { case "join": addOtherPlayer(splitMessage[1], splitMessage[2], splitMessage[3]); break; case "leave": removeOtherPlayer(splitMessage[1]); break; case "move": moveOtherPlayer(splitMessage[1], splitMessage[2], splitMessage[3]); break; case "population": splitMessage.shift(); setRoomPopulation(splitMessage); break; default: console.log(message); break; } } async function getPlayerData(playerKey) { if (otherPlayerCache[playerKey]) { return otherPlayerCache[playerKey]; } let result = await ajaxGet(`${apidataurl}/${playerKey}`); otherPlayerCache[playerKey] = result; return otherPlayerCache[playerKey]; } function addOtherPlayer(playerKey, x, y) { let cleanName = userData.name.replace(/\s/g, ''); if (playerKey == `user-${cleanName}` || playerKey == "") { return; } otherPlayers[playerKey] = { name: "", position: { x: x, y: y }, size: { width: 0.0125, height: 0.0167 }, sprite: 'medievalUnit_01.png' }; getPlayerData(playerKey).then((response) => { otherPlayers[playerKey].name = response.name; otherPlayers[playerKey].size.width = response.size.width; otherPlayers[playerKey].size.height = response.size.height; otherPlayers[playerKey].sprite = response.sprite; //TODO: populate other data }); } function removeOtherPlayer(playerKey) { let cleanName = userData.name.replace(/\s/g, ''); if (playerKey == `user-${cleanName}`) { return; } delete otherPlayers[playerKey]; } function moveOtherPlayer(playerKey, x, y) { let cleanName = userData.name.replace(/\s/g, ''); if (playerKey == `user-${cleanName}`) { return; } if (!otherPlayers[playerKey]) { addOtherPlayer(playerKey, x, y); } otherPlayers[playerKey].position.x = parseFloat(x); otherPlayers[playerKey].position.y = parseFloat(y); } function setRoomPopulation(otherUserData) { for (let i = 0; i < otherUserData.length; i += 3) { addOtherPlayer(otherUserData[i], otherUserData[i + 1], otherUserData[i + 2]); } } document.addEventListener("DOMContentLoaded", () => { for (let i = 0; i < 256; i++) { keys[i] = false; } canvas = document.getElementsByTagName("canvas")[0]; context = canvas.getContext("2d"); context.canvas.width = canvas.clientWidth; context.canvas.height = canvas.clientHeight; atlas.load().then(() => { animateLoop(); getRoom(0, 0); webSocket = new WebSocket(websocketurl); webSocket.onmessage = function (event) { handleMessage(event.data); } webSocket.onopen = function (event) { } if (!username) { username = prompt("Please enter your username"); } if (!username) { return; } let cleanName = username.replace(/\s/g, ''); ajaxGet(`${apidataurl}/user-${cleanName}`).then(result => { userData = result; localStorage.setItem("username", cleanName); getRoom(userData.coordinates.x, userData.coordinates.y); }, (error) => { if (error.errorCode == 404) { userData.name = username; let create = confirm("Would you like to create the new user '" + userData.name + "'?"); if (!create) { localStorage.removeItem("username"); return; } let validStartingUnits = [ 'medievalUnit_01.png', // 'medievalUnit_02.png', // 'medievalUnit_03.png', // 'medievalUnit_04.png', // 'medievalUnit_05.png', 'medievalUnit_06.png', 'medievalUnit_07.png', // 'medievalUnit_08.png', // 'medievalUnit_09.png', // 'medievalUnit_10.png', // 'medievalUnit_11.png', 'medievalUnit_12.png', 'medievalUnit_13.png', // 'medievalUnit_14.png', // 'medievalUnit_15.png', // 'medievalUnit_16.png', // 'medievalUnit_17.png', 'medievalUnit_18.png', 'medievalUnit_19.png', // 'medievalUnit_20.png', // 'medievalUnit_21.png', // 'medievalUnit_22.png', // 'medievalUnit_23.png', 'medievalUnit_24.png', ]; userData.sprite = validStartingUnits[Math.floor(validStartingUnits.length * Math.random())]; saveUser().then(() => { localStorage.setItem("username", cleanName); getRoom(userData.coordinates.x, userData.coordinates.y); }); } else { console.error(error); } }); }); }); let delta = { x: 0, y: 0 }; function draw(context) { delta.x = 0; delta.y = 0; if (keys[38] || keys[87]) { delta.y = -userData.speed; sendMovement = true; } if (keys[37] || keys[65]) { delta.x = -userData.speed; sendMovement = true; } if (keys[40] || keys[83]) { delta.y = userData.speed; sendMovement = true; } if (keys[39] || keys[68]) { delta.x = +userData.speed; sendMovement = true; } let userCurrentBounds = { x: userData.position.x, y: userData.position.y, width: userData.size.width, height: userData.size.height, }; let userFutureBounds = { x: userData.position.x + delta.x, y: userData.position.y + delta.y, width: userData.size.width, height: userData.size.height, }; for (let i = 0; i < roomData.walls.length; i++) { let wall = roomData.walls[i]; if (boxCollision(wall, userFutureBounds) && !boxCollision(wall, userCurrentBounds)) { let userFutureBoundsY = { x: userData.position.x, y: userData.position.y + delta.y, width: userData.size.width, height: userData.size.height, }; let userFutureBoundsX = { x: userData.position.x + delta.x, y: userData.position.y, width: userData.size.width, height: userData.size.height, }; if (boxCollision(wall, userFutureBoundsX)) { delta.x = 0; } if (boxCollision(wall, userFutureBoundsY)) { delta.y = 0; } if (delta.x == 0 && delta.y == 0) { sendMovement = false; } } } if (!lockUserMovement) { userData.position.x += delta.x; userData.position.y += delta.y; if (userData.position.x * canvas.width > canvas.width) { lockUserMovement = true; loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50); getRoom(userData.coordinates.x + 1, userData.coordinates.y).then(() => { userData.position.x = 0; userData.coordinates.x++; saveUser(); lockUserMovement = false; }); } if (userData.position.x * canvas.width < -userData.size.width) { lockUserMovement = true; loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50); getRoom(userData.coordinates.x - 1, userData.coordinates.y).then(() => { userData.position.x = 1 - userData.size.width; userData.coordinates.x--; saveUser(); lockUserMovement = false; }); } if (userData.position.y * canvas.height > canvas.height) { lockUserMovement = true; loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50); getRoom(userData.coordinates.x, userData.coordinates.y + 1).then(() => { userData.position.y = 0; userData.coordinates.y++; saveUser(); lockUserMovement = false; }); } if (userData.position.y * canvas.height < -userData.size.height) { lockUserMovement = true; loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50); getRoom(userData.coordinates.x, userData.coordinates.y - 1).then(() => { userData.position.y = 1 - userData.size.height; userData.coordinates.y--; saveUser(); lockUserMovement = false; }); } } context.save(); context.translate(-0.5, -0.5); drawRoom(context); drawOtherPlayers(context); drawPlayer(context); if (showLoadingMessage) { context.font = "30px Arial"; context.textAlign = "center"; context.fillStyle = "#FFFFFF"; context.fillText("Loading... please wait", 0.5 * canvas.width, 0.5 * canvas.height); } else { clearTimeout(loadingDelayTimeout); showLoadingMessage = false; } context.restore(); if (drawEditor != undefined) { drawEditor(context); } } function boxCollision(box1, box2) { return box1.x < box2.x + box2.width && box1.x + box1.width > box2.x && box1.y < box2.y + box2.height && box1.y + box1.height > box2.y; } function pointCollision(box, point) { return box.x < point.x && box.x + box.width > point.x && box.y < point.y && box.y + box.height > point.y; } async function saveUser() { if (!userData.name) { return; } let cleanName = userData.name.replace(/\s/g, ''); let result = await ajaxPost(`${apidataurl}/user-${cleanName}`, userData); userData = result; } async function getRoom(x, y) { try { let result = await ajaxGet(`${apidataurl}/room-${x},${y}`); roomData.name = result.name ?? ""; roomData.walls = result.walls ?? []; roomData.tiles = result.tiles ?? []; roomData.decals = result.decals ?? []; } catch (error) { let emptyRoom = { name: `${x},${y}`, walls: [], tiles: [], decals: [] }; addRandomTiles(emptyRoom); let result = await ajaxPost(`${apidataurl}/room-${x},${y}`, emptyRoom); roomData = result; } let cleanName = userData.name.replace(/\s/g, ''); otherPlayers = {}; if (userData.name) { websocketSafeSend(`subscribe user-${cleanName} room-${x},${y} ${userData.position.x} ${userData.position.y}`); } } const colors = [ "red", "green", "blue", "black", "purple", "orange" ]; function addRandomItems(room) { let items = []; for (let i = Math.floor(10 * Math.random()); i > 0; i--) { let newitem = { coordinates: { x: Math.random(), y: Math.random() }, color: colors[Math.floor(colors.length * Math.random())] }; items.push(newitem); } room.items = items; } function addRandomTiles(room) { let tileSize = 8 * 5; room.tiles = []; for (let i = 0; i < tileSize; i++) { room.tiles.push(57 + (Math.floor(2 * Math.random()))); } } function drawPlayer(context) { context.font = "14px Arial"; context.textAlign = "center"; let pos = inScreenSpace(userData.position); let size = inScreenSize(userData.size); let centerPos = { x: pos.x + (size.width / 2), y: pos.y + (size.height / 2) }; atlas.drawCentered(context, userData.sprite, centerPos); context.fillStyle = "#000000"; context.fillText(userData.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) - 1); context.fillText(userData.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) - 1); context.fillText(userData.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) + 1); context.fillText(userData.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) + 1); context.fillStyle = "#FFFFFF"; context.fillText(userData.name, centerPos.x, pos.y - 0.02 * canvas.height); } function inScreenSpace(serverPosition) { return { x: serverPosition.x * canvas.width, y: serverPosition.y * canvas.height }; } function inScreenSize(serverSize) { return { width: serverSize.width * canvas.width, height: serverSize.height * canvas.height }; } function drawOtherPlayers(context) { context.font = "14px Arial"; context.textAlign = "center"; for (let index in otherPlayers) { let player = otherPlayers[index]; let pos = inScreenSpace(player.position); let size = inScreenSize(player.size); let centerPos = { x: pos.x + (size.width / 2), y: pos.y + (size.height / 2) }; atlas.drawCentered(context, player.sprite, centerPos); context.fillStyle = "#000000"; context.fillText(player.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) - 1); context.fillText(player.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) - 1); context.fillText(player.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) + 1); context.fillText(player.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) + 1); context.fillStyle = "#FFFFFF"; context.fillText(player.name, centerPos.x, pos.y - 0.02 * canvas.height); } } function drawRoom(context) { let width = 126; let height = 126; for (var i = 0; i < roomData.tiles.length; i++) { let tile = roomData.tiles[i]; try { atlas.getSprite(tile); atlas.draw(context, tile, { x: ((i % 8) * width), y: (Math.floor(i / 8) * height) }); } catch (e) {} if (tile < 10) { tile = "0" + tile; } let tileName = `medievalTile_${tile}.png`; try { atlas.getSprite(tileName); atlas.draw(context, tileName, { x: ((i % 8) * width), y: (Math.floor(i / 8) * height) }); } catch (e) { // console.error("Room data error: tile index ", i, " is not defined"); continue; } } // for (var i = 0; i < roomData.items.length; i++) { // let item = roomData.items[i]; // context.beginPath(); // context.strokeStyle = item.color; // context.rect(item.coordinates.x * canvas.width, item.coordinates.y * canvas.height, 10, 10); // context.stroke(); // } // for (var i = 0; i < roomData.walls.length; i++) { // let wall = roomData.walls[i]; // context.beginPath(); // context.strokeStyle = wall.color ?? "red"; // context.rect(wall.x * canvas.width, wall.y * canvas.height, wall.width * canvas.width, wall.height * canvas.height); // context.stroke(); // } for (var i = 0; i < roomData.decals.length; i++) { let decal = roomData.decals[i]; atlas.draw(context, decal.spriteName, { x: decal.x * canvas.width, y: decal.y * canvas.height }); } context.font = "30px Arial"; context.fillText(roomData.name, 20, 40); } function animateLoop() { canvas.width = canvas.width; draw(context); window.requestAnimationFrame(animateLoop); } document.addEventListener("keydown", event => { keys[event.keyCode] = true; }); document.addEventListener("keyup", event => { keys[event.keyCode] = false; }); setInterval(() => { if (!sendMovement) { return; } userChanged = true; sendMovement = false; let cleanName = userData.name.replace(/\s/g, ''); websocketSafeSend(`move user-${cleanName} ${userData.position.x} ${userData.position.y}`); }, Math.floor(1000 / 30)); setInterval(() => { if (!userChanged) { return; } saveUser(); userChanged = false; }, Math.floor(60000));