Celestial/viewer.js

448 lines
11 KiB
JavaScript

// Demo https://codepen.io/deltabouche/full/WJPbaQ/
// Requires p5.js and lodash
const MapBase = "http://localhost:3000/";
const Maps = [
"0-Intro",
"1-ForsakenCity",
"1H-ForsakenCity",
"1X-ForsakenCity",
"2-OldSite",
"2H-OldSite",
"2X-OldSite",
"3-CelestialResort",
"3H-CelestialResort",
"3X-CelestialResort",
"4-GoldenRidge",
"4H-GoldenRidge",
"4X-GoldenRidge",
"5-MirrorTemple",
"5H-MirrorTemple",
"5X-MirrorTemple",
"6-Reflection",
"6H-Reflection",
"6X-Reflection",
"7-Summit",
"7H-Summit",
"7X-Summit",
"8-Epilogue",
"9-Core",
"9H-Core",
"9X-Core"
]
let state = {
mapPath: "0-Intro",
dataReady: false,
camera: {
x: 200,
y: 200,
scale: 1
},
levelCamera: {
x: 5,
y: 5
},
screen: {
width: 1280,
height: 720
},
map: null,
thumbnails: [],
levelIndex: -1,
hoverLevel: -1
};
const SolidColors = {
"0": "#000000", //0
"1": "#FF0000", //1
"2": "#00FF00", //2
"3": "#0000FF", //3
"4": "#FF00FF", //4
"5": "#FFFF00", //5
"6": "#00FFFF", //6
"7": "#888888", //7
"g": "#FF00FF"
};
function setupThumbnails() {
const levels = childByName(state.map.root, 'levels');
let visIndex = 0;
_.each(levels.children, (lvl, lvlIndex) => {
let pg = createGraphics(lvl.attributes.width, lvl.attributes.height);
const solids = childrenByName(lvl, 'solids');
_.each(solids, solid => {
let tx = 0;
let ty = 0;
_.each(solid.attributes.innerText, v => {
if (v == 10) {
ty++;
tx=0;
return;
}
const tRect = {
x: solid.attributes.offsetX + tx * 8 + 1,
y: solid.attributes.offsetY + ty * 8 + 1,
width: 7,
height: 7
}
pg.noStroke();
const color = SolidColors[String.fromCharCode(v)] || 200;
pg.fill(color);
pg.rect(tRect.x / 2, tRect.y/ 2, tRect.width/ 2, tRect.height/ 2);
tx++;
});
});
state.thumbnails.push(pg)
});
}
function resetCamera() {
state.camera = { x: 200, y: 200, scale: 0.5 };
}
function resetLevelCamera() {
state.levelCamera = { x: 5, y: 5 };
}
function loadMap(path) {
state.dataReady = false;
state.thumbnails = [];
state.map = null;
state.mapPath = path;
state.levelIndex = -1;
fetch(`${MapBase}/${state.mapPath}.json`).then(res => res.json()).then(json => {
state.map = json;
setupThumbnails();
state.dataReady = true;
addLevelSelect();
resetCamera();
})
}
function loadLevel(idx) {
if (idx >= 0) resetLevelCamera();
state.levelIndex = parseInt(idx, 10);
}
function setup() {
addMapSelect();
let canvasElement = createCanvas(state.screen.width, state.screen.height).elt;
canvasElement.addEventListener('contextmenu', event => { event.preventDefault(); });
let context = canvasElement.getContext('2d');
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
loadMap(state.mapPath);
}
function childrenByName(el, name) {
return _.filter(el.children, { name });
}
function childByName(el, name) {
return _.find(el.children, { name });
}
function drawAxes() {
stroke("#0000FF");
line(state.camera.x, 0, state.camera.x, state.screen.height);
line(0, state.camera.y, state.screen.width, state.camera.y);
}
function drawHeadings() {
text(state.map.root.package, 20, 20);
}
function screenTransformRect(rect) {
return {
x: rect.x * state.camera.scale + state.camera.x,
y: rect.y * state.camera.scale + state.camera.y,
width: rect.width * state.camera.scale,
height: rect.height * state.camera.scale
}
}
function screenLevelTransformRect(rect) {
return {
x: (rect.x + state.levelCamera.x * 8) * 2,
y: (rect.y + state.levelCamera.y * 8) * 2,
width: rect.width * 2,
height: rect.height * 2
}
}
function pointInRect(x, y, rect) {
return x > rect.x && x < rect.x + rect.width && y > rect.y && y < rect.y + rect.height;
}
function rectIsVisible(rect) {
return rect.x + rect.width > 0 && rect.y + rect.height > 0 && rect.x < state.screen.width && rect.y < state.screen.height
}
function drawFillers() {
const fillers = childByName(state.map.root, 'Filler');
_.each(fillers.children, (fillRect) => {
const drawRect = screenTransformRect({
x: fillRect.attributes.x * 8,
y: fillRect.attributes.y * 8,
width: fillRect.attributes.w * 8,
height: fillRect.attributes.h * 8
});
if (!rectIsVisible(drawRect)) return;
stroke("#FF0000");
fill("#330000");
rect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
});
}
function drawLevels() {
const levels = childByName(state.map.root, 'levels');
let visIndex = 0;
_.each(levels.children, (lvl, lvlIndex) => {
fill(255);
stroke(255);
const drawRect = screenTransformRect(lvl.attributes);
if (!rectIsVisible(drawRect)) return;
// text(`${drawRect.x}, ${drawRect.y}, ${drawRect.width}, ${drawRect.height}`, 10, 80 + 20 * visIndex);
visIndex++;
image(state.thumbnails[lvlIndex], drawRect.x, drawRect.y, drawRect.width, drawRect.height);
noFill();
if (pointInRect(mouseX, mouseY, screenTransformRect(lvl.attributes))) {
text(lvl.attributes.name, 10, 80);
stroke(255);
state.hoverLevel = lvlIndex;
} else {
stroke(64);
}
rect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
});
}
function processInput() {
if (state.levelIndex === -1) {
if (keyIsDown(LEFT_ARROW)) {
state.camera.x += 8;
}
if (keyIsDown(RIGHT_ARROW)) {
state.camera.x -= 8;
}
if (keyIsDown(UP_ARROW)) {
state.camera.y += 8;
}
if (keyIsDown(DOWN_ARROW)) {
state.camera.y -= 8;
}
} else {
if (keyIsDown(LEFT_ARROW)) {
state.levelCamera.x += 1;
}
if (keyIsDown(RIGHT_ARROW)) {
state.levelCamera.x -= 1;
}
if (keyIsDown(UP_ARROW)) {
state.levelCamera.y += 1;
}
if (keyIsDown(DOWN_ARROW)) {
state.levelCamera.y -= 1;
}
}
}
function keyPressed() {
if (keyCode === 81) {
state.camera.x *= 1.5;
state.camera.y *= 1.5;
state.camera.scale *= 1.5;
}
if (keyCode === 65) {
state.camera.x /= 1.5;
state.camera.y /= 1.5;
state.camera.scale /= 1.5;
}
if (keyCode === 27) {
let select = document.getElementById('level-select');
select.value = -1;
loadLevel(-1);
}
}
function mousePressed() {
if (mouseButton === RIGHT) {
if (state.hoverLevel >= 0) {
let idx = state.hoverLevel;
state.hoverLevel = -1;
let select = document.getElementById('level-select');
select.value = idx;
loadLevel(idx);
}
}
}
function drawHud() {
text(`${mouseX}, ${mouseY}`, 10, 50);
}
function drawLoading() {
text("Loading...", state.screen.width / 2, state.screen.height / 2);
}
function drawOverview() {
drawAxes();
drawHud();
drawHeadings();
drawFillers();
drawLevels();
}
function drawLevel() {
const levels = childByName(state.map.root, 'levels');
const level = levels.children[state.levelIndex];
// draw surrounding levels
_.each(levels.children, (sLevel, i) => {
if (i == state.levelIndex) return;
const drawRect = screenLevelTransformRect({
x: sLevel.attributes.x - level.attributes.x,
y: sLevel.attributes.y - level.attributes.y,
width: sLevel.attributes.width,
height: sLevel.attributes.height
});
if (!rectIsVisible(drawRect)) return;
if (pointInRect(mouseX, mouseY, drawRect)) {
stroke(128, 128, 128, 255);
tint(255, 192);
state.hoverLevel = i;
} else {
stroke(128, 128, 128, 128);
tint(255, 128);
}
image(state.thumbnails[i], drawRect.x, drawRect.y, drawRect.width, drawRect.height);
noFill();
rect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
tint(255, 255);
});
// noFill(64);
// rect((state.levelCamera.x * 16) + 16, (state.levelCamera.y * 16) + 16, level.attributes.width * 2, level.attributes.height * 2);
const solids = childByName(level, 'solids');
const solidMap = solids.attributes.innerText;
let tx = 0;
let ty = 0;
const levelDrawRect = screenLevelTransformRect({
x: 0,
y: 0,
width: level.attributes.width,
height: level.attributes.height
})
image(state.thumbnails[state.levelIndex], levelDrawRect.x, levelDrawRect.y, levelDrawRect.width, levelDrawRect.height);
// _.each(solidMap, (v, i) => {
// if (v === 10) {
// ty++;
// tx=0;
// return;
// }
// const drawRect = screenLevelTransformRect({
// x: tx * 8,
// y: ty * 8,
// width: 8,
// height: 8
// })
// tx++;
// if (!rectIsVisible(drawRect)) return;
// stroke(32);
// // const color = SolidColors[String.fromCharCode(v)] || 200;
// // fill(color);
// // rect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
// fill(0);
// text(String.fromCharCode(v), drawRect.x + 6, drawRect.y + 12);
// });
const entities = childByName(level, 'entities');
_.each(entities.children, entity => {
const drawRect = screenLevelTransformRect({
x: entity.attributes.x - (entity.attributes.originX || 0),
y: entity.attributes.y - (entity.attributes.originY || 0),
width: entity.attributes.width || 8,
height: entity.attributes.height || 8
})
if (!rectIsVisible(drawRect)) return;
stroke(255, 255, 255, 128);
fill(255, 255, 255, 128);
text(entity.name, drawRect.x, drawRect.y);
stroke(255, 64, 64, 128);
fill(64, 0, 0, 128);
rect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
})
stroke(128);
noFill();
rect(levelDrawRect.x, levelDrawRect.y, levelDrawRect.width, levelDrawRect.height);
}
function draw() {
processInput();
background(0);
stroke(255);
fill(255);
if (state.dataReady) {
if (state.levelIndex === -1) {
drawOverview();
} else {
drawLevel();
}
} else {
drawLoading();
}
}
function addMapSelect() {
let mapSelectContainer = document.getElementById("map-select-container");
let selectList = document.createElement("select");
selectList.id = "map-select";
mapSelectContainer.appendChild(selectList);
_.each(Maps, mapName => {
let option = document.createElement("option");
option.value = mapName;
option.text = mapName;
selectList.appendChild(option);
})
selectList.addEventListener('change', function(){
loadMap(this.value);
});
}
function addLevelSelect() {
let levelSelectContainer = document.getElementById("level-select-container");
levelSelectContainer.innerHTML = "";
let selectList = document.createElement("select");
selectList.id = "level-select";
levelSelectContainer.appendChild(selectList);
let defOption = document.createElement("option");
defOption.value = -1;
defOption.text = "Overview";
selectList.appendChild(defOption);
const levels = childByName(state.map.root, 'levels');
_.each(levels.children, (lvl, idx) => {
let option = document.createElement("option");
option.value = idx;
option.text = lvl.attributes.name;
selectList.appendChild(option);
})
selectList.addEventListener('change', function(){
loadLevel(this.value);
});
}