EmulatorJS/data/emulator.js

3618 lines
209 KiB
JavaScript
Raw Normal View History

class EmulatorJS {
2023-07-06 16:33:12 +00:00
version = 1; //All versions for the core
2023-07-01 16:46:52 +00:00
getCore(generic) {
const core = this.config.system;
2023-07-05 00:27:38 +00:00
//todo: sega32x, TurboGrafs-16 (pce), Wanderswan (ws), ngp, msx
2023-07-01 16:46:52 +00:00
if (generic) {
const options = {
2023-07-05 00:27:38 +00:00
'virtualjaguar': 'jaguar',
'handy': 'lynx',
2023-07-05 18:01:22 +00:00
'yabause': 'segaSaturn',
2023-07-06 16:45:41 +00:00
'genesis_plus_gx': 'sega',//MS, MD, GG, CD... which do we return?
2023-07-01 16:46:52 +00:00
'fceumm': 'nes',
'snes9x': 'snes',
'a5200': 'atari5200',
'gambatte': 'gb',
'mgba': 'gba',
'beetle_vb': 'vb',
'mupen64plus_next': 'n64',
'desmume2015': 'nds',
'mame2003': 'mame2003',
'fbalpha2012_cps1': 'arcade',
'fbalpha2012_cps2': 'arcade',
'mednafen_psx': 'psx',
'mednafen_psx_hw': 'psx',
'melonds': 'nds',
'nestopia': 'nes',
2023-07-05 00:27:38 +00:00
'opera': '3do',
'prosystem': 'atari7800',
'stella2014': 'atari2600'
2023-07-01 16:46:52 +00:00
}
return options[core] || core;
}
const options = {
2023-07-05 00:27:38 +00:00
'jaguar': 'virtualjaguar',
'lynx': 'handy',
'segaSaturn': 'yabause',
'segaMS': 'genesis_plus_gx',
'segaMD': 'genesis_plus_gx',
'segaGG': 'genesis_plus_gx',
'segaCD': 'genesis_plus_gx',
'atari2600': 'stella2014',
'atari7800': 'prosystem',
2023-07-01 16:46:52 +00:00
'nes': 'fceumm',
'snes': 'snes9x',
'atari5200': 'a5200',
'gb': 'gambatte',
'gba': 'mgba',
'vb': 'beetle_vb',
'n64': 'mupen64plus_next',
2023-07-06 14:34:12 +00:00
'nds': 'melonds',
2023-07-01 16:46:52 +00:00
'mame2003': 'mame2003',
'arcade': 'fbalpha2012_cps1', // I need to find a more compatible arcade core
'psx': 'mednafen_psx_hw',
'3do': 'opera'
}
return options[core] || core;
}
extensions = {
'fceumm': ['fds', 'nes', 'unif', 'unf'],
'snes9x': ['smc', 'sfc', 'swc', 'fig', 'bs', 'st'],
'a5200': ['a52', 'bin'],
'gambatte': ['gb', 'gbc', 'dmg'],
'mgba': ['gb', 'gbc', 'gba'],
'beetle_vb': ['vb', 'vboy', 'bin'],
'mupen64plus_next': ['n64', 'v64', 'z64', 'bin', 'u1', 'ndd', 'gb'],
'fbalpha2012_cps1': ['zip'],
'fbalpha2012_cps2': ['zip'],
'mame2003': ['zip'],
'desmume2015': ['nds', 'bin'],
'melonds': ['nds'],
'mednafen_psx': ['cue', 'toc', 'm3u', 'ccd', 'exe', 'pbp', 'chd'],
'mednafen_psx_hw': ['cue', 'toc', 'm3u', 'ccd', 'exe', 'pbp', 'chd'],
'nestopia': ['fds', 'nes', 'unif', 'unf'],
'opera': ['iso', 'bin', 'chd', 'cue']
}
createElement(type) {
return document.createElement(type);
}
addEventListener(element, listener, callback) {
const listeners = listener.split(" ");
2023-06-28 17:57:02 +00:00
let rv = [];
for (let i=0; i<listeners.length; i++) {
element.addEventListener(listeners[i], callback);
2023-06-28 17:57:02 +00:00
const data = {cb:callback, elem:element, listener:listeners[i]};
rv.push(data);
this.listeners.push(data);
}
return rv;
}
removeEventListener(data) {
for (let i=0; i<data.length; i++) {
data[i].elem.removeEventListener(data[i].listener, data[i].cb);
}
}
downloadFile(path, cb, progressCB, notWithPath, opts) {
const basePath = notWithPath ? '' : this.config.dataPath;
path = basePath + path;
2023-07-03 15:49:28 +00:00
if (!notWithPath && this.config.filePaths) {
if (typeof this.config.filePaths[path.split('/').pop()] === "string") {
path = this.config.filePaths[path.split('/').pop()];
}
}
let url;
try {url=new URL(path)}catch(e){};
if ((url && ['http:', 'https:'].includes(url.protocol)) || !url) {
const xhr = new XMLHttpRequest();
if (progressCB instanceof Function) {
xhr.addEventListener('progress', (e) => {
const progress = e.total ? ' '+Math.floor(e.loaded / e.total * 100).toString()+'%' : ' '+(e.loaded/1048576).toFixed(2)+'MB';
progressCB(progress);
});
}
xhr.onload = function() {
if (xhr.readyState === xhr.DONE) {
let data = xhr.response;
try {data=JSON.parse(data)}catch(e){}
cb({
data: data,
headers: {
2023-06-30 15:19:52 +00:00
"content-length": xhr.getResponseHeader('content-length')
}
});
}
}
2023-06-30 15:19:52 +00:00
if (opts.responseType) xhr.responseType = opts.responseType;
xhr.onerror = () => cb(-1);
xhr.open(opts.method, path, true);
xhr.send();
} else {
(async () => {
//Most commonly blob: urls. Not sure what else it could be
if (opts.method === 'HEAD') {
cb({headers:{}});
return;
}
let res;
try {
res = await fetch(path);
2023-07-04 03:56:29 +00:00
if ((opts.type && opts.type.toLowerCase() === 'arraybuffer') || !opts.type) {
res = await res.arrayBuffer();
} else {
res = await res.text();
try {res = JSON.parse(res)} catch(e) {}
}
} catch(e) {
cb(-1);
}
if (path.startsWith('blob:')) URL.revokeObjectURL(path);
cb({
data: res,
headers: {}
});
2023-07-04 03:56:29 +00:00
})();
}
}
constructor(element, config) {
2023-07-03 17:56:04 +00:00
this.ejs_version = "4.0";
2023-07-18 12:52:32 +00:00
//this.netplay = false; //DO NOT ENABLE UNLESS YOU KNOW WHAT YOU'RE DOING
2023-07-03 14:34:48 +00:00
this.config = config;
2023-06-23 16:33:20 +00:00
window.EJS_TESTING = this;
2023-07-01 20:16:25 +00:00
this.currentPopup = null;
this.touch = false;
this.debug = (window.EJS_DEBUG_XX === true);
2023-06-29 15:35:25 +00:00
this.cheats = [];
this.started = false;
2023-07-03 14:34:48 +00:00
this.volume = (typeof this.config.volume === "number") ? this.config.volume : 0.5;
2023-07-03 14:38:29 +00:00
if (this.config.defaultControllers) this.defaultControllers = this.config.defaultControllers;
2023-07-01 21:21:48 +00:00
this.muted = false;
this.paused = true;
this.listeners = [];
2023-07-01 18:15:26 +00:00
this.setElements(element);
this.setColor(this.config.color || "");
2023-07-01 18:59:39 +00:00
if (this.config.adUrl) this.setupAds(this.config.adUrl);
this.canvas = this.createElement('canvas');
this.canvas.classList.add('ejs_canvas');
this.bindListeners();
2023-07-17 17:12:05 +00:00
this.config.netplayUrl = this.config.netplayUrl || "https://netplay.emulatorjs.org";
2023-06-23 16:33:20 +00:00
this.fullscreen = false;
2023-07-06 17:13:57 +00:00
this.isMobile = (function() {
let check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
return check;
})();
2023-06-30 15:19:52 +00:00
this.storage = {
rom: new window.EJS_STORAGE("EmulatorJS-roms", "rom"),
bios: new window.EJS_STORAGE("EmulatorJS-bios", "bios"),
2023-07-03 17:03:00 +00:00
core: new window.EJS_STORAGE("EmulatorJS-core", "core"),
states: new window.EJS_STORAGE("EmulatorJS-states", "states")
2023-06-30 15:19:52 +00:00
}
this.game.classList.add("ejs_game");
2023-07-18 14:59:31 +00:00
if (typeof this.config.backgroundImg === "string") {
this.game.style["background-image"] = "url('"+this.config.backgroundImg+"')";
this.game.style["background-size"] = "contain";
this.game.style["background-position"] = "center";
this.game.style["background-repeat"] = "no-repeat";
this.on("start", () => {
this.game.style["background-image"] = "";
this.game.style["background-size"] = "";
this.game.style["background-position"] = "";
this.game.style["background-repeat"] = "";
})
}
2023-07-03 16:25:22 +00:00
if (Array.isArray(this.config.cheats)) {
for (let i=0; i<this.config.cheats.length; i++) {
const cheat = this.config.cheats[i];
if (Array.isArray(cheat) && cheat[0] && cheat[1]) {
this.cheats.push({
desc: cheat[0],
checked: false,
code: cheat[1]
})
}
}
}
this.createStartButton();
console.log(this)
}
2023-07-01 18:15:26 +00:00
setColor(color) {
if (typeof color !== "string") color = "";
let getColor = function(color) {
color = color.toLowerCase();
if (color && /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(color)) {
if (color.length === 4) {
2023-07-07 15:02:00 +00:00
let rv = '#';
2023-07-01 18:15:26 +00:00
for (let i=1; i<4; i++) {
rv += color.slice(i, i+1)+color.slice(i, i+1);
}
color = rv;
}
let rv = [];
for (let i=1; i<7; i+=2) {
rv.push(parseInt('0x'+color.slice(i, i+2), 16));
}
return rv.join(", ");
}
return null;
}
if (!color || getColor(color) === null) {
this.elements.parent.setAttribute("style", "--ejs-primary-color: 26,175,255;");
return;
}
this.elements.parent.setAttribute("style", "--ejs-primary-color:" + getColor(color) + ";");
}
2023-07-01 18:59:39 +00:00
setupAds(ads) {
const div = this.createElement("div");
div.classList.add("ejs_ad_iframe");
const frame = this.createElement("iframe");
frame.src = ads;
frame.setAttribute("scrolling", "no");
frame.setAttribute("frameborder", "no");
frame.style.width = "300px";
frame.style.height = "250px";
const closeParent = this.createElement("div");
closeParent.classList.add("ejs_ad_close");
const closeButton = this.createElement("a");
closeParent.appendChild(closeButton);
closeParent.setAttribute("hidden", "");
div.appendChild(closeParent);
div.appendChild(frame);
this.elements.parent.appendChild(div);
this.addEventListener(closeButton, "click", () => {
div.remove();
})
this.on("start", () => {
closeParent.removeAttribute("hidden");
2023-07-01 19:06:16 +00:00
const time = (typeof this.config.adTimer === "number" && this.config.adTimer > 0) ? this.config.adTimer : 10000;
if (this.config.adTimer === 0) return;
setTimeout(() => {
div.remove();
}, time);
2023-07-01 18:59:39 +00:00
})
}
functions = {};
on(event, func) {
if (!Array.isArray(this.functions[event])) this.functions[event] = [];
this.functions[event].push(func);
}
callEvent(event, data) {
if (!Array.isArray(this.functions[event])) return 0;
this.functions[event].forEach(e => e(data));
return this.functions[event].length;
}
setElements(element) {
2023-07-01 18:15:26 +00:00
const game = this.createElement("div");
const elem = document.querySelector(element);
elem.innerHTML = "";
elem.appendChild(game);
this.game = game;
this.elements = {
main: this.game,
2023-07-01 18:15:26 +00:00
parent: elem
}
this.elements.parent.classList.add("ejs_parent");
2023-06-29 16:34:34 +00:00
this.elements.parent.setAttribute("tabindex", -1);
}
// Start button
createStartButton() {
const button = this.createElement("div");
button.classList.add("ejs_start_button");
button.innerText = this.localization("Start Game");
this.elements.parent.appendChild(button);
this.addEventListener(button, "touchstart", () => {
this.touch = true;
})
this.addEventListener(button, "click", this.startButtonClicked.bind(this));
2023-07-03 15:49:28 +00:00
if (this.config.startOnLoad === true) {
this.startButtonClicked(button);
}
}
startButtonClicked(e) {
2023-07-05 00:32:00 +00:00
if (e.pointerType === "touch") {
this.touch = true;
}
2023-07-03 15:49:28 +00:00
if (e.preventDefault) {
e.preventDefault();
e.target.remove();
} else {
e.remove();
}
this.createText();
this.downloadGameCore();
}
// End start button
createText() {
this.textElem = this.createElement("div");
this.textElem.classList.add("ejs_loading_text");
this.textElem.innerText = this.localization("Loading...");
this.elements.parent.appendChild(this.textElem);
}
localization(text) {
2023-07-03 16:37:43 +00:00
if (!isNaN(text)) return text;
if (this.config.langJson) {
if (!this.config.langJson[text]) {
console.log("Translation not found for '"+text+"'. Language set to '"+this.config.language+"'");
}
return this.config.langJson[text] || text;
}
return text;
}
checkCompression(data, msg) {
if (msg) {
this.textElem.innerText = msg;
}
//to be put in another file
function isCompressed(data) { //https://www.garykessler.net/library/file_sigs.html
//todo. Use hex instead of numbers
if ((data[0] === 80 && data[1] === 75) && ((data[2] === 3 && data[3] === 4) || (data[2] === 5 && data[3] === 6) || (data[2] === 7 && data[3] === 8))) {
return 'zip';
} else if (data[0] === 55 && data[1] === 122 && data[2] === 188 && data[3] === 175 && data[4] === 39 && data[5] === 28) {
return '7z';
} else if ((data[0] === 82 && data[1] === 97 && data[2] === 114 && data[3] === 33 && data[4] === 26 && data[5] === 7) && ((data[6] === 0) || (data[6] === 1 && data[7] == 0))) {
return 'rar';
}
}
const createWorker = (path) => {
return new Promise((resolve, reject) => {
this.downloadFile(path, (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
const blob = new Blob([res.data], {
'type': 'application/javascript'
})
const url = window.URL.createObjectURL(blob);
resolve(new Worker(url));
}, null, false, {responseType: "arraybuffer", method: "GET"});
})
}
const decompress7z = (file) => {
return new Promise((resolve, reject) => {
const files = {};
const onMessage = (data) => {
if (!data.data) return;
//data.data.t/ 4=progress, 2 is file, 1 is zip done
if (data.data.t === 4 && msg) {
const pg = data.data;
const num = Math.floor(pg.current / pg.total * 100);
if (isNaN(num)) return;
const progress = ' '+num.toString()+'%';
this.textElem.innerText = msg + progress;
}
if (data.data.t === 2) {
files[data.data.file] = data.data.data;
}
if (data.data.t === 1) {
resolve(files);
}
}
createWorker('compression/extract7z.js').then((worker) => {
worker.onmessage = onMessage;
worker.postMessage(file);
//console.log(file);
})
})
}
const decompressRar = (file) => {
return new Promise((resolve, reject) => {
const files = {};
const onMessage = (data) => {
if (!data.data) return;
//data.data.t/ 4=progress, 2 is file, 1 is zip done
if (data.data.t === 4 && msg) {
const pg = data.data;
const num = Math.floor(pg.current / pg.total * 100);
if (isNaN(num)) return;
const progress = ' '+num.toString()+'%';
this.textElem.innerText = msg + progress;
}
if (data.data.t === 2) {
files[data.data.file] = data.data.data;
}
if (data.data.t === 1) {
resolve(files);
}
}
this.downloadFile("compression/libunrar.js", (res) => {
2023-07-06 16:33:12 +00:00
this.downloadFile("compression/libunrar.js.mem", (res2) => {
if (res === -1 || res2 === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
const path = URL.createObjectURL(new Blob([res2]));
let data = '\nlet dataToPass = [];\nModule = {\n monitorRunDependencies: function(left) {\n if (left == 0) {\n setTimeout(function() {\n unrar(dataToPass, null);\n }, 100);\n }\n },\n onRuntimeInitialized: function() {\n },\n locateFile: function(file) {\n return \''+path+'\';\n }\n};\n'+res.data+'\nlet unrar = function(data, password) {\n let cb = function(fileName, fileSize, progress) {\n postMessage({"t":4,"current":progress,"total":fileSize, "name": fileName});\n };\n\n let rarContent = readRARContent(data.map(function(d) {\n return {\n name: d.name,\n content: new Uint8Array(d.content)\n }\n }), password, cb)\n let rec = function(entry) {\n if (entry.type === \'file\') {\n postMessage({"t":2,"file":entry.fullFileName,"size":entry.fileSize,"data":entry.fileContent});\n } else if (entry.type === \'dir\') {\n Object.keys(entry.ls).forEach(function(k) {\n rec(entry.ls[k]);\n })\n } else {\n throw "Unknown type";\n }\n }\n rec(rarContent);\n postMessage({"t":1});\n return rarContent;\n};\nonmessage = function(data) {\n dataToPass.push({name: \'test.rar\', content: data.data});\n};\n ';
const blob = new Blob([data], {
'type': 'application/javascript'
})
const url = window.URL.createObjectURL(blob);
const worker = new Worker(url);
worker.onmessage = onMessage;
worker.postMessage(file);
}, null, false, {responseType: "text", method: "GET"})
}, null, false, {responseType: "text", method: "GET"});
})
}
const decompressZip = (file) => {
return new Promise((resolve, reject) => {
const files = {};
const onMessage = (data) => {
2023-06-26 16:39:31 +00:00
//console.log(data);
if (!data.data) return;
//data.data.t/ 4=progress, 2 is file, 1 is zip done
if (data.data.t === 4 && msg) {
const pg = data.data;
const num = Math.floor(pg.current / pg.total * 100);
if (isNaN(num)) return;
const progress = ' '+num.toString()+'%';
this.textElem.innerText = msg + progress;
}
if (data.data.t === 2) {
files[data.data.file] = data.data.data;
}
if (data.data.t === 1) {
resolve(files);
}
}
createWorker('compression/extractzip.js').then((worker) => {
worker.onmessage = onMessage;
worker.postMessage(file);
})
})
}
const compression = isCompressed(data.slice(0, 10));
if (compression) {
//Need to do zip and rar still
if (compression === "7z") {
return decompress7z(data);
} else if (compression === "zip") {
return decompressZip(data);
} else if (compression === "rar") {
return decompressRar(data);
}
} else {
2023-06-26 16:39:31 +00:00
return new Promise(resolve => resolve({"!!notCompressedData": data}));
}
}
downloadGameCore() {
this.textElem.innerText = this.localization("Download Game Core");
2023-06-30 15:19:52 +00:00
const gotCore = (data) => {
this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Core")).then((data) => {
let js, wasm;
for (let k in data) {
if (k.endsWith(".wasm")) {
wasm = data[k];
} else if (k.endsWith(".js")) {
js = data[k];
}
}
this.initGameCore(js, wasm);
});
2023-06-30 15:19:52 +00:00
}
this.storage.core.get(this.getCore()+'-wasm.data').then((result) => {
2023-07-06 16:33:12 +00:00
if (result && result.version === this.version && !this.debug) {
2023-06-30 15:19:52 +00:00
gotCore(result.data);
return;
}
this.downloadFile('cores/'+this.getCore()+'-wasm.data', (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
gotCore(res.data);
this.storage.core.put(this.getCore()+'-wasm.data', {
2023-07-06 16:33:12 +00:00
version: this.version,
2023-06-30 15:19:52 +00:00
data: res.data
});
}, (progress) => {
this.textElem.innerText = this.localization("Download Game Core") + progress;
}, false, {responseType: "arraybuffer", method: "GET"});
})
}
initGameCore(js, wasm) {
this.initModule(wasm);
let script = this.createElement("script");
script.src = URL.createObjectURL(new Blob([js], {type: "application/javascript"}));
document.body.appendChild(script);
}
2023-06-26 16:39:31 +00:00
getBaseFileName() {
//Only once game and core is loaded
if (!this.started) return null;
2023-07-03 17:03:00 +00:00
if (typeof this.config.gameName === "string") {
2023-07-01 18:15:26 +00:00
const invalidCharacters = /[#<$+%>!`&*'|{}/\\?"=@:^\r\n]/ig;
const name = this.config.gameName.replace(invalidCharacters, "").trim();
if (name) return name;
}
2023-06-26 16:39:31 +00:00
let parts = this.fileName.split(".");
parts.splice(parts.length-1, 1);
return parts.join(".");
}
2023-07-03 17:03:00 +00:00
saveInBrowserSupported() {
return !!window.indexedDB && (typeof this.config.gameName === "string" || !this.config.gameUrl.startsWith("blob:"));
}
2023-07-03 17:24:25 +00:00
displayMessage(message) {
if (!this.msgElem) {
this.msgElem = this.createElement("div");
this.msgElem.classList.add("ejs_message");
this.elements.parent.appendChild(this.msgElem);
}
clearTimeout(this.msgTimeout);
this.msgTimeout = setTimeout(() => {
this.msgElem.innerText = "";
}, 3000)
this.msgElem.innerText = message;
}
2023-07-03 16:01:26 +00:00
downloadStartState() {
2023-07-11 16:30:27 +00:00
return new Promise((resolve, reject) => {
if (typeof this.config.loadState !== "string") {
resolve();
2023-07-03 16:01:26 +00:00
return;
}
2023-07-11 16:30:27 +00:00
this.textElem.innerText = this.localization("Download Game State");
this.downloadFile(this.config.loadState, (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
2023-06-30 15:19:52 +00:00
return;
}
2023-07-11 16:30:27 +00:00
this.on("start", () => {
setTimeout(() => {
this.gameManager.loadState(new Uint8Array(res.data));
}, 10);
})
resolve();
}, (progress) => {
this.textElem.innerText = this.localization("Download Game State") + progress;
}, true, {responseType: "arraybuffer", method: "GET"});
})
}
downloadGamePatch() {
return new Promise((resolve, reject) => {
if (typeof this.config.gamePatchUrl !== "string" || !this.config.gamePatchUrl.trim()) {
resolve();
return;
}
this.textElem.innerText = this.localization("Download Game Patch");
const gotData = (data) => {
this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Patch")).then((data) => {
for (const k in data) {
if (k === "!!notCompressedData") {
FS.writeFile(this.config.gamePatchUrl.split('/').pop().split("#")[0].split("?")[0], data[k]);
break;
}
if (k.endsWith('/')) continue;
FS.writeFile("/" + k.split('/').pop(), data[k]);
}
resolve();
})
}
this.downloadFile(this.config.gamePatchUrl, (res) => {
this.storage.rom.get(this.config.gamePatchUrl.split("/").pop()).then((result) => {
if (result && result['content-length'] === res.headers['content-length'] && !this.debug) {
gotData(result.data);
2023-06-30 15:19:52 +00:00
return;
}
2023-07-11 16:30:27 +00:00
this.downloadFile(this.config.gamePatchUrl, (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
gotData(res.data);
const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824;
if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported()) {
this.storage.rom.put(this.config.gamePatchUrl.split("/").pop(), {
"content-length": res.headers['content-length'],
data: res.data
})
}
}, (progress) => {
this.textElem.innerText = this.localization("Download Game Patch") + progress;
}, true, {responseType: "arraybuffer", method: "GET"});
})
}, null, true, {method: "HEAD"})
})
}
downloadGameParent() {
return new Promise((resolve, reject) => {
if (typeof this.config.gameParentUrl !== "string" || !this.config.gameParentUrl.trim()) {
resolve();
return;
}
this.textElem.innerText = this.localization("Download Game Parent");
const gotData = (data) => {
this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Parent")).then((data) => {
for (const k in data) {
if (k === "!!notCompressedData") {
FS.writeFile(this.config.gameParentUrl.split('/').pop().split("#")[0].split("?")[0], data[k]);
break;
}
if (k.endsWith('/')) continue;
FS.writeFile("/" + k.split('/').pop(), data[k]);
2023-07-04 03:56:29 +00:00
}
2023-07-11 16:30:27 +00:00
resolve();
})
}
this.downloadFile(this.config.gameParentUrl, (res) => {
this.storage.rom.get(this.config.gameParentUrl.split("/").pop()).then((result) => {
if (result && result['content-length'] === res.headers['content-length'] && !this.debug) {
gotData(result.data);
return;
}
this.downloadFile(this.config.gameParentUrl, (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
gotData(res.data);
const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824;
if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported()) {
this.storage.rom.put(this.config.gameParentUrl.split("/").pop(), {
"content-length": res.headers['content-length'],
data: res.data
})
}
}, (progress) => {
this.textElem.innerText = this.localization("Download Game Parent") + progress;
}, true, {responseType: "arraybuffer", method: "GET"});
})
}, null, true, {method: "HEAD"})
})
2023-06-26 16:39:31 +00:00
}
2023-07-11 16:30:27 +00:00
downloadBios() {
return new Promise((resolve, reject) => {
if (typeof this.config.biosUrl !== "string" || !this.config.biosUrl.trim()) {
resolve();
2023-07-01 16:46:52 +00:00
return;
}
2023-07-11 16:30:27 +00:00
this.textElem.innerText = this.localization("Download Game BIOS");
const gotBios = (data) => {
this.checkCompression(new Uint8Array(data), this.localization("Decompress Game BIOS")).then((data) => {
for (const k in data) {
if (k === "!!notCompressedData") {
FS.writeFile(this.config.biosUrl.split('/').pop().split("#")[0].split("?")[0], data[k]);
break;
2023-07-06 16:33:12 +00:00
}
2023-07-11 16:30:27 +00:00
if (k.endsWith('/')) continue;
FS.writeFile("/" + k.split('/').pop(), data[k]);
2023-06-26 16:39:31 +00:00
}
2023-07-11 16:30:27 +00:00
resolve();
})
}
this.downloadFile(this.config.biosUrl, (res) => {
this.storage.bios.get(this.config.biosUrl.split("/").pop()).then((result) => {
if (result && result['content-length'] === res.headers['content-length'] && !this.debug) {
gotBios(result.data);
return;
2023-06-26 16:39:31 +00:00
}
2023-07-11 16:30:27 +00:00
this.downloadFile(this.config.biosUrl, (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
gotBios(res.data);
if (this.saveInBrowserSupported()) {
this.storage.bios.put(this.config.biosUrl.split("/").pop(), {
"content-length": res.headers['content-length'],
data: res.data
})
}
}, (progress) => {
this.textElem.innerText = this.localization("Download Game BIOS") + progress;
}, true, {responseType: "arraybuffer", method: "GET"});
})
}, null, true, {method: "HEAD"})
})
}
downloadRom() {
return new Promise((resolve, reject) => {
this.gameManager = new window.EJS_GameManager(this.Module, this);
this.textElem.innerText = this.localization("Download Game Data");
const gotGameData = (data) => {
if (['arcade', 'mame2003'].includes(this.getCore(true))) {
this.fileName = this.config.gameUrl.split('/').pop().split("#")[0].split("?")[0];
FS.writeFile(this.fileName, new Uint8Array(data));
resolve();
return;
}
this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Data")).then((data) => {
const altName = this.config.gameUrl.startsWith("blob:") ? this.config.gameName || "game" : this.config.gameUrl.split('/').pop().split("#")[0].split("?")[0];
const fileNames = (() => {
let rv = [];
for (const k in data) rv.push(k);
return rv;
})();
if (fileNames.length === 1) fileNames[0] = altName;
let execFile = null;
if (this.getCore(true) === "psx") {
execFile = this.gameManager.createCueFile(fileNames);
2023-07-01 16:46:52 +00:00
}
2023-07-11 16:30:27 +00:00
for (const k in data) {
if (k === "!!notCompressedData") {
if (this.getCore(true) === "psx" && execFile !== null) {
this.fileName = execFile;
} else {
this.fileName = altName;
}
FS.writeFile(altName, data[k]);
break;
}
if (k.endsWith('/')) {
FS.mkdir(k);
continue;
}
2023-07-18 14:49:33 +00:00
if (!this.fileName || ((this.extensions[this.getCore()] || []).includes(k.split(".").pop()) &&
2023-07-11 16:30:27 +00:00
//always prefer m3u files for psx cores
!(this.getCore(true) === "psx" && ["m3u", "ccd"].includes(this.fileName.split(".").pop())))) {
this.fileName = k;
}
if (k.includes("/")) {
const paths = k.split("/");
let cp = "";
for (let i=0; i<paths.length-1; i++) {
if (paths[i] === "") continue;
cp += "/"+paths[i];
if (!FS.analyzePath(cp).exists) {
FS.mkdir(cp);
}
2023-07-06 16:33:12 +00:00
}
}
2023-07-11 16:30:27 +00:00
if (this.getCore(true) === "psx" && execFile !== null && ["m3u", "cue"].includes(k.split(".").pop().toLowerCase())) continue;
FS.writeFile("/"+k, data[k]);
2023-07-06 16:33:12 +00:00
}
2023-07-11 16:30:27 +00:00
if (this.getCore(true) === "psx" && execFile !== null) {
this.fileName = execFile;
2023-06-30 15:19:52 +00:00
}
2023-07-11 16:30:27 +00:00
resolve();
});
}
this.downloadFile(this.config.gameUrl, (res) => {
this.storage.rom.get(this.config.gameUrl.split("/").pop()).then((result) => {
if (result && result['content-length'] === res.headers['content-length'] && !this.debug) {
gotGameData(result.data);
return;
2023-07-03 16:16:44 +00:00
}
2023-07-11 16:30:27 +00:00
this.downloadFile(this.config.gameUrl, (res) => {
if (res === -1) {
this.textElem.innerText = "Error";
this.textElem.style.color = "red";
return;
}
gotGameData(res.data);
const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824;
if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported()) {
this.storage.rom.put(this.config.gameUrl.split("/").pop(), {
"content-length": res.headers['content-length'],
data: res.data
})
}
}, (progress) => {
this.textElem.innerText = this.localization("Download Game Data") + progress;
}, true, {responseType: "arraybuffer", method: "GET"});
})
}, null, true, {method: "HEAD"})
})
}
downloadFiles() {
(async () => {
await this.downloadRom();
await this.downloadBios();
await this.downloadStartState();
await this.downloadGameParent();
await this.downloadGamePatch();
this.startGame();
})();
}
initModule(wasmData) {
window.Module = {
'noInitialRun': true,
2023-07-11 16:30:27 +00:00
'onRuntimeInitialized': this.downloadFiles.bind(this),
'arguments': [],
'preRun': [],
'postRun': [],
'canvas': this.canvas,
'print': (msg) => {
if (this.debug) {
console.log(msg);
}
},
'printErr': (msg) => {
if (this.debug) {
console.log(msg);
}
},
'totalDependencies': 0,
'monitorRunDependencies': () => {},
'locateFile': function(fileName) {
console.log(fileName);
if (fileName.endsWith(".wasm")) {
return URL.createObjectURL(new Blob([wasmData], {type: "application/wasm"}));
}
}
};
this.Module = window.Module;
}
startGame() {
2023-07-03 15:34:18 +00:00
try {
2023-07-15 15:05:55 +00:00
2023-07-03 15:34:18 +00:00
const args = [];
if (this.debug) args.push('-v');
args.push('/'+this.fileName);
console.log(args);
this.Module.callMain(args);
this.Module.resumeMainLoop();
if (this.touch) {
this.virtualGamepad.style.display = "";
}
this.checkSupportedOpts();
this.setupSettingsMenu();
this.loadSettings();
this.updateCheatUI();
this.updateGamepadLabels();
2023-07-15 15:05:55 +00:00
if (!this.muted) this.setVolume(this.volume);
2023-07-03 15:34:18 +00:00
this.elements.parent.focus();
this.textElem.remove();
this.textElem = null;
this.game.classList.remove("ejs_game");
this.game.appendChild(this.canvas);
this.handleResize();
2023-07-04 03:56:29 +00:00
this.started = true;
this.paused = false;
2023-07-03 15:34:18 +00:00
} catch(e) {
console.warn("failed to start game", e);
this.textElem.innerText = "Failed to start game";
this.textElem.style.color = "red";
2023-07-04 03:56:29 +00:00
return;
}
2023-07-01 18:59:39 +00:00
this.callEvent("start");
}
bindListeners() {
this.createContextMenu();
this.createBottomMenuBar();
2023-06-22 19:08:34 +00:00
this.createControlSettingMenu();
2023-06-29 15:35:25 +00:00
this.createCheatsMenu()
2023-07-15 16:51:10 +00:00
this.createNetplayMenu();
2023-06-24 05:29:19 +00:00
this.setVirtualGamepad();
2023-06-29 16:34:34 +00:00
this.addEventListener(this.elements.parent, "keydown keyup", this.keyChange.bind(this));
2023-07-03 15:34:18 +00:00
this.addEventListener(this.elements.parent, "mousedown touchstart", (e) => {
if (document.activeElement !== this.elements.parent) this.elements.parent.focus();
2023-06-29 16:34:34 +00:00
})
2023-07-09 00:35:59 +00:00
this.addEventListener(window, "resize", this.handleResize.bind(this));
2023-07-03 15:34:18 +00:00
//this.addEventListener(window, "blur", e => console.log(e), true); //TODO - add "click to make keyboard keys work" message?
2023-06-29 21:26:30 +00:00
this.gamepad = new GamepadHandler(); //https://github.com/ethanaobrien/Gamepad
this.gamepad.on('connected', (e) => {
if (!this.gamepadLabels) return;
this.updateGamepadLabels();
})
this.gamepad.on('disconnected', (e) => {
setTimeout(this.updateGamepadLabels.bind(this), 10);
})
2023-06-30 01:02:55 +00:00
this.gamepad.on('axischanged', this.gamepadEvent.bind(this));
this.gamepad.on('buttondown', this.gamepadEvent.bind(this));
this.gamepad.on('buttonup', this.gamepadEvent.bind(this));
2023-06-29 21:26:30 +00:00
}
2023-07-03 14:31:38 +00:00
checkSupportedOpts() {
if (!this.gameManager.supportsStates()) {
2023-07-17 17:25:30 +00:00
this.elements.bottomBar.saveState[0].style.display = "none";
this.elements.bottomBar.loadState[0].style.display = "none";
this.elements.bottomBar.netplay[0].style.display = "none";
2023-07-17 17:12:05 +00:00
this.elements.contextMenu.save.style.display = "none";
this.elements.contextMenu.load.style.display = "none";
}
2023-07-18 12:52:32 +00:00
if (typeof this.config.gameId !== "number" || !this.config.netplayUrl || this.netplay === false) {
2023-07-17 17:25:30 +00:00
this.elements.bottomBar.netplay[0].style.display = "none";
2023-07-03 14:31:38 +00:00
}
}
2023-06-29 21:26:30 +00:00
updateGamepadLabels() {
for (let i=0; i<this.gamepadLabels.length; i++) {
if (this.gamepad.gamepads[i]) {
this.gamepadLabels[i].innerText = this.gamepad.gamepads[i].id;
} else {
this.gamepadLabels[i].innerText = "n/a";
}
}
}
createContextMenu() {
this.elements.contextmenu = this.createElement('div');
this.elements.contextmenu.classList.add("ejs_context_menu");
this.addEventListener(this.game, 'contextmenu', (e) => {
if (this.started) {
this.elements.contextmenu.style.display = "block";
2023-07-04 15:27:58 +00:00
this.elements.contextmenu.style.left = e.offsetX+"px";
this.elements.contextmenu.style.top = e.offsetY+"px";
}
e.preventDefault();
})
const hideMenu = () => {
this.elements.contextmenu.style.display = "none";
}
this.addEventListener(this.elements.contextmenu, 'contextmenu', (e) => e.preventDefault());
this.addEventListener(this.elements.parent, 'contextmenu', (e) => e.preventDefault());
this.addEventListener(this.game, 'mousedown', hideMenu);
const parent = this.createElement("ul");
const addButton = (title, hidden, functi0n) => {
//<li><a href="#" onclick="return false">'+title+'</a></li>
const li = this.createElement("li");
if (hidden) li.hidden = true;
const a = this.createElement("a");
if (functi0n instanceof Function) {
2023-06-22 17:27:21 +00:00
this.addEventListener(li, 'click', (e) => {
e.preventDefault();
functi0n();
});
}
a.href = "#";
a.onclick = "return false";
a.innerText = title;
li.appendChild(a);
parent.appendChild(li);
hideMenu();
2023-07-01 19:15:00 +00:00
return li;
}
let screenshotUrl;
2023-07-01 19:15:00 +00:00
const screenshot = addButton("Take Screenshot", false, () => {
if (screenshotUrl) URL.revokeObjectURL(screenshotUrl);
const screenshot = this.gameManager.screenshot();
const blob = new Blob([screenshot]);
screenshotUrl = URL.createObjectURL(blob);
const a = this.createElement("a");
a.href = screenshotUrl;
2023-06-26 16:39:31 +00:00
const date = new Date();
a.download = this.getBaseFileName()+"-"+date.getMonth()+"-"+date.getDate()+"-"+date.getFullYear()+".png";
a.click();
hideMenu();
});
2023-07-01 19:15:00 +00:00
const qSave = addButton("Quick Save", false, () => {
2023-07-03 17:03:00 +00:00
const slot = this.settings['save-state-slot'] ? this.settings['save-state-slot'] : "1";
this.gameManager.quickSave(slot);
2023-07-03 17:24:25 +00:00
this.displayMessage(this.localization("SAVED STATE TO SLOT")+" "+slot);
hideMenu();
});
2023-07-01 19:15:00 +00:00
const qLoad = addButton("Quick Load", false, () => {
2023-07-03 17:03:00 +00:00
const slot = this.settings['save-state-slot'] ? this.settings['save-state-slot'] : "1";
this.gameManager.quickLoad(slot);
2023-07-03 17:24:25 +00:00
this.displayMessage(this.localization("LOADED STATE FROM SLOT")+" "+slot);
hideMenu();
});
2023-07-17 17:12:05 +00:00
this.elements.contextMenu = {
screenshot: screenshot,
save: qSave,
load: qLoad
}
2023-07-03 17:56:04 +00:00
addButton("EmulatorJS v"+this.ejs_version, false, () => {
2023-06-22 17:27:21 +00:00
hideMenu();
const body = this.createPopup("EmulatorJS", {
"Close": () => {
this.closePopup();
}
});
2023-07-03 17:56:04 +00:00
body.innerText = "EmulatorJS v"+this.ejs_version;
body.appendChild(this.createElement("br"));
body.appendChild(this.createElement("br"));
const gh = this.createElement("a");
gh.href = "https://github.com/EmulatorJS/EmulatorJS";
gh.target = "_blank";
gh.innerText = "View on GitHub";
body.appendChild(gh);
body.appendChild(this.createElement("br"));
const dc = this.createElement("a");
dc.href = "https://discord.gg/6akryGkETU";
dc.target = "_blank";
dc.innerText = "Join the discord";
body.appendChild(dc);
body.appendChild(this.createElement("br"));
let license = this.createElement("div");
license.style.display = "none";
const lc = this.createElement("a");
this.addEventListener(lc, "click", () => {
license.style.display = (license.style.display === "none") ? "" : "none";
lc.innerText = (lc.innerText === "Close License") ? "View the license" : "Close License";
})
lc.innerText = "View the license";
lc.style.cursor = "pointer";
body.appendChild(lc);
body.appendChild(this.createElement("br"));
body.appendChild(this.createElement("br"));
body.appendChild(license);
2023-07-11 13:40:42 +00:00
license.style['text-align'] = "left";
license.style['padding'] = "10px";
2023-07-03 17:56:04 +00:00
license.innerText = ' GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n Preamble\n\n The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users. We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors. You can apply it to\nyour programs, too.\n\n When we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights. Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received. You must make sure that they, too, receive\nor can get the source code. And you must show them these terms so they\nknow their rights.\n\n Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n For the developers\' and authors\' protection, the GPL clearly explains\nthat there is no warranty for this free software. For both users\' and\nauthors\' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so. This is fundamentally incompatible with the aim of\nprotecting users\' freedom to change the software. The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable. Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts. If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary. To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n The precise terms and conditions for copying, distribution and\nmodification follow.\n\n TERMS AND CONDITIONS\n\n 0. Definitions.\n\n "This License" refers to version 3 of the GNU General Public License.\n\n "Copyright" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n "The Program" refers to any copyrightable work licensed under this\nLicense. Each licensee is addres
2023-06-22 17:27:21 +00:00
});
2023-07-01 19:15:00 +00:00
if (this.config.buttonOpts) {
2023-07-18 14:49:33 +00:00
if (this.config.buttonOpts.screenshot === false) screenshot.setAttribute("hidden", "");
if (this.config.buttonOpts.quickSave === false) qSave.setAttribute("hidden", "");
if (this.config.buttonOpts.quickLoad === false) qLoad.setAttribute("hidden", "");
2023-07-01 19:15:00 +00:00
}
this.elements.contextmenu.appendChild(parent);
this.elements.parent.appendChild(this.elements.contextmenu);
}
2023-06-22 17:27:21 +00:00
closePopup() {
if (this.currentPopup !== null) {
try {
this.currentPopup.remove();
} catch(e){}
this.currentPopup = null;
}
}
//creates a full box popup.
2023-06-22 19:08:34 +00:00
createPopup(popupTitle, buttons, hidden) {
if (!hidden) this.closePopup();
const popup = this.createElement('div');
popup.classList.add("ejs_popup_container");
this.elements.parent.appendChild(popup);
2023-06-22 17:27:21 +00:00
const title = this.createElement("h4");
2023-07-03 16:09:19 +00:00
title.innerText = this.localization(popupTitle);
2023-06-22 17:27:21 +00:00
const main = this.createElement("div");
main.classList.add("ejs_popup_body");
2023-06-22 19:08:34 +00:00
popup.appendChild(title);
popup.appendChild(main);
2023-06-22 17:27:21 +00:00
for (let k in buttons) {
const button = this.createElement("a");
if (buttons[k] instanceof Function) {
button.addEventListener("click", (e) => {
buttons[k]();
e.preventDefault();
});
}
button.classList.add("ejs_button");
2023-07-03 16:09:19 +00:00
button.innerText = this.localization(k);
2023-06-22 19:08:34 +00:00
popup.appendChild(button);
}
if (!hidden) {
this.currentPopup = popup;
2023-06-23 16:33:20 +00:00
} else {
popup.style.display = "none";
2023-06-22 17:27:21 +00:00
}
return main;
}
selectFile() {
return new Promise((resolve, reject) => {
const file = this.createElement("input");
file.type = "file";
this.addEventListener(file, "change", (e) => {
resolve(e.target.files[0]);
})
file.click();
})
}
2023-06-29 16:34:34 +00:00
isPopupOpen() {
2023-07-15 16:51:10 +00:00
return this.cheatMenu.style.display !== "none" || this.netplayMenu.style.display !== "none" || this.controlMenu.style.display !== "none" || this.currentPopup !== null;
2023-06-29 16:34:34 +00:00
}
2023-07-11 15:15:43 +00:00
isChild(first, second) {
if (!first || !second) return false;
const adown = first.nodeType === 9 ? first.documentElement : first;
if (first === second) return true;
if (adown.contains) {
return adown.contains(second);
}
return first.compareDocumentPosition && first.compareDocumentPosition(second) & 16;
}
createBottomMenuBar() {
this.elements.menu = this.createElement("div");
this.elements.menu.classList.add("ejs_menu_bar");
this.elements.menu.classList.add("ejs_menu_bar_hidden");
let timeout = null;
2023-07-11 15:15:43 +00:00
let ignoreEvents = false;
const hide = () => {
2023-06-29 15:53:17 +00:00
if (this.paused || this.settingsMenuOpen) return;
this.elements.menu.classList.add("ejs_menu_bar_hidden");
}
this.addEventListener(this.elements.parent, 'mousemove click', (e) => {
2023-07-11 15:15:43 +00:00
if (e.pointerType === "touch") return;
if (!this.started || ignoreEvents || document.pointerLockElement === this.canvas) return;
2023-06-29 16:34:34 +00:00
if (this.isPopupOpen()) return;
2023-07-11 13:40:42 +00:00
clearTimeout(timeout);
timeout = setTimeout(hide, 3000);
this.elements.menu.classList.remove("ejs_menu_bar_hidden");
})
2023-07-11 15:15:43 +00:00
this.addEventListener(this.elements.menu, 'touchstart touchend', (e) => {
2023-07-02 23:03:29 +00:00
if (!this.started) return;
if (this.isPopupOpen()) return;
2023-07-11 13:40:42 +00:00
clearTimeout(timeout);
2023-07-02 23:03:29 +00:00
timeout = setTimeout(hide, 3000);
this.elements.menu.classList.remove("ejs_menu_bar_hidden");
})
this.menu = {
close: () => {
if (!this.started) return;
2023-07-11 13:40:42 +00:00
clearTimeout(timeout);
2023-07-11 15:15:43 +00:00
this.elements.menu.classList.add("ejs_menu_bar_hidden");
},
open: () => {
if (!this.started) return;
2023-07-11 13:40:42 +00:00
clearTimeout(timeout);
timeout = setTimeout(hide, 3000);
this.elements.menu.classList.remove("ejs_menu_bar_hidden");
2023-07-11 13:40:42 +00:00
},
toggle: () => {
if (!this.started) return;
clearTimeout(timeout);
if (this.elements.menu.classList.contains("ejs_menu_bar_hidden")) {
timeout = setTimeout(hide, 3000);
}
this.elements.menu.classList.toggle("ejs_menu_bar_hidden");
}
}
this.elements.parent.appendChild(this.elements.menu);
2023-07-11 15:15:43 +00:00
let tmout;
this.addEventListener(this.elements.parent, "mousedown touchstart", (e) => {
if (this.isChild(this.elements.menu, e.target) || this.isChild(this.elements.menuToggle, e.target)) return;
if (!this.started || this.elements.menu.classList.contains("ejs_menu_bar_hidden") || this.isPopupOpen()) return;
const width = this.elements.parent.getBoundingClientRect().width;
if (width > 575) return;
clearTimeout(tmout);
tmout = setTimeout(() => {
ignoreEvents = false;
}, 2000)
ignoreEvents = true;
this.menu.close();
})
//Now add buttons
2023-06-27 21:36:57 +00:00
const addButton = (title, image, callback, element, both) => {
const button = this.createElement("button");
button.type = "button";
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute("role", "presentation");
svg.setAttribute("focusable", "false");
svg.innerHTML = image;
const text = this.createElement("span");
2023-07-03 16:09:19 +00:00
text.innerText = this.localization(title);
text.classList.add("ejs_menu_text");
button.classList.add("ejs_menu_button");
button.appendChild(svg);
button.appendChild(text);
2023-06-27 16:54:35 +00:00
if (element) {
element.appendChild(button);
} else {
this.elements.menu.appendChild(button);
}
if (callback instanceof Function) {
this.addEventListener(button, 'click', callback);
}
2023-06-27 21:36:57 +00:00
return both ? [button, svg, text] : button;
}
//todo. Center text on not restart button
2023-07-01 19:15:00 +00:00
const restartButton = addButton("Restart", '<svg viewBox="0 0 512 512"><path d="M496 48V192c0 17.69-14.31 32-32 32H320c-17.69 0-32-14.31-32-32s14.31-32 32-32h63.39c-29.97-39.7-77.25-63.78-127.6-63.78C167.7 96.22 96 167.9 96 256s71.69 159.8 159.8 159.8c34.88 0 68.03-11.03 95.88-31.94c14.22-10.53 34.22-7.75 44.81 6.375c10.59 14.16 7.75 34.22-6.375 44.81c-39.03 29.28-85.36 44.86-134.2 44.86C132.5 479.9 32 379.4 32 256s100.5-223.9 223.9-223.9c69.15 0 134 32.47 176.1 86.12V48c0-17.69 14.31-32 32-32S496 30.31 496 48z"/></svg>', () => {
2023-07-17 16:44:18 +00:00
if (this.isNetplay && this.netplay.owner) {
this.gameManager.saveSaveFiles();
this.gameManager.restart();
this.netplay.sendMessage({restart:true});
2023-07-18 12:52:32 +00:00
this.netplay.current_frame = 0;
this.play();
2023-07-17 16:44:18 +00:00
} else if (!this.isNetplay) {
this.gameManager.saveSaveFiles();
this.gameManager.restart();
}
});
2023-06-23 16:33:20 +00:00
const pauseButton = addButton("Pause", '<svg viewBox="0 0 320 512"><path d="M272 63.1l-32 0c-26.51 0-48 21.49-48 47.1v288c0 26.51 21.49 48 48 48L272 448c26.51 0 48-21.49 48-48v-288C320 85.49 298.5 63.1 272 63.1zM80 63.1l-32 0c-26.51 0-48 21.49-48 48v288C0 426.5 21.49 448 48 448l32 0c26.51 0 48-21.49 48-48v-288C128 85.49 106.5 63.1 80 63.1z"/></svg>', () => {
2023-07-17 16:44:18 +00:00
if (this.isNetplay && this.netplay.owner) {
this.pause();
this.netplay.sendMessage({pause:true});
} else if (!this.isNetplay) {
this.pause();
}
2023-06-23 16:33:20 +00:00
});
const playButton = addButton("Play", '<svg viewBox="0 0 320 512"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z"/></svg>', () => {
2023-07-17 16:44:18 +00:00
if (this.isNetplay && this.netplay.owner) {
this.play();
this.netplay.sendMessage({play:true});
} else if (!this.isNetplay) {
this.play();
}
2023-06-23 16:33:20 +00:00
});
playButton.style.display = "none";
2023-07-17 16:44:18 +00:00
this.togglePlaying = (dontUpdate) => {
2023-06-23 16:33:20 +00:00
this.paused = !this.paused;
2023-07-17 16:44:18 +00:00
if (!dontUpdate) {
if (this.paused) {
pauseButton.style.display = "none";
playButton.style.display = "";
} else {
pauseButton.style.display = "";
playButton.style.display = "none";
}
2023-06-23 16:33:20 +00:00
}
this.gameManager.toggleMainLoop(this.paused ? 0 : 1);
2023-07-02 23:03:29 +00:00
//I now realize its not easy to pause it while the cursor is locked, just in case I guess
if (this.getCore(true) === "nds") {
if (this.canvas.exitPointerLock) {
this.canvas.exitPointerLock();
} else if (this.canvas.mozExitPointerLock) {
this.canvas.mozExitPointerLock();
}
}
2023-06-23 16:33:20 +00:00
}
2023-07-17 16:44:18 +00:00
this.play = (dontUpdate) => {
if (this.paused) this.togglePlaying(dontUpdate);
2023-07-17 16:10:34 +00:00
}
2023-07-17 16:44:18 +00:00
this.pause = (dontUpdate) => {
if (!this.paused) this.togglePlaying(dontUpdate);
2023-07-17 16:10:34 +00:00
}
2023-06-23 16:33:20 +00:00
let stateUrl;
2023-07-01 19:15:00 +00:00
const saveState = addButton("Save State", '<svg viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"/></svg>', async () => {
const state = await this.gameManager.getState();
2023-07-01 18:59:39 +00:00
const called = this.callEvent("save", {
screenshot: this.gameManager.screenshot(),
state: state
});
if (called > 0) return;
if (stateUrl) URL.revokeObjectURL(stateUrl);
2023-07-03 17:03:00 +00:00
if (this.settings['save-state-location'] === "browser" && this.saveInBrowserSupported()) {
this.storage.states.put(this.getBaseFileName()+".state", state);
2023-07-07 15:02:00 +00:00
this.displayMessage(this.localization("SAVE SAVED TO BROWSER"));
2023-07-03 17:03:00 +00:00
} else {
const blob = new Blob([state]);
stateUrl = URL.createObjectURL(blob);
const a = this.createElement("a");
a.href = stateUrl;
a.download = this.getBaseFileName()+".state";
a.click();
}
});
2023-07-01 19:15:00 +00:00
const loadState = addButton("Load State", '<svg viewBox="0 0 576 512"><path fill="currentColor" d="M572.694 292.093L500.27 416.248A63.997 63.997 0 0 1 444.989 448H45.025c-18.523 0-30.064-20.093-20.731-36.093l72.424-124.155A64 64 0 0 1 152 256h399.964c18.523 0 30.064 20.093 20.73 36.093zM152 224h328v-48c0-26.51-21.49-48-48-48H272l-64-64H48C21.49 64 0 85.49 0 112v278.046l69.077-118.418C86.214 242.25 117.989 224 152 224z"/></svg>', async () => {
2023-07-03 15:49:28 +00:00
const called = this.callEvent("load");
if (called > 0) return;
2023-07-03 17:03:00 +00:00
if (this.settings['save-state-location'] === "browser" && this.saveInBrowserSupported()) {
this.storage.states.get(this.getBaseFileName()+".state").then(e => {
this.gameManager.loadState(e);
2023-07-07 15:02:00 +00:00
this.displayMessage(this.localization("SAVE LOADED FROM BROWSER"));
2023-07-03 17:03:00 +00:00
})
} else {
const file = await this.selectFile();
const state = new Uint8Array(await file.arrayBuffer());
this.gameManager.loadState(state);
}
});
2023-07-01 19:15:00 +00:00
const controlMenu = addButton("Control Settings", '<svg viewBox="0 0 640 512"><path fill="currentColor" d="M480 96H160C71.6 96 0 167.6 0 256s71.6 160 160 160c44.8 0 85.2-18.4 114.2-48h91.5c29 29.6 69.5 48 114.2 48 88.4 0 160-71.6 160-160S568.4 96 480 96zM256 276c0 6.6-5.4 12-12 12h-52v52c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-52H76c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h52v-52c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h52c6.6 0 12 5.4 12 12v40zm184 68c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm80-80c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48z"/></svg>', () => {
2023-06-22 19:08:34 +00:00
this.controlMenu.style.display = "";
});
2023-07-01 19:15:00 +00:00
const cheatMenu = addButton("Cheats", '<svg viewBox="0 0 496 512"><path fill="currentColor" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z" class=""></path></svg>', () => {
2023-06-29 15:35:25 +00:00
this.cheatMenu.style.display = "";
});
2023-06-22 19:08:34 +00:00
2023-07-01 20:16:25 +00:00
const cache = addButton("Cache Manager", '<svg viewBox="0 0 1800 1800"><path d="M896 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5T231 896 128 768V598q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128V982q119 84 325 127t443 43zM896 0q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5T896 640t-385-34.5T231 512 128 384V256q0-69 103-128t280-93.5T896 0z"/></svg>', () => {
this.openCacheMenu();
});
2023-07-07 15:02:00 +00:00
let savUrl;
const saveSavFiles = addButton("Export Save File", '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 23 23"><path d="M3 6.5V5C3 3.89543 3.89543 3 5 3H16.1716C16.702 3 17.2107 3.21071 17.5858 3.58579L20.4142 6.41421C20.7893 6.78929 21 7.29799 21 7.82843V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V17.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M8 3H16V8.4C16 8.73137 15.7314 9 15.4 9H8.6C8.26863 9 8 8.73137 8 8.4V3Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M18 21V13.6C18 13.2686 17.7314 13 17.4 13H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M6 21V17.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M12 12H1M1 12L4 9M1 12L4 15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path></svg>', async () => {
const file = await this.gameManager.getSaveFile();
const blob = new Blob([file]);
savUrl = URL.createObjectURL(blob);
const a = this.createElement("a");
a.href = savUrl;
a.download = this.gameManager.getSaveFilePath().split("/").pop();
a.click();
});
const loadSavFiles = addButton("Import Save File", '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 23 23"><path d="M3 7.5V5C3 3.89543 3.89543 3 5 3H16.1716C16.702 3 17.2107 3.21071 17.5858 3.58579L20.4142 6.41421C20.7893 6.78929 21 7.29799 21 7.82843V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V16.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M6 21V17" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path><path d="M18 21V13.6C18 13.2686 17.7314 13 17.4 13H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M16 3V8.4C16 8.73137 15.7314 9 15.4 9H13.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path><path d="M8 3V6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path><path d="M1 12H12M12 12L9 9M12 12L9 15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path></svg>', async () => {
const file = await this.selectFile();
const sav = new Uint8Array(await file.arrayBuffer());
const path = this.gameManager.getSaveFilePath();
const paths = path.split("/");
let cp = "";
for (let i=0; i<paths.length-1; i++) {
if (paths[i] === "") continue;
cp += "/"+paths[i];
if (!FS.analyzePath(cp).exists) FS.mkdir(cp);
}
if (FS.analyzePath(path).exists) FS.unlink(path);
FS.writeFile(path, sav);
2023-07-07 17:22:20 +00:00
this.gameManager.loadSaveFiles();
2023-07-07 15:02:00 +00:00
});
2023-07-15 16:51:10 +00:00
const netplay = addButton("Netplay", '<svg viewBox="0 0 512 512"><path fill="currentColor" d="M364.215 192h131.43c5.439 20.419 8.354 41.868 8.354 64s-2.915 43.581-8.354 64h-131.43c5.154-43.049 4.939-86.746 0-128zM185.214 352c10.678 53.68 33.173 112.514 70.125 151.992.221.001.44.008.661.008s.44-.008.661-.008c37.012-39.543 59.467-98.414 70.125-151.992H185.214zm174.13-192h125.385C452.802 84.024 384.128 27.305 300.95 12.075c30.238 43.12 48.821 96.332 58.394 147.925zm-27.35 32H180.006c-5.339 41.914-5.345 86.037 0 128h151.989c5.339-41.915 5.345-86.037-.001-128zM152.656 352H27.271c31.926 75.976 100.6 132.695 183.778 147.925-30.246-43.136-48.823-96.35-58.393-147.925zm206.688 0c-9.575 51.605-28.163 104.814-58.394 147.925 83.178-15.23 151.852-71.949 183.778-147.925H359.344zm-32.558-192c-10.678-53.68-33.174-112.514-70.125-151.992-.221 0-.44-.008-.661-.008s-.44.008-.661.008C218.327 47.551 195.872 106.422 185.214 160h141.572zM16.355 192C10.915 212.419 8 233.868 8 256s2.915 43.581 8.355 64h131.43c-4.939-41.254-5.154-84.951 0-128H16.355zm136.301-32c9.575-51.602 28.161-104.81 58.394-147.925C127.872 27.305 59.198 84.024 27.271 160h125.385z"/></svg>', async () => {
this.openNetplayMenu();
});
2023-07-07 15:02:00 +00:00
2023-06-23 16:33:20 +00:00
const spacer = this.createElement("span");
2023-07-11 13:40:42 +00:00
spacer.classList.add("ejs_menu_bar_spacer");
2023-06-23 16:33:20 +00:00
this.elements.menu.appendChild(spacer);
2023-07-01 21:21:48 +00:00
const volumeSettings = this.createElement("div");
volumeSettings.classList.add("ejs_volume_parent");
const muteButton = addButton("Mute", '<svg viewBox="0 0 640 512"><path d="M412.6 182c-10.28-8.334-25.41-6.867-33.75 3.402c-8.406 10.24-6.906 25.35 3.375 33.74C393.5 228.4 400 241.8 400 255.1c0 14.17-6.5 27.59-17.81 36.83c-10.28 8.396-11.78 23.5-3.375 33.74c4.719 5.806 11.62 8.802 18.56 8.802c5.344 0 10.75-1.779 15.19-5.399C435.1 311.5 448 284.6 448 255.1S435.1 200.4 412.6 182zM473.1 108.2c-10.22-8.334-25.34-6.898-33.78 3.34c-8.406 10.24-6.906 25.35 3.344 33.74C476.6 172.1 496 213.3 496 255.1s-19.44 82.1-53.31 110.7c-10.25 8.396-11.75 23.5-3.344 33.74c4.75 5.775 11.62 8.771 18.56 8.771c5.375 0 10.75-1.779 15.22-5.431C518.2 366.9 544 313 544 255.1S518.2 145 473.1 108.2zM534.4 33.4c-10.22-8.334-25.34-6.867-33.78 3.34c-8.406 10.24-6.906 25.35 3.344 33.74C559.9 116.3 592 183.9 592 255.1s-32.09 139.7-88.06 185.5c-10.25 8.396-11.75 23.5-3.344 33.74C505.3 481 512.2 484 519.2 484c5.375 0 10.75-1.779 15.22-5.431C601.5 423.6 640 342.5 640 255.1S601.5 88.34 534.4 33.4zM301.2 34.98c-11.5-5.181-25.01-3.076-34.43 5.29L131.8 160.1H48c-26.51 0-48 21.48-48 47.96v95.92c0 26.48 21.49 47.96 48 47.96h83.84l134.9 119.8C272.7 477 280.3 479.8 288 479.8c4.438 0 8.959-.9314 13.16-2.835C312.7 471.8 320 460.4 320 447.9V64.12C320 51.55 312.7 40.13 301.2 34.98z"/></svg>', () => {
muteButton.style.display = "none";
unmuteButton.style.display = "";
this.muted = true;
this.setVolume(0);
}, volumeSettings);
const unmuteButton = addButton("Unmute", '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M301.2 34.85c-11.5-5.188-25.02-3.122-34.44 5.253L131.8 160H48c-26.51 0-48 21.49-48 47.1v95.1c0 26.51 21.49 47.1 48 47.1h83.84l134.9 119.9c5.984 5.312 13.58 8.094 21.26 8.094c4.438 0 8.972-.9375 13.17-2.844c11.5-5.156 18.82-16.56 18.82-29.16V64C319.1 51.41 312.7 40 301.2 34.85zM513.9 255.1l47.03-47.03c9.375-9.375 9.375-24.56 0-33.94s-24.56-9.375-33.94 0L480 222.1L432.1 175c-9.375-9.375-24.56-9.375-33.94 0s-9.375 24.56 0 33.94l47.03 47.03l-47.03 47.03c-9.375 9.375-9.375 24.56 0 33.94c9.373 9.373 24.56 9.381 33.94 0L480 289.9l47.03 47.03c9.373 9.373 24.56 9.381 33.94 0c9.375-9.375 9.375-24.56 0-33.94L513.9 255.1z"/></svg>', () => {
2023-07-03 15:34:18 +00:00
if (this.volume === 0) this.volume = 0.5;
2023-07-01 21:21:48 +00:00
muteButton.style.display = "";
unmuteButton.style.display = "none";
this.muted = false;
this.setVolume(this.volume);
}, volumeSettings);
unmuteButton.style.display = "none";
const volumeSlider = this.createElement("input");
volumeSlider.setAttribute("data-range", "volume");
volumeSlider.setAttribute("type", "range");
volumeSlider.setAttribute("min", 0);
volumeSlider.setAttribute("max", 1);
volumeSlider.setAttribute("step", 0.01);
volumeSlider.setAttribute("autocomplete", "off");
volumeSlider.setAttribute("role", "slider");
volumeSlider.setAttribute("aria-label", "Volume");
volumeSlider.setAttribute("aria-valuemin", 0);
volumeSlider.setAttribute("aria-valuemax", 100);
this.setVolume = (volume) => {
2023-07-03 15:34:18 +00:00
this.saveSettings();
2023-07-02 23:03:29 +00:00
this.muted = (volume === 0);
volumeSlider.value = volume;
2023-07-01 21:21:48 +00:00
volumeSlider.setAttribute("aria-valuenow", volume*100);
volumeSlider.setAttribute("aria-valuetext", (volume*100).toFixed(1) + "%");
volumeSlider.setAttribute("style", "--value: "+volume*100+"%;margin-left: 5px;position: relative;z-index: 2;");
2023-07-15 15:05:55 +00:00
if (window.AL && AL.currentCtx && AL.currentCtx.sources) {
AL.currentCtx.sources.forEach(e => {
2023-07-15 15:33:32 +00:00
e.gain.gain.value = volume;
2023-07-15 15:05:55 +00:00
})
2023-07-02 21:06:25 +00:00
}
2023-07-03 15:34:18 +00:00
unmuteButton.style.display = (volume === 0) ? "" : "none";
muteButton.style.display = (volume === 0) ? "none" : "";
2023-07-02 21:06:25 +00:00
}
2023-07-15 15:05:55 +00:00
if (!this.muted) this.setVolume(this.volume);
2023-07-01 21:21:48 +00:00
this.addEventListener(volumeSlider, "change mousemove touchmove mousedown touchstart mouseup", (e) => {
setTimeout(() => {
2023-07-03 15:34:18 +00:00
const newVal = parseFloat(volumeSlider.value);
if (newVal === 0 && this.muted) return;
this.volume = newVal;
2023-07-01 21:21:48 +00:00
this.setVolume(this.volume);
}, 5);
})
volumeSettings.appendChild(volumeSlider);
this.elements.menu.appendChild(volumeSettings);
2023-06-27 16:54:35 +00:00
this.settingParent = this.createElement("div");
2023-06-29 15:53:17 +00:00
this.settingsMenuOpen = false;
2023-06-27 21:36:57 +00:00
const settingButton = addButton("Settings", '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M495.9 166.6C499.2 175.2 496.4 184.9 489.6 191.2L446.3 230.6C447.4 238.9 448 247.4 448 256C448 264.6 447.4 273.1 446.3 281.4L489.6 320.8C496.4 327.1 499.2 336.8 495.9 345.4C491.5 357.3 486.2 368.8 480.2 379.7L475.5 387.8C468.9 398.8 461.5 409.2 453.4 419.1C447.4 426.2 437.7 428.7 428.9 425.9L373.2 408.1C359.8 418.4 344.1 427 329.2 433.6L316.7 490.7C314.7 499.7 307.7 506.1 298.5 508.5C284.7 510.8 270.5 512 255.1 512C241.5 512 227.3 510.8 213.5 508.5C204.3 506.1 197.3 499.7 195.3 490.7L182.8 433.6C167 427 152.2 418.4 138.8 408.1L83.14 425.9C74.3 428.7 64.55 426.2 58.63 419.1C50.52 409.2 43.12 398.8 36.52 387.8L31.84 379.7C25.77 368.8 20.49 357.3 16.06 345.4C12.82 336.8 15.55 327.1 22.41 320.8L65.67 281.4C64.57 273.1 64 264.6 64 256C64 247.4 64.57 238.9 65.67 230.6L22.41 191.2C15.55 184.9 12.82 175.3 16.06 166.6C20.49 154.7 25.78 143.2 31.84 132.3L36.51 124.2C43.12 113.2 50.52 102.8 58.63 92.95C64.55 85.8 74.3 83.32 83.14 86.14L138.8 103.9C152.2 93.56 167 84.96 182.8 78.43L195.3 21.33C197.3 12.25 204.3 5.04 213.5 3.51C227.3 1.201 241.5 0 256 0C270.5 0 284.7 1.201 298.5 3.51C307.7 5.04 314.7 12.25 316.7 21.33L329.2 78.43C344.1 84.96 359.8 93.56 373.2 103.9L428.9 86.14C437.7 83.32 447.4 85.8 453.4 92.95C461.5 102.8 468.9 113.2 475.5 124.2L480.2 132.3C486.2 143.2 491.5 154.7 495.9 166.6V166.6zM256 336C300.2 336 336 300.2 336 255.1C336 211.8 300.2 175.1 256 175.1C211.8 175.1 176 211.8 176 255.1C176 300.2 211.8 336 256 336z"/></svg>', () => {
2023-06-29 15:53:17 +00:00
this.settingsMenuOpen = !this.settingsMenuOpen;
settingButton[1].classList.toggle("ejs_svg_rotate", this.settingsMenuOpen);
this.settingsMenu.style.display = this.settingsMenuOpen ? "" : "none";
2023-07-11 13:40:42 +00:00
settingButton[2].classList.toggle("ejs_settings_text", this.settingsMenuOpen);
2023-06-27 21:36:57 +00:00
}, this.settingParent, true);
2023-06-27 16:54:35 +00:00
this.elements.menu.appendChild(this.settingParent);
2023-06-29 15:53:17 +00:00
this.closeSettingsMenu = () => {
if (!this.settingsMenu) return;
this.settingsMenuOpen = false;
settingButton[1].classList.toggle("ejs_svg_rotate", this.settingsMenuOpen);
2023-07-11 13:40:42 +00:00
settingButton[2].classList.toggle("ejs_settings_text", this.settingsMenuOpen);
2023-06-29 15:53:17 +00:00
this.settingsMenu.style.display = "none";
}
2023-07-11 15:15:43 +00:00
this.addEventListener(this.elements.parent, "mousedown touchstart", (e) => {
if (this.isChild(this.settingsMenu, e.target)) return;
if (e.pointerType === "touch") return;
2023-07-11 13:40:42 +00:00
if (e.target === settingButton[0] || e.target === settingButton[2]) return;
2023-07-11 15:15:43 +00:00
this.closeSettingsMenu();
2023-06-29 15:53:17 +00:00
})
2023-07-02 23:03:29 +00:00
this.addEventListener(this.canvas, "click", (e) => {
2023-07-03 17:56:04 +00:00
if (e.pointerType === "touch") return;
2023-07-02 23:03:29 +00:00
if (this.getCore(true) === "nds" && !this.paused) {
if (this.canvas.requestPointerLock) {
this.canvas.requestPointerLock();
} else if (this.canvas.mozRequestPointerLock) {
this.canvas.mozRequestPointerLock();
}
2023-07-11 15:15:43 +00:00
this.menu.close();
2023-07-02 23:03:29 +00:00
}
})
2023-06-23 16:33:20 +00:00
const enter = addButton("Enter Fullscreen", '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M208 281.4c-12.5-12.5-32.76-12.5-45.26-.002l-78.06 78.07l-30.06-30.06c-6.125-6.125-14.31-9.367-22.63-9.367c-4.125 0-8.279 .7891-12.25 2.43c-11.97 4.953-19.75 16.62-19.75 29.56v135.1C.0013 501.3 10.75 512 24 512h136c12.94 0 24.63-7.797 29.56-19.75c4.969-11.97 2.219-25.72-6.938-34.87l-30.06-30.06l78.06-78.07c12.5-12.49 12.5-32.75 .002-45.25L208 281.4zM487.1 0h-136c-12.94 0-24.63 7.797-29.56 19.75c-4.969 11.97-2.219 25.72 6.938 34.87l30.06 30.06l-78.06 78.07c-12.5 12.5-12.5 32.76 0 45.26l22.62 22.62c12.5 12.5 32.76 12.5 45.26 0l78.06-78.07l30.06 30.06c9.156 9.141 22.87 11.84 34.87 6.937C504.2 184.6 512 172.9 512 159.1V23.1C512 10.74 501.3 0 487.1 0z"/></svg>', () => {
if (this.elements.parent.requestFullscreen) {
this.elements.parent.requestFullscreen();
} else if (this.elements.parent.mozRequestFullScreen) {
this.elements.parent.mozRequestFullScreen();
} else if (this.elements.parent.webkitRequestFullscreen) {
this.elements.parent.webkitRequestFullscreen();
} else if (this.elements.parent.msRequestFullscreen) {
this.elements.parent.msRequestFullscreen();
}
exit.style.display = "";
enter.style.display = "none";
2023-07-06 17:13:57 +00:00
if (this.isMobile) {
try {
screen.orientation.lock(this.getCore(true) === "nds" ? "portrait" : "landscape").catch(e => {});;
} catch(e) {}
}
2023-06-23 16:33:20 +00:00
});
const exit = addButton("Exit Fullscreen", '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M215.1 272h-136c-12.94 0-24.63 7.797-29.56 19.75C45.47 303.7 48.22 317.5 57.37 326.6l30.06 30.06l-78.06 78.07c-12.5 12.5-12.5 32.75-.0012 45.25l22.62 22.62c12.5 12.5 32.76 12.5 45.26 .0013l78.06-78.07l30.06 30.06c6.125 6.125 14.31 9.367 22.63 9.367c4.125 0 8.279-.7891 12.25-2.43c11.97-4.953 19.75-16.62 19.75-29.56V296C239.1 282.7 229.3 272 215.1 272zM296 240h136c12.94 0 24.63-7.797 29.56-19.75c4.969-11.97 2.219-25.72-6.938-34.87l-30.06-30.06l78.06-78.07c12.5-12.5 12.5-32.76 .0002-45.26l-22.62-22.62c-12.5-12.5-32.76-12.5-45.26-.0003l-78.06 78.07l-30.06-30.06c-9.156-9.141-22.87-11.84-34.87-6.937c-11.97 4.953-19.75 16.62-19.75 29.56v135.1C272 229.3 282.7 240 296 240z"/></svg>', () => {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
exit.style.display = "none";
enter.style.display = "";
2023-07-06 17:13:57 +00:00
if (this.isMobile) {
try {
screen.orientation.unlock();
} catch(e) {}
}
2023-06-23 16:33:20 +00:00
});
exit.style.display = "none";
2023-06-29 16:34:34 +00:00
this.addEventListener(document, "webkitfullscreenchange mozfullscreenchange fullscreenchange", (e) => {
if (e.target !== this.elements.parent) return;
if (document.fullscreenElement === null) {
exit.style.display = "none";
enter.style.display = "";
} else {
//not sure if this is possible, lets put it here anyways
exit.style.display = "";
enter.style.display = "none";
}
})
2023-07-03 14:31:38 +00:00
const hasFullscreen = !!(this.elements.parent.requestFullscreen || this.elements.parent.mozRequestFullScreen || this.elements.parent.webkitRequestFullscreen || this.elements.parent.msRequestFullscreen);
if (!hasFullscreen) {
exit.style.display = "none";
enter.style.display = "none";
}
this.elements.bottomBar = {
playPause: [pauseButton, playButton],
restart: [restartButton],
settings: [settingButton],
fullscreen: [enter, exit],
saveState: [saveState],
loadState: [loadState],
gamepad: [controlMenu],
cheat: [cheatMenu],
2023-07-17 16:44:18 +00:00
cacheManager: [cache],
saveSavFiles: [saveSavFiles],
2023-07-17 17:12:05 +00:00
loadSavFiles: [loadSavFiles],
netplay: [netplay]
2023-07-03 14:31:38 +00:00
}
2023-07-01 20:16:25 +00:00
2023-07-01 19:15:00 +00:00
if (this.config.buttonOpts) {
2023-07-17 18:33:42 +00:00
console.log(this.config.buttonOpts);
if (this.config.buttonOpts.playPause === false) {
2023-07-01 19:15:00 +00:00
pauseButton.style.display = "none";
playButton.style.display = "none";
}
2023-07-17 18:33:42 +00:00
if (this.config.buttonOpts.restart === false) restartButton.style.display = "none"
if (this.config.buttonOpts.settings === false) settingButton[0].style.display = "none"
if (this.config.buttonOpts.fullscreen === false) {
2023-07-01 19:15:00 +00:00
enter.style.display = "none";
exit.style.display = "none";
}
2023-07-17 18:33:42 +00:00
if (this.config.buttonOpts.saveState === false) saveState.style.display = "none"
if (this.config.buttonOpts.loadState === false) loadState.style.display = "none"
if (this.config.buttonOpts.saveSavFiles === false) saveSavFiles.style.display = "none"
if (this.config.buttonOpts.loadSavFiles === false) loadSavFiles.style.display = "none"
if (this.config.buttonOpts.gamepad === false) controlMenu.style.display = "none"
if (this.config.buttonOpts.cheat === false) cheatMenu.style.display = "none"
if (this.config.buttonOpts.cacheManager === false) cache.style.display = "none"
if (this.config.buttonOpts.netplay === false) netplay.style.display = "none"
2023-07-01 19:15:00 +00:00
}
2023-07-01 20:16:25 +00:00
}
openCacheMenu() {
(async () => {
const list = this.createElement("table");
const tbody = this.createElement("tbody");
const body = this.createPopup("Cache Manager", {
"Clear All": async () => {
const roms = await this.storage.rom.getSizes();
for (const k in roms) {
await this.storage.rom.remove(k);
}
tbody.innerHTML = "";
},
"Close": () => {
this.closePopup();
}
});
const roms = await this.storage.rom.getSizes();
list.style.width = "100%";
list.style["padding-left"] = "10px";
list.style["text-align"] = "left";
body.appendChild(list);
list.appendChild(tbody);
const getSize = function(size) {
let i = -1;
do {
size /= 1024, i++;
} while (size > 1024);
return Math.max(size, 0.1).toFixed(1) + [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'][i];
}
for (const k in roms) {
const line = this.createElement("tr");
const name = this.createElement("td");
const size = this.createElement("td");
const remove = this.createElement("td");
remove.style.cursor = "pointer";
name.innerText = k;
size.innerText = getSize(roms[k]);
const a = this.createElement("a");
2023-07-03 16:09:19 +00:00
a.innerText = this.localization("Remove");
2023-07-01 20:16:25 +00:00
this.addEventListener(remove, "click", () => {
this.storage.rom.remove(k);
line.remove();
})
remove.appendChild(a);
line.appendChild(name);
line.appendChild(size);
line.appendChild(remove);
tbody.appendChild(line);
}
})();
2023-06-22 19:08:34 +00:00
}
createControlSettingMenu() {
2023-06-29 16:34:34 +00:00
let buttonListeners = [];
2023-06-30 01:02:55 +00:00
this.checkGamepadInputs = () => buttonListeners.forEach(elem => elem());
2023-06-29 21:26:30 +00:00
this.gamepadLabels = [];
2023-06-30 01:02:55 +00:00
this.controls = JSON.parse(JSON.stringify(this.defaultControllers));
2023-06-22 19:08:34 +00:00
const body = this.createPopup("Control Settings", {
2023-06-29 16:34:34 +00:00
"Reset": () => {
this.controls = JSON.parse(JSON.stringify(this.defaultControllers));
2023-06-30 01:02:55 +00:00
this.checkGamepadInputs();
2023-07-03 15:34:18 +00:00
this.saveSettings();
2023-06-29 16:34:34 +00:00
},
"Clear": () => {
this.controls = {0:{},1:{},2:{},3:{}};
2023-06-30 01:02:55 +00:00
this.checkGamepadInputs();
2023-07-03 15:34:18 +00:00
this.saveSettings();
2023-06-29 16:34:34 +00:00
},
2023-06-22 19:08:34 +00:00
"Close": () => {
this.controlMenu.style.display = "none";
}
2023-06-23 16:33:20 +00:00
}, true);
2023-06-22 19:08:34 +00:00
this.controlMenu = body.parentElement;
2023-06-23 13:38:35 +00:00
body.classList.add("ejs_control_body");
2023-06-22 19:08:34 +00:00
2023-07-05 00:41:37 +00:00
let buttons;
if ('nes' === this.getCore(true)) {
buttons = {
8: 'A',
0: 'B',
2: 'SELECT',
3: 'START',
4: 'UP',
5: 'DOWN',
6: 'LEFT',
7: 'RIGHT',
24: this.localization('QUICK SAVE STATE'),
25: this.localization('QUICK LOAD STATE'),
26: this.localization('CHANGE STATE SLOT')
}
} else if ('snes' === this.getCore(true)) {
buttons = {
0: 'B',
1: 'Y',
2: 'SELECT',
3: 'START',
4: 'UP',
5: 'DOWN',
6: 'LEFT',
7: 'RIGHT',
8: 'A',
9: 'X',
10: 'L',
11: 'R',
24: this.localization('QUICK SAVE STATE'),
25: this.localization('QUICK LOAD STATE'),
26: this.localization('CHANGE STATE SLOT')
};
} else if ('n64' === this.getCore(true)) {
buttons = {
0: 'A',
1: 'B',
3: 'START',
4: 'UP',
5: 'DOWN',
6: 'LEFT',
7: 'RIGHT',
10: 'L',
11: 'R',
12: 'Z',
19: 'L STICK UP',
18: 'L STICK DOWN',
17: 'L STICK LEFT',
16: 'L STICK RIGHT',
23: 'R STICK UP',
22: 'R STICK DOWN',
21: 'R STICK LEFT',
20: 'R STICK RIGHT',
24: this.localization('QUICK SAVE STATE'),
25: this.localization('QUICK LOAD STATE'),
26: this.localization('CHANGE STATE SLOT')
};
} else if ('nds' === this.getCore(true)) {
buttons = {
0: 'B',
1: 'Y',
2: 'SELECT',
3: 'START',
4: 'UP',
5: 'DOWN',
6: 'LEFT',
7: 'RIGHT',
8: 'A',
9: 'X',
10: 'L',
11: 'R',
14: 'Microphone',
24: this.localization('QUICK SAVE STATE'),
25: this.localization('QUICK LOAD STATE'),
26: this.localization('CHANGE STATE SLOT')
};
} else {
buttons = {
0: 'B',
1: 'Y',
2: 'SELECT',
3: 'START',
4: 'UP',
5: 'DOWN',
6: 'LEFT',
7: 'RIGHT',
8: 'A',
9: 'X',
10: 'L',
11: 'R',
12: 'L2',
13: 'R2',
14: 'L3',
15: 'R3',
19: 'L STICK UP',
18: 'L STICK DOWN',
17: 'L STICK LEFT',
16: 'L STICK RIGHT',
23: 'R STICK UP',
22: 'R STICK DOWN',
21: 'R STICK LEFT',
20: 'R STICK RIGHT',
24: this.localization('QUICK SAVE STATE'),
25: this.localization('QUICK LOAD STATE'),
26: this.localization('CHANGE STATE SLOT')
};
}
if (['arcade', 'mame'].includes(this.getCore(true))) {
buttons[2] = this.localization('INSERT COIN');
2023-06-22 19:08:34 +00:00
}
2023-07-05 00:41:37 +00:00
//if (_this.statesSupported === false) {
// delete buttons[24];
// delete buttons[25];
// delete buttons[26];
//}
2023-06-23 13:38:35 +00:00
let selectedPlayer;
let players = [];
2023-06-23 16:33:20 +00:00
let playerDivs = [];
2023-06-22 19:08:34 +00:00
2023-06-23 13:38:35 +00:00
const playerSelect = this.createElement("ul");
playerSelect.classList.add("ejs_control_player_bar");
for (let i=1; i<5; i++) {
const playerContainer = this.createElement("li");
playerContainer.classList.add("tabs-title");
playerContainer.setAttribute("role", "presentation");
const player = this.createElement("a");
2023-07-03 16:09:19 +00:00
player.innerText = this.localization("Player")+" "+i;
2023-06-23 13:38:35 +00:00
player.setAttribute("role", "tab");
player.setAttribute("aria-controls", "controls-"+(i-1));
player.setAttribute("aria-selected", "false");
player.id = "controls-"+(i-1)+"-label";
2023-06-23 16:33:20 +00:00
this.addEventListener(player, "click", (e) => {
e.preventDefault();
2023-06-23 13:38:35 +00:00
players[selectedPlayer].classList.remove("ejs_control_selected");
2023-06-23 16:33:20 +00:00
playerDivs[selectedPlayer].setAttribute("hidden", "");
2023-06-23 13:38:35 +00:00
selectedPlayer = i-1;
players[i-1].classList.add("ejs_control_selected");
2023-06-23 16:33:20 +00:00
playerDivs[i-1].removeAttribute("hidden");
2023-06-22 19:08:34 +00:00
})
2023-06-23 13:38:35 +00:00
playerContainer.appendChild(player);
playerSelect.appendChild(playerContainer);
players.push(playerContainer);
2023-06-22 19:08:34 +00:00
}
2023-06-23 13:38:35 +00:00
body.appendChild(playerSelect);
2023-06-23 16:33:20 +00:00
const controls = this.createElement("div");
for (let i=0; i<4; i++) {
2023-07-03 14:38:29 +00:00
if (!this.controls[i]) this.controls[i] = {};
2023-06-23 16:33:20 +00:00
const player = this.createElement("div");
const playerTitle = this.createElement("div");
const gamepadTitle = this.createElement("div");
gamepadTitle.style = "font-size:12px;";
gamepadTitle.innerText = "Connected Gamepad: ";
const gamepadName = this.createElement("span");
2023-06-29 21:26:30 +00:00
this.gamepadLabels.push(gamepadName);
2023-06-23 16:33:20 +00:00
gamepadName.innerText = "n/a";
gamepadTitle.appendChild(gamepadName);
const leftPadding = this.createElement("div");
leftPadding.style = "width:25%;float:left;";
leftPadding.innerHTML = "&nbsp;";
const aboutParent = this.createElement("div");
aboutParent.style = "font-size:12px;width:50%;float:left;";
const gamepad = this.createElement("div");
gamepad.style = "text-align:center;width:50%;float:left;";
gamepad.innerText = "Gamepad";
aboutParent.appendChild(gamepad);
const keyboard = this.createElement("div");
keyboard.style = "text-align:center;width:50%;float:left;";
keyboard.innerText = "Keyboard";
aboutParent.appendChild(keyboard);
const headingPadding = this.createElement("div");
headingPadding.style = "clear:both;";
playerTitle.appendChild(gamepadTitle);
playerTitle.appendChild(leftPadding);
playerTitle.appendChild(aboutParent);
playerTitle.appendChild(headingPadding);
player.appendChild(playerTitle);
for (const k in buttons) {
const buttonText = this.createElement("div");
buttonText.setAttribute("data-id", k);
buttonText.setAttribute("data-index", i);
buttonText.setAttribute("data-label", buttons[k]);
buttonText.style = "margin-bottom:10px;";
buttonText.classList.add("ejs_control_bar");
const title = this.createElement("div");
title.style = "width:25%;float:left;font-size:12px;";
const label = this.createElement("label");
label.innerText = buttons[k]+":";
title.appendChild(label);
const textBoxes = this.createElement("div");
textBoxes.style = "width:50%;float:left;";
const textBox1Parent = this.createElement("div");
textBox1Parent.style = "width:50%;float:left;padding: 0 5px;";
const textBox1 = this.createElement("input");
textBox1.style = "text-align:center;height:25px;width: 100%;";
textBox1.type = "text";
textBox1.setAttribute("readonly", "");
textBox1.setAttribute("placeholder", "");
textBox1Parent.appendChild(textBox1);
const textBox2Parent = this.createElement("div");
textBox2Parent.style = "width:50%;float:left;padding: 0 5px;";
const textBox2 = this.createElement("input");
textBox2.style = "text-align:center;height:25px;width: 100%;";
textBox2.type = "text";
textBox2.setAttribute("readonly", "");
textBox2.setAttribute("placeholder", "");
textBox2Parent.appendChild(textBox2);
2023-06-29 16:34:34 +00:00
buttonListeners.push(() => {
textBox2.value = "";
textBox1.value = "";
2023-06-30 01:02:55 +00:00
if (this.controls[i][k] && this.controls[i][k].value !== undefined) {
2023-06-29 16:34:34 +00:00
textBox2.value = this.controls[i][k].value;
}
2023-06-30 01:02:55 +00:00
if (this.controls[i][k] && this.controls[i][k].value2 !== undefined) {
2023-06-29 16:34:34 +00:00
textBox1.value = this.controls[i][k].value2;
}
})
2023-06-23 16:33:20 +00:00
if (this.controls[i][k] && this.controls[i][k].value) {
textBox2.value = this.controls[i][k].value;
}
if (this.controls[i][k] && this.controls[i][k].value2) {
2023-06-30 01:02:55 +00:00
textBox1.value = "button " + this.controls[i][k].value2;
2023-06-23 16:33:20 +00:00
}
textBoxes.appendChild(textBox1Parent);
textBoxes.appendChild(textBox2Parent);
const padding = this.createElement("div");
padding.style = "clear:both;";
textBoxes.appendChild(padding);
const setButton = this.createElement("div");
setButton.style = "width:25%;float:left;";
2023-06-29 16:34:34 +00:00
const button = this.createElement("a");
button.classList.add("ejs_control_set_button");
2023-07-03 16:09:19 +00:00
button.innerText = this.localization("Set");
2023-06-23 16:33:20 +00:00
setButton.appendChild(button);
const padding2 = this.createElement("div");
padding2.style = "clear:both;";
buttonText.appendChild(title);
buttonText.appendChild(textBoxes);
buttonText.appendChild(setButton);
buttonText.appendChild(padding2);
player.appendChild(buttonText);
this.addEventListener(buttonText, "mousedown", (e) => {
e.preventDefault();
2023-07-11 15:15:43 +00:00
this.controlPopup.parentElement.parentElement.removeAttribute("hidden");
this.controlPopup.innerText = "[ " + buttons[k] + " ]\nPress Keyboard";
2023-06-23 16:33:20 +00:00
this.controlPopup.setAttribute("button-num", k);
this.controlPopup.setAttribute("player-num", i);
})
}
controls.appendChild(player);
player.setAttribute("hidden", "");
playerDivs.push(player);
}
body.appendChild(controls);
2023-06-23 13:38:35 +00:00
selectedPlayer = 0;
players[0].classList.add("ejs_control_selected");
2023-06-23 16:33:20 +00:00
playerDivs[0].removeAttribute("hidden");
2023-06-22 19:08:34 +00:00
2023-06-23 16:33:20 +00:00
const popup = this.createElement('div');
popup.classList.add("ejs_popup_container");
const popupMsg = this.createElement("div");
2023-07-11 15:15:43 +00:00
this.addEventListener(popup, "mousedown click touchstart", (e) => {
if (this.isChild(popupMsg, e.target)) return;
this.controlPopup.parentElement.parentElement.setAttribute("hidden", "");
})
const btn = this.createElement("a");
btn.classList.add("ejs_control_set_button");
btn.innerText = this.localization("Clear");
this.addEventListener(btn, "mousedown click touchstart", (e) => {
const num = this.controlPopup.getAttribute("button-num");
const player = this.controlPopup.getAttribute("player-num");
if (!this.controls[player][num]) {
this.controls[player][num] = {};
}
this.controls[player][num].value = "";
this.controls[player][num].value2 = "";
this.controlPopup.parentElement.parentElement.setAttribute("hidden", "");
this.checkGamepadInputs();
this.saveSettings();
})
2023-06-23 16:33:20 +00:00
popupMsg.classList.add("ejs_popup_box");
2023-06-29 15:35:25 +00:00
popupMsg.innerText = "";
2023-06-23 16:33:20 +00:00
popup.setAttribute("hidden", "");
2023-07-11 15:15:43 +00:00
const popMsg = this.createElement("div");
this.controlPopup = popMsg;
2023-06-23 16:33:20 +00:00
popup.appendChild(popupMsg);
2023-07-11 15:15:43 +00:00
popupMsg.appendChild(popMsg);
popupMsg.appendChild(this.createElement("br"));
popupMsg.appendChild(btn);
2023-06-23 16:33:20 +00:00
this.controlMenu.appendChild(popup);
2023-06-25 03:18:12 +00:00
2023-06-23 16:33:20 +00:00
}
defaultControllers = {
0: {
0: {
'value': 'x'
},
1: {
'value': 's'
},
2: {
'value': 'v'
},
3: {
'value': 'enter'
},
4: {
'value': 'arrowup'
},
5: {
'value': 'arrowdown'
},
6: {
'value': 'arrowleft'
},
7: {
'value': 'arrowright'
},
8: {
'value': 'z'
},
9: {
'value': 'a'
},
10: {
'value': 'q'
},
11: {
'value': 'e'
},
12: {
'value': 'e'
},
13: {
'value': 'w'
},
14: {},
15: {},
16: {
'value': 'h'
},
17: {
'value': 'f'
},
18: {
'value': 'g'
},
19: {
'value': 't'
},
20: {'value': 'l'},
21: {'value': 'j'},
22: {'value': 'k'},
23: {'value': 'i'},
24: {},
25: {},
26: {}
},
1: {},
2: {},
3: {}
}
controls;
keyChange(e) {
2023-07-03 17:24:25 +00:00
if (e.repeat) return;
2023-06-23 16:33:20 +00:00
if (!this.started) return;
2023-07-11 15:15:43 +00:00
if (this.controlPopup.parentElement.parentElement.getAttribute("hidden") === null) {
2023-06-23 16:33:20 +00:00
const num = this.controlPopup.getAttribute("button-num");
const player = this.controlPopup.getAttribute("player-num");
if (!this.controls[player][num]) {
this.controls[player][num] = {};
}
this.controls[player][num].value = e.key.toLowerCase();
2023-07-11 15:15:43 +00:00
this.controlPopup.parentElement.parentElement.setAttribute("hidden", "");
2023-06-30 01:02:55 +00:00
this.checkGamepadInputs();
2023-07-03 15:34:18 +00:00
this.saveSettings();
2023-06-23 16:33:20 +00:00
return;
}
if (this.settingsMenu.style.display !== "none" || this.isPopupOpen()) return;
e.preventDefault();
2023-06-23 16:33:20 +00:00
const special = [16, 17, 18, 19, 20, 21, 22, 23];
for (let i=0; i<4; i++) {
2023-07-03 17:24:25 +00:00
for (let j=0; j<27; j++) {
2023-06-23 16:33:20 +00:00
if (this.controls[i][j] && this.controls[i][j].value === e.key.toLowerCase()) {
this.gameManager.simulateInput(i, j, (e.type === 'keyup' ? 0 : (special.includes(j) ? 0x7fff : 1)));
}
}
}
2023-06-24 05:29:19 +00:00
}
2023-06-30 01:02:55 +00:00
gamepadEvent(e) {
if (!this.started) return;
const value = function(value) {
if (value > 0.5 || value < -0.5) {
return (value > 0) ? 1 : -1;
} else {
return 0;
}
}(e.value || 0);
2023-07-11 15:15:43 +00:00
if (this.controlPopup.parentElement.parentElement.getAttribute("hidden") === null) {
2023-06-30 01:02:55 +00:00
if ('buttonup' === e.type || (e.type === "axischanged" && value === 0)) return;
const num = this.controlPopup.getAttribute("button-num");
const player = this.controlPopup.getAttribute("player-num");
if (!this.controls[player][num]) {
this.controls[player][num] = {};
}
this.controls[player][num].value2 = (e.type === "axischanged" ? e.axis+":"+value : e.index);
2023-07-11 15:15:43 +00:00
this.controlPopup.parentElement.parentElement.setAttribute("hidden", "");
2023-06-30 01:02:55 +00:00
this.checkGamepadInputs();
2023-07-03 15:34:18 +00:00
this.saveSettings();
2023-06-30 01:02:55 +00:00
return;
}
if (this.settingsMenu.style.display !== "none" || this.isPopupOpen()) return;
2023-06-30 01:02:55 +00:00
const special = [16, 17, 18, 19, 20, 21, 22, 23];
for (let i=0; i<4; i++) {
2023-07-03 17:24:25 +00:00
for (let j=0; j<27; j++) {
if (['buttonup', 'buttondown'].includes(e.type) && (this.controls[i][j] && this.controls[i][j].value2 === e.index)) {
2023-06-30 01:02:55 +00:00
this.gameManager.simulateInput(i, j, (e.type === 'buttondown' ? 0 : (special.includes(j) ? 0x7fff : 1)));
} else if (e.type === "axischanged") {
if (this.controls[i][j] && typeof this.controls[i][j].value2 === 'string' && this.controls[i][j].value2.split(":")[0] === e.axis) {
2023-06-30 01:02:55 +00:00
if (special.includes(j)) {
if (e.axis === 'LEFT_STICK_X') {
if (e.value > 0) {
this.gameManager.simulateInput(e.gamepadIndex, 16, 0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 17, 0);
} else {
this.gameManager.simulateInput(e.gamepadIndex, 17, -0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 16, 0);
}
} else if (e.axis === 'LEFT_STICK_Y') {
if (e.value > 0) {
this.gameManager.simulateInput(e.gamepadIndex, 18, 0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 19, 0);
} else {
this.gameManager.simulateInput(e.gamepadIndex, 19, -0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 18, 0);
}
} else if (e.axis === 'RIGHT_STICK_X') {
if (e.value > 0) {
this.gameManager.simulateInput(e.gamepadIndex, 20, 0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 21, 0);
} else {
this.gameManager.simulateInput(e.gamepadIndex, 21, -0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 20, 0);
}
} else if (e.axis === 'RIGHT_STICK_Y') {
if (e.value > 0) {
this.gameManager.simulateInput(e.gamepadIndex, 22, 0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 23, 0);
} else {
this.gameManager.simulateInput(e.gamepadIndex, 23, 0x7fff * e.value);
this.gameManager.simulateInput(e.gamepadIndex, 22, 0);
}
}
} else if (this.controls[i][j].value2 === e.axis+":"+value || value === 0) {
this.gameManager.simulateInput(i, j, ((value === 0) ? 0 : 1));
}
}
}
}
}
}
2023-06-24 05:29:19 +00:00
setVirtualGamepad() {
this.virtualGamepad = this.createElement("div");
2023-07-03 17:56:04 +00:00
this.toggleVirtualGamepad = (show) => {
this.virtualGamepad.style.display = show ? "" : "none";
}
2023-06-24 05:29:19 +00:00
this.virtualGamepad.classList.add("ejs_virtualGamepad_parent");
this.elements.parent.appendChild(this.virtualGamepad);
let info;
if (this.config.VirtualGamepadSettings && function(set) {
if (!Array.isArray(set)) {
console.warn("Vritual gamepad settings is not array! Using default gamepad settings");
return false;
}
if (!set.length) {
console.warn("Virtual gamepad settings is empty! Using default gamepad settings");
return false;
}
for (let i=0; i<set.length; i++) {
if (!set[i].type) continue;
try {
if (set[i].type === 'zone' || set[i].type === 'dpad') {
if (!set[i].location) {
console.warn("Missing location value for "+set[i].type+"! Using default gamepad settings");
return false;
} else if (!set[i].inputValues) {
console.warn("Missing inputValues for "+set[i].type+"! Using default gamepad settings");
return false;
}
continue;
}
if (!set[i].location) {
console.warn("Missing location value for button "+set[i].text+"! Using default gamepad settings");
return false;
} else if (!set[i].type) {
console.warn("Missing type value for button "+set[i].text+"! Using default gamepad settings");
return false;
} else if (!set[i].id.toString()) {
console.warn("Missing id value for button "+set[i].text+"! Using default gamepad settings");
return false;
} else if (!set[i].input_value.toString()) {
console.warn("Missing input_value for button "+set[i].text+"! Using default gamepad settings");
return false;
}
} catch(e) {
console.warn("Error checking values! Using default gamepad settings");
return false;
}
}
return true;
}(this.config.VirtualGamepadSettings)) {
info = this.config.VirtualGamepadSettings;
2023-07-18 14:49:33 +00:00
} else if ("gba" === this.getCore(true)) {
info = [{"type":"button","text":"A","id":"a","location":"right","left":81,"top":40,"bold":true,"input_value":8},{"type":"button","text":"B","id":"b","location":"right","left":10,"top":70,"bold":true,"input_value":0},{"type":"dpad","location":"left","left":"50%","top":"50%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2},{"type":"button","text":"L","id":"l","location":"left","left":3,"top":-100,"bold":true,"block":true,"input_value":10},{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-100,"bold":true,"block":true,"input_value":11}];
} else if ("gb" === this.getCore(true)) {
info = [{"type":"button","text":"A","id":"a","location":"right","left":81,"top":40,"bold":true,"input_value":8},{"type":"button","text":"B","id":"b","location":"right","left":10,"top":70,"bold":true,"input_value":0},{"type":"dpad","location":"left","left":"50%","top":"50%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2}];
} else if (['vb', 'nes'].includes(this.getCore(true))) {
info = [{"type":"button","text":"B","id":"b","location":"right","right":-10,"top":70,"bold":true,"input_value":0},{"type":"button","text":"A","id":"a","location":"right","right":60,"top":70,"bold":true,"input_value":8},{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2}];
} else if (this.getCore(true) === 'n64') {
info = [{"type":"button","text":"B","id":"b","location":"right","left":-10,"top":95,"input_value":1,"bold":true},{"type":"button","text":"A","id":"a","location":"right","left":40,"top":150,"input_value":0,"bold":true},{"type":"zone","location":"left","left":"50%","top":"100%","joystickInput":true,"inputValues":[16, 17, 18, 19]},{"type":"zone","location":"left","left":"50%","top":"0%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":30,"top":-10,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"L","id":"l","block":true,"location":"top","left":10,"top":-40,"bold":true,"input_value":10},{"type":"button","text":"R","id":"r","block":true,"location":"top","right":10,"top":-40,"bold":true,"input_value":11},{"type":"button","text":"Z","id":"z","block":true,"location":"top","left":10,"bold":true,"input_value":12},{"fontSize":20,"type":"button","text":"CU","id":"cu","location":"right","left":25,"top":-65,"input_value":23},{"fontSize":20,"type":"button","text":"CD","id":"cd","location":"right","left":25,"top":15,"input_value":22},{"fontSize":20,"type":"button","text":"CL","id":"cl","location":"right","left":-15,"top":-25,"input_value":21},{"fontSize":20,"type":"button","text":"CR","id":"cr","location":"right","left":65,"top":-25,"input_value":20}];
2023-07-18 14:49:33 +00:00
} else if (this.getCore(true) === "nds") {
info = [{"type":"button","text":"X","id":"x","location":"right","left":40,"bold":true,"input_value":9},{"type":"button","text":"Y","id":"y","location":"right","top":40,"bold":true,"input_value":1},{"type":"button","text":"A","id":"a","location":"right","left":81,"top":40,"bold":true,"input_value":8},{"type":"button","text":"B","id":"b","location":"right","left":40,"top":80,"bold":true,"input_value":0},{"type":"dpad","location":"left","left":"50%","top":"50%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2},{"type":"button","text":"L","id":"l","location":"left","left":3,"top":-100,"bold":true,"block":true,"input_value":10},{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-100,"bold":true,"block":true,"input_value":11}];
} else if (this.getCore(true) === "snes") {
info = [{"type":"button","text":"X","id":"x","location":"right","left":40,"bold":true,"input_value":9},{"type":"button","text":"Y","id":"y","location":"right","top":40,"bold":true,"input_value":1},{"type":"button","text":"A","id":"a","location":"right","left":81,"top":40,"bold":true,"input_value":8},{"type":"button","text":"B","id":"b","location":"right","left":40,"top":80,"bold":true,"input_value":0},{"type":"dpad","location":"left","left":"50%","top":"50%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2},{"type":"button","text":"L","id":"l","location":"left","left":3,"top":-100,"bold":true,"block":true,"input_value":10},{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-100,"bold":true,"block":true,"input_value":11}];
} else {
info = [{"type":"button","text":"Y","id":"y","location":"right","left":40,"bold":true,"input_value":9},{"type":"button","text":"X","id":"X","location":"right","top":40,"bold":true,"input_value":1},{"type":"button","text":"B","id":"b","location":"right","left":81,"top":40,"bold":true,"input_value":8},{"type":"button","text":"A","id":"a","location":"right","left":40,"top":80,"bold":true,"input_value":0},{"type":"zone","location":"left","left":"50%","top":"50%","joystickInput":false,"inputValues":[4,5,6,7]},{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2}];
}
info = JSON.parse(JSON.stringify(info));
2023-06-24 05:29:19 +00:00
const up = this.createElement("div");
up.classList.add("ejs_virtualGamepad_top");
const down = this.createElement("div");
down.classList.add("ejs_virtualGamepad_bottom");
const left = this.createElement("div");
left.classList.add("ejs_virtualGamepad_left");
const right = this.createElement("div");
right.classList.add("ejs_virtualGamepad_right");
const elems = {top:up, center:down, left, right};
this.virtualGamepad.appendChild(up);
this.virtualGamepad.appendChild(down);
this.virtualGamepad.appendChild(left);
this.virtualGamepad.appendChild(right);
2023-07-03 17:56:04 +00:00
this.toggleVirtualGamepadLeftHanded = (enabled) => {
left.classList.toggle("ejs_virtualGamepad_left", !enabled);
right.classList.toggle("ejs_virtualGamepad_right", !enabled);
left.classList.toggle("ejs_virtualGamepad_right", enabled);
right.classList.toggle("ejs_virtualGamepad_left", enabled);
}
2023-06-24 05:29:19 +00:00
const leftHandedMode = false;
const blockCSS = 'height:31px;text-align:center;border:1px solid #ccc;border-radius:5px;line-height:31px;';
for (let i=0; i<info.length; i++) {
if (info[i].type !== 'button') continue;
if (leftHandedMode && ['left', 'right'].includes(info[i].location)) {
info[i].location = (info[i].location==='left') ? 'right' : 'left';
const amnt = JSON.parse(JSON.stringify(info[i]));
if (amnt.left) {
info[i].right = amnt.left;
}
if (amnt.right) {
info[i].left = amnt.right;
}
}
let style = '';
if (info[i].left) {
style += 'left:'+info[i].left+(typeof info[i].left === 'number'?'px':'')+';';
}
if (info[i].right) {
style += 'right:'+info[i].right+(typeof info[i].right === 'number'?'px':'')+';';
}
if (info[i].top) {
style += 'top:'+info[i].top+(typeof info[i].top === 'number'?'px':'')+';';
}
if (!info[i].bold) {
style += 'font-weight:normal;';
} else if (info[i].bold) {
style += 'font-weight:bold;';
}
info[i].fontSize = info[i].fontSize || 30;
style += 'font-size:'+info[i].fontSize+'px;';
if (info[i].block) {
style += blockCSS;
}
if (['top', 'center', 'left', 'right'].includes(info[i].location)) {
const button = this.createElement("div");
button.style = style;
button.innerText = info[i].text;
button.classList.add("ejs_virtualGamepad_button");
elems[info[i].location].appendChild(button);
const value = info[i].input_new_cores || info[i].input_value;
this.addEventListener(button, "touchstart touchend touchcancel", (e) => {
e.preventDefault();
if (e.type === 'touchend' || e.type === 'touchcancel') {
e.target.classList.remove("ejs_virtualGamepad_button_down");
window.setTimeout(() => {
this.gameManager.simulateInput(0, value, 0);
})
} else {
e.target.classList.add("ejs_virtualGamepad_button_down");
this.gameManager.simulateInput(0, value, 1);
}
})
}
}
const createDPad = (opts) => {
const container = opts.container;
const callback = opts.event;
const dpadMain = this.createElement("div");
dpadMain.classList.add("ejs_dpad_main");
const vertical = this.createElement("div");
vertical.classList.add("ejs_dpad_vertical");
const horizontal = this.createElement("div");
horizontal.classList.add("ejs_dpad_horizontal");
const bar1 = this.createElement("div");
bar1.classList.add("ejs_dpad_bar");
const bar2 = this.createElement("div");
bar2.classList.add("ejs_dpad_bar");
horizontal.appendChild(bar1);
vertical.appendChild(bar2);
dpadMain.appendChild(vertical);
dpadMain.appendChild(horizontal);
const updateCb = (e) => {
e.preventDefault();
const touch = e.targetTouches[0];
if (!touch) return;
const rect = dpadMain.getBoundingClientRect();
const x = touch.clientX - rect.left - dpadMain.clientWidth / 2;
const y = touch.clientY - rect.top - dpadMain.clientHeight / 2;
let up = 0,
down = 0,
left = 0,
right = 0,
angle = Math.atan(x / y) / (Math.PI / 180);
if (y <= -10) {
up = 1;
}
if (y >= 10) {
down = 1;
}
if (x >= 10) {
right = 1;
left = 0;
if (angle < 0 && angle >= -35 || angle > 0 && angle <= 35) {
right = 0;
}
up = (angle < 0 && angle >= -55 ? 1 : 0);
down = (angle > 0 && angle <= 55 ? 1 : 0);
}
if (x <= -10) {
right = 0;
left = 1;
if (angle < 0 && angle >= -35 || angle > 0 && angle <= 35) {
left = 0;
}
up = (angle > 0 && angle <= 55 ? 1 : 0);
down = (angle < 0 && angle >= -55 ? 1 : 0);
}
dpadMain.classList.toggle("ejs_dpad_up_pressed", up);
dpadMain.classList.toggle("ejs_dpad_down_pressed", down);
dpadMain.classList.toggle("ejs_dpad_right_pressed", right);
dpadMain.classList.toggle("ejs_dpad_left_pressed", left);
callback(up, down, left, right);
}
const cancelCb = (e) => {
e.preventDefault();
dpadMain.classList.remove("ejs_dpad_up_pressed");
dpadMain.classList.remove("ejs_dpad_down_pressed");
dpadMain.classList.remove("ejs_dpad_right_pressed");
dpadMain.classList.remove("ejs_dpad_left_pressed");
callback(0, 0, 0, 0);
2023-06-24 05:29:19 +00:00
}
this.addEventListener(dpadMain, 'touchstart touchmove', updateCb);
this.addEventListener(dpadMain, 'touchend touchcancel', cancelCb);
container.appendChild(dpadMain);
2023-06-24 05:29:19 +00:00
}
info.forEach((dpad, index) => {
if (dpad.type !== 'dpad') return;
if (leftHandedMode && ['left', 'right'].includes(dpad.location)) {
dpad.location = (dpad.location==='left') ? 'right' : 'left';
const amnt = JSON.parse(JSON.stringify(dpad));
if (amnt.left) {
dpad.right = amnt.left;
}
if (amnt.right) {
dpad.left = amnt.right;
}
}
const elem = this.createElement("div");
let style = '';
if (dpad.left) {
style += 'left:'+dpad.left+';';
}
if (dpad.right) {
style += 'right:'+dpad.right+';';
}
if (dpad.top) {
style += 'top:'+dpad.top+';';
}
elem.style = style;
elems[dpad.location].appendChild(elem);
createDPad({container: elem, event: (up, down, left, right) => {
if (dpad.joystickInput) {
if (up === 1) up=0x7fff;
if (down === 1) up=0x7fff;
if (left === 1) up=0x7fff;
if (right === 1) up=0x7fff;
}
this.gameManager.simulateInput(0, dpad.inputValues[0], up);
this.gameManager.simulateInput(0, dpad.inputValues[1], down);
this.gameManager.simulateInput(0, dpad.inputValues[2], left);
this.gameManager.simulateInput(0, dpad.inputValues[3], right);
}});
})
info.forEach((zone, index) => {
if (zone.type !== 'zone') return;
if (leftHandedMode && ['left', 'right'].includes(zone.location)) {
zone.location = (zone.location==='left') ? 'right' : 'left';
const amnt = JSON.parse(JSON.stringify(zone));
if (amnt.left) {
zone.right = amnt.left;
}
if (amnt.right) {
zone.left = amnt.right;
}
}
const elem = this.createElement("div");
this.addEventListener(elem, "touchstart touchmove touchend touchcancel", (e) => {
e.preventDefault();
});
elems[zone.location].appendChild(elem);
const zoneObj = nipplejs.create({
'zone': elem,
'mode': 'static',
'position': {
'left': zone.left,
'top': zone.top
},
'color': zone.color || 'red'
});
zoneObj.on('end', () => {
this.gameManager.simulateInput(0, zone.inputValues[0], 0);
this.gameManager.simulateInput(0, zone.inputValues[1], 0);
this.gameManager.simulateInput(0, zone.inputValues[2], 0);
this.gameManager.simulateInput(0, zone.inputValues[3], 0);
});
zoneObj.on('move', (e, info) => {
const degree = info.angle.degree;
const distance = info.distance;
if (zone.joystickInput === true) {
let x = 0, y = 0;
if (degree > 0 && degree <= 45) {
x = distance / 50;
y = -0.022222222222222223 * degree * distance / 50;
}
if (degree > 45 && degree <= 90) {
x = 0.022222222222222223 * (90 - degree) * distance / 50;
y = -distance / 50;
}
if (degree > 90 && degree <= 135) {
x = 0.022222222222222223 * (90 - degree) * distance / 50;
y = -distance / 50;
}
if (degree > 135 && degree <= 180) {
x = -distance / 50;
y = -0.022222222222222223 * (180 - degree) * distance / 50;
}
if (degree > 135 && degree <= 225) {
x = -distance / 50;
y = -0.022222222222222223 * (180 - degree) * distance / 50;
}
if (degree > 225 && degree <= 270) {
x = -0.022222222222222223 * (270 - degree) * distance / 50;
y = distance / 50;
}
if (degree > 270 && degree <= 315) {
x = -0.022222222222222223 * (270 - degree) * distance / 50;
y = distance / 50;
}
if (degree > 315 && degree <= 359.9) {
x = distance / 50;
y = 0.022222222222222223 * (360 - degree) * distance / 50;
}
if (x > 0) {
this.gameManager.simulateInput(0, zone.inputValues[0], 0x7fff * x);
this.gameManager.simulateInput(0, zone.inputValues[1], 0);
} else {
this.gameManager.simulateInput(0, zone.inputValues[1], 0x7fff * -x);
this.gameManager.simulateInput(0, zone.inputValues[0], 0);
}
if (y > 0) {
this.gameManager.simulateInput(0, zone.inputValues[2], 0x7fff * y);
this.gameManager.simulateInput(0, zone.inputValues[3], 0);
} else {
this.gameManager.simulateInput(0, zone.inputValues[3], 0x7fff * -y);
this.gameManager.simulateInput(0, zone.inputValues[2], 0);
}
} else {
if (degree >= 30 && degree < 150) {
this.gameManager.simulateInput(0, zone.inputValues[0], 1);
} else {
window.setTimeout(() => {
this.gameManager.simulateInput(0, zone.inputValues[0], 0);
}, 30);
}
if (degree >= 210 && degree < 330) {
this.gameManager.simulateInput(0, zone.inputValues[1], 1);
} else {
window.setTimeout(() => {
this.gameManager.simulateInput(0, zone.inputValues[1], 0);
}, 30);
}
if (degree >= 120 && degree < 240) {
this.gameManager.simulateInput(0, zone.inputValues[2], 1);
} else {
window.setTimeout(() => {
this.gameManager.simulateInput(0, zone.inputValues[2], 0);
}, 30);
}
if (degree >= 300 || degree >= 0 && degree < 60) {
this.gameManager.simulateInput(0, zone.inputValues[3], 1);
} else {
window.setTimeout(() => {
this.gameManager.simulateInput(0, zone.inputValues[3], 0);
}, 30);
}
}
});
})
2023-07-06 17:13:57 +00:00
if (this.touch || navigator.maxTouchPoints > 0) {
const menuButton = this.createElement("div");
menuButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 96C0 78.33 14.33 64 32 64H416C433.7 64 448 78.33 448 96C448 113.7 433.7 128 416 128H32C14.33 128 0 113.7 0 96zM0 256C0 238.3 14.33 224 32 224H416C433.7 224 448 238.3 448 256C448 273.7 433.7 288 416 288H32C14.33 288 0 273.7 0 256zM416 448H32C14.33 448 0 433.7 0 416C0 398.3 14.33 384 32 384H416C433.7 384 448 398.3 448 416C448 433.7 433.7 448 416 448z"/></svg>';
menuButton.classList.add("ejs_virtualGamepad_open");
menuButton.style.display = "none";
this.on("start", () => menuButton.style.display = "");
this.elements.parent.appendChild(menuButton);
2023-07-11 13:40:42 +00:00
let timeout;
let ready = true;
2023-07-06 17:13:57 +00:00
this.addEventListener(menuButton, "touchstart touchend mousedown mouseup click", (e) => {
2023-07-11 13:40:42 +00:00
if (!ready) return;
clearTimeout(timeout);
timeout = setTimeout(() => {
ready = true;
}, 2000)
ready = false;
2023-07-06 17:13:57 +00:00
e.preventDefault();
2023-07-11 13:40:42 +00:00
this.menu.toggle();
2023-07-06 17:13:57 +00:00
})
2023-07-11 15:15:43 +00:00
this.elements.menuToggle = menuButton;
2023-07-06 17:13:57 +00:00
}
2023-07-06 14:01:15 +00:00
this.virtualGamepad.style.display = "none";
2023-06-26 16:39:31 +00:00
}
handleResize() {
2023-06-27 16:54:35 +00:00
if (!this.Module) return;
2023-06-26 16:39:31 +00:00
const dpr = window.devicePixelRatio || 1;
2023-07-04 15:27:58 +00:00
const positionInfo = this.elements.parent.getBoundingClientRect();
2023-06-26 16:39:31 +00:00
const width = positionInfo.width * dpr;
const height = (positionInfo.height * dpr);
2023-06-27 16:54:35 +00:00
this.Module.setCanvasSize(width, height);
2023-07-11 12:28:34 +00:00
this.handleSettingsResize();
2023-07-11 15:15:43 +00:00
if (this.virtualGamepad.style.display === "none") {
this.virtualGamepad.style.opacity = 0;
this.virtualGamepad.style.display = "";
setTimeout(() => {
this.virtualGamepad.style.display = "none";
this.virtualGamepad.style.opacity = "";
}, 250)
}
2023-06-27 16:54:35 +00:00
}
2023-06-28 17:57:02 +00:00
getElementSize(element) {
let elem = element.cloneNode(true);
elem.style.position = 'absolute';
elem.style.opacity = 0;
elem.removeAttribute('hidden');
element.parentNode.appendChild(elem);
2023-07-11 12:28:34 +00:00
const res = elem.getBoundingClientRect();
2023-06-28 17:57:02 +00:00
elem.remove();
return {
2023-07-11 12:28:34 +00:00
'width': res.width,
'height': res.height
2023-06-28 17:57:02 +00:00
};
}
2023-07-03 15:34:18 +00:00
saveSettings() {
if (!window.localStorage || !this.settingsLoaded) return;
const coreSpecific = {
controlSettings: this.controls,
settings: this.settings,
cheats: this.cheats
}
const ejs_settings = {
volume: this.volume,
muted: this.muted
}
localStorage.setItem("ejs-settings", JSON.stringify(ejs_settings));
localStorage.setItem("ejs-"+this.getCore()+"-settings", JSON.stringify(coreSpecific));
}
loadSettings() {
if (!window.localStorage) return;
this.settingsLoaded = true;
let ejs_settings = localStorage.getItem("ejs-settings");
let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings");
if (coreSpecific) {
try {
coreSpecific = JSON.parse(coreSpecific);
2023-07-03 16:25:22 +00:00
if (!(coreSpecific.controlSettings instanceof Object) || !(coreSpecific.settings instanceof Object) || !Array.isArray(coreSpecific.cheats)) return;
2023-07-03 15:34:18 +00:00
this.controls = coreSpecific.controlSettings;
this.checkGamepadInputs();
for (const k in coreSpecific.settings) {
this.changeSettingOption(k, coreSpecific.settings[k]);
}
2023-07-03 16:25:22 +00:00
for (let i=0; i<coreSpecific.cheats.length; i++) {
const cheat = coreSpecific.cheats[i];
let includes = false;
for (let j=0; j<this.cheats.length; j++) {
if (this.cheats[j].desc === cheat.desc && this.cheats[j].code === cheat.code) {
this.cheats[j].checked = cheat.checked;
includes = true;
break;
}
}
if (includes) continue;
this.cheats.push(cheat);
}
2023-07-03 15:34:18 +00:00
} catch(e) {
console.warn("Could not load previous settings", e);
}
}
if (ejs_settings) {
try {
ejs_settings = JSON.parse(ejs_settings);
if (typeof ejs_settings.volume !== "number" || typeof ejs_settings.muted !== "boolean") return;
this.volume = ejs_settings.volume;
this.muted = ejs_settings.muted;
this.setVolume(this.muted ? 0 : this.volume);
} catch(e) {
console.warn("Could not load previous settings", e);
}
}
}
2023-06-28 18:27:06 +00:00
menuOptionChanged(option, value) {
2023-07-03 15:34:18 +00:00
this.saveSettings();
2023-07-03 17:03:00 +00:00
console.log(option, value);
2023-06-29 16:49:48 +00:00
if (option === "shader") {
try {
this.Module.FS.unlink("/shader/shader.glslp");
} catch(e) {}
if (value === "disabled") {
this.gameManager.toggleShader(0);
return;
}
this.Module.FS.writeFile("/shader/shader.glslp", window.EJS_SHADERS[value]);
this.gameManager.toggleShader(1);
return;
2023-07-01 16:46:52 +00:00
} else if (option === "disk") {
this.gameManager.setCurrentDisk(value);
return;
2023-07-03 17:56:04 +00:00
} else if (option === "virtual-gamepad") {
this.toggleVirtualGamepad(value !== "disabled");
} else if (option === "virtual-gamepad-left-handed-mode") {
this.toggleVirtualGamepadLeftHanded(value !== "disabled");
2023-06-29 16:49:48 +00:00
}
2023-06-28 18:27:06 +00:00
this.gameManager.setVariable(option, value);
}
2023-06-27 16:54:35 +00:00
setupSettingsMenu() {
2023-06-27 21:36:57 +00:00
this.settingsMenu = this.createElement("div");
this.settingsMenu.classList.add("ejs_settings_parent");
2023-06-28 00:25:04 +00:00
const nested = this.createElement("div");
2023-06-28 17:57:02 +00:00
nested.classList.add("ejs_settings_transition");
2023-06-28 00:30:22 +00:00
this.settings = {};
2023-06-27 16:54:35 +00:00
2023-06-28 00:30:22 +00:00
const home = this.createElement("div");
2023-07-11 12:28:34 +00:00
home.style.overflow = "auto";
const menus = [];
this.handleSettingsResize = () => {
2023-07-17 18:33:42 +00:00
const x = this.settingsMenu.parentElement.getBoundingClientRect().x;
2023-07-11 12:28:34 +00:00
let height = this.elements.parent.getBoundingClientRect().height;
2023-07-17 18:33:42 +00:00
let width = this.elements.parent.getBoundingClientRect().width;
2023-07-11 12:28:34 +00:00
if (height > 375) height = 375;
home.style['max-height'] = (height - 95) + "px";
nested.style['max-height'] = (height - 95) + "px";
for (let i=0; i<menus.length; i++) {
menus[i].style['max-height'] = (height - 95) + "px";
}
2023-07-17 18:33:42 +00:00
if (width < 575) {
this.settingsMenu.classList.toggle("ejs_settings_leftside", !((window.innerWidth/2) > x));
} else {
this.settingsMenu.classList.remove("ejs_settings_leftside");
}
2023-07-11 12:28:34 +00:00
}
2023-06-28 00:30:22 +00:00
home.classList.add("ejs_setting_menu");
nested.appendChild(home);
2023-07-03 15:34:18 +00:00
let funcs = [];
this.changeSettingOption = (title, newValue) => {
this.settings[title] = newValue;
funcs.forEach(e => e(title));
}
2023-07-03 16:43:59 +00:00
let allOpts = {};
2023-06-27 16:54:35 +00:00
2023-06-28 18:27:06 +00:00
const addToMenu = (title, id, options, defaultOption) => {
2023-06-27 16:54:35 +00:00
const menuOption = this.createElement("div");
menuOption.classList.add("ejs_settings_main_bar");
const span = this.createElement("span");
span.innerText = title;
const current = this.createElement("div");
2023-06-28 18:27:06 +00:00
current.innerText = "";
2023-06-27 16:54:35 +00:00
current.classList.add("ejs_settings_main_bar_selected");
span.appendChild(current);
menuOption.appendChild(span);
home.appendChild(menuOption);
2023-06-28 00:30:22 +00:00
2023-06-28 00:25:04 +00:00
const menu = this.createElement("div");
2023-07-11 12:28:34 +00:00
menus.push(menu);
menu.style.overflow = "auto";
2023-06-28 00:30:22 +00:00
menu.setAttribute("hidden", "");
2023-06-28 00:25:04 +00:00
const button = this.createElement("button");
2023-06-28 18:27:06 +00:00
const goToHome = () => {
2023-06-28 17:57:02 +00:00
const homeSize = this.getElementSize(home);
nested.style.width = (homeSize.width+20) + "px";
2023-06-28 17:57:02 +00:00
nested.style.height = homeSize.height + "px";
2023-06-28 00:30:22 +00:00
menu.setAttribute("hidden", "");
2023-06-28 17:57:02 +00:00
home.removeAttribute("hidden");
2023-06-28 18:27:06 +00:00
}
this.addEventListener(menuOption, "click", (e) => {
const targetSize = this.getElementSize(menu);
nested.style.width = (targetSize.width+20) + "px";
2023-06-28 18:27:06 +00:00
nested.style.height = targetSize.height + "px";
menu.removeAttribute("hidden");
home.setAttribute("hidden", "");
2023-06-28 00:30:22 +00:00
})
2023-06-28 18:27:06 +00:00
this.addEventListener(button, "click", goToHome);
2023-06-28 00:30:22 +00:00
2023-06-28 00:25:04 +00:00
button.type = "button";
button.classList.add("ejs_back_button");
menu.appendChild(button);
const pageTitle = this.createElement("span");
pageTitle.innerText = title;
pageTitle.classList.add("ejs_menu_text_a");
button.appendChild(pageTitle);
const optionsMenu = this.createElement("div");
optionsMenu.classList.add("ejs_setting_menu");
//optionsMenu.style["max-height"] = "385px";
//optionsMenu.style.overflow = "auto";
2023-06-28 00:25:04 +00:00
2023-06-28 17:57:02 +00:00
let buttons = [];
2023-06-28 18:27:06 +00:00
let opts = options;
if (Array.isArray(options)) {
opts = {};
for (let i=0; i<options.length; i++) {
opts[options[i]] = options[i];
}
}
2023-07-03 16:43:59 +00:00
allOpts[id] = opts;
2023-06-28 18:27:06 +00:00
2023-07-03 15:34:18 +00:00
funcs.push((title) => {
if (id !== title) return;
for (let j=0; j<buttons.length; j++) {
buttons[j].classList.toggle("ejs_option_row_selected", buttons[j].getAttribute("ejs_value") === this.settings[id]);
}
this.menuOptionChanged(id, this.settings[id]);
current.innerText = opts[this.settings[id]];
});
2023-06-28 18:27:06 +00:00
for (const opt in opts) {
2023-06-28 00:25:04 +00:00
const optionButton = this.createElement("button");
2023-06-28 17:57:02 +00:00
buttons.push(optionButton);
2023-07-03 15:34:18 +00:00
optionButton.setAttribute("ejs_value", opt);
2023-06-28 00:25:04 +00:00
optionButton.type = "button";
2023-06-28 18:27:06 +00:00
optionButton.value = opts[opt];
2023-06-28 00:25:04 +00:00
optionButton.classList.add("ejs_option_row");
optionButton.classList.add("ejs_button_style");
2023-06-28 00:35:01 +00:00
this.addEventListener(optionButton, "click", (e) => {
2023-06-28 18:27:06 +00:00
this.settings[id] = opt;
2023-06-28 17:57:02 +00:00
for (let j=0; j<buttons.length; j++) {
buttons[j].classList.remove("ejs_option_row_selected");
}
optionButton.classList.add("ejs_option_row_selected");
2023-06-28 18:27:06 +00:00
this.menuOptionChanged(id, opt);
current.innerText = opts[opt];
goToHome();
2023-06-28 00:35:01 +00:00
})
2023-06-28 18:27:06 +00:00
if (defaultOption === opt) {
optionButton.classList.add("ejs_option_row_selected");
this.menuOptionChanged(id, opt);
current.innerText = opts[opt];
}
2023-06-28 00:30:22 +00:00
2023-06-28 00:25:04 +00:00
const msg = this.createElement("span");
2023-06-28 18:27:06 +00:00
msg.innerText = opts[opt];
2023-06-28 00:25:04 +00:00
optionButton.appendChild(msg);
optionsMenu.appendChild(optionButton);
}
menu.appendChild(optionsMenu);
nested.appendChild(menu);
2023-06-27 16:54:35 +00:00
}
2023-06-28 18:27:06 +00:00
//addToMenu("Test", 'test', {a:1, b:2, c:3}, 2);
//addToMenu("Test2", 'test_2', [4, 5, 6]);
//addToMenu("Testertthgfd", 'booger', [7, 8, 9]);
2023-07-01 16:46:52 +00:00
if (this.gameManager.getDiskCount() > 1) {
const diskLabels = {};
for (let i=0; i<this.gameManager.getDiskCount(); i++) {
diskLabels[i.toString()] = "Disk "+(i+1);
}
addToMenu(this.localization("Disk"), "disk", diskLabels, this.gameManager.getCurrentDisk().toString());
}
2023-06-29 16:49:48 +00:00
if (window.EJS_SHADERS) {
addToMenu(this.localization('Shaders'), 'shader', {
2023-07-03 16:09:19 +00:00
'disabled': this.localization("Disabled"),
'2xScaleHQ.glslp': this.localization("2xScaleHQ"),
'4xScaleHQ.glslp': this.localization("4xScaleHQ"),
'crt-easymode.glslp': this.localization('CRT easymode'),
'crt-aperture.glslp': this.localization('CRT aperture'),
'crt-geom.glslp': this.localization('CRT geom'),
'crt-mattias.glslp': this.localization('CRT mattias')
2023-06-29 16:49:48 +00:00
}, 'disabled');
}
2023-07-03 17:03:00 +00:00
addToMenu(this.localization('FPS'), 'fps', {
'show': this.localization("show"),
'hide': this.localization("hide")
}, 'hide');
if (this.saveInBrowserSupported()) {
addToMenu(this.localization('Save State Slot'), 'save-state-slot', ["1", "2", "3", "4", "5", "6", "7", "8", "9"], "1");
addToMenu(this.localization('Save State Location'), 'save-state-location', {
'download': this.localization("Download"),
'browser': this.localization("Keep in Browser")
}, 'download');
}
2023-07-05 00:32:00 +00:00
if (this.touch || navigator.maxTouchPoints > 0) {
2023-07-03 17:56:04 +00:00
addToMenu(this.localization('Virtual Gamepad'), 'virtual-gamepad', {
'enabled': this.localization("Enabled"),
'disabled': this.localization("Disabled")
2023-07-17 17:12:05 +00:00
}, this.isMobile ? 'enabled' : 'disabled');
2023-07-03 17:56:04 +00:00
addToMenu(this.localization('Left Handed Mode'), 'virtual-gamepad-left-handed-mode', {
'enabled': this.localization("Enabled"),
'disabled': this.localization("Disabled")
}, 'disabled');
}
2023-07-05 00:32:00 +00:00
if (this.gameManager.getCoreOptions()) {
this.gameManager.getCoreOptions().split('\n').forEach((line, index) => {
let option = line.split('; ');
let name = option[0];
let options = option[1].split('|'),
optionName = name.split("|")[0].replace(/_/g, ' ').replace(/.+\-(.+)/, '$1');
options.slice(1, -1);
if (options.length === 1) return;
let availableOptions = {};
for (let i=0; i<options.length; i++) {
availableOptions[options[i]] = this.localization(options[i]);
}
addToMenu(this.localization(optionName),
name.split("|")[0], availableOptions,
(name.split("|").length > 1) ? name.split("|")[1] : options[0].replace('(Default) ', ''));
})
}
2023-07-01 16:46:52 +00:00
2023-06-28 00:25:04 +00:00
this.settingsMenu.appendChild(nested);
2023-06-27 16:54:35 +00:00
2023-06-27 21:36:57 +00:00
this.settingParent.appendChild(this.settingsMenu);
2023-06-27 16:54:35 +00:00
this.settingParent.style.position = "relative";
2023-06-28 17:57:02 +00:00
const homeSize = this.getElementSize(home);
2023-07-04 15:27:58 +00:00
nested.style.width = (homeSize.width+20) + "px";
2023-06-28 17:57:02 +00:00
nested.style.height = homeSize.height + "px";
this.settingsMenu.style.display = "none";
2023-07-03 16:43:59 +00:00
if (this.debug) {
console.log("Available core options", allOpts);
}
if (this.config.defaultOptions) {
for (const k in this.config.defaultOptions) {
this.changeSettingOption(k, this.config.defaultOptions[k]);
}
}
}
2023-06-29 15:35:25 +00:00
createSubPopup(hidden) {
const popup = this.createElement('div');
popup.classList.add("ejs_popup_container");
popup.classList.add("ejs_popup_container_box");
const popupMsg = this.createElement("div");
popupMsg.innerText = "";
if (hidden) popup.setAttribute("hidden", "");
popup.appendChild(popupMsg);
return [popup, popupMsg];
}
2023-07-15 16:51:10 +00:00
createNetplayMenu() {
const body = this.createPopup("Netplay", {
"Create a Room": () => {
2023-07-17 13:07:52 +00:00
if (this.isNetplay) {
2023-07-17 14:00:32 +00:00
this.netplay.leaveRoom();
2023-07-17 13:07:52 +00:00
} else {
this.netplay.showOpenRoomDialog();
}
2023-07-15 16:51:10 +00:00
},
"Close": () => {
this.netplayMenu.style.display = "none";
this.netplay.updateList.stop();
}
}, true);
this.netplayMenu = body.parentElement;
2023-07-17 13:07:52 +00:00
const createButton = this.netplayMenu.getElementsByTagName("a")[0];
2023-07-15 16:51:10 +00:00
const rooms = this.createElement("div");
const title = this.createElement("strong");
title.innerText = "Rooms";
const table = this.createElement("table");
table.classList.add("ejs_netplay_table");
table.style.width = "100%";
table.setAttribute("cellspacing", "0");
const thead = this.createElement("thead");
const row = this.createElement("tr");
const addToHeader = (text) => {
const item = this.createElement("td");
item.innerText = text;
item.style["text-align"] = "center";
row.appendChild(item);
return item;
}
thead.appendChild(row);
addToHeader("Room Name").style["text-align"] = "left";
addToHeader("Players").style.width = "80px";
addToHeader("").style.width = "80px"; //"join" button
table.appendChild(thead);
const tbody = this.createElement("tbody");
table.appendChild(tbody);
rooms.appendChild(title);
rooms.appendChild(table);
2023-07-16 22:49:33 +00:00
2023-07-17 13:07:52 +00:00
const joined = this.createElement("div");
const title2 = this.createElement("strong");
title2.innerText = "{roomname}";
const password = this.createElement("div");
password.innerText = "Password: ";
const table2 = this.createElement("table");
table2.classList.add("ejs_netplay_table");
table2.style.width = "100%";
table2.setAttribute("cellspacing", "0");
const thead2 = this.createElement("thead");
const row2 = this.createElement("tr");
const addToHeader2 = (text) => {
const item = this.createElement("td");
item.innerText = text;
row2.appendChild(item);
return item;
}
thead2.appendChild(row2);
addToHeader2("Player").style.width = "80px";
addToHeader2("Name");
addToHeader2("").style.width = "80px"; //"join" button
table2.appendChild(thead2);
const tbody2 = this.createElement("tbody");
table2.appendChild(tbody2);
joined.appendChild(title2);
joined.appendChild(password);
joined.appendChild(table2);
joined.style.display = "none";
2023-07-15 16:51:10 +00:00
body.appendChild(rooms);
2023-07-17 13:07:52 +00:00
body.appendChild(joined);
2023-07-15 16:51:10 +00:00
this.openNetplayMenu = () => {
this.netplayMenu.style.display = "";
if (!this.netplay) {
this.netplay = {};
this.netplay.table = tbody;
2023-07-17 13:07:52 +00:00
this.netplay.playerTable = tbody2;
this.netplay.passwordElem = password;
this.netplay.roomNameElem = title2;
this.netplay.createButton = createButton;
this.netplay.tabs = [rooms, joined];
2023-07-15 16:51:10 +00:00
this.defineNetplayFunctions();
const popups = this.createSubPopup();
this.netplayMenu.appendChild(popups[0]);
popups[1].classList.add("ejs_cheat_parent"); //Hehe
const popup = popups[1];
const header = this.createElement("div");
const title = this.createElement("h2");
title.innerText = this.localization("Set Player Name");
title.classList.add("ejs_netplay_name_heading");
header.appendChild(title);
popup.appendChild(header);
const main = this.createElement("div");
2023-07-16 22:49:33 +00:00
main.classList.add("ejs_netplay_header");
2023-07-15 16:51:10 +00:00
const head = this.createElement("strong");
head.innerText = "Player Name";
const input = this.createElement("input");
input.type = "text";
input.setAttribute("maxlength", 20);
main.appendChild(head);
main.appendChild(this.createElement("br"));
main.appendChild(input);
popup.appendChild(main);
popup.appendChild(this.createElement("br"));
const submit = this.createElement("button");
submit.classList.add("ejs_button_button");
submit.classList.add("ejs_popup_submit");
submit.style["background-color"] = "rgba(var(--ejs-primary-color),1)";
submit.innerText = "Submit";
popup.appendChild(submit);
this.addEventListener(submit, "click", (e) => {
if (!input.value.trim()) return;
this.netplay.name = input.value.trim();
popups[0].remove();
})
}
this.netplay.updateList.start();
}
}
defineNetplayFunctions() {
2023-07-17 13:07:52 +00:00
function guidGenerator() {
const S4 = function() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}
2023-07-15 20:10:56 +00:00
this.netplay.url = this.config.netplayUrl;
2023-07-17 16:10:34 +00:00
while (this.netplay.url.endsWith("/")) {
this.netplay.url = this.netplay.url.substring(0, this.netplay.url.length-1);
}
2023-07-17 14:18:05 +00:00
this.netplay.current_frame = 0;
2023-07-15 16:51:10 +00:00
this.netplay.getOpenRooms = async () => {
2023-07-17 13:07:52 +00:00
return JSON.parse(await (await fetch(this.netplay.url+"/list?domain="+window.location.host+"&game_id="+this.config.gameId)).text());
2023-07-15 16:51:10 +00:00
}
this.netplay.updateTableList = async () => {
2023-07-17 14:00:32 +00:00
const addToTable = (id, name, current, max) => {
2023-07-15 16:51:10 +00:00
const row = this.createElement("tr");
row.classList.add("ejs_netplay_table_row");
const addToHeader = (text) => {
const item = this.createElement("td");
item.innerText = text;
item.style.padding = "10px 0";
item.style["text-align"] = "center";
row.appendChild(item);
return item;
}
addToHeader(name).style["text-align"] = "left";
addToHeader(current + "/" + max).style.width = "80px";
const parent = addToHeader("");
parent.style.width = "80px";
2023-07-17 13:07:52 +00:00
this.netplay.table.appendChild(row);
2023-07-15 16:51:10 +00:00
if (current < max) {
const join = this.createElement("button");
join.classList.add("ejs_netplay_join_button");
join.classList.add("ejs_button_button");
join.style["background-color"] = "rgba(var(--ejs-primary-color),1)";
join.innerText = "Join";
parent.appendChild(join);
2023-07-17 14:00:32 +00:00
this.addEventListener(join, "click", (e) => {
this.netplay.joinRoom(id, name);
})
2023-07-17 13:07:52 +00:00
return join;
2023-07-15 16:51:10 +00:00
}
}
const open = await this.netplay.getOpenRooms();
2023-07-17 13:07:52 +00:00
//console.log(open);
2023-07-18 12:52:32 +00:00
this.netplay.table.innerHTML = "";
2023-07-17 13:07:52 +00:00
for (const k in open) {
2023-07-17 14:00:32 +00:00
addToTable(k, open[k].room_name, open[k].current, open[k].max);//todo: password
2023-07-15 16:51:10 +00:00
}
2023-07-15 20:10:56 +00:00
}
2023-07-16 22:49:33 +00:00
this.netplay.showOpenRoomDialog = () => {
const popups = this.createSubPopup();
this.netplayMenu.appendChild(popups[0]);
popups[1].classList.add("ejs_cheat_parent"); //Hehe
const popup = popups[1];
const header = this.createElement("div");
const title = this.createElement("h2");
title.innerText = this.localization("Create a room");
title.classList.add("ejs_netplay_name_heading");
header.appendChild(title);
popup.appendChild(header);
const main = this.createElement("div");
main.classList.add("ejs_netplay_header");
const rnhead = this.createElement("strong");
rnhead.innerText = "Room Name";
const rninput = this.createElement("input");
rninput.type = "text";
rninput.setAttribute("maxlength", 20);
const maxhead = this.createElement("strong");
maxhead.innerText = "Max Players";
const maxinput = this.createElement("select");
maxinput.setAttribute("disabled", "disabled");
const val2 = this.createElement("option");
val2.value = 2;
val2.innerText = "2";
const val3 = this.createElement("option");
val3.value = 3;
val3.innerText = "3";
const val4 = this.createElement("option");
val4.value = 4;
val4.innerText = "4";
maxinput.appendChild(val2);
maxinput.appendChild(val3);
maxinput.appendChild(val4);
const pwhead = this.createElement("strong");
pwhead.innerText = "Password (optional)";
const pwinput = this.createElement("input");
pwinput.type = "text";
pwinput.setAttribute("maxlength", 20);
main.appendChild(rnhead);
main.appendChild(this.createElement("br"));
main.appendChild(rninput);
2023-07-15 20:10:56 +00:00
2023-07-16 22:49:33 +00:00
main.appendChild(maxhead);
main.appendChild(this.createElement("br"));
main.appendChild(maxinput);
main.appendChild(pwhead);
main.appendChild(this.createElement("br"));
main.appendChild(pwinput);
popup.appendChild(main);
popup.appendChild(this.createElement("br"));
const submit = this.createElement("button");
submit.classList.add("ejs_button_button");
submit.classList.add("ejs_popup_submit");
submit.style["background-color"] = "rgba(var(--ejs-primary-color),1)";
submit.style.margin = "0 10px";
submit.innerText = "Submit";
popup.appendChild(submit);
this.addEventListener(submit, "click", (e) => {
if (!rninput.value.trim()) return;
2023-07-17 14:00:32 +00:00
this.netplay.openRoom(rninput.value.trim(), parseInt(maxinput.value), pwinput.value.trim());
2023-07-17 13:07:52 +00:00
popups[0].remove();
2023-07-16 22:49:33 +00:00
})
const close = this.createElement("button");
close.classList.add("ejs_button_button");
close.classList.add("ejs_popup_submit");
close.style.margin = "0 10px";
close.innerText = "Close";
popup.appendChild(close);
this.addEventListener(close, "click", (e) => {
popups[0].remove();
})
}
2023-07-17 14:00:32 +00:00
this.netplay.startSocketIO = (callback) => {
this.netplay.socket = io(this.netplay.url);
this.netplay.socket.on("connect", () => callback());
this.netplay.socket.on("users-updated", (users) => {
console.log(users);
this.netplay.players = users;
this.netplay.updatePlayersTable();
2023-07-17 14:18:05 +00:00
if (this.netplay.owner) this.netplay.sync();
2023-07-17 14:00:32 +00:00
})
this.netplay.socket.on("disconnect", () => this.netplay.roomLeft());
this.netplay.socket.on("data-message", (data) => {
this.netplay.dataMessage(data);
})
}
2023-07-16 22:49:33 +00:00
this.netplay.openRoom = (roomName, maxPlayers, password) => {
2023-07-17 14:00:32 +00:00
const sessionid = guidGenerator();
2023-07-17 13:07:52 +00:00
this.netplay.playerID = guidGenerator();
this.netplay.players = {};
this.netplay.extra = {
domain: window.location.host,
game_id: this.config.gameId,
room_name: roomName,
player_name: this.netplay.name,
2023-07-17 14:00:32 +00:00
userid: this.netplay.playerID,
sessionid: sessionid
2023-07-17 13:07:52 +00:00
}
this.netplay.players[this.netplay.playerID] = this.netplay.extra;
2023-07-17 14:18:05 +00:00
this.netplay.users = {};
2023-07-17 14:00:32 +00:00
this.netplay.startSocketIO((error) => {
this.netplay.socket.emit("open-room", {
extra: this.netplay.extra,
maxPlayers: maxPlayers,
password: password
}, (error) => {
if (error) {
console.log("error: ", error);
return;
}
this.netplay.roomJoined(true, roomName, password, sessionid);
})
});
}
this.netplay.leaveRoom = () => {
2023-07-17 16:10:34 +00:00
console.log("asd");
2023-07-17 14:00:32 +00:00
this.netplay.roomLeft();
2023-07-17 13:07:52 +00:00
}
2023-07-17 14:00:32 +00:00
this.netplay.joinRoom = (sessionid, roomName) => {
this.netplay.playerID = guidGenerator();
this.netplay.players = {};
this.netplay.extra = {
domain: window.location.host,
game_id: this.config.gameId,
room_name: roomName,
player_name: this.netplay.name,
userid: this.netplay.playerID,
sessionid: sessionid
}
this.netplay.players[this.netplay.playerID] = this.netplay.extra;
this.netplay.startSocketIO((error) => {
this.netplay.socket.emit("join-room", {
extra: this.netplay.extra//,
//password: password
}, (error, users) => {
if (error) {
console.log("error: ", error);
return;
}
this.netplay.players = users;
//this.netplay.roomJoined(false, roomName, password, sessionid);
this.netplay.roomJoined(false, roomName, "", sessionid);
})
});
2023-07-17 13:07:52 +00:00
}
2023-07-17 14:00:32 +00:00
this.netplay.roomJoined = (isOwner, roomName, password, roomId) => {
2023-07-17 13:07:52 +00:00
//Will already assume this.netplay.players has been refreshed
this.isNetplay = true;
2023-07-17 16:10:34 +00:00
this.netplay.inputs = {};
2023-07-17 14:18:05 +00:00
this.netplay.owner = isOwner;
2023-07-17 13:07:52 +00:00
console.log(this.netplay.extra);
this.netplay.roomNameElem.innerText = roomName;
this.netplay.tabs[0].style.display = "none";
this.netplay.tabs[1].style.display = "";
if (password) {
this.netplay.passwordElem.style.display = "";
this.netplay.passwordElem.innerText = "Password: "+password
} else {
this.netplay.passwordElem.style.display = "none";
}
this.netplay.createButton.innerText = "Leave Room";
this.netplay.updatePlayersTable();
2023-07-17 16:44:18 +00:00
if (!this.netplay.owner) {
this.netplay.oldStyles = [
this.elements.bottomBar.cheat[0].style.display,
this.elements.bottomBar.playPause[0].style.display,
this.elements.bottomBar.playPause[1].style.display,
this.elements.bottomBar.restart[0].style.display,
this.elements.bottomBar.loadState[0].style.display,
this.elements.bottomBar.saveState[0].style.display,
this.elements.bottomBar.saveSavFiles[0].style.display,
2023-07-17 17:12:05 +00:00
this.elements.bottomBar.loadSavFiles[0].style.display,
this.elements.contextMenu.save.style.display,
this.elements.contextMenu.load.style.display
2023-07-17 16:44:18 +00:00
]
this.elements.bottomBar.cheat[0].style.display = "none";
this.elements.bottomBar.playPause[0].style.display = "none";
this.elements.bottomBar.playPause[1].style.display = "none";
this.elements.bottomBar.restart[0].style.display = "none";
this.elements.bottomBar.loadState[0].style.display = "none";
this.elements.bottomBar.saveState[0].style.display = "none";
this.elements.bottomBar.saveSavFiles[0].style.display = "none";
this.elements.bottomBar.loadSavFiles[0].style.display = "none";
2023-07-17 17:12:05 +00:00
this.elements.contextMenu.save.style.display = "none";
this.elements.contextMenu.load.style.display = "none";
2023-07-17 16:44:18 +00:00
this.gameManager.resetCheat();
} else {
this.netplay.oldStyles = [
this.elements.bottomBar.cheat[0].style.display
]
2023-07-17 16:10:34 +00:00
}
2023-07-17 16:44:18 +00:00
this.elements.bottomBar.cheat[0].style.display = "none";
2023-07-16 22:49:33 +00:00
}
2023-07-17 13:07:52 +00:00
this.netplay.updatePlayersTable = () => {
const table = this.netplay.playerTable;
table.innerHTML = "";
const addToTable = (num, playerName) => {
const row = this.createElement("tr");
const addToHeader = (text) => {
const item = this.createElement("td");
item.innerText = text;
row.appendChild(item);
return item;
}
addToHeader(num).style.width = "80px";
addToHeader(playerName);
addToHeader("").style.width = "80px"; //"join" button
table.appendChild(row);
}
let i=1;
for (const k in this.netplay.players) {
addToTable(i, this.netplay.players[k].player_name);
i++;
}
}
this.netplay.roomLeft = () => {
this.isNetplay = false;
this.netplay.tabs[0].style.display = "";
this.netplay.tabs[1].style.display = "none";
this.netplay.extra = null;
this.netplay.playerID = null;
this.netplay.createButton.innerText = "Create a Room";
2023-07-17 14:00:32 +00:00
this.netplay.socket.disconnect();
2023-07-17 16:44:18 +00:00
this.elements.bottomBar.cheat[0].style.display = this.netplay.oldStyles[0];
if (!this.netplay.owner) {
this.elements.bottomBar.playPause[0].style.display = this.netplay.oldStyles[1];
this.elements.bottomBar.playPause[1].style.display = this.netplay.oldStyles[2];
this.elements.bottomBar.restart[0].style.display = this.netplay.oldStyles[3];
this.elements.bottomBar.loadState[0].style.display = this.netplay.oldStyles[4];
this.elements.bottomBar.saveState[0].style.display = this.netplay.oldStyles[5];
this.elements.bottomBar.saveSavFiles[0].style.display = this.netplay.oldStyles[6];
this.elements.bottomBar.loadSavFiles[0].style.display = this.netplay.oldStyles[7];
2023-07-17 17:12:05 +00:00
this.elements.contextMenu.save.style.display = this.netplay.oldStyles[8];
this.elements.contextMenu.load.style.display = this.netplay.oldStyles[9];
2023-07-17 16:44:18 +00:00
}
this.updateCheatUI();
2023-07-17 14:00:32 +00:00
}
2023-07-18 12:52:32 +00:00
this.netplay.setLoading = (loading) => {
console.log("loading:", loading);
}
let syncing = false;
2023-07-17 14:18:05 +00:00
this.netplay.sync = async () => {
2023-07-18 12:52:32 +00:00
if (syncing) return;
syncing = true;
console.log("sync")
2023-07-17 16:10:34 +00:00
this.netplay.ready = 0;
2023-07-17 14:18:05 +00:00
const state = await this.gameManager.getState();
this.netplay.sendMessage({
2023-07-17 16:10:34 +00:00
state: state
2023-07-17 14:18:05 +00:00
});
2023-07-18 12:52:32 +00:00
this.netplay.setLoading(true);
2023-07-17 16:44:18 +00:00
this.pause(true);
2023-07-17 16:10:34 +00:00
this.netplay.ready++;
2023-07-17 14:18:05 +00:00
this.netplay.current_frame = 0;
2023-07-17 16:44:18 +00:00
if (this.netplay.ready === this.netplay.getUserCount()) {
this.play(true);
}
2023-07-18 12:52:32 +00:00
syncing = false;
2023-07-17 14:18:05 +00:00
}
2023-07-17 16:10:34 +00:00
this.netplay.getUserIndex = (user) => {
let i=0;
for (const k in this.netplay.players) {
if (k === user) return i;
i++;
}
return -1;
}
this.netplay.getUserCount = () => {
let i=0;
for (const k in this.netplay.players) i++;
return i;
}
2023-07-18 12:52:32 +00:00
let justReset = false;
2023-07-17 14:00:32 +00:00
this.netplay.dataMessage = (data) => {
2023-07-17 16:10:34 +00:00
//console.log(data);
2023-07-17 14:18:05 +00:00
if (data.state) {
2023-07-18 12:52:32 +00:00
this.netplay.setLoading(true);
2023-07-17 16:44:18 +00:00
this.pause(true);
2023-07-17 14:18:05 +00:00
this.gameManager.loadState(new Uint8Array(data.state));
2023-07-17 16:10:34 +00:00
this.netplay.sendMessage({ready:true});
}
if (data.play && !this.owner) {
2023-07-17 16:44:18 +00:00
this.play(true);
}
if (data.pause && !this.owner) {
this.pause(true);
2023-07-17 16:10:34 +00:00
}
if (data.ready && this.netplay.owner) {
this.netplay.ready++;
if (this.netplay.ready === this.netplay.getUserCount()) {
2023-07-18 12:52:32 +00:00
this.netplay.sendMessage({readyready:true, resetCurrentFrame: true});
2023-07-17 16:44:18 +00:00
setTimeout(() => this.play(true), 100);
2023-07-18 12:52:32 +00:00
this.netplay.setLoading(false);
2023-07-17 16:10:34 +00:00
this.netplay.current_frame = 0;
2023-07-18 12:52:32 +00:00
justReset = true;
2023-07-17 16:10:34 +00:00
}
2023-07-17 14:18:05 +00:00
}
2023-07-18 12:52:32 +00:00
if (data.readyready) {
this.netplay.setLoading(false);
this.netplay.current_frame = 0;
this.play(true)
}
2023-07-17 14:18:05 +00:00
if (data.resetCurrentFrame) {
2023-07-17 16:44:18 +00:00
this.play(true);
2023-07-17 14:18:05 +00:00
this.netplay.current_frame = 0;
2023-07-17 16:10:34 +00:00
this.netplay.inputs = {};
2023-07-17 14:18:05 +00:00
}
if (data.user_frame && this.netplay.owner) {
2023-07-18 12:52:32 +00:00
if (justReset) {
justReset = false;
this.netplay.current_frame = 0;
this.netplay.inputs = {};
}
2023-07-17 14:18:05 +00:00
this.netplay.users[data.user_frame.user] = data.user_frame.frame;
2023-07-17 16:10:34 +00:00
//console.log(data.user_frame.frame, this.netplay.current_frame);
}
if (data.shortPause === this.netplay.playerID) {
2023-07-17 16:44:18 +00:00
this.pause(true);
setTimeout(() => this.play(true), 5);
2023-07-18 12:52:32 +00:00
} else if (data.lessShortPause === this.netplay.playerID) {
this.pause(true);
setTimeout(() => this.play(true), 10);
2023-07-17 16:10:34 +00:00
}
if (data.input && this.netplay.owner) {
this.netplay.simulateInput(this.netplay.getUserIndex(data.user), data.input[0], data.input[1], true);
}
if (data.connected_input && !this.netplay.owner) {
if (!this.netplay.inputs[data.frame]) {
this.netplay.inputs[data.frame] = [];
}
this.netplay.inputs[data.frame].push([data.connected_input[0], data.connected_input[1], data.connected_input[2]]);
}
2023-07-17 16:44:18 +00:00
if (data.restart) {
this.gameManager.restart();
this.netplay.current_frame = 0;
this.netplay.inputs = {};
this.play(true);
}
2023-07-17 16:10:34 +00:00
}
this.netplay.simulateInput = (player, index, value, resp) => {
if (!this.isNetplay) return;
if (player !== 0 && !resp) return;
if (this.netplay.owner) {
const frame = this.netplay.current_frame;
this.gameManager.functions.simulateInput(player, index, value);
this.netplay.sendMessage({
frame: frame,
connected_input: [player, index, value]
});
} else {
this.netplay.sendMessage({
user: this.netplay.playerID,
input: [index, value]
});
2023-07-17 14:18:05 +00:00
}
}
this.netplay.sendMessage = (data) => {
this.netplay.socket.emit("data-message", data);
}
2023-07-17 16:10:34 +00:00
//let fps;
//let lastTime;
2023-07-17 14:18:05 +00:00
this.Module.postMainLoop = () => {
2023-07-17 16:10:34 +00:00
//const newTime = window.performance.now();
//fps = 1000 / (newTime - lastTime);
//console.log(fps);
//lastTime = newTime;
2023-07-17 14:18:05 +00:00
if (!this.isNetplay || this.paused) return;
this.netplay.current_frame++;
if (this.netplay.owner) {
2023-07-17 16:10:34 +00:00
for (const k in this.netplay.users) {
if (this.netplay.getUserIndex(k) === -1) {
delete this.netplay.users[k];
continue;
}
const diff = this.netplay.current_frame - this.netplay.users[k];
2023-07-18 12:52:32 +00:00
//console.log(diff);
if (Math.abs(diff) > 75) {
2023-07-17 16:10:34 +00:00
this.netplay.sync();
return;
}
//this'll be adjusted if needed
2023-07-18 12:52:32 +00:00
if (diff < 0) {
this.netplay.sendMessage({
lessShortPause: k
})
}
2023-07-17 16:10:34 +00:00
if (diff < 5) {
this.netplay.sendMessage({
shortPause: k
})
2023-07-18 12:52:32 +00:00
} else if (diff > 30) {
this.pause(true);
setTimeout(() => this.play(true), 10);
2023-07-17 16:10:34 +00:00
} else if (diff > 10) {
2023-07-17 16:44:18 +00:00
this.pause(true);
2023-07-17 17:12:05 +00:00
setTimeout(() => this.play(true), 5);
2023-07-17 16:10:34 +00:00
}
}
2023-07-17 14:18:05 +00:00
} else {
this.netplay.sendMessage({
user_frame: {
user: this.netplay.playerID,
frame: this.netplay.current_frame
}
});
2023-07-17 16:10:34 +00:00
for (const k in this.netplay.inputs) {
if (k <= this.netplay.current_frame) {
this.netplay.inputs[k].forEach(data => {
this.gameManager.functions.simulateInput(data[0], data[1], data[2]);
})
delete this.netplay.inputs[k];
}
}
2023-07-17 14:18:05 +00:00
}
2023-07-15 16:51:10 +00:00
}
this.netplay.updateList = {
start: () => {
this.netplay.updateList.interval = setInterval(this.netplay.updateTableList.bind(this), 1000);
},
stop: () => {
clearInterval(this.netplay.updateList.interval);
}
}
}
2023-06-29 15:35:25 +00:00
createCheatsMenu() {
const body = this.createPopup("Cheats", {
"Add Cheat": () => {
const popups = this.createSubPopup();
this.cheatMenu.appendChild(popups[0]);
popups[1].classList.add("ejs_cheat_parent");
popups[1].style.width = "100%";
const popup = popups[1];
const header = this.createElement("div");
header.classList.add("ejs_cheat_header");
const title = this.createElement("h2");
2023-07-03 16:09:19 +00:00
title.innerText = this.localization("Add Cheat Code");
2023-06-29 15:35:25 +00:00
title.classList.add("ejs_cheat_heading");
const close = this.createElement("button");
close.classList.add("ejs_cheat_close");
header.appendChild(title);
header.appendChild(close);
popup.appendChild(header);
this.addEventListener(close, "click", (e) => {
popups[0].remove();
})
const main = this.createElement("div");
main.classList.add("ejs_cheat_main");
const header3 = this.createElement("strong");
2023-07-03 16:09:19 +00:00
header3.innerText = this.localization("Code");
2023-06-29 15:35:25 +00:00
main.appendChild(header3);
main.appendChild(this.createElement("br"));
const mainText = this.createElement("textarea");
mainText.classList.add("ejs_cheat_code");
mainText.style.width = "100%";
mainText.style.height = "80px";
main.appendChild(mainText);
main.appendChild(this.createElement("br"));
const header2 = this.createElement("strong");
2023-07-03 16:09:19 +00:00
header2.innerText = this.localization("Description");
2023-06-29 15:35:25 +00:00
main.appendChild(header2);
main.appendChild(this.createElement("br"));
const mainText2 = this.createElement("input");
mainText2.type = "text";
mainText2.classList.add("ejs_cheat_code");
main.appendChild(mainText2);
main.appendChild(this.createElement("br"));
popup.appendChild(main);
const footer = this.createElement("footer");
const submit = this.createElement("button");
const closeButton = this.createElement("button");
2023-07-03 16:09:19 +00:00
submit.innerText = this.localization("Submit");
closeButton.innerText = this.localization("Close");
2023-06-29 15:35:25 +00:00
submit.classList.add("ejs_button_button");
closeButton.classList.add("ejs_button_button");
submit.classList.add("ejs_popup_submit");
closeButton.classList.add("ejs_popup_submit");
submit.style["background-color"] = "rgba(var(--ejs-primary-color),1)";
footer.appendChild(submit);
const span = this.createElement("span");
span.innerText = " ";
footer.appendChild(span);
footer.appendChild(closeButton);
popup.appendChild(footer);
this.addEventListener(submit, "click", (e) => {
if (!mainText.value.trim() || !mainText2.value.trim()) return;
popups[0].remove();
this.cheats.push({
code: mainText.value,
desc: mainText2.value,
checked: false
});
this.updateCheatUI();
2023-07-03 15:38:45 +00:00
this.saveSettings();
2023-06-29 15:35:25 +00:00
})
this.addEventListener(closeButton, "click", (e) => {
popups[0].remove();
})
},
"Close": () => {
this.cheatMenu.style.display = "none";
}
}, true);
this.cheatMenu = body.parentElement;
const rows = this.createElement("div");
body.appendChild(rows);
rows.classList.add("ejs_cheat_rows");
this.elements.cheatRows = rows;
}
updateCheatUI() {
this.elements.cheatRows.innerHTML = "";
const addToMenu = (desc, checked, code, i) => {
const row = this.createElement("div");
row.classList.add("ejs_cheat_row");
const input = this.createElement("input");
input.type = "checkbox";
input.checked = checked;
input.value = i;
input.id = "ejs_cheat_switch_"+i;
row.appendChild(input);
const label = this.createElement("label");
label.for = "ejs_cheat_switch_"+i;
label.innerText = desc;
row.appendChild(label);
label.addEventListener("click", (e) => {
input.checked = !input.checked;
this.cheats[i].checked = input.checked;
this.cheatChanged(input.checked, code, i);
2023-07-03 15:38:45 +00:00
this.saveSettings();
2023-06-29 15:35:25 +00:00
})
const close = this.createElement("a");
close.classList.add("ejs_cheat_row_button");
close.innerText = "×";
row.appendChild(close);
this.elements.cheatRows.appendChild(row);
this.cheatChanged(checked, code, i);
}
this.gameManager.resetCheat();
for (let i=0; i<this.cheats.length; i++) {
addToMenu(this.cheats[i].desc, this.cheats[i].checked, this.cheats[i].code, i);
}
}
cheatChanged(checked, code, index) {
this.gameManager.setCheat(index, checked, code);
}
}