Skip to content

Instantly share code, notes, and snippets.

@lovroselic
Created October 17, 2019 13:42
Show Gist options
  • Save lovroselic/6ea73654fd73d782e4d8c7faedf97d83 to your computer and use it in GitHub Desktop.
Save lovroselic/6ea73654fd73d782e4d8c7faedf97d83 to your computer and use it in GitHub Desktop.
Engine LS v2.20.A
//////////////////engine.js/////////////////////////
// //
// ENGINE version 2.20 by LS //
// //
// //
////////////////////////////////////////////////////
//COUPLED
//MAP
//GAME.level
//AI
var ENGINE = {
VERSION: "2.20.A",
CSS: "color: #0FA",
INI: {
ANIMATION_INTERVAL: 17,
SPRITESHEET_HEIGHT: 48,
SPRITESHEET_DEFAULT_WIDTH: 48,
SPRITESHEET_DEFAULT_HEIGHT: 48,
sprite_maxW: 300,
sprite_maxH: 100,
GRIDPIX: 48,
FADE_FRAMES: 50,
COLLISION_SAFE: 48,
PATH_ROUNDS: 999,
MAX_PATH: 999
},
readyCall: null,
SOURCE: "https://www.c00lsch00l.eu/Games/AA/",
WASM_SOURCE: "https://www.c00lsch00l.eu/WASM/",
AUDIO_SOURCE: "https://www.c00lsch00l.eu/Mp3/",
FONT_SOURCE: "https://www.c00lsch00l.eu/Fonts/",
checkIntersection: false, //use linear intersection collision method after pixelperfect collision; set to false to exclude
checkProximity: true, //check proximity before pixel perfect evaluation of collision to background
pixelPerfectCollision: false, //false by default
LOAD_W: 160,
LOAD_H: 22,
gameWindowId: "#game",
gameWIDTH: 960,
gameHEIGHT: 768,
sideWIDTH: 960,
sideHEIGHT: 768,
titleHEIGHT: 120,
titleWIDTH: 960,
bottomHEIGHT: 40,
bottomWIDTH: 960,
mapWIDTH: 512,
statusWIDTH: 312,
currentTOP: 0,
currentLEFT: 0,
directions: [LEFT, UP, RIGHT, DOWN],
corners: [
new Vector(-1, -1),
new Vector(1, -1),
new Vector(-1, 1),
new Vector(1, 1)
],
layersToClear: new Set(),
disableKey: function(key) {
$(document).keydown(function(event) {
if (event.which === ENGINE.KEY.map[key]) {
event.preventDefault();
}
});
$(document).keyup(function(event) {
if (event.which === ENGINE.KEY.map[key]) {
event.preventDefault();
}
});
$(document).keypress(function(event) {
if (event.which === ENGINE.KEY.map[key]) {
event.preventDefault();
}
});
},
disableArrows: function() {
ENGINE.disableKey("up");
ENGINE.disableKey("down");
},
init: function() {
console.log(`%cInitializing ENGINE V${String(ENGINE.VERSION)}`, ENGINE.CSS);
$("#temp").append(
"<canvas id ='temp_canvas' width='" +
ENGINE.INI.sprite_maxW +
"' height='" +
ENGINE.INI.sprite_maxH +
"'></canvas>"
);
$("#temp2").append(
"<canvas id ='temp_canvas2' width='" +
ENGINE.INI.sprite_maxW +
"' height='" +
ENGINE.INI.sprite_maxH +
"'></canvas>"
);
LAYER.temp = $("#temp_canvas")[0].getContext("2d");
LAYER.temp2 = $("#temp_canvas2")[0].getContext("2d");
VIEW.init();
},
fill: function(ctx, pattern) {
let CTX = ctx;
let pat = CTX.createPattern(pattern, "repeat");
CTX.fillStyle = pat;
CTX.fillRect(0, 0, CTX.canvas.width, CTX.canvas.height);
},
clearLayer: function(layer) {
let CTX = LAYER[layer];
CTX.clearRect(0, 0, CTX.canvas.width, CTX.canvas.height);
},
clearLayerStack: function() {
let CLR = ENGINE.layersToClear.length;
if (CLR === 0) return;
ENGINE.layersToClear.forEach(ENGINE.clearLayer);
ENGINE.layersToClear.clear();
},
fillLayer: function(layer, colour) {
let CTX = LAYER[layer];
CTX.fillStyle = colour;
CTX.fillRect(0, 0, CTX.canvas.width, CTX.canvas.height);
},
resizeBOX: function(id, width, height) {
width = width || ENGINE.gameWIDTH;
height = height || ENGINE.gameHEIGHT;
let box = $("#" + id).children();
for (let a = 0; a < box.length; a++) {
box[a].width = width;
box[a].height = height;
}
},
addBOX: function(id, width, height, alias, type) {
//types null, side, fside
if (id === null) return;
if (width === null) return;
if (height === null) return;
let layers = alias.length;
$(ENGINE.gameWindowId).append(
`<div id ='${id}' style='position: relative'></div>`
);
if (type === "side" || type === "fside") {
$(`#${id}`).addClass("gw"); //adds gw class: side by side windows
} else {
$(`#${id}`).addClass("gh"); //adds gh class: windows below
}
let prop;
let canvasElement;
for (let x = 0; x < layers; x++) {
canvasElement = `<canvas class='layer' id='${id}_canvas_${x}' width='${width}' height='${height}' style='z-index:${x}; top:${
ENGINE.currentTOP
}px; left:${ENGINE.currentLEFT}px'></canvas>`;
$(`#${id}`).append(canvasElement);
prop = alias.shift();
LAYER[prop] = $(`#${id}_canvas_${x}`)[0].getContext("2d");
}
if (type === "side") {
ENGINE.currentLEFT += width;
} else if (type === "fside") {
ENGINE.currentTOP += height;
ENGINE.currentLEFT = 0;
} else {
ENGINE.currentTOP += height;
ENGINE.currentLEFT = 0;
}
},
addCanvas: function(id, w, h) {
//adds canvas to div
let canvas = `<canvas id="c_${id}" width="${w}" height="${h}"></canvas>`;
$(`#${id}`).append(canvas);
LAYER[id] = $(`#c_${id}`)[0].getContext("2d");
},
copyLayer: function(copyFrom, copyTo, orX, orY, orW, orH) {
let CTX = LAYER[copyTo];
CTX.drawImage(LAYER[copyFrom].canvas, orX, orY, orW, orH, 0, 0, orW, orH);
},
flattenLayers: function(src, dest) {
let W = LAYER[dest].canvas.width;
let H = LAYER[dest].canvas.height;
ENGINE.copyLayer(src, dest, 0, 0, W, H, 0, 0, W, H);
},
spriteDraw: function(layer, X, Y, image) {
let CX = Math.floor(X - image.width / 2);
let CY = Math.floor(Y - image.height / 2);
let CTX = LAYER[layer];
CTX.drawImage(image, CX, CY);
},
drawToGrid: function(layer, grid, image) {
let p = GRID.gridToCoord(grid);
ENGINE.draw(layer, p.x, p.y, image);
},
spriteToGrid: function(layer, grid, image) {
let p = GRID.gridToCenterPX(grid);
ENGINE.spriteDraw(layer, p.x, p.y, image);
},
draw: function(layer, X, Y, image) {
let CTX = LAYER[layer];
CTX.drawImage(image, X, Y);
},
drawPart: function(layer, X, Y, image, line) {
let CTX = LAYER[layer];
CTX.drawImage(
image,
0,
line,
image.width,
image.height - line,
X,
Y,
image.width,
image.height - line
);
},
drawPool: function(layer, pool, sprite) {
let CTX = LAYER[layer];
let PL = pool.length;
if (PL === 0) return;
for (let i = 0; i < PL; i++) {
ENGINE.spriteDraw(layer, pool[i].x, pool[i].y, sprite);
}
},
trimCanvas: function(data) {
var top = 0,
bottom = data.height,
left = 0,
right = data.width;
var width = data.width;
while (top < bottom && rowBlank(data, width, top)) ++top;
while (bottom - 1 > top && rowBlank(data, width, bottom - 1)) --bottom;
while (left < right && columnBlank(data, width, left, top, bottom)) ++left;
while (right - 1 > left && columnBlank(data, width, right - 1, top, bottom))
--right;
return { left: left, top: top, right: right, bottom: bottom };
function rowBlank(data, width, y) {
for (let x = 0; x < width; ++x) {
if (data.data[y * width * 4 + x * 4 + 3] !== 0) return false;
}
return true;
}
function columnBlank(data, width, x, top, bottom) {
for (let y = top; y < bottom; ++y) {
if (data.data[y * width * 4 + x * 4 + 3] !== 0) return false;
}
return true;
}
},
rotateImage: function(image, degree, newName) {
let CTX = LAYER.temp;
let CW = image.width;
let CH = image.height;
let max = Math.max(CW, CH);
let min = Math.max(CW, CH);
CTX.canvas.width = max * 2;
CTX.canvas.height = max * 2;
CTX.save();
CTX.translate(max, max);
CTX.rotate(degree * Math.PI / 180);
CTX.drawImage(image, -min / 2, -min / 2);
CTX.restore();
let imgDATA = CTX.getImageData(0, 0, CTX.canvas.width, CTX.canvas.height);
let TRIM = ENGINE.trimCanvas(imgDATA);
let trimmed = CTX.getImageData(
TRIM.left,
TRIM.top,
TRIM.right - TRIM.left,
TRIM.bottom - TRIM.top
);
CTX.canvas.width = TRIM.right - TRIM.left;
CTX.canvas.height = TRIM.bottom - TRIM.top;
CTX.putImageData(trimmed, 0, 0);
SPRITE[newName] = new Image();
SPRITE[newName].onload = ENGINE.creationSpriteCount;
SPRITE[newName].crossOrigin = "Anonymous";
SPRITE[newName].src = CTX.canvas.toDataURL("image/png");
SPRITE[newName].width = CTX.canvas.width;
SPRITE[newName].height = CTX.canvas.height;
},
setCollisionsafe: function(safe) {
if (safe !== undefined) {
ENGINE.INI.COLLISION_SAFE = safe;
} else {
for (let sprite in SPRITE) {
if (SPRITE[sprite].width > ENGINE.INI.COLLISION_SAFE) {
ENGINE.INI.COLLISION_SAFE = SPRITE[sprite].width;
}
if (SPRITE[sprite].height > ENGINE.INI.COLLISION_SAFE) {
ENGINE.INI.COLLISION_SAFE = SPRITE[sprite].height;
}
}
ENGINE.INI.COLLISION_SAFE++;
}
console.log(
`%cENGINE.INI.COLLISION_SAFE set to: ${ENGINE.INI.COLLISION_SAFE}`,
ENGINE.CSS
);
},
ready: function() {
ENGINE.setCollisionsafe();
console.log("%cENGINE ready!", ENGINE.CSS);
$("#buttons").prepend("<input type='button' id='startGame' value='START'>");
$("#load").addClass("hidden");
$("#startGame").on("click", PRG.start);
ENGINE.readyCall.call();
},
intersectionCollision: function(actor1, actor2) {
if (actor1.class !== "bullet" && actor2.class !== "bullet") return;
if (actor1.prevX === null || actor2.prevX === null) return;
let AL = arguments.length;
let line1 = {};
let line2 = {};
for (let q = 0; q < AL; q++) {
switch (arguments[q].class) {
case "bullet":
// for 5px*5px bullet
line1.x1 = arguments[q].prevX;
line1.y1 = arguments[q].prevY + 3;
line1.x2 = arguments[q].x;
line1.y2 = arguments[q].y - 3;
break;
default:
//linear representation of object, angle not considered
line2.x1 = parseInt(
(arguments[q].prevX + arguments[q].x) / 2 + arguments[q].width / 2,
10
);
line2.y1 = parseInt((arguments[q].prevY + arguments[q].y) / 2, 10);
line2.x2 = parseInt(
(arguments[q].prevX + arguments[q].x) / 2 - arguments[q].width / 2,
10
);
line2.y2 = line2.y1;
break;
}
}
return ENGINE.lineIntersects(
line1.x1,
line1.y1,
line1.x2,
line1.y2,
line2.x1,
line2.y1,
line2.x2,
line2.y2
);
},
lineIntersects: function(a, b, c, d, p, q, r, s) {
//https://stackoverflow.com/a/24392281/4154250
var det, gamma, lambda;
det = (c - a) * (s - q) - (r - p) * (d - b);
if (det === 0) {
return false;
} else {
lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
return 0 < lambda && lambda < 1 && (0 < gamma && gamma < 1);
}
},
pixPerfectCollision: function(actor1, actor2) {
var w1 = parseInt(actor1.width / 2, 10);
var w2 = parseInt(actor2.width / 2, 10);
var h1 = parseInt(actor1.height / 2, 10);
var h2 = parseInt(actor2.height / 2, 10);
var act1 = new Vector(actor1.x, actor1.y);
var act2 = new Vector(actor2.x, actor2.y);
var SQ1 = new Square(act1.x - w1, act1.y - h1, w1 * 2, h1 * 2);
var SQ2 = new Square(act2.x - w2, act2.y - h2, w2 * 2, h2 * 2);
var x = parseInt(Math.max(SQ1.x, SQ2.x), 10) - 1;
var y = parseInt(Math.max(SQ1.y, SQ2.y), 10) - 1;
var w = parseInt(Math.min(SQ1.x + SQ1.w - x, SQ2.x + SQ2.w - x), 10) + 1;
var h = parseInt(Math.min(SQ1.y + SQ1.h - y, SQ2.y + SQ2.h - y), 10) + 1;
if (w === 0 || h === 0) return false;
var area = new Square(x, y, w, h);
var area1 = new Square(area.x - SQ1.x, area.y - SQ1.y, area.w, area.h);
var area2 = new Square(area.x - SQ2.x, area.y - SQ2.y, area.w, area.h);
var CTX1 = LAYER.temp;
var CTX2 = LAYER.temp2;
CTX1.canvas.width = ENGINE.INI.sprite_maxW;
CTX1.canvas.height = ENGINE.INI.sprite_maxH;
CTX2.canvas.width = ENGINE.INI.sprite_maxW;
CTX2.canvas.height = ENGINE.INI.sprite_maxH;
ENGINE.draw("temp", 0, 0, SPRITE[actor1.name]);
ENGINE.draw("temp2", 0, 0, SPRITE[actor2.name]);
var data1 = CTX1.getImageData(area1.x, area1.y, area1.w, area1.h);
var data2 = CTX2.getImageData(area2.x, area2.y, area2.w, area2.h);
var DL = data1.data.length;
var index;
for (index = 3; index < DL; index += 4) {
if (data1.data[index] > 0 && data2.data[index] > 0) {
return true;
}
}
//intersectionCollision check
if (ENGINE.checkIntersection) {
return ENGINE.intersectionCollision(actor1, actor2);
} else return false;
},
collision: function(actor1, actor2) {
var X = Math.abs(actor1.x - actor2.x);
var Y = Math.abs(actor1.y - actor2.y);
if (Y >= ENGINE.INI.COLLISION_SAFE) return false;
if (X >= ENGINE.INI.COLLISION_SAFE) return false;
var w1 = parseInt(actor1.width / 2, 10);
var w2 = parseInt(actor2.width / 2, 10);
var h1 = parseInt(actor1.height / 2, 10);
var h2 = parseInt(actor2.height / 2, 10);
if (X >= w1 + w2 || Y >= h1 + h2) return false;
if (ENGINE.pixelPerfectCollision) {
return ENGINE.pixPerfectCollision(actor1, actor2);
} else return true;
},
collisionToBackground: function(actor, layer) {
var CTX = layer;
var maxSq = Math.max(actor.width, actor.height);
var R = Math.ceil(0.5 * Math.sqrt(2 * Math.pow(maxSq, 2)));
var X = actor.x;
var Y = actor.y;
var proximity = false;
if (ENGINE.checkProximity) {
var imgDATA = CTX.getImageData(X - R, Y - R, 2 * R, 2 * R);
var check = 1;
var left, right, down, top;
while (check < R) {
left = imgDATA.data[toIndex(X - check, Y)];
right = imgDATA.data[toIndex(X + check, Y)];
down = imgDATA.data[toIndex(X, Y + check)];
top = imgDATA.data[toIndex(X, Y - check)];
if (left || right || down || top) {
proximity = true;
break;
}
check++;
}
} else proximity = true;
if (!proximity) {
return false;
} else {
var CX = Math.floor(X - actor.width / 2);
var CY = Math.floor(Y - actor.height / 2);
var CTX1 = LAYER.temp;
CTX1.canvas.width = actor.width;
CTX1.canvas.height = actor.height;
ENGINE.draw("temp", 0, 0, SPRITE[actor.name]);
var data1 = CTX1.getImageData(0, 0, actor.width, actor.height); //actor data
var data2 = CTX.getImageData(CX, CY, actor.width, actor.height); //layer data
var DL = data1.data.length;
var index;
for (index = 3; index < DL; index += 4) {
if (data1.data[index] > 0 && data2.data[index] > 0) {
return true;
}
}
return false;
}
function toIndex(x, y) {
var index = (y - Y) * 4 * (2 * R) + (x - (X - R)) * 4 + 3;
return index;
}
},
drawLoadingGraph: function(counter) {
var count = ENGINE.LOAD[counter];
var HMI = ENGINE.LOAD["HM" + counter];
var text = counter;
var percent = Math.floor(count / HMI * 100);
var CTX = LAYER.PRELOAD[counter];
CTX.clearRect(0, 0, ENGINE.LOAD_W, ENGINE.LOAD_H);
CTX.beginPath();
CTX.lineWidth = "1";
CTX.strokeStyle = "black";
CTX.rect(0, 0, ENGINE.LOAD_W, ENGINE.LOAD_H);
CTX.closePath();
CTX.stroke();
CTX.fillStyle = "#999";
CTX.fillRect(
1,
1,
Math.floor((ENGINE.LOAD_W - 2) * (percent / 100)),
ENGINE.LOAD_H - 2
);
CTX.fillStyle = "black";
CTX.font = "10px Verdana";
CTX.fillText(
text + ": " + percent + "%",
ENGINE.LOAD_W * 0.1,
ENGINE.LOAD_H * 0.62
);
return;
},
statusBar: function(CTX, x, y, w, h, value, max, color) {
CTX.save();
ENGINE.resetShadow(CTX);
let fs = h / 2;
CTX.font = `${fs}px Verdana`;
CTX.strokeStyle = color;
CTX.fillStyle = color;
CTX.beginPath();
CTX.lineWidth = "1";
CTX.rect(x, y, w, h);
CTX.closePath();
CTX.stroke();
let fraction = value / max;
CTX.fillRect(x, y, Math.round(fraction * w), h);
CTX.fillStyle = "#FFF";
CTX.textAlign = "center";
let tx = x + w / 2 + fs / 2;
let ty = y + h / 2 + fs / 2;
CTX.fillText(`${value}/${max}`, tx, ty);
CTX.restore();
},
resetShadow: function(CTX) {
CTX.shadowColor = "#000";
CTX.shadowOffsetX = 0;
CTX.shadowOffsetY = 0;
CTX.shadowBlur = 0;
},
spriteDump: function(layer, spriteList) {
console.log("%c********* SPRITE DUMP *********", ENGINE.CSS);
console.log(SPRITE);
var x = 0;
var y = 0;
var dy = 0;
if (spriteList === undefined) {
var keys = Object.keys(SPRITE);
spriteList = keys.map(x => SPRITE[x]);
}
spriteList.forEach(function(q) {
ENGINE.draw(layer, x, y, q);
x += q.width;
if (q.height > dy) dy = q.height;
if (x > LAYER[layer].canvas.width - 64) {
y += dy;
x = 0;
}
});
},
window: function(
width = ENGINE.gameWIDTH / 2,
height = ENGINE.gameHEIGHT / 2,
CTX = LAYER.text,
x = Math.floor((ENGINE.gameWIDTH - width) / 2),
y = Math.floor((ENGINE.gameHEIGHT - height) / 2)
) {
CTX.save();
CTX.fillStyle = "#000";
CTX.shadowColor = "#000";
CTX.shadowOffsetX = 0;
CTX.shadowOffsetY = 0;
CTX.shadowBlur = 0;
CTX.globalAlpha = 0.8;
CTX.roundRect(
x,
y,
width,
height,
{
upperLeft: 15,
upperRight: 15,
lowerLeft: 15,
lowerRight: 15
},
true,
true
);
CTX.restore();
return new Point(x, y);
},
mouseOverOK: function(event, cname) {
var canvasOffset = $(cname).offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var mouseX = parseInt(event.pageX - offsetX - ENGINE.alertButton.okX, 10);
var mouseY = parseInt(event.pageY - offsetY - ENGINE.alertButton.okY, 10);
if (
mouseX >= 0 &&
mouseX < ENGINE.alertButton.width &&
mouseY >= 0 &&
mouseY < ENGINE.alertButton.heigth
) {
$(cname).css("cursor", "pointer");
} else {
$(cname).css("cursor", "auto");
}
},
mouseOver: function(event, cname) {
$(cname).css("cursor", "pointer");
},
mouseClick: function(event, cname, func) {
var canvasOffset = $(cname).offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var mouseX = parseInt(event.pageX - offsetX, 10);
var mouseY = parseInt(event.pageY - offsetY, 10);
ENGINE.mouseX = mouseX;
ENGINE.mouseY = mouseY;
func.call();
return;
},
mouseClickOK: function(event, cname) {
var canvasOffset = $(cname).offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var mouseX = parseInt(event.pageX - offsetX - ENGINE.alertButton.okX, 10);
var mouseY = parseInt(event.pageY - offsetY - ENGINE.alertButton.okY, 10);
if (
mouseX >= 0 &&
mouseX < ENGINE.alertButton.width &&
mouseY >= 0 &&
mouseY < ENGINE.alertButton.heigth
) {
$(cname).css("cursor", "auto");
$(cname).off();
ENGINE.alertMode = false;
ENGINE.clearLayer("alert");
$(document).keydown(ENGINE.GAME.checkKey);
$(document).keyup(ENGINE.GAME.clearKey);
GAME.continue(); //not DECOUPLED!!
}
},
getCanvasNumber: function(id) {
var cnv = $("#" + id + " .layer");
return cnv.length;
},
getCanvasName: function(id) {
var CL = ENGINE.getCanvasNumber("ROOM");
var cname = "#ROOM_canvas_" + --CL;
return cname;
},
cutGrid: function(layer, point) {
var CTX = layer;
CTX.clearRect(point.x, point.y, ENGINE.INI.GRIDPIX, ENGINE.INI.GRIDPIX);
return;
},
cutManyGrids: function(layer, point, N) {
var CTX = layer;
CTX.clearRect(
point.x,
point.y,
N * ENGINE.INI.GRIDPIX,
N * ENGINE.INI.GRIDPIX
);
return;
},
spreadAroundCenter: function(toDo, x, delta) {
var xS = [];
var odd = toDo % 2;
var n = 2;
if (odd) {
xS.push(x);
toDo--;
while (toDo > 0) {
xS.push(x + (n - 1) * delta);
xS.push(x - (n - 1) * delta);
toDo -= 2;
n++;
}
} else {
while (toDo > 0) {
xS.push(x + (n - 1) * delta / 2);
xS.push(x - (n - 1) * delta / 2);
toDo -= 2;
n += 2;
}
}
xS.sort((a, b) => a - b);
return xS;
},
imgToTexture: function(obj) {
TEXTURE[obj.name] = obj.img;
},
imgToSprite: function(obj) {
SPRITE[obj.name] = obj.img;
SPRITE[obj.name].width = obj.img.width;
SPRITE[obj.name].height = obj.img.height;
},
imgToSeqSprite: function(obj) {
SPRITE[obj.name] = obj.img;
SPRITE[obj.name].width = obj.img.width;
SPRITE[obj.name].height = obj.img.height;
},
extractImg: function(x, y, CTX) {
var data, imgDATA;
var NTX = LAYER.temp2;
data = CTX.getImageData(
x,
y,
ENGINE.INI.SPRITESHEET_DEFAULT_WIDTH,
ENGINE.INI.SPRITESHEET_DEFAULT_HEIGHT
);
NTX.canvas.width = ENGINE.INI.SPRITESHEET_DEFAULT_WIDTH;
NTX.canvas.height = ENGINE.INI.SPRITESHEET_DEFAULT_HEIGHT;
NTX.putImageData(data, 0, 0);
imgDATA = NTX.getImageData(
0,
0,
ENGINE.INI.SPRITESHEET_DEFAULT_WIDTH,
ENGINE.INI.SPRITESHEET_DEFAULT_HEIGHT
);
var TRIM = ENGINE.trimCanvas(imgDATA);
var trimmed = NTX.getImageData(
TRIM.left,
TRIM.top,
TRIM.right - TRIM.left,
TRIM.bottom - TRIM.top
);
NTX.canvas.width = TRIM.right - TRIM.left;
NTX.canvas.height = TRIM.bottom - TRIM.top;
NTX.putImageData(trimmed, 0, 0);
return NTX;
},
drawSheet: function(spriteSheet) {
var CTX = LAYER.temp;
CTX.canvas.width = spriteSheet.width;
CTX.canvas.height = spriteSheet.height;
ENGINE.draw("temp", 0, 0, spriteSheet);
return CTX;
},
contextToSprite: function(newName, NTX) {
SPRITE[newName] = new Image();
SPRITE[newName].crossOrigin = "Anonymous";
SPRITE[newName].src = NTX.canvas.toDataURL("image/png");
SPRITE[newName].width = NTX.canvas.width;
SPRITE[newName].height = NTX.canvas.height;
return SPRITE[newName];
},
packToSprite: function(obj) {
var tag = ["left", "right", "front", "back"];
var CTX = ENGINE.drawSheet(obj.img);
let x, y;
let newName;
for (var W = 0, LN = tag.length; W < LN; W++) {
for (var q = 0; q < obj.count; q++) {
x = q * ENGINE.INI.SPRITESHEET_DEFAULT_WIDTH;
y = W * ENGINE.INI.SPRITESHEET_DEFAULT_HEIGHT;
let NTX = ENGINE.extractImg(x, y, CTX);
newName = obj.name + "_" + tag[W] + "_" + q;
ASSET[obj.name][tag[W]].push(ENGINE.contextToSprite(newName, NTX));
}
}
},
seqToSprite: function(obj) {
var CTX = ENGINE.drawSheet(obj.img);
let x;
let newName;
for (var q = 0; q < obj.count; q++) {
x = q * ENGINE.INI.SPRITESHEET_DEFAULT_WIDTH;
let NTX = ENGINE.extractImg(x, 0, CTX);
newName = obj.name + "_" + q.toString().padStart(2, "0");
ASSET[obj.name].linear.push(ENGINE.contextToSprite(newName, NTX));
}
},
sheetToSprite: function(obj) {
var CTX = ENGINE.drawSheet(obj.img);
let x;
let newName;
for (var q = 0; q < obj.count; q++) {
x = q * ENGINE.INI.SPRITESHEET_DEFAULT_WIDTH;
let NTX = ENGINE.extractImg(x, 0, CTX);
newName = obj.name + "_" + q;
ASSET[obj.parent][obj.tag].push(ENGINE.contextToSprite(newName, NTX));
}
},
audioToAudio: function(obj) {
AUDIO[obj.name] = obj.audio;
},
linkToWasm: function(obj) {
var bin = obj.exports;
for (var fn in bin) {
if (typeof bin[fn] === "function") {
WASM[fn] = bin[fn];
}
}
},
KEY: {
on: function() {
$(document).keydown(ENGINE.GAME.checkKey);
$(document).keyup(ENGINE.GAME.clearKey);
},
off: function() {
$(document).off("keyup", ENGINE.GAME.clearKey);
$(document).off("keydown", ENGINE.GAME.checkKey);
},
map: {
ctrl: 17,
back: 8,
tab: 9,
alt: 18,
left: 37,
up: 38,
right: 39,
down: 40,
space: 32,
enter: 13,
F9: 120,
F8: 119,
A: 65,
D: 68,
C: 67,
H: 72,
M: 77
},
waitFor: function(func, key = "enter") {
if (ENGINE.GAME.stopAnimation) return;
let map = ENGINE.GAME.keymap;
if (map[ENGINE.KEY.map[key]]) {
ENGINE.GAME.ANIMATION.stop();
func.call();
ENGINE.GAME.keymap[ENGINE.KEY.map[key]] = false;
return;
}
}
},
GAME: {
running: false,
keymap: {
17: false, //CTRL
37: false, //LEFT
38: false, //UP
39: false, //RIGHT
40: false, //Down
32: false, //SPACE
13: false, //ENTER
120: false, //F9
119: false, //F8
65: false, //A
68: false, //D
67: false, //C
8: false, //back
9: false, //tab
72: false, //h
77: false //m
},
clearAllKeys: function() {
for (var key in ENGINE.GAME.keymap) {
ENGINE.GAME.keymap[key] = false;
}
},
clearKey: function(e) {
e = e || window.event;
if (e.keyCode in ENGINE.GAME.keymap) {
ENGINE.GAME.keymap[e.keyCode] = false;
}
},
checkKey: function(e) {
e = e || window.event;
if (e.keyCode in ENGINE.GAME.keymap) {
ENGINE.GAME.keymap[e.keyCode] = true;
e.preventDefault();
}
},
run: function(func, nextFunct) {
ENGINE.GAME.running = true;
if (!ENGINE.GAME.frame.start) ENGINE.GAME.frame.start = performance.now();
ENGINE.GAME.frame.delta = performance.now() - ENGINE.GAME.frame.start;
if (ENGINE.GAME.frame.delta >= ENGINE.INI.ANIMATION_INTERVAL) {
if (ENGINE.GAME.stopAnimation) {
if (nextFunct) nextFunct.call();
console.log(
`%cAnimation stopped BEFORE execution ${func.name}`,
"color: #f00"
);
ENGINE.GAME.running = false;
return;
}
func.call();
ENGINE.GAME.frame.start = null;
}
if (!ENGINE.GAME.stopAnimation) {
requestAnimationFrame(ENGINE.GAME.run.bind(null, func, nextFunct));
} else {
if (nextFunct) nextFunct.call();
console.log(
`%cAnimation stopped AFTER execution ${func.name}`,
"color: #f00"
);
ENGINE.GAME.running = false;
return;
}
},
start: function() {
$("#DOWN")[0].scrollIntoView();
ENGINE.GAME.stopAnimation = false;
ENGINE.GAME.started = Date.now();
ENGINE.GAME.frame = {};
ENGINE.GAME.frame.start = null;
},
ANIMATION: {
start: function(wrapper) {
console.log(
`%cENGINE.GAME.ANIMATION.start --> ${wrapper.name}`,
"color: #0F0"
);
ENGINE.GAME.stopAnimation = false;
ENGINE.GAME.run(wrapper, ENGINE.GAME.ANIMATION.queue);
},
stop: function() {
ENGINE.GAME.stopAnimation = true;
},
queue: function() {
ENGINE.GAME.ANIMATION.stop();
setTimeout(() => {
console.log(`%cGame running? ${ENGINE.GAME.running}`, ENGINE.CSS);
if (ENGINE.GAME.ANIMATION.STACK.length > 0) {
let next = ENGINE.GAME.ANIMATION.STACK.shift();
console.log(`%c..... animation queue: ${next.name}`, ENGINE.CSS);
ENGINE.GAME.ANIMATION.start(next);
} else {
console.log(
"%cAnimation STACK EMPTY! Game stopped running.",
ENGINE.CSS
);
}
}, ENGINE.INI.ANIMATION_INTERVAL);
},
waitThen: function(func, n = 1) {
setTimeout(func, ENGINE.INI.ANIMATION_INTERVAL * n);
},
STACK: []
}
},
VIEWPORT: {
max: {
x: 0,
y: 0
},
setMax: function(max) {
ENGINE.VIEWPORT.max.x = max.x;
ENGINE.VIEWPORT.max.y = max.y;
},
changed: false,
reset: function() {
ENGINE.VIEWPORT.vx = 0;
ENGINE.VIEWPORT.vy = 0;
},
vx: 0,
vy: 0,
change: function(from, to) {
ENGINE.copyLayer(
from,
to,
ENGINE.VIEWPORT.vx,
ENGINE.VIEWPORT.vy,
ENGINE.gameWIDTH,
ENGINE.gameHEIGHT
);
},
check: function(actor, max = ENGINE.VIEWPORT.max) {
var vx = actor.x - ENGINE.gameWIDTH / 2;
var vy = actor.y - ENGINE.gameHEIGHT / 2;
if (vx < 0) vx = 0;
if (vy < 0) vy = 0;
if (vx > max.x - ENGINE.gameWIDTH) vx = max.x - ENGINE.gameWIDTH;
if (vy > max.y - ENGINE.gameHEIGHT) vy = max.y - ENGINE.gameHEIGHT;
if (vx != ENGINE.VIEWPORT.vx || vy != ENGINE.VIEWPORT.vy) {
ENGINE.VIEWPORT.vx = vx;
ENGINE.VIEWPORT.vy = vy;
ENGINE.VIEWPORT.changed = true;
}
},
alignTo: function(actor) {
actor.vx = actor.x - ENGINE.VIEWPORT.vx;
actor.vy = actor.y - ENGINE.VIEWPORT.vy;
}
},
TEXT: {
font: "Arcade",
fs: "40",
setFS: function(fs) {
if (isNaN(fs)) fs = 40;
ENGINE.TEXT.fs = fs.toString();
},
text: function(
text,
x,
y,
layer = "text",
fs = ENGINE.TEXT.fs,
font = ENGINE.TEXT.font
) {
var CTX = LAYER[layer];
CTX.fillStyle = "#FFF";
CTX.font = fs + "px " + font;
CTX.shadowColor = "#333333";
CTX.shadowOffsetX = 3;
CTX.shadowOffsetY = 3;
CTX.shadowBlur = 3;
CTX.textAlign = "center";
CTX.fillText(text, x, y);
},
centeredText: function(
text,
y,
layer = "text",
fs = ENGINE.TEXT.fs,
font = ENGINE.TEXT.font
) {
var x = ENGINE.gameWIDTH / 2;
ENGINE.TEXT.text(text, x, y, layer, fs, font);
}
},
LOAD: {
Textures: 0,
Sprites: 0,
Sequences: 0,
Sheets: 0,
Rotated: 0,
WASM: 0,
Sounds: 0,
Fonts: 0,
Packs: 0,
SheetSequences: 0,
HMSheetSequences: null,
HMFonts: null,
HMSequences: null,
HMTextures: null,
HMSprites: null,
HMSheets: null,
HMRotated: null,
HMWASM: null,
HMSounds: null,
HMPacks: null,
preload: function() {
console.log("%cPreloading ...", ENGINE.CSS);
var AllLoaded = Promise.all([
loadTextures(),
loadSprites(),
loadSequences(),
loadSheets(),
loadPacks(),
loadSheetSequences(),
loadRotated(),
loadingSounds(),
loadWASM(),
loadAllFonts()
]).then(function() {
console.log("%cAll assets loaded and ready!", ENGINE.CSS);
console.log("%c****************************", ENGINE.CSS);
//console.log("SPRITE", SPRITE);
ENGINE.ready();
});
return;
function appendCanvas(name) {
let id = "preload_" + name;
$("#load").append(
"<canvas id ='" +
id +
"' width='" +
ENGINE.LOAD_W +
"' height='" +
ENGINE.LOAD_H +
"'></canvas>"
);
LAYER.PRELOAD[name] = $("#" + id)[0].getContext("2d");
}
function loadTextures(arrPath = LoadTextures) {
console.log(`%c ...loading ${arrPath.length} textures`, ENGINE.CSS);
ENGINE.LOAD.HMTextures = arrPath.length;
if (ENGINE.LOAD.HMTextures) appendCanvas("Textures");
const temp = Promise.all(
arrPath.map(img => loadImage(img, "Textures"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.imgToTexture(el);
});
});
return temp;
}
function loadSprites(arrPath = LoadSprites) {
console.log(`%c ...loading ${arrPath.length} sprites`, ENGINE.CSS);
ENGINE.LOAD.HMSprites = arrPath.length;
if (ENGINE.LOAD.HMSprites) appendCanvas("Sprites");
const temp = Promise.all(
arrPath.map(img => loadImage(img, "Sprites"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.imgToSprite(el);
});
});
return temp;
}
function loadSequences(arrPath = LoadSequences) {
console.log(`%c ...loading ${arrPath.length} sequences`, ENGINE.CSS);
var toLoad = [];
arrPath.forEach(function(el) {
for (let i = 1; i <= el.count; i++) {
toLoad.push({
srcName:
el.srcName +
"_" +
i.toString().padStart(2, "0") +
"." +
el.type,
name: el.name + (i - 1).toString().padStart(2, "0")
});
}
});
ENGINE.LOAD.HMSequences = toLoad.length;
if (ENGINE.LOAD.HMSequences) appendCanvas("Sequences");
const temp = Promise.all(
toLoad.map(img => loadImage(img, "Sequences"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.imgToSeqSprite(el);
});
});
return temp;
}
function loadPacks(arrPath = LoadPacks) {
console.log(`%c ...loading ${arrPath.length} packs`, ENGINE.CSS);
var toLoad = [];
arrPath.forEach(function(el) {
ASSET[el.name] = new LiveSPRITE("4D", [], [], [], []);
toLoad.push({
srcName: el.srcName,
name: el.name,
count: el.count
});
});
ENGINE.LOAD.HMPacks = toLoad.length;
if (ENGINE.LOAD.HMPacks) appendCanvas("Packs");
const temp = Promise.all(
toLoad.map(img => loadImage(img, "Packs"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.packToSprite(el);
});
});
return temp;
}
function loadSheets(arrPath = LoadSheets, addTag = ExtendSheetTag) {
console.log(`%c ...loading ${arrPath.length} sheets`, ENGINE.CSS);
var toLoad = [];
var tag = ["left", "right", "front", "back", ...addTag];
arrPath.forEach(function(el) {
ASSET[el.name] = new LiveSPRITE("4D", [], [], [], []);
for (let q = 0, TL = tag.length; q < TL; q++) {
toLoad.push({
srcName: el.srcName + "_" + tag[q] + "." + el.type,
name: el.name + "_" + tag[q],
count: el.count,
tag: tag[q],
parent: el.name
});
}
});
ENGINE.LOAD.HMSheets = toLoad.length;
if (ENGINE.LOAD.HMSheets) appendCanvas("Sheets");
const temp = Promise.all(
toLoad.map(img => loadImage(img, "Sheets"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.sheetToSprite(el);
});
});
return temp;
}
function loadSheetSequences(arrPath = LoadSheetSequences) {
console.log(
`%c ...loading ${arrPath.length} sheet sequences`,
ENGINE.CSS
);
var toLoad = [];
arrPath.forEach(function(el) {
ASSET[el.name] = new LiveSPRITE("1D", []);
toLoad.push({
srcName: el.srcName,
name: el.name,
count: el.count
});
});
ENGINE.LOAD.HMSheetSequences = toLoad.length;
if (ENGINE.LOAD.HMSheetSequences) appendCanvas("SheetSequences");
const temp = Promise.all(
toLoad.map(img => loadImage(img, "SheetSequences"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.seqToSprite(el);
});
});
return temp;
}
function loadRotated(arrPath = LoadRotated) {
console.log(
`%c ...loading ${arrPath.length} rotated sprites`,
ENGINE.CSS
);
ENGINE.LOAD.HMRotated = arrPath.length;
if (ENGINE.LOAD.HMRotated) appendCanvas("Rotated");
const temp = Promise.all(
arrPath.map(img => loadImage(img, "Rotated"))
).then(function(obj) {
obj.forEach(function(el) {
for (
let q = el.rotate.first;
q <= el.rotate.last;
q += el.rotate.step
) {
ENGINE.rotateImage(el.img, q, el.name + "_" + q);
}
});
});
return temp;
}
function loadWASM(arrPath = LoadExtWasm) {
var LoadIntWasm = []; //internal hard coded ENGINE requirements
var toLoad = [...arrPath, ...LoadIntWasm];
console.log(`%c ...loading ${toLoad.length} WASM files`, ENGINE.CSS);
ENGINE.LOAD.HMWASM = toLoad.length;
if (ENGINE.LOAD.HMWASM) appendCanvas("WASM");
const temp = Promise.all(
toLoad.map(wasm => loadWebAssembly(wasm, "WASM"))
).then(instance => {
instance.forEach(function(el) {
ENGINE.linkToWasm(el);
});
});
return temp;
}
function loadingSounds(arrPath = LoadAudio) {
console.log(`%c ...loading ${arrPath.length} sounds`, ENGINE.CSS);
ENGINE.LOAD.HMSounds = arrPath.length;
if (ENGINE.LOAD.HMSounds) appendCanvas("Sounds");
const temp = Promise.all(
arrPath.map(audio => loadAudio(audio, "Sounds"))
).then(function(obj) {
obj.forEach(function(el) {
ENGINE.audioToAudio(el);
});
});
}
function loadAllFonts(arrPath = LoadFonts) {
console.log(`%c ...loading ${arrPath.length} fonts`, ENGINE.CSS);
ENGINE.LOAD.HMFonts = arrPath.length;
if (ENGINE.LOAD.HMFonts) appendCanvas("Fonts");
const temp = Promise.all(arrPath.map(font => loadFont(font))).then(
function(obj) {
obj.map(x => document.fonts.add(x));
ENGINE.LOAD.Fonts = ENGINE.LOAD.HMFonts;
ENGINE.drawLoadingGraph("Fonts");
}
);
}
function loadImage(srcData, counter, dir = ENGINE.SOURCE) {
var srcName, name, count, tag, parent, rotate;
switch (typeof srcData) {
case "string":
srcName = srcData;
name = srcName.substr(0, srcName.indexOf("."));
break;
case "object":
srcName = srcData.srcName;
name = srcData.name;
count = srcData.count || null;
tag = srcData.tag || null;
parent = srcData.parent || null;
rotate = srcData.rotate || null;
break;
default:
console.error(`ENGINE: loadImage srcData ERROR: ${typeof srcData}`);
}
var src = dir + srcName;
return new Promise((resolve, reject) => {
const img = new Image();
var obj = {
img: img,
name: name,
count: count,
tag: tag,
parent: parent,
rotate: rotate
};
img.onload = function() {
ENGINE.LOAD[counter]++;
ENGINE.drawLoadingGraph(counter);
resolve(obj);
};
img.onerror = err => resolve(err);
img.crossOrigin = "Anonymous";
img.src = src;
});
}
function loadAudio(srcData, counter, dir = ENGINE.AUDIO_SOURCE) {
var srcName, name;
switch (typeof srcData) {
case "string":
srcName = srcData;
name = srcName.substr(0, srcName.indexOf("."));
break;
case "object":
srcName = srcData.srcName;
name = srcData.name;
break;
default:
console.error(`ENGINE: loadAudio srcData ERROR: ${typeof srcData}`);
}
var src = dir + srcName;
return new Promise((resolve, reject) => {
const audio = new Audio();
var obj = {
audio: audio,
name: name
};
audio.oncanplaythrough = function() {
ENGINE.LOAD[counter]++;
ENGINE.drawLoadingGraph(counter);
resolve(obj);
};
audio.onerror = err => resolve(err);
audio.src = src;
audio.load();
});
}
function loadWebAssembly(fileName, counter) {
fileName = ENGINE.WASM_SOURCE + fileName;
return fetch(fileName)
.then(response => response.arrayBuffer())
.then(bits => WebAssembly.compile(bits))
.then(module => {
ENGINE.LOAD[counter]++;
ENGINE.drawLoadingGraph(counter);
return new WebAssembly.Instance(module);
});
}
function loadFont(srcData, dir = ENGINE.FONT_SOURCE) {
const fontSource = dir + srcData.srcName;
const url = `url(${fontSource})`;
const temp = new FontFace(srcData.name, url);
return temp.load();
}
}
}
};
var TEXTURE = {};
var LAYER = {
PRELOAD: {}
};
var SPRITE = {};
var AUDIO = {};
var ASSET = {};
var WASM = {};
var PATTERN = {
create: function(which) {
var image = TEXTURE[which];
var CTX = LAYER.temp;
PATTERN[which] = CTX.createPattern(image, "repeat");
}
};
var AnimationSPRITE = function(x, y, type, howmany) {
this.x = x;
this.y = y;
this.pool = [];
for (var i = 0; i < howmany; i++) {
this.pool.push(type + i.toString().padStart(2, "0"));
}
};
class TextSprite {
constructor(text, point, color, frame) {
this.text = text;
this.point = point;
this.color = color || "#FFF";
this.frame = frame || ENGINE.INI.FADE_FRAMES; //magic number
}
}
var TEXTPOOL = {
pool: [],
draw: function(layer) {
var TPL = TEXTPOOL.pool.length;
if (TPL === 0) return;
ENGINE.layersToClear.add(layer);
var CTX = LAYER[layer];
CTX.font = "10px Consolas";
CTX.textAlign = "center";
var vx, vy;
for (let q = TPL - 1; q >= 0; q--) {
CTX.fillStyle = TEXTPOOL.pool[q].color;
vx =
TEXTPOOL.pool[q].point.x - ENGINE.VIEWPORT.vx + ENGINE.INI.GRIDPIX / 2;
vy =
TEXTPOOL.pool[q].point.y - ENGINE.VIEWPORT.vy + ENGINE.INI.GRIDPIX / 2;
CTX.save();
CTX.globalAlpha =
(1000 -
(ENGINE.INI.FADE_FRAMES - TEXTPOOL.pool[q].frame) *
(1000 / ENGINE.INI.FADE_FRAMES)) /
1000;
CTX.fillText(TEXTPOOL.pool[q].text, vx, vy);
CTX.restore();
TEXTPOOL.pool[q].frame--;
if (TEXTPOOL.pool[q].frame <= 0) {
TEXTPOOL.pool.splice(q, 1);
}
}
}
};
class PartSprite {
constructor(point, sprite, line, speed) {
this.point = point;
this.sprite = sprite;
this.line = line;
this.speed = speed;
}
}
var SpritePOOL = {
pool: [],
draw: function(layer) {
var SPL = SpritePOOL.pool.length;
if (SPL === 0) return;
ENGINE.layersToClear.add(layer);
var vx, vy, line;
for (var q = SPL - 1; q >= 0; q--) {
vx = SpritePOOL.pool[q].point.x - ENGINE.VIEWPORT.vx;
vy = SpritePOOL.pool[q].point.y - ENGINE.VIEWPORT.vy;
line = SpritePOOL.pool[q].sprite.height - SpritePOOL.pool[q].line;
ENGINE.drawPart(layer, vx, vy, SpritePOOL.pool[q].sprite, line);
SpritePOOL.pool[q].line--;
if (SpritePOOL.pool[q].line <= 0) {
SpritePOOL.pool.splice(q, 1);
}
}
}
};
var EXPLOSIONS = {
pool: [],
draw: function(layer) {
// draws AnimationSPRITE(x, y, type, howmany) from EXPLOSIONS.pool
// example new AnimationSPRITE(actor.x, actor.y, "AlienExp", 6)
layer = layer || "explosion";
var PL = EXPLOSIONS.pool.length;
if (PL === 0) return;
ENGINE.layersToClear.add(layer);
for (var instance = PL - 1; instance >= 0; instance--) {
var sprite = EXPLOSIONS.pool[instance].pool.shift();
ENGINE.spriteDraw(
layer,
EXPLOSIONS.pool[instance].x - ENGINE.VIEWPORT.vx,
EXPLOSIONS.pool[instance].y - ENGINE.VIEWPORT.vy,
SPRITE[sprite]
);
if (EXPLOSIONS.pool[instance].pool.length === 0) {
EXPLOSIONS.pool.splice(instance, 1);
}
}
}
};
class LiveSPRITE {
constructor(type, left, right, front, back) {
this.type = type || null;
switch (type) {
case "1D":
this.linear = left;
break;
case "4D":
this.left = left || null;
this.right = right || null;
this.front = front || null;
this.back = back || null;
break;
default:
throw "LiveSPRITE type ERROR";
break;
}
}
}
class ACTOR {
constructor(sprite_class, x, y, orientation, asset) {
this.class = sprite_class;
this.x = x || 0;
this.y = y || 0;
this.orientation = orientation || null;
this.asset = asset || null;
this.vx = 0;
this.vy = 0;
this.resetIndexes();
if (this.class !== null) this.refresh();
}
resetIndexes() {
this.left_index = 0;
this.right_index = 0;
this.front_index = 0;
this.back_index = 0;
this.linear_index = 0;
}
refresh() {
if (this.orientation === null) {
this.name = this.class;
return;
}
switch (this.asset.type) {
case "4D":
this.name = `${this.class}_${this.orientation}_${
this[this.orientation + "_index"]
}`;
break;
case "1D":
this.name = `${this.class}_${this.linear_index
.toString()
.padStart(2, "0")}`;
break;
default:
throw "actor.refresh asset type ERRoR";
}
this.width = SPRITE[this.name].width;
this.height = SPRITE[this.name].height;
}
sprite() {
return SPRITE[this.name];
}
getOrientation(dir) {
var orientation;
if (this.asset.type === "1D") {
orientation = "linear";
return orientation;
}
switch (dir.x) {
case 1:
orientation = "right";
break;
case -1:
orientation = "left";
break;
case 0:
switch (dir.y) {
case 1:
orientation = "front";
break;
case -1:
orientation = "back";
break;
case 0:
orientation = "front";
break;
}
break;
}
return orientation;
}
animateMove(orientation) {
this[orientation + "_index"]++;
if (this[orientation + "_index"] >= this.asset[orientation].length)
this[orientation + "_index"] = 0;
this.refresh();
}
static gridToClass(grid, sprite_class) {
var p = GRID.gridToCenterPX(grid);
return new ACTOR(sprite_class, p.x, p.y);
}
}
var GRID = {
collision: function(actor, grid) {
let actorGrid = actor.MoveState.homeGrid;
return GRID.same(actorGrid, grid);
},
spriteToSpriteCollision: function(actor1, actor2) {
return GRID.same(actor1.MoveState.homeGrid, actor2.MoveState.homeGrid);
},
gridToCenterPX: function(grid) {
var x = grid.x * ENGINE.INI.GRIDPIX + ENGINE.INI.GRIDPIX / 2;
var y = grid.y * ENGINE.INI.GRIDPIX + ENGINE.INI.GRIDPIX / 2;
return new Point(x, y);
},
gridToSprite: function(grid, actor) {
GRID.coordToSprite(GRID.gridToCoord(grid), actor);
},
coordToSprite: function(coord, actor) {
actor.x = coord.x + ENGINE.INI.GRIDPIX / 2;
actor.y = coord.y + ENGINE.INI.GRIDPIX / 2;
},
gridToCoord: function(grid) {
var x = grid.x * ENGINE.INI.GRIDPIX;
var y = grid.y * ENGINE.INI.GRIDPIX;
return new Point(x, y);
},
coordToGrid: function(x, y) {
var tx = Math.floor(x / ENGINE.INI.GRIDPIX);
var ty = Math.floor(y / ENGINE.INI.GRIDPIX);
return new Grid(tx, ty);
},
create: function(x, y) {
var temp = [];
var string = "1".repeat(x);
for (var iy = 0; iy < y; iy++) {
temp.push(string);
}
return temp;
},
grid: function() {
var CTX = LAYER.grid;
var x = 0;
var y = 0;
CTX.strokeStyle = "#FFF";
//horizonal lines
do {
y += ENGINE.INI.GRIDPIX;
CTX.beginPath();
CTX.setLineDash([1, 3]);
CTX.moveTo(x, y);
CTX.lineTo(CTX.canvas.width, y);
CTX.closePath();
CTX.stroke();
} while (y <= CTX.canvas.height);
//vertical lines
y = 0;
do {
x += ENGINE.INI.GRIDPIX;
CTX.beginPath();
CTX.setLineDash([1, 3]);
CTX.moveTo(x, y);
CTX.lineTo(x, CTX.canvas.height);
CTX.closePath();
CTX.stroke();
} while (x <= CTX.canvas.width);
},
paintText: function(point, text, layer, color = "#FFF") {
var CTX = LAYER[layer];
CTX.font = "10px Consolas";
var y = point.y + ENGINE.INI.GRIDPIX / 2;
var x = point.x + ENGINE.INI.GRIDPIX / 2;
CTX.fillStyle = color;
CTX.textAlign = "center";
CTX.fillText(text, x, y);
},
paint: function(
grid,
floorIMG,
wallIMG,
floorLayer = "floor",
wallLayer = "wall",
drawGrid = false
) {
ENGINE.clearLayer(floorLayer);
ENGINE.clearLayer(wallLayer);
ENGINE.fill(LAYER[floorLayer], floorIMG);
ENGINE.fill(LAYER[wallLayer], wallIMG);
if (drawGrid) {
ENGINE.clearLayer("grid");
GRID.grid();
}
},
repaint: function(
grid,
floorIMG,
wallIMG,
floorLayer = "floor",
wallLayer = "wall",
drawGrid = false
) {
GRID.paint(grid, floorIMG, wallIMG, floorLayer, wallLayer, drawGrid);
const height = grid.length;
const width = grid[0].length;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
if (grid[y].charAt(x) === "0") {
let point = GRID.gridToCoord({ x: x, y: y });
ENGINE.cutGrid(LAYER[wallLayer], point);
}
}
}
},
map: {
pack: function(grid) {
var RL = grid.length;
var converted = [];
for (var i = 0; i < RL; i++) {
converted.push(parseInt(grid[i], 2));
}
return converted;
},
unpack: function(map) {
var h = map.height;
var w = map.width;
if (h != map.grid.length) {
throw "Map corrupted: height:" + h + " grid.length: " + map.grid.length;
}
var binary = [];
for (var i = 0; i < h; i++) {
let binTemp = float64ToInt64Binary(map.grid[i]).padStart(w, "0");
binary.push(binTemp);
}
return binary;
}
},
isBlock: function(x, y, map = MAP[GAME.level]) {
var block = map.grid[y].charAt(x);
if (block === "1") {
return true;
} else return false;
},
gridIsBlock: function(grid, map = MAP[GAME.level]) {
return GRID.isBlock(grid.x, grid.y, map);
},
trueToGrid: function(actor) {
var TX = actor.x - ENGINE.INI.GRIDPIX / 2;
var TY = actor.y - ENGINE.INI.GRIDPIX / 2;
var GX = TX / ENGINE.INI.GRIDPIX;
var GY = TY / ENGINE.INI.GRIDPIX;
var MX = TX % ENGINE.INI.GRIDPIX;
var MY = TY % ENGINE.INI.GRIDPIX;
if (MX || MY) {
return null;
} else return { x: GX, y: GY };
},
same: function(grid1, grid2) {
if (grid1 === null || grid2 === null) return false;
if (grid1.x === grid2.x && grid1.y === grid2.y) {
return true;
} else return false;
},
isGridIn: function(grid, gridArray) {
for (var q = 0; q < gridArray.length; q++) {
if (grid.x === gridArray[q].x && grid.y === gridArray[q].y) {
return q;
}
}
return -1;
},
getDirections: function(grid, obstacles = []) {
var directions = [];
for (let D = 0; D < ENGINE.directions.length; D++) {
let newGrid = grid.add(ENGINE.directions[D]);
if (!GRID.gridIsBlock(newGrid) && newGrid.isInAt(obstacles) === -1) {
directions.push(ENGINE.directions[D]);
}
}
return directions;
},
findCrossroad: function(start, dir) {
let directions = GRID.getDirections(start);
let back = dir.mirror();
let BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
while (directions.length < 2) {
dir = directions[0];
start = start.add(dir);
directions = GRID.getDirections(start);
back = dir.mirror();
BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
}
return start;
},
findCrossroadAndLastDir: function(start, dir) {
let directions = GRID.getDirections(start);
let back = dir.mirror();
let BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
while (directions.length < 2) {
dir = directions[0];
start = start.add(dir);
directions = GRID.getDirections(start);
back = dir.mirror();
BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
}
return { finish: start, dir: dir };
},
pathToCrossroad: function(start, dir) {
let path = [];
path.push(dir);
start = start.add(dir);
let directions = GRID.getDirections(start);
if (directions.length === 1) return path;
let back = dir.mirror();
let BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
while (directions.length === 1) {
dir = directions[0];
path.push(dir);
start = start.add(dir);
directions = GRID.getDirections(start);
if (directions.length === 1) return path;
back = dir.mirror();
BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
}
return path;
},
findLengthToCrossroad: function(start, stack) {
if (stack === null) return;
var q = 0;
do {
if (stack[q] === undefined) return null;
start = start.add(stack[q]);
q++;
} while (GRID.getDirections(start).length < 3);
return q;
},
translateMove: function(entity, changeView = false, onFinish = null) {
entity.actor.x += entity.MoveState.dir.x * entity.speed;
entity.actor.y += entity.MoveState.dir.y * entity.speed;
entity.actor.orientation = entity.actor.getOrientation(
entity.MoveState.dir
);
entity.actor.animateMove(entity.actor.orientation);
entity.MoveState.homeGrid = GRID.coordToGrid(
entity.actor.x,
entity.actor.y
);
if (changeView) {
ENGINE.VIEWPORT.check(entity.actor);
}
ENGINE.VIEWPORT.alignTo(entity.actor);
if (GRID.same(entity.MoveState.endGrid, GRID.trueToGrid(entity.actor))) {
entity.MoveState.moving = false;
entity.MoveState.startGrid = entity.MoveState.endGrid;
entity.MoveState.homeGrid = entity.MoveState.endGrid;
if (onFinish) onFinish.call();
}
return;
},
blockMove: function(entity, changeView = false) {
let newGrid = entity.MoveState.startGrid.add(entity.MoveState.dir);
entity.MoveState.reset(newGrid);
GRID.gridToSprite(newGrid, entity.actor);
entity.actor.orientation = entity.actor.getOrientation(
entity.MoveState.dir
);
entity.actor.animateMove(entity.actor.orientation);
if (changeView) {
ENGINE.VIEWPORT.check(entity.actor);
}
ENGINE.VIEWPORT.alignTo(entity.actor);
},
teleportToGrid: function(entity, grid, changeView = false) {
entity.MoveState.reset(grid);
GRID.gridToSprite(grid, entity.actor);
if (changeView) {
ENGINE.VIEWPORT.check(entity.actor);
}
ENGINE.VIEWPORT.alignTo(entity.actor);
},
findDungeonPath: function(
start,
finish,
obstacles,
limit = ENGINE.INI.MAX_PATH,
firstDir = new Vector(0, 0)
) {
//for dungeons with rooms
const finishID = MAP[GAME.level].DUNGEON.isInWhichRoom(finish);
var Q = new NodeQ();
Q.list.push(new Node(start, finish, [firstDir]));
if (Q.list[0].dist === 0) return null;
var selected;
var round = 0;
while (Q.list.length > 0) {
selected = Q.list.shift();
if (selected.path > limit) {
selected.status = "Excess";
selected.stack.splice(0, 1);
return selected;
}
const selectedID = MAP[GAME.level].DUNGEON.isInWhichRoom(selected.grid);
if ((selectedID && finishID && selectedID.id !== finishID.id) || (selectedID && finishID === null)) {
let doors = selectedID.door.clone();
for (let i = doors.length - 1; i >= 0; i--){
if (doors[i].isInAt(selected.history) >= 0){
doors.splice(i, 1);
}
}
for (let i = doors.length - 1; i >= 0; i--){
if (doors[i].isInAt(MAP[GAME.level].DUNGEON.obstacles) >= 0){
doors.splice(i, 1);
}
}
let Q_array = [];
if (doors.length > 0){
for (let i = doors.length - 1; i >= 0; i--){
let newDoorNode = GRID.findPath(selected.grid, doors[i], MAP[GAME.level].DUNGEON.obstacles, 99, selected.stack[selected.stack.length - 1]);
Q_array.push(newDoorNode);
}
}
for (let i = 0, LN = Q_array.length; i < LN; i++){
let node = selected.append(Q_array[i], finish);
Q.queue(node);
}
} else {
let nodes = GRID.FP.nextNodesFromQueue(selected, finish, round, obstacles);
for (let q = 0; q < nodes.length; q++){
if (nodes[q].dist === 0) {
nodes[q].stack.splice(0, 1);
nodes[q].status = "Found";
return nodes[q];
} else Q.queue(nodes[q]);
}
}
round++;
if (round > ENGINE.INI.PATH_ROUNDS) {
break;
}
}
return GRID.FP.returnSolution(Q, selected);
},
findPath: function(
start,
finish,
obstacles,
limit = ENGINE.INI.MAX_PATH,
firstDir = new Vector(0, 0)
) {
//for corridors only
var Q = new NodeQ();
Q.list.push(new Node(start, finish, [firstDir]));
if (Q.list[0].dist === 0) return null;
var selected;
var round = 0;
while (Q.list.length > 0) {
selected = Q.list.shift();
if (selected.path > limit) {
selected.status = "Excess";
selected.stack.splice(0, 1);
return selected;
}
let nodes = GRID.FP.nextNodesFromQueue(selected, finish, round, obstacles);
for (let q = 0; q < nodes.length; q++){
if (nodes[q].dist === 0) {
nodes[q].stack.splice(0, 1);
nodes[q].status = "Found";
return nodes[q];
} else Q.queue(nodes[q]);
}
round++;
if (round > ENGINE.INI.PATH_ROUNDS) {
break;
}
}
return GRID.FP.returnSolution(Q, selected);
},
FP: {
nextNodesFromQueue: function(selected, finish, round, obstacles){
let nodes = [];
let dirs = GRID.getDirections(selected.grid, obstacles);
let back = selected.stack[selected.stack.length - 1].mirror();
let iB = back.isInAt(dirs);
if (iB !== -1) {
let waste = dirs.splice(iB, 1);
}
for (let q = 0; q < dirs.length; q++) {
let HG = selected.grid.add(dirs[q]);
if (HG.isInAt(selected.history) >= 0) {
continue;
}
let history = [].concat(selected.history);
history.push(HG);
let I_stack = [].concat(selected.stack);
I_stack.push(dirs[q]);
let node = new Node(HG, finish, I_stack, selected.path + 1, history, round);
nodes.push(node);
}
return nodes;
},
returnSolution: function(Q, selected){
if (Q.list.length > 0) {
Q.list[0].stack.splice(0, 1);
Q.list[0].status = "Abandoned";
return Q.list[0];
} else {
selected.status = "NoSolution";
selected.stack.splice(0, 1);
return selected;
}
}
},
findPathToFirstCrossroad: function(
start,
finish,
firstDir = new Vector(0, 0)
) {
let path = GRID.findPath(start, finish, firstDir);
let len = GRID.findLengthToCrossroad(start, path);
if (len > 0) path.splice(len);
return path;
},
paintPath: function(layer, color, path, start, z = 0) {
if (path === null) return;
var CTX = LAYER[layer];
ENGINE.clearLayer(layer);
CTX.strokeStyle = color;
var point = GRID.gridToCenterPX(start);
point.toViewport();
var PL = path.length;
CTX.beginPath();
CTX.moveTo(point.x + z, point.y + z);
for (let q = 0; q < PL; q++) {
point = point.translate(path[q]);
CTX.lineTo(point.x + z, point.y + z);
CTX.stroke();
}
},
AI: {
// assumes HERO is hunted entity; please refactor this!
advancer: {
hunt: function() {
let next = GRID.findCrossroadAndLastDir(
HERO.MoveState.startGrid,
HERO.MoveState.dir
);
let nextCR = next.finish;
let directions = GRID.getDirections(nextCR);
let back = next.dir.mirror();
let BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
if (HERO.MoveState.dir.isInAt(directions) !== -1) {
return {
type: "grid",
return: GRID.findCrossroad(
nextCR.add(HERO.MoveState.dir),
HERO.MoveState.dir
)
};
} else {
let LNs = [];
let CRs = [];
for (let q = 0; q < directions.length; q++) {
CRs.push(
GRID.findCrossroad(nextCR.add(directions[q]), directions[q])
);
LNs.push(CRs[q].distance(HERO.MoveState.startGrid));
}
let qq = LNs.indexOf(Math.min(...LNs));
return { type: "grid", return: CRs[qq] };
}
}
},
default: {
hunt: function() {
return {
type: "grid",
return: GRID.findCrossroad(
HERO.MoveState.startGrid,
HERO.MoveState.dir
)
};
}
},
shadower: {
hunt: function(MS, tolerance) {
let solutions = MS.endGrid.directionSolutions(HERO.MoveState.homeGrid);
let directions = GRID.getDirections(MS.endGrid);
let back = MS.dir.mirror();
let BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
let selected;
if (directions.length === 1) {
selected = directions[0];
} else {
if (
MS.goingAway(HERO.MoveState) ||
!MS.towards(HERO.MoveState, tolerance)
) {
if (HERO.MoveState.dir.isInAt(directions) !== -1) {
selected = HERO.MoveState.dir;
} else selected = solve();
} else {
let contra = HERO.MoveState.dir.mirror();
if (contra.isInAt(directions) !== -1) {
selected = contra;
} else selected = solve();
}
}
if (!selected) {
selected = directions.chooseRandom();
}
let path = GRID.pathToCrossroad(MS.endGrid, selected);
return { type: "path", return: path };
function solve() {
for (let q = 0; q < 2; q++) {
if (solutions[q].dir.isInAt(directions) !== -1)
return solutions[q].dir;
}
return null;
}
}
},
follower: {
hunt: function() {
return {
type: "grid",
return: GRID.findCrossroad(
HERO.MoveState.startGrid,
HERO.MoveState.dir.mirror()
)
};
}
},
wanderer: {
hunt: function(MS) {
let directions = GRID.getDirections(MS.endGrid);
if (directions.length > 1) {
let back = MS.dir.mirror();
let BI = back.isInAt(directions);
if (BI !== -1) directions.splice(BI, 1);
}
let selected = directions.chooseRandom();
let path = GRID.pathToCrossroad(MS.endGrid, selected);
return { type: "path", return: path };
}
},
keepTheDistance: {
hunt:function(MS, reference, setDistance){
// if all distances below setDistance it does nothing --> it should run if possible
//console.log("keeping the distance", arguments);
let directions = GRID.getDirections(MS.endGrid, MAP[GAME.level].DUNGEON.obstacles);
//console.log("directions", directions);
let possible = [];
let max = [];
let curMax = 0;
for (let i = 0; i < directions.length; i++){
let test = MS.endGrid.add(directions[i]);
let distance = test.distanceDiagonal(reference);
console.log(i, directions[i], test, distance);
if (distance === setDistance) possible.push(directions[i]);
if (distance > curMax){
max.clear();
curMax = distance;
max.push(directions[i]);
} else if (distance === curMax) max.push(directions[i]);
}
let path;
//console.log("possible", possible, max);
if (possible.length > 0){
path = [possible.chooseRandom()];
} else if (max.length > 0){
path = [max.chooseRandom()];
} else path = [];
return { type: "path", return: path };
}
}
},
gridToIndex: function(grid) {
return grid.x + grid.y * MAP[GAME.level].width;
},
indexToGrid: function(index) {
let x = index % MAP[GAME.level].width;
let y = Math.floor(index / MAP[GAME.level].width);
return new Grid(x, y);
},
vision: function(startGrid, endGrid) {
if (GRID.same(startGrid, endGrid)) return true;
let path = GRID.raycasting(startGrid, endGrid);
return GRID.pathClear(path);
},
raycasting: function(startGrid, endGrid) {
let normDir = startGrid.direction(endGrid);
let path = [];
path.push(Grid.toClass(startGrid));
let x = startGrid.x;
let y = startGrid.y;
let dx = Math.abs(endGrid.x - x);
let dy = -Math.abs(endGrid.y - y);
let Err = dx + dy;
let E2, node;
do {
E2 = Err * 2;
if (E2 >= dy) {
Err += dy;
x += normDir.x;
}
if (E2 <= dx) {
Err += dx;
y += normDir.y;
}
node = new Grid(x, y);
path.push(node);
} while (!GRID.same(node, endGrid));
return path;
},
pathClear: function(path) {
if (path.length === 0) return true;
for (let q = 0; q < path.length; q++) {
if (GRID.gridIsBlock(path[q])) return false;
}
return true;
}
};
class Node {
constructor(HG, goal, stack, path, history, iterations) {
this.grid = HG;
this.stack = stack;
this.history = history || [HG];
this.path = path || 0;
this.dist = this.grid.distance(goal);
this.priority = this.path + this.dist;
this.status = "Progress";
this.iterations = iterations || 0;
}
append(node, goal){
let stack = this.stack.concat(node.stack);
let history = this.history.concat(node.history.slice(1));
let path = this.path + node.path;
return new Node(node.grid, goal, stack, path, history);
}
}
class NodeQ {
constructor() {
this.list = [];
}
queue(node) {
var included = false;
for (let q = 0; q < this.list.length; q++) {
if (this.list[q].priority >= node.priority) {
this.list.splice(q, 0, node);
included = true;
break;
}
}
if (!included) this.list.push(node);
}
}
class MoveState {
constructor(startGrid, dir) {
this.startGrid = Grid.toClass(startGrid);
this.dir = dir || null;
this.homeGrid = Grid.toClass(startGrid);
this.endGrid = Grid.toClass(startGrid);
this.moving = false;
}
setEnd() {
if (this.dir !== null) {
this.endGrid = this.startGrid.add(this.dir);
this.moving = true;
}
}
next(dir) {
if (dir !== null) {
this.startGrid = this.endGrid;
this.dir = dir;
this.setEnd();
}
}
flip() {
this.homeGrid = this.startGrid;
this.startGrid = this.endGrid;
this.endGrid = this.homeGrid;
}
reverse() {
this.dir = this.dir.mirror();
this.flip();
}
goingAway(MS) {
let oldDistance = this.homeGrid.distance(MS.startGrid);
let newDistance = this.homeGrid.distance(MS.startGrid.add(MS.dir));
return newDistance > oldDistance;
}
towards(MS, tolerance = 5) {
let oldDistance = this.homeGrid.distance(MS.startGrid);
let newDistance = this.homeGrid.distance(MS.startGrid.add(MS.dir));
return newDistance < oldDistance && newDistance < tolerance;
}
closerGrid(MS) {
if (
this.startGrid.distance(MS.homeGrid) < this.endGrid.distance(MS.homeGrid)
) {
return this.startGrid;
} else {
return this.endGrid;
}
}
reset(grid) {
this.startGrid = Grid.toClass(grid);
this.homeGrid = Grid.toClass(grid);
this.endGrid = Grid.toClass(grid);
this.moving = false;
}
}
var VIEW = {
init: function() {
VIEW.x = 0;
VIEW.y = 0;
VIEW.speed = 1;
VIEW.actor = new ACTOR(null, 0, 0);
},
move: function(dir) {
VIEW.actor.x += VIEW.speed * ENGINE.INI.GRIDPIX * dir.x;
VIEW.actor.y += VIEW.speed * ENGINE.INI.GRIDPIX * dir.y;
if (VIEW.actor.x < 0) VIEW.actor.x = 0;
if (VIEW.actor.y < 0) VIEW.actor.y = 0;
if (VIEW.actor.x > ENGINE.VIEWPORT.max.x)
VIEW.actor.x = ENGINE.VIEWPORT.max.x;
if (VIEW.actor.y > ENGINE.VIEWPORT.max.y)
VIEW.actor.y = ENGINE.VIEWPORT.max.y;
ENGINE.VIEWPORT.check(VIEW.actor);
ENGINE.VIEWPORT.alignTo(VIEW.actor);
}
};
var FORM = {
INI: {
DIV: "#ROOM"
}
};
class Form {
constructor(name, x, y, w, h, wedge) {
this.name = name;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
$(FORM.INI.DIV).append(
`<div id = 'FORM' class = 'form'><h1>${this.name}</h1><hr></div>`
);
$("#FORM").css({
top: this.y,
left: this.x,
width: this.w,
height: this.h
});
$("#FORM").append(wedge);
}
}
class Inventory {
constructor() {
this.list = [];
}
add(element) {
for (let q = 0, QL = this.list.length; q < QL; q++) {
let item = this.list[q].object;
if (element.id === item.id) {
this.list[q].count++;
return;
}
}
this.list.push(new Item(element, 1));
}
remove(index) {
let element = this.list[index].object;
this.list[index].count--;
if (this.list[index].count === 0) this.list.splice(index, 1);
return element;
}
size() {
return this.list.length;
}
find(prop, value) {
for (let q = 0, QL = this.list.length; q < QL; q++) {
let item = this.list[q].object;
if (item[prop] === value) return q;
}
return null;
}
getCount(prop, value) {
let index = this.find(prop, value);
if (index !== null) {
return this.list[index].count;
} else return 0;
}
}
class Item {
constructor(object, count) {
this.object = object;
this.count = count;
}
}
class SimpleTimer {
constructor(seconds, func) {
this.value = seconds;
this.start = performance.now();
this.now = null;
this.delta = null;
this.func = func;
}
update() {
this.now = performance.now();
this.delta = Math.round((this.now - this.start) / 1000);
if (this.delta >= this.value) this.func.call();
}
}
class Timer {
constructor() {
this.start = Date.now();
}
time() {
let time = (Date.now() - this.start) / 1000;
let hours = Math.floor(time / 3600);
let min = Math.floor((time % 3600) / 60);
let sec = Math.floor((time % 3600) % 60);
return { h: hours, m: min, s: sec };
}
timeString() {
let time = this.time();
let str = time.h.toString().padStart(2, "0") + ":";
str += time.m.toString().padStart(2, "0") + ":";
str += time.s.toString().padStart(2, "0");
return str;
}
}
var CONSOLE = {
id: "Console",
set: function(id) {
CONSOLE.id = id;
},
print: function(text) {
$(`#${CONSOLE.id}`).append(`<p>${text}</p>`);
$(`#${CONSOLE.id}`)
.children()
.last()[0]
.scrollIntoView();
}
};
//END
console.log(`%cENGINE ${ENGINE.VERSION} loaded.`, ENGINE.CSS);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment