var ctx; var canvas; var lastX=0; var lastY=0; var dragStart = null; var dragged = false; var scaleFactor = 1.1; var roomSize = {width: 64, height: 64}; var spacing = 32; var rooms = [ {"id":0,"x":16,"y":16,"name":"temple"}, {"id":1,"x":16,"y":112,"name":"street"}, {"id":2,"x":112,"y":112,"name":"street"}, {"id":3,"x":208,"y":112,"name":"street"}, {"id":4,"x":208,"y":208,"name":"guild"}, {"id":5,"x":304,"y":112,"name":"street"}, {"id":6,"x":400,"y":112,"name":"street"}, {"id":7,"x":208,"y":16,"name":"shop"}, {"id":8,"x":112,"y":208,"name":"street"}, {"id":9,"x":112,"y":304,"name":"old well"}, {"id":10,"x":208,"y":304,"name":"bank"}, {"id":11,"x":112,"y":400,"name":"post office"}, {"id":12,"x":16,"y":304,"name":"street"}, {"id":13,"x":16,"y":400,"name":"street"}, {"id":14,"x":16,"y":496,"name":"street"}, {"id":15,"x":16,"y":592,"name":"street"}, {"id":16,"x":16,"y":688,"name":"south gate"}, {"id":17,"x":-80,"y":304,"name":"street"}, {"id":18,"x":-176,"y":304,"name":"street"}, {"id":19,"x":-176,"y":400,"name":"cemetary"}, {"id":21,"x":-176,"y":112,"name":"street"}, {"id":23,"x":-368,"y":112,"name":"street"}, {"id":24,"x":-464,"y":112,"name":"west gate"}, {"id":25,"x":-368,"y":16,"name":"street"}, {"id":26,"x":-272,"y":-80,"name":"street"}, {"id":27,"x":-368,"y":-80,"name":"mikes"}, {"id":28,"x":-176,"y":16,"name":"merchantrow"}, {"id":29,"x":-80,"y":16,"name":"smithy"}, {"id":30,"x":-80,"y":-80,"name":"forge"}, {"id":31,"x":-176,"y":-80,"name":"armourer"}, {"id":32,"x":-80,"y":112,"name":"street"}, {"id":33,"x":-272,"y":112,"name":"street"}, ]; var loadedRooms = JSON.parse(localStorage.getItem("map-rooms")); if(loadedRooms != null) { rooms = loadedRooms; } var links = [ {"start":0,"end":1,"exit":"s","enter":"n"}, {"start":1,"end":2,"exit":"e","enter":"w"}, {"start":3,"end":2,"exit":"w","enter":"e"}, {"start":4,"end":3,"exit":"n","enter":"s"}, {"start":3,"end":5,"exit":"e","enter":"w"}, {"start":5,"end":6,"exit":"e","enter":"w"}, {"start":3,"end":7,"exit":"n","enter":"s"}, {"start":2,"end":8,"exit":"s","enter":"n"}, {"start":8,"end":9,"exit":"s","enter":"n"}, {"start":9,"end":10,"exit":"e","enter":"w"}, {"start":9,"end":11,"exit":"s","enter":"n"}, {"start":9,"end":12,"exit":"w","enter":"e"}, {"start":12,"end":13,"exit":"s","enter":"n"}, {"start":13,"end":14,"exit":"s","enter":"n"}, {"start":14,"end":15,"exit":"s","enter":"n"}, {"start":15,"end":16,"exit":"s","enter":"n"}, {"start":12,"end":17,"exit":"w","enter":"e"}, {"start":17,"end":18,"exit":"w","enter":"e"}, {"start":18,"end":19,"exit":"s","enter":"n"}, {"start":23,"end":24,"exit":"w","enter":"e"}, {"start":23,"end":25,"exit":"n","enter":"s"}, {"start":25,"end":26,"exit":"ne","enter":"sw"}, {"start":25,"end":27,"exit":"n","enter":"s"}, {"start":26,"end":28,"exit":"se","enter":"nw"}, {"start":28,"end":29,"exit":"e","enter":"w"}, {"start":29,"end":30,"exit":"n","enter":"s"}, {"start":30,"end":31,"exit":"w","enter":"e"}, {"start":31,"end":28,"exit":"s","enter":"n"}, {"start":28,"end":21,"exit":"s","enter":"n"}, {"start":21,"end":32,"exit":"e","enter":"w"}, {"start":32,"end":29,"exit":"n","enter":"s"}, {"start":32,"end":1,"exit":"e","enter":"w"}, {"start":23,"end":33,"exit":"e","enter":"w"}, {"start":33,"end":21,"exit":"e","enter":"w"}, ]; var loadedLinks = JSON.parse(localStorage.getItem("map-links")); if(loadedLinks != null) { links = loadedLinks; } var activeRoom = rooms[0]; var movingRoom = false; var roomToMove = null; var moveLink = false; var linkToMove = null; function resizeCanvas() { // look up the size the canvas is being displayed const width = canvas.clientWidth; const height = canvas.clientHeight; // If its resolution does not match change it if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; return true; } return false; } function clearMap() { var p1 = ctx.transformedPoint(0,0); var p2 = ctx.transformedPoint(canvas.width,canvas.height); ctx.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); ctx.save(); ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.restore(); } function drawRoom(room) { if(room.id == activeRoom.id) { ctx.fillStyle = "#FFFFFF"; } else { ctx.fillStyle = "#DDDDDD"; } ctx.strokeStyle = "#000000"; ctx.fillRect(room.x,room.y,roomSize.width,roomSize.height); ctx.strokeRect(room.x,room.y,roomSize.width,roomSize.height); ctx.fillStyle = "#000000"; ctx.fillText(room.name, room.x + 5, room.y + 11); } function getRoomExit(room, direction) { var exit = {x:0, y:0}; var exitOffset = {x:0, y:0}; switch(direction) { case "n": exitOffset.x = roomSize.width / 2; exitOffset.y = 0; break; case "s": exitOffset.x = roomSize.width / 2; exitOffset.y = roomSize.height; break; case "e": exitOffset.x = roomSize.width; exitOffset.y = roomSize.height / 2; break; case "w": exitOffset.x = 0; exitOffset.y = roomSize.height / 2; break; case "nw": exitOffset.x = 0; exitOffset.y = 0; break; case "ne": exitOffset.x = roomSize.width; exitOffset.y = 0; break; case "sw": exitOffset.x = 0; exitOffset.y = roomSize.height; break; case "se": exitOffset.x = roomSize.width; exitOffset.y = roomSize.height; break; } exit.x = exitOffset.x + room.x; exit.y = exitOffset.y + room.y; return exit; } function getWiggle(start, end, exit, enter) { var wiggle = {start: {x: 0, y: 0}, end: {x:0, y:0}}; switch(exit) { case "n": wiggle.start.x = -16; wiggle.start.y = -16; break; case "s": wiggle.start.x = -16; wiggle.start.y = 16; break; case "e": wiggle.start.x = 16; wiggle.start.y = -16; break; case "w": wiggle.start.x = -16; wiggle.start.y = -16; break; case "ne": wiggle.start.x = 0; wiggle.start.y = -16; break; case "nw": wiggle.start.x = 0; wiggle.start.y = -16; break; case "se": wiggle.start.x = 16; wiggle.start.y = 0; break; case "sw": wiggle.start.x = -16; wiggle.start.y = 0; break; } switch(enter) { case "n": wiggle.end.x = 16; wiggle.end.y = -16; break; case "s": wiggle.end.x = 16; wiggle.end.y = 16; break; case "e": wiggle.end.x = 16; wiggle.end.y = 16; break; case "w": wiggle.end.x = -16; wiggle.end.y = 16; break; case "ne": wiggle.end.x = 16; wiggle.end.y = 0; break; case "se": wiggle.end.x = 0; wiggle.end.y = 16; break; case "nw": wiggle.end.x = -16; wiggle.end.y = 0; break; case "sw": wiggle.end.x = 0; wiggle.end.y = 16; break; } return wiggle; } function getRoomById(roomId) { for(var roomIndex in rooms) { var room = rooms[roomIndex]; if(room.id == roomId) { return room; } } } function drawLink(link, rooms) { var startRoom = getRoomById(link.start); var endRoom = getRoomById(link.end); if(startRoom == null || endRoom == null) { //TODO: delete link return; } var start = getRoomExit(startRoom, link.exit); var end = getRoomExit(endRoom, link.enter); var wiggle = getWiggle(start, end, link.exit, link.enter); ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.bezierCurveTo(start.x+wiggle.start.x,start.y+wiggle.start.y,end.x+wiggle.end.x,end.y+wiggle.end.y,end.x,end.y); ctx.stroke(); } function drawLinkToPoint(link, point) { var startRoom = getRoomById(link.start); var endRoom = getRoomById(link.end); if(startRoom == null || endRoom == null) { //TODO: delete link return; } var start = getRoomExit(startRoom, link.exit); var end = getRoomExit(endRoom, link.enter); var wiggle = getWiggle(start, end, link.exit, link.enter); ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.bezierCurveTo(start.x+wiggle.start.x,start.y+wiggle.start.y,end.x+wiggle.end.x,end.y+wiggle.end.y,end.x,end.y); ctx.stroke(); } function getOppositeDir(dir) { switch(dir) { case "n": return "s"; case "s": return "n"; case "e": return "w"; case "w": return "e"; case "ne": return "sw"; case "nw": return "se"; case "se": return "nw"; case "sw": return "ne"; case "u": return "d"; case "d": return "u"; } } function drawMap() { clearMap(); rooms.map(room => drawRoom(room)); links.map(link => drawLink(link, rooms)); if(moveLink) { drawLink(linkToMove, rooms); } } function handleMouseDown(evt) { if(movingRoom) { movingRoom = false; localStorage.setItem("map-rooms", JSON.stringify(rooms)); return; } if(moveLink && activeRoom.id != linkToMove.start) { moveLink = false; links.push(linkToMove); localStorage.setItem("map-links", JSON.stringify(links)); return; } document.body.style.mozUserSelect = 'none'; document.body.style.webkitUserSelect = 'none'; document.body.style.userSelect = 'none'; lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); dragStart = ctx.transformedPoint(lastX,lastY); dragged = false; } function checkRoomHover(room, mouseTransform) { if(mouseTransform.x >= room.x && mouseTransform.x <= room.x + roomSize.width && mouseTransform.y >= room.y && mouseTransform.y <= room.y + roomSize.height) { setContextToRoom(room); drawMap(); } } function handleMouseMove(evt) { if(movingRoom) { newX = evt.offsetX || (evt.pageX - canvas.offsetLeft); newY = evt.offsetY || (evt.pageY - canvas.offsetTop); var pt = ctx.transformedPoint(newX - (roomSize.width / 2),newY - (roomSize.height / 2)); roomToMove.x = pt.x; roomToMove.y = pt.y; drawMap(); return; } if(moveLink) { linkToMove.end = activeRoom.id; } setContextToWorld(); var mouseTransform = ctx.transformedPoint(event.clientX, event.clientY); rooms.map(room => checkRoomHover(room, mouseTransform)); lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); if (dragStart){ dragged = true; var pt = ctx.transformedPoint(lastX,lastY); ctx.translate(pt.x-dragStart.x,pt.y-dragStart.y); drawMap(); } hideContextMenu(); } function handleMouseUp(evt) { dragStart = null; dragged = false; } function zoomMap(clicks){ var pt = ctx.transformedPoint(lastX,lastY); ctx.translate(pt.x,pt.y); var factor = Math.pow(scaleFactor,clicks); ctx.scale(factor,factor); ctx.translate(-pt.x,-pt.y); drawMap(); } function handleScroll(evt){ var delta = evt.wheelDelta ? evt.wheelDelta/40 : evt.detail ? -evt.detail : 0; if (delta) { zoomMap(delta); } return evt.preventDefault() && false; }; function showContextMenu(x, y) { var menu = document.getElementsByClassName("context-menu-container")[0]; menu.style.display="block"; menu.style.top = y-15 + "px"; menu.style.left = x-15 + "px"; } function hideContextMenu() { var menu = document.getElementsByClassName("context-menu-container")[0]; menu.style.display="none"; } function setContextToWorld() { document.getElementById("world-menu").style.display= "block"; document.getElementById("room-menu").style.display= "none"; } function setContextToRoom(room) { activeRoom = room; document.getElementById("world-menu").style.display= "none"; var roomMenu = document.getElementById("room-menu"); roomMenu.style.display= "block"; roomMenu.getElementsByClassName("title")[0].innerHTML = room.name; } function handleContextMenu(event) { event.preventDefault(); showContextMenu(event.clientX, event.clientY); dragStart = null; dragged = false; } function handleResize(event) { var canvas = document.getElementById("mapper"); var ctx = canvas.getContext("2d"); ctx.setTransform(1,0,0,1,0,0); resizeCanvas(); drawMap(); } function setActiveRoom(input) { for(var linkId in links) { var link = links[linkId]; if(activeRoom.id == link.start && input == link.exit) { activeRoom = getRoomById(link.end); return true; } if(activeRoom.id == link.end && input == link.enter) { activeRoom = getRoomById(link.start); return true; } } return false; } function getNewRoomIndex() { var newIndex = 0; for(var roomId in rooms) { if(rooms[roomId].id > newIndex) { newIndex = rooms[roomId].id; } } return newIndex + 1; } function setOrCreateActiveRoom(input) { if(setActiveRoom(input)) { return; } var newRoom = {id: getNewRoomIndex(), x: activeRoom.x, y: activeRoom.y, name:"newroom"}; if(input.indexOf("n") != -1) { newRoom.y = activeRoom.y - (spacing + roomSize.height); } if(input.indexOf("s") != -1) { newRoom.y = activeRoom.y + (spacing + roomSize.height); } if(input.indexOf("w") != -1) { newRoom.x = activeRoom.x - (spacing + roomSize.width); } if(input.indexOf("e") != -1) { newRoom.x = activeRoom.x + (spacing + roomSize.width); } rooms.push(newRoom); links.push({start:activeRoom.id, end:newRoom.id, exit:input, enter:getOppositeDir(input)}) activeRoom = newRoom; localStorage.setItem("map-rooms", JSON.stringify(rooms)); localStorage.setItem("map-links", JSON.stringify(links)); } function keyboardListener(event) { var key = event.keyCode; switch(key) { case 78: inputBuffer = "n"; break; case 83: inputBuffer = "s"; break; case 69: inputBuffer += "e"; break; case 87: inputBuffer += "w"; break; case 38: setActiveRoom("n"); inputBuffer = ""; break; case 40: setActiveRoom("s"); inputBuffer = ""; break; case 39: setActiveRoom("e"); inputBuffer = ""; break; case 37: setActiveRoom("w"); inputBuffer = ""; break; case 13: if(inputBuffer != "") { setOrCreateActiveRoom(inputBuffer); } inputBuffer = ""; break; default: inputBuffer = ""; console.warn("Unhandled keypress: ", key); break; } drawMap(); } function showModal(modalId) { var modal = document.getElementById(modalId); modal.parentNode.style.display = "block"; modal.style.display = "block"; } function closeModal(modalId) { var modal = document.getElementById(modalId); modal.parentNode.style.display = "none"; modal.style.display = "none"; } window.addEventListener("resize", handleResize, false); document.addEventListener("DOMContentLoaded", function(event) { document.getElementById("reset-view").addEventListener('click', function(event) { var canvas = document.getElementById("mapper"); var ctx = canvas.getContext("2d"); ctx.setTransform(1,0,0,1,0,0); lastX=canvas.width/2; lastY=canvas.height/2; hideContextMenu(); resizeCanvas(); drawMap(); }); document.getElementById("delete-room").addEventListener('click', function(event) { var roomToDelete = rooms.indexOf(activeRoom); rooms.splice(roomToDelete, 1); for(var linkId = links.length - 1; linkId >= 0; linkId--) { var link = links[linkId]; if(activeRoom.id == link.start || activeRoom.id == link.end) { links.splice(linkId, 1); } } localStorage.setItem("map-rooms", JSON.stringify(rooms)); localStorage.setItem("map-links", JSON.stringify(links)); hideContextMenu(); drawMap(); }); document.getElementById("rename-room").addEventListener('click', function(event) { var newName = prompt("Please enter the new room name:", activeRoom.name); activeRoom.name = newName; localStorage.setItem("map-rooms", JSON.stringify(rooms)); localStorage.setItem("map-links", JSON.stringify(links)); hideContextMenu(); drawMap(); }); document.getElementById("move-room").addEventListener('click', function(event) { movingRoom = true; roomToMove = activeRoom; hideContextMenu(); drawMap(); }); var modalCloseButtons = document.getElementsByClassName("modal-close"); for(var i = 0; i < modalCloseButtons.length; i++) { modalCloseButtons[i].addEventListener("click", function(event) { this.parentNode.style.display = "none"; this.parentNode.parentNode.style.display = "none"; }); } document.getElementById("manage-links").addEventListener('click', function(event) { var modal = document.getElementById("link-editor-modal"); modal.style.display = "block"; modal.parentNode.style.display = "block"; hideContextMenu(); }); document.getElementById("add-link").addEventListener('click', function(event) { var linkSourceId = activeRoom.id; moveLink = true; var exitDir = prompt("Please enter the exit direction (n, s, e, w):"); var oppositeDir = getOppositeDir(exitDir); linkToMove = {start:linkSourceId, end:null, exit:exitDir, enter:oppositeDir}; hideContextMenu(); }); document.getElementById("export-map").addEventListener('click', function(event) { showModal("export-modal"); var exportData = {"rooms": rooms, "links": links, "version": 1}; var exportText = JSON.stringify(exportData); var exportField = document.getElementById("export-text"); exportField.value = btoa(exportText); hideContextMenu(); }); document.getElementById("import-map").addEventListener('click', function(event) { showModal("import-modal"); document.getElementById("import-text").value = ""; hideContextMenu(); }); document.getElementById("load-import-data").addEventListener("click", function(event) { var newData = document.getElementById("import-text").value; var potentialLoad = null; try { potentialLoad = JSON.parse(atob(newData)); } catch (exception) { console.error("Invalid import data", exception); } if(potentialLoad != null) { rooms = potentialLoad.rooms; links = potentialLoad.links; localStorage.setItem("map-rooms", JSON.stringify(rooms)); localStorage.setItem("map-links", JSON.stringify(links)); drawMap(); closeModal("import-modal"); } }); canvas = document.getElementById("mapper"); canvas.addEventListener('mousedown',handleMouseDown,false); canvas.addEventListener('mousemove',handleMouseMove,false); canvas.addEventListener('mouseup',handleMouseUp, false); canvas.addEventListener('DOMMouseScroll',handleScroll,false); canvas.addEventListener('mousewheel',handleScroll,false); canvas.addEventListener('contextmenu', handleContextMenu, false); ctx = canvas.getContext('2d'); attachTransforms(ctx); lastX=canvas.width/2; lastY=canvas.height/2; resizeCanvas(); drawMap(); }); var inputBuffer = ""; window.addEventListener("keydown", keyboardListener);