EmulatorJS/data/emulator.js

4533 lines
259 KiB
JavaScript
Raw Normal View History

class EmulatorJS {
2023-08-26 15:49:05 +00:00
version = 9; //Increase by 1 when cores are updated
2023-07-01 16:46:52 +00:00
getCore(generic) {
const core = this.config.system;
/*todo:
2023-08-31 14:30:20 +00:00
Systems: msx
Cores:
- FreeChaF
- FreeIntv
- NeoCD
- O2EM
- Vecx
*/
2023-07-01 16:46:52 +00:00
if (generic) {
const options = {
'a5200': 'atari5200',
2023-07-28 13:49:10 +00:00
'beetle_vb': 'vb',
2023-07-01 16:46:52 +00:00
'desmume2015': 'nds',
'fbalpha2012_cps1': 'arcade',
'fbalpha2012_cps2': 'arcade',
'fbneo': 'arcade',
'fceumm': 'nes',
'gambatte': 'gb',
2023-08-31 14:30:20 +00:00
'gearcoleco': 'coleco',
'genesis_plus_gx': 'sega',
'handy': 'lynx',
'mame2003': 'mame2003',
2023-08-30 20:16:17 +00:00
'mednafen_ngp': 'ngp',
'mednafen_pce': 'pce',
2023-08-31 15:51:41 +00:00
'mednafen_pcfx': 'pcfx',
2023-07-01 16:46:52 +00:00
'mednafen_psx_hw': 'psx',
2023-08-31 14:30:20 +00:00
'mednafen_wswan': 'ws',
2023-07-01 16:46:52 +00:00
'melonds': 'nds',
'mgba': 'gba',
'mupen64plus_next': 'n64',
2023-07-01 16:46:52 +00:00
'nestopia': 'nes',
2023-07-05 00:27:38 +00:00
'opera': '3do',
2023-08-07 02:28:08 +00:00
'parallel_n64': 'n64',
'pcsx_rearmed': 'psx',
'picodrive': 'sega',
2023-07-28 13:49:10 +00:00
'ppsspp': 'psp',
2023-07-05 00:27:38 +00:00
'prosystem': 'atari7800',
'snes9x': 'snes',
'stella2014': 'atari2600',
'virtualjaguar': 'jaguar',
'yabause': 'segaSaturn'
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',
'sega32x': 'picodrive',
2023-07-05 00:27:38 +00:00
'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': 'fbneo',
'psx': 'pcsx_rearmed',
2023-07-28 13:49:10 +00:00
'3do': 'opera',
'psp': 'ppsspp',
2023-08-30 20:16:17 +00:00
'pce': 'mednafen_pce',
2023-08-31 15:51:41 +00:00
'pcfx': 'mednafen_pcfx',
2023-08-31 14:30:20 +00:00
'ngp': 'mednafen_ngp',
'ws': 'mednafen_wswan',
'coleco': 'gearcoleco',
2023-07-01 16:46:52 +00:00
}
2023-08-07 02:28:08 +00:00
if (this.isSafari && this.isMobile && this.getCore(true) === "n64") {
return "parallel_n64";
}
2023-07-01 16:46:52 +00:00
return options[core] || core;
}
extensions = {
'a5200': ['a52', 'bin'],
'desmume2015': ['nds', 'bin'],
2023-07-01 16:46:52 +00:00
'fbalpha2012_cps1': ['zip'],
'fbalpha2012_cps2': ['zip'],
'fbneo': ['zip', '7z'],
'fceumm': ['fds', 'nes', 'unif', 'unf'],
'gambatte': ['gb', 'gbc', 'dmg'],
2023-08-31 14:30:20 +00:00
'gearcoleco': ['col', 'cv', 'bin', 'rom'],
'genesis_plus_gx': ['m3u', 'mdx', 'md', 'smd', 'gen', 'bin', 'cue', 'iso', 'chd', 'bms', 'sms', 'gg', 'sg', '68k', 'sgd'],
'handy': ['lnx'],
2023-07-01 16:46:52 +00:00
'mame2003': ['zip'],
2023-08-30 20:16:17 +00:00
'mednafen_ngp': ['ngp', 'ngc'],
2023-08-30 17:25:53 +00:00
'mednafen_pce': ['pce', 'cue', 'ccd', 'iso', 'img', 'bin', 'chd'],
2023-08-31 15:51:41 +00:00
'mednafen_pcfx': ['cue', 'ccd', 'toc', 'chd'],
2023-07-01 16:46:52 +00:00
'mednafen_psx': ['cue', 'toc', 'm3u', 'ccd', 'exe', 'pbp', 'chd'],
2023-08-31 14:30:20 +00:00
'mednafen_wswan': ['ws', 'wsc', 'pc2'],
2023-07-01 16:46:52 +00:00
'mednafen_psx_hw': ['cue', 'toc', 'm3u', 'ccd', 'exe', 'pbp', 'chd'],
'beetle_vb': ['vb', 'vboy', 'bin'],
'melonds': ['nds'],
'mgba': ['gb', 'gbc', 'gba'],
'mupen64plus_next': ['n64', 'v64', 'z64', 'bin', 'u1', 'ndd', 'gb'],
2023-07-01 16:46:52 +00:00
'nestopia': ['fds', 'nes', 'unif', 'unf'],
'opera': ['iso', 'bin', 'chd', 'cue'],
2023-08-07 02:28:08 +00:00
'parallel_n64': ['n64', 'v64', 'z64', 'bin', 'u1', 'ndd', 'gb'],
'pcsx_rearmed': ['bin', 'cue', 'img', 'mdf', 'pbp', 'toc', 'cbn', 'm3u', 'ccd'],
'picodrive': ['bin', 'gen', 'smd', 'md', '32x', 'cue', 'iso', 'sms', '68k', 'chd'],
2023-07-28 13:49:10 +00:00
'ppsspp': ['elf', 'iso', 'cso', 'prx', 'pbp'],
'prosystem': ['a78', 'bin'],
'snes9x': ['smc', 'sfc', 'swc', 'fig', 'bs', 'st'],
'stella2014': ['a26', 'bin', 'zip'],
'virtualjaguar': ['j64', 'jag', 'rom', 'abs', 'cof', 'bin', 'prg'],
'yabause': ['cue', 'iso', 'ccd', 'mds', 'chd', 'zip', 'm3u']
2023-07-01 16:46:52 +00:00
}
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) {
2023-07-22 16:40:40 +00:00
const data = this.toData(path);//check other data types
if (data) {
data.then((game) => {
if (opts.method === 'HEAD') {
cb({headers:{}});
return;
} else {
cb({headers:{}, data:game});
return;
}
})
return;
}
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;
2023-07-18 15:13:12 +00:00
if (xhr.status.toString().startsWith("4") || xhr.status.toString().startsWith("5")) {
cb(-1);
return;
}
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
})();
}
}
2023-07-22 16:40:40 +00:00
toData(data, rv) {
if (!(data instanceof ArrayBuffer) && !(data instanceof Uint8Array) && !(data instanceof Blob)) return null;
if (rv) return true;
return new Promise(async (resolve) => {
if (data instanceof ArrayBuffer) {
resolve(new Uint8Array(data));
} else if (data instanceof Uint8Array) {
resolve(data);
} else if (data instanceof Blob) {
resolve(new Uint8Array(await data.arrayBuffer()));
}
resolve();
})
}
2023-07-18 15:56:27 +00:00
checkForUpdates() {
fetch('https://raw.githack.com/EmulatorJS/EmulatorJS/main/data/version.json').then(response => {
if (response.ok) {
response.text().then(body => {
let version = JSON.parse(body);
if (this.ejs_num_version < version.current_version) {
console.log('Using emulatorjs version ' + this.ejs_num_version + ' but the newest version is ' + version.current_version + '\nopen https://github.com/EmulatorJS/EmulatorJS to update');
}
})
}
})
}
constructor(element, config) {
2023-08-07 16:31:20 +00:00
this.ejs_version = "4.0.6";
this.ejs_num_version = 40.6;
2023-07-18 15:56:27 +00:00
this.debug = (window.EJS_DEBUG_XX === true);
if (this.debug || (window.location && ['localhost', '127.0.0.1'].includes(location.hostname))) this.checkForUpdates();
this.netplayEnabled = (window.EJS_DEBUG_XX === true) && (window.EJS_EXPERIMENTAL_NETPLAY === true);
this.settingsLanguage = window.EJS_settingsLanguage || false;
2023-07-03 14:34:48 +00:00
this.config = config;
2023-07-01 20:16:25 +00:00
this.currentPopup = null;
2023-08-08 14:04:13 +00:00
this.isFastForward = false;
2023-08-11 16:20:11 +00:00
this.isSlowMotion = false;
this.rewindEnabled = this.loadRewindEnabled();
this.touch = false;
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 = [];
this.missingLang = [];
2023-07-01 18:15:26 +00:00
this.setElements(element);
this.setColor(this.config.color || "");
2023-08-14 23:19:17 +00:00
this.config.alignStartButton = (typeof this.config.alignStartButton === "string") ? this.config.alignStartButton : "bottom";
this.config.backgroundColor = (typeof this.config.backgroundColor === "string") ? this.config.backgroundColor : "rgb(51, 51, 51)";
if (this.config.adUrl) {
this.config.adSize = (Array.isArray(this.config.adSize)) ? this.config.adSize : ["300px", "250px"];
this.setupAds(this.config.adUrl, this.config.adSize[0], this.config.adSize[1]);
}
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-08-07 02:28:08 +00:00
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
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.classList.add("ejs_game_background");
if (this.config.backgroundBlur) this.game.classList.add("ejs_game_background_blur");
this.game.setAttribute("style", "--ejs-background-image: url("+this.config.backgroundImg+"); --ejs-background-color: "+this.config.backgroundColor+";");
2023-07-18 14:59:31 +00:00
this.on("start", () => {
this.game.classList.remove("ejs_game_background");
if (this.config.backgroundBlur) this.game.classList.remove("ejs_game_background_blur");
2023-07-18 14:59:31 +00:00
})
}else{
this.game.setAttribute("style", "--ejs-background-color: "+this.config.backgroundColor+";");
2023-07-18 14:59:31 +00:00
}
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();
}
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) + ";");
}
setupAds(ads, width, height) {
2023-07-01 18:59:39 +00:00
const div = this.createElement("div");
const time = (typeof this.config.adMode === "number" && this.config.adMode > -1 && this.config.adMode < 3) ? this.config.adMode : 2;
2023-07-01 18:59:39 +00:00
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 = width;
frame.style.height = height;
2023-07-01 18:59:39 +00:00
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);
if (this.config.adMode !== 1) {
this.elements.parent.appendChild(div);
}
2023-07-01 18:59:39 +00:00
this.addEventListener(closeButton, "click", () => {
div.remove();
})
this.on("start-clicked", () => {
if (this.config.adMode === 0) div.remove();
if (this.config.adMode === 1){
this.elements.parent.appendChild(div);
}
})
2023-07-01 18:59:39 +00:00
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 === -1) div.remove();
2023-07-01 19:06:16 +00:00
if (this.config.adTimer === 0) return;
setTimeout(() => {
div.remove();
}, time);
2023-07-01 18:59:39 +00:00
})
}
adBlocked(url, del){
if (del){
document.querySelector('div[class="ejs_ad_iframe"]').remove();
}else{
document.querySelector('iframe[src="'+this.config.adUrl+'"]').src = url;
this.config.adUrl = url;
}
}
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");
2023-08-14 23:19:17 +00:00
let border = 0;
if (typeof this.config.backgroundImg === "string"){
button.classList.add("ejs_start_button_border");
border = 1;
}
button.innerText = this.localization("Start Game");
2023-08-14 23:19:17 +00:00
if (this.config.alignStartButton == "top"){
button.style.bottom = "calc(100% - 20px)";
}else if (this.config.alignStartButton == "center"){
button.style.bottom = "calc(50% + 22.5px + "+border+"px)";
}
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);
}
setTimeout(() => {
this.callEvent("ready");
}, 20);
}
startButtonClicked(e) {
this.callEvent("start-clicked");
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");
if (typeof this.config.backgroundImg === "string") this.textElem.classList.add("ejs_loading_text_glow");
this.textElem.innerText = this.localization("Loading...");
this.elements.parent.appendChild(this.textElem);
}
2023-08-12 02:10:05 +00:00
localization(text, log) {
if (typeof text === "undefined") return;
text = text.toString();
2023-08-12 02:10:05 +00:00
if (text.includes("EmulatorJS v")) return text;
2023-07-03 16:37:43 +00:00
if (this.config.langJson) {
if (typeof log === "undefined") log = true;
2023-08-12 02:10:05 +00:00
if (!this.config.langJson[text] && log) {
if (!this.missingLang.includes(text)) this.missingLang.push(text);
2023-07-03 16:37:43 +00:00
console.log("Translation not found for '"+text+"'. Language set to '"+this.config.language+"'");
}
return this.config.langJson[text] || text;
}
return text;
}
2023-07-21 14:54:49 +00:00
checkCompression(data, msg, fileCbFunc) {
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) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network 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"});
})
}
2023-07-23 00:51:04 +00:00
const files = {};
let res;
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) {
if (typeof fileCbFunc === "function") {
fileCbFunc(data.data.file, data.data.data);
files[data.data.file] = true;
} else {
files[data.data.file] = data.data.data;
}
}
if (data.data.t === 1) {
res(files);
}
}
const decompress7z = (file) => {
return new Promise((resolve, reject) => {
2023-07-23 00:51:04 +00:00
res = resolve;
createWorker('compression/extract7z.js').then((worker) => {
worker.onmessage = onMessage;
worker.postMessage(file);
//console.log(file);
})
})
}
const decompressRar = (file) => {
return new Promise((resolve, reject) => {
2023-07-23 00:51:04 +00:00
res = resolve;
this.downloadFile("compression/libunrar.js", (res) => {
2023-07-23 00:51:04 +00:00
this.downloadFile("compression/libunrar.wasm", (res2) => {
2023-07-06 16:33:12 +00:00
if (res === -1 || res2 === -1) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-07-06 16:33:12 +00:00
this.textElem.style.color = "red";
return;
}
2023-07-23 00:51:04 +00:00
const path = URL.createObjectURL(new Blob([res2.data], {type: "application/wasm"}));
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) return;\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 ';
2023-07-06 16:33:12 +00:00
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);
2023-07-23 00:51:04 +00:00
}, null, false, {responseType: "arraybuffer", method: "GET"})
}, null, false, {responseType: "text", method: "GET"});
})
}
const decompressZip = (file) => {
return new Promise((resolve, reject) => {
2023-07-23 00:51:04 +00:00
res = resolve;
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-07-21 14:54:49 +00:00
if (typeof fileCbFunc === "function") {
fileCbFunc("!!notCompressedData", data);
return new Promise(resolve => resolve({"!!notCompressedData": true}));
} else {
return new Promise(resolve => resolve({"!!notCompressedData": data}));
}
}
}
downloadGameCore() {
this.textElem.innerText = this.localization("Download Game Core");
2023-08-26 15:49:05 +00:00
if (this.config.threads && ((typeof window.SharedArrayBuffer) !== "function")) {
this.textElem.innerText = this.localization('Error for site owner')+"\n"+this.localization("Check console");
this.textElem.style.color = "red";
console.warn("Threads is set to true, but the SharedArrayBuffer function is not exposed. Threads requires 2 headers to be set when sending you html page. See https://stackoverflow.com/a/68630724");
return;
}
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, thread, wasm;
for (let k in data) {
if (k.endsWith(".wasm")) {
wasm = data[k];
} else if (k.endsWith(".worker.js")) {
thread = data[k];
} else if (k.endsWith(".js")) {
js = data[k];
}
}
this.initGameCore(js, wasm, thread);
});
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;
}
2023-08-24 16:57:46 +00:00
let corePath = 'cores/'+this.getCore()+(this.config.threads ? "-thread" : "")+'-wasm.data';
this.downloadFile(corePath, (res) => {
2023-06-30 15:19:52 +00:00
if (res === -1) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-06-30 15:19:52 +00:00
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, thread) {
this.initModule(wasm, thread);
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) => {
2023-07-22 16:40:40 +00:00
if (typeof this.config.loadState !== "string" && !this.toData(this.config.loadState, true)) {
2023-07-11 16:30:27 +00:00
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) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-07-11 16:30:27 +00:00
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) => {
2023-07-22 16:40:40 +00:00
if ((typeof this.config.gamePatchUrl !== "string" || !this.config.gamePatchUrl.trim()) && !this.toData(this.config.gamePatchUrl, true)) {
2023-07-11 16:30:27 +00:00
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) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-07-11 16:30:27 +00:00
this.textElem.style.color = "red";
return;
}
2023-07-22 16:40:40 +00:00
if (this.toData(this.config.gamePatchUrl, true)) {
this.config.gamePatchUrl = "game";
}
2023-07-11 16:30:27 +00:00
gotData(res.data);
const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824;
2023-07-22 16:40:40 +00:00
if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gamePatchUrl !== "game") {
2023-07-11 16:30:27 +00:00
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) => {
2023-07-22 16:40:40 +00:00
if ((typeof this.config.gameParentUrl !== "string" || !this.config.gameParentUrl.trim()) && !this.toData(this.config.gameParentUrl, true)) {
2023-07-11 16:30:27 +00:00
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) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-07-11 16:30:27 +00:00
this.textElem.style.color = "red";
return;
}
2023-07-22 16:40:40 +00:00
if (this.toData(this.config.gameParentUrl, true)) {
this.config.gameParentUrl = "game";
}
2023-07-11 16:30:27 +00:00
gotData(res.data);
const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824;
2023-07-22 16:40:40 +00:00
if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameParentUrl !== "game") {
2023-07-11 16:30:27 +00:00
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) => {
2023-07-22 16:40:40 +00:00
if ((typeof this.config.biosUrl !== "string" || !this.config.biosUrl.trim()) && !this.toData(this.config.biosUrl, true)) {
2023-07-11 16:30:27 +00:00
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) => {
2023-07-18 15:13:12 +00:00
if (res === -1) {
this.textElem.innerText = this.localization('Network Error');
this.textElem.style.color = "red";
return;
}
2023-07-11 16:30:27 +00:00
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) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-07-11 16:30:27 +00:00
this.textElem.style.color = "red";
return;
}
2023-07-22 16:40:40 +00:00
if (this.toData(this.config.biosUrl, true)) {
this.config.biosUrl = "game";
}
2023-07-11 16:30:27 +00:00
gotBios(res.data);
2023-07-22 16:40:40 +00:00
if (this.saveInBrowserSupported() && this.config.biosUrl !== "game") {
2023-07-11 16:30:27 +00:00
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() {
2023-08-31 21:59:49 +00:00
const extractFileNameFromUrl = url => {
if (!url) return null;
return url.split('/').pop().split("#")[0].split("?")[0];
};
const supportsExt = (ext) => {
const core = this.getCore();
if (!this.extensions[core]) return false;
return this.extensions[core].includes(ext);
};
return new Promise(resolve => {
2023-07-11 16:30:27 +00:00
this.textElem.innerText = this.localization("Download Game Data");
2023-08-31 21:59:49 +00:00
2023-07-11 16:30:27 +00:00
const gotGameData = (data) => {
if (['arcade', 'mame2003'].includes(this.getCore(true))) {
2023-08-31 21:59:49 +00:00
this.fileName = extractFileNameFromUrl(this.config.gameUrl);
2023-07-11 16:30:27 +00:00
FS.writeFile(this.fileName, new Uint8Array(data));
resolve();
return;
}
2023-08-31 21:59:49 +00:00
const altName = this.config.gameUrl.startsWith("blob:") ? (this.config.gameName || "game") : extractFileNameFromUrl(this.config.gameUrl);
let disableCue = false;
if (['pcsx_rearmed', 'genesis_plus_gx', 'picodrive', 'mednafen_pce'].includes(this.getCore()) && this.config.disableCue === undefined) {
disableCue = true;
} else {
disableCue = this.config.disableCue;
}
2023-08-31 21:59:49 +00:00
let fileNames = [];
2023-07-21 14:54:49 +00:00
this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Data"), (fileName, fileData) => {
if (fileName.includes("/")) {
const paths = fileName.split("/");
let cp = "";
for (let i=0; i<paths.length-1; i++) {
if (paths[i] === "") continue;
2023-08-31 21:59:49 +00:00
cp += `/${paths[i]}`;
2023-07-21 14:54:49 +00:00
if (!FS.analyzePath(cp).exists) {
FS.mkdir(cp);
}
}
}
if (fileName.endsWith('/')) {
FS.mkdir(fileName);
return;
}
if (fileName === "!!notCompressedData") {
FS.writeFile(altName, fileData);
2023-08-31 21:59:49 +00:00
fileNames.push(altName);
2023-07-21 14:54:49 +00:00
} else {
2023-08-31 21:59:49 +00:00
FS.writeFile(`/${fileName}`, fileData);
fileNames.push(fileName);
2023-07-21 14:54:49 +00:00
}
}).then(() => {
2023-08-31 21:59:49 +00:00
let isoFile = null;
let supportedFile = null;
let cueFile = null;
let selectedCueExt = null;
fileNames.forEach(fileName => {
const ext = fileName.split('.').pop().toLowerCase();
if (supportedFile === null && supportsExt(ext)) {
2023-08-31 21:59:49 +00:00
supportedFile = fileName;
}
if (isoFile === null && ['iso', 'cso', 'chd', 'elf'].includes(ext)) {
2023-08-31 21:59:49 +00:00
isoFile = fileName;
}
if (['cue', 'ccd', 'toc', 'm3u'].includes(ext)) {
if (this.getCore(true) === 'psx') {
//always prefer m3u files for psx cores
if (selectedCueExt !== 'm3u') {
if (cueFile === null || ext === 'm3u') {
2023-08-31 21:59:49 +00:00
cueFile = fileName;
selectedCueExt = ext;
}
}
2023-07-11 16:30:27 +00:00
} else {
2023-08-31 21:59:49 +00:00
//prefer cue or ccd files over toc or m3u
if (!['cue', 'ccd'].includes(selectedCueExt)) {
if (cueFile === null || ['cue', 'ccd'].includes(ext)) {
2023-08-31 21:59:49 +00:00
cueFile = fileName;
selectedCueExt = ext;
}
}
2023-07-11 16:30:27 +00:00
}
}
2023-08-31 21:59:49 +00:00
});
if (supportedFile !== null) {
this.fileName = supportedFile;
} else {
this.fileName = fileNames[0];
}
2023-08-31 21:59:49 +00:00
if (isoFile !== null && (supportsExt('iso') || supportsExt('cso') || supportsExt('chd') || supportsExt('elf'))) {
this.fileName = isoFile;
} else if (supportsExt('cue') || supportsExt('ccd') || supportsExt('toc') || supportsExt('m3u')) {
2023-08-31 21:59:49 +00:00
if (cueFile !== null) {
this.fileName = cueFile;
} else if (!disableCue) {
2023-08-31 21:59:49 +00:00
this.fileName = this.gameManager.createCueFile(fileNames);
2023-07-06 16:33:12 +00:00
}
2023-06-30 15:19:52 +00:00
}
2023-07-11 16:30:27 +00:00
resolve();
});
}
2023-07-21 14:54:49 +00:00
2023-07-11 16:30:27 +00:00
this.downloadFile(this.config.gameUrl, (res) => {
2023-07-18 15:13:12 +00:00
if (res === -1) {
this.textElem.innerText = this.localization('Network Error');
this.textElem.style.color = "red";
return;
}
2023-07-22 16:40:40 +00:00
const name = (typeof this.config.gameUrl === "string") ? this.config.gameUrl.split('/').pop() : "game";
this.storage.rom.get(name).then((result) => {
if (result && result['content-length'] === res.headers['content-length'] && !this.debug && name !== "game") {
2023-07-11 16:30:27 +00:00
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) {
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization('Network Error');
2023-07-11 16:30:27 +00:00
this.textElem.style.color = "red";
return;
}
2023-07-22 16:40:40 +00:00
if (this.toData(this.config.gameUrl, true)) {
this.config.gameUrl = "game";
}
2023-07-11 16:30:27 +00:00
gotGameData(res.data);
const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824;
2023-07-22 16:40:40 +00:00
if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameUrl !== "game") {
2023-07-11 16:30:27 +00:00
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 () => {
this.gameManager = new window.EJS_GameManager(this.Module, this);
if (this.getCore() === "ppsspp") {
await this.gameManager.loadPpssppAssets();
}
2023-07-11 16:30:27 +00:00
await this.downloadRom();
await this.downloadBios();
await this.downloadStartState();
await this.downloadGameParent();
await this.downloadGamePatch();
this.startGame();
})();
}
initModule(wasmData, threadData) {
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) {
2023-07-18 16:10:21 +00:00
if (this.debug) console.log(fileName);
if (fileName.endsWith(".wasm")) {
return URL.createObjectURL(new Blob([wasmData], {type: "application/wasm"}));
} else if (fileName.endsWith(".worker.js")) {
return URL.createObjectURL(new Blob([threadData], {type: "application/javascript"}));
}
}
};
this.Module = window.Module;
}
startGame() {
2023-07-03 15:34:18 +00:00
try {
const args = [];
if (this.debug) args.push('-v');
args.push('/'+this.fileName);
2023-07-18 16:10:21 +00:00
if (this.debug) console.log(args);
2023-07-03 15:34:18 +00:00
this.Module.callMain(args);
this.Module.resumeMainLoop();
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;
if (this.touch) {
this.virtualGamepad.style.display = "";
}
2023-08-08 18:04:42 +00:00
this.handleResize();
if (this.config.fullscreenOnLoad) {
try {
this.toggleFullscreen(true);
} catch(e) {
if (this.debug) console.warn("Could not fullscreen on load");
}
}
2023-07-03 15:34:18 +00:00
} catch(e) {
console.warn("failed to start game", e);
2023-07-18 15:13:12 +00:00
this.textElem.innerText = this.localization("Failed to start game");
2023-07-03 15:34:18 +00:00
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-21 15:14:04 +00:00
if (typeof this.config.gameId !== "number" || !this.config.netplayUrl || this.netplayEnabled === 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";
2023-08-12 02:10:05 +00:00
a.innerText = this.localization(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-19 14:23:22 +00:00
const menu = this.createElement('div');
menu.classList.add("ejs_list_selector");
const parent = this.createElement("ul");
const addButton = (title, hidden, functi0n) => {
const li = this.createElement("li");
if (hidden) li.hidden = true;
const a = this.createElement("a");
if (functi0n instanceof Function) {
this.addEventListener(li, 'click', (e) => {
e.preventDefault();
functi0n();
});
}
a.href = "#";
a.onclick = "return false";
2023-08-12 02:10:05 +00:00
a.innerText = this.localization(title);
2023-07-19 14:23:22 +00:00
li.appendChild(a);
parent.appendChild(li);
hideMenu();
return li;
}
//body.style["padding-left"] = "20%";
const home = this.createElement("div");
const license = this.createElement("div");
license.style.display = "none";
const retroarch = this.createElement("div");
retroarch.style.display = "none";
body.appendChild(home);
body.appendChild(license);
body.appendChild(retroarch);
let current = home;
home.innerText = "EmulatorJS v"+this.ejs_version;
home.appendChild(this.createElement("br"));
home.appendChild(this.createElement("br"));
2023-07-03 17:56:04 +00:00
const gh = this.createElement("a");
gh.href = "https://github.com/EmulatorJS/EmulatorJS";
gh.target = "_blank";
2023-07-18 15:13:12 +00:00
gh.innerText = this.localization("View on GitHub");
2023-07-19 14:23:22 +00:00
home.appendChild(gh);
home.appendChild(this.createElement("br"));
2023-07-03 17:56:04 +00:00
const dc = this.createElement("a");
dc.href = "https://discord.gg/6akryGkETU";
dc.target = "_blank";
2023-07-18 15:13:12 +00:00
dc.innerText = this.localization("Join the discord");
2023-07-19 14:23:22 +00:00
home.appendChild(dc);
home.appendChild(this.createElement("br"));
menu.appendChild(parent);
body.appendChild(menu);
const setElem = (element) => {
if (current === element) return;
if (current) {
current.style.display = "none";
}
current = element;
element.style.display = "";
}
addButton("Home", false, () => {
setElem(home);
2023-07-03 17:56:04 +00:00
})
2023-07-19 14:23:22 +00:00
addButton("EmulatorJS License", false, () => {
setElem(license);
})
addButton("RetroArch License", false, () => {
setElem(retroarch);
})
//Todo - Core specific licenses, contributors.
retroarch.innerText = "This project is powered by ";
const a = this.createElement("a");
a.href = "https://github.com/libretro/RetroArch";
a.target = "_blank";
a.innerText = "RetroArch";
retroarch.appendChild(a);
const licenseLink = this.createElement("a");
licenseLink.target = "_blank";
licenseLink.href = "https://github.com/libretro/RetroArch/blob/master/COPYING";
licenseLink.innerText = "View the RetroArch license here.";
a.appendChild(this.createElement("br"));
a.appendChild(licenseLink);
license.style['text-align'] = "center";
2023-07-11 13:40:42 +00:00
license.style['padding'] = "10px";
2023-07-19 14:23:22 +00:00
//license.style["white-space"] = "pre-wrap";
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);
2023-08-26 22:24:11 +00:00
if (title == "Enter Fullscreen" || title == "Exit Fullscreen") text.classList.add("ejs_menu_text_right");
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>', () => {
this.toggleFullscreen(true);
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>', () => {
this.toggleFullscreen(false);
2023-06-23 16:33:20 +00:00
});
exit.style.display = "none";
this.toggleFullscreen = (fullscreen) => {
if (fullscreen) {
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";
if (this.isMobile) {
try {
screen.orientation.lock(this.getCore(true) === "nds" ? "portrait" : "landscape").catch(e => {});;
} catch(e) {}
}
} else {
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 = "";
if (this.isMobile) {
try {
screen.orientation.unlock();
} catch(e) {}
}
}
}
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-18 16:10:21 +00:00
if (this.debug) console.log(this.config.buttonOpts);
2023-07-17 18:33:42 +00:00
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
}
getControlScheme() {
2023-08-05 19:43:46 +00:00
if (this.config.controlScheme && typeof this.config.controlScheme === 'string') {
return this.config.controlScheme;
} else {
return this.getCore(true);
}
}
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', 'gb'].includes(this.getControlScheme())) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('snes' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 9, label: this.localization('X')},
{id: 1, label: this.localization('Y')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
];
} else if ('n64' === this.getControlScheme()) {
buttons = [
{id: 0, label: this.localization('A')},
{id: 1, label: this.localization('B')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('D-PAD UP')},
{id: 5, label: this.localization('D-PAD DOWN')},
{id: 6, label: this.localization('D-PAD LEFT')},
{id: 7, label: this.localization('D-PAD RIGHT')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
{id: 12, label: this.localization('Z')},
{id: 19, label: this.localization('STICK UP')},
{id: 18, label: this.localization('STICK DOWN')},
{id: 17, label: this.localization('STICK LEFT')},
{id: 16, label: this.localization('STICK RIGHT')},
{id: 23, label: this.localization('C-PAD UP')},
{id: 22, label: this.localization('C-PAD DOWN')},
{id: 21, label: this.localization('C-PAD LEFT')},
{id: 20, label: this.localization('C-PAD RIGHT')},
];
} else if ('gba' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('nds' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 9, label: this.localization('X')},
{id: 1, label: this.localization('Y')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
{id: 14, label: this.localization('Microphone')},
];
} else if ('vb' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('LEFT D-PAD UP')},
{id: 5, label: this.localization('LEFT D-PAD DOWN')},
{id: 6, label: this.localization('LEFT D-PAD LEFT')},
{id: 7, label: this.localization('LEFT D-PAD RIGHT')},
{id: 19, label: this.localization('RIGHT D-PAD UP')},
{id: 18, label: this.localization('RIGHT D-PAD DOWN')},
{id: 17, label: this.localization('RIGHT D-PAD LEFT')},
{id: 16, label: this.localization('RIGHT D-PAD RIGHT')},
];
} else if (['segaMD', 'segaCD', 'sega32x'].includes(this.getControlScheme())) {
buttons = [
{id: 1, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 8, label: this.localization('C')},
{id: 10, label: this.localization('X')},
{id: 9, label: this.localization('Y')},
{id: 11, label: this.localization('Z')},
{id: 3, label: this.localization('START')},
{id: 2, label: this.localization('MODE')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('segaMS' === this.getControlScheme()) {
buttons = [
{id: 0, label: this.localization('BUTTON 1 / START')},
{id: 8, label: this.localization('BUTTON 2')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('segaGG' === this.getControlScheme()) {
buttons = [
{id: 0, label: this.localization('BUTTON 1')},
{id: 8, label: this.localization('BUTTON 2')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('segaSaturn' === this.getControlScheme()) {
buttons = [
{id: 1, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 8, label: this.localization('C')},
{id: 9, label: this.localization('X')},
{id: 10, label: this.localization('Y')},
{id: 11, label: this.localization('Z')},
{id: 12, label: this.localization('L')},
{id: 13, label: this.localization('R')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('3do' === this.getControlScheme()) {
buttons = [
{id: 1, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 8, label: this.localization('C')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
{id: 2, label: this.localization('X')},
{id: 3, label: this.localization('P')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('atari2600' === this.getControlScheme()) {
buttons = [
{id: 0, label: this.localization('FIRE')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('RESET')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
{id: 10, label: this.localization('LEFT DIFFICULTY A')},
{id: 12, label: this.localization('LEFT DIFFICULTY B')},
{id: 11, label: this.localization('RIGHT DIFFICULTY A')},
{id: 13, label: this.localization('RIGHT DIFFICULTY B')},
{id: 14, label: this.localization('COLOR')},
{id: 15, label: this.localization('B/W')},
];
} else if ('atari7800' === this.getControlScheme()) {
buttons = [
{id: 0, label: this.localization('BUTTON 1')},
{id: 8, label: this.localization('BUTTON 2')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('PAUSE')},
{id: 9, label: this.localization('RESET')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
{id: 10, label: this.localization('LEFT DIFFICULTY')},
{id: 11, label: this.localization('RIGHT DIFFICULTY')},
];
} else if ('lynx' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 10, label: this.localization('OPTION 1')},
{id: 11, label: this.localization('OPTION 2')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('jaguar' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 1, label: this.localization('C')},
{id: 2, label: this.localization('PAUSE')},
{id: 3, label: this.localization('OPTION')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
} else if ('pce' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('I')},
{id: 0, label: this.localization('II')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('RUN')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
2023-08-30 20:16:17 +00:00
} else if ('ngp' === this.getControlScheme()) {
buttons = [
{id: 0, label: this.localization('A')},
{id: 8, label: this.localization('B')},
{id: 3, label: this.localization('OPTION')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
2023-08-31 14:30:20 +00:00
} else if ('ws' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('A')},
{id: 0, label: this.localization('B')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('X UP')},
{id: 5, label: this.localization('X DOWN')},
{id: 6, label: this.localization('X LEFT')},
{id: 7, label: this.localization('X RIGHT')},
{id: 13, label: this.localization('Y UP')},
{id: 12, label: this.localization('Y DOWN')},
{id: 10, label: this.localization('Y LEFT')},
{id: 11, label: this.localization('Y RIGHT')},
];
} else if ('coleco' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('LEFT BUTTON')},
{id: 0, label: this.localization('RIGHT BUTTON')},
{id: 9, label: this.localization('1')},
{id: 1, label: this.localization('2')},
{id: 11, label: this.localization('3')},
{id: 10, label: this.localization('4')},
{id: 13, label: this.localization('5')},
{id: 12, label: this.localization('6')},
{id: 15, label: this.localization('7')},
{id: 14, label: this.localization('8')},
{id: 2, label: this.localization('*')},
{id: 3, label: this.localization('#')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
2023-08-31 15:51:41 +00:00
} else if ('pcfx' === this.getControlScheme()) {
buttons = [
{id: 8, label: this.localization('I')},
{id: 0, label: this.localization('II')},
{id: 9, label: this.localization('III')},
{id: 1, label: this.localization('IV')},
{id: 10, label: this.localization('V')},
{id: 11, label: this.localization('VI')},
{id: 3, label: this.localization('RUN')},
{id: 2, label: this.localization('SELECT')},
{id: 12, label: this.localization('MODE1')},
{id: 13, label: this.localization('MODE2')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
];
2023-07-05 00:41:37 +00:00
} else {
buttons = [
{id: 0, label: this.localization('B')},
{id: 1, label: this.localization('Y')},
{id: 2, label: this.localization('SELECT')},
{id: 3, label: this.localization('START')},
{id: 4, label: this.localization('UP')},
{id: 5, label: this.localization('DOWN')},
{id: 6, label: this.localization('LEFT')},
{id: 7, label: this.localization('RIGHT')},
{id: 8, label: this.localization('A')},
{id: 9, label: this.localization('X')},
{id: 10, label: this.localization('L')},
{id: 11, label: this.localization('R')},
{id: 12, label: this.localization('L2')},
{id: 13, label: this.localization('R2')},
{id: 14, label: this.localization('L3')},
{id: 15, label: this.localization('R3')},
{id: 19, label: this.localization('L STICK UP')},
{id: 18, label: this.localization('L STICK DOWN')},
{id: 17, label: this.localization('L STICK LEFT')},
{id: 16, label: this.localization('L STICK RIGHT')},
{id: 23, label: this.localization('R STICK UP')},
{id: 22, label: this.localization('R STICK DOWN')},
{id: 21, label: this.localization('R STICK LEFT')},
{id: 20, label: this.localization('R STICK RIGHT')},
];
2023-07-05 00:41:37 +00:00
}
if (['arcade', 'mame'].includes(this.getControlScheme())) {
for (const buttonIdx in buttons) {
if (buttons[buttonIdx].id === 2) {
buttons[buttonIdx].label = this.localization('INSERT COIN');
}
}
2023-06-22 19:08:34 +00:00
}
2023-08-10 21:40:25 +00:00
buttons.push(
{id: 24, label: this.localization('QUICK SAVE STATE')},
{id: 25, label: this.localization('QUICK LOAD STATE')},
{id: 26, label: this.localization('CHANGE STATE SLOT')},
{id: 27, label: this.localization('FAST FORWARD')},
2023-08-11 16:20:11 +00:00
{id: 29, label: this.localization('SLOW MOTION')},
{id: 28, label: this.localization('REWIND')}
2023-08-10 21:40:25 +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;";
2023-07-18 15:13:12 +00:00
gamepadTitle.innerText = this.localization("Connected Gamepad")+": ";
2023-06-23 16:33:20 +00:00
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;";
2023-07-18 15:13:12 +00:00
gamepad.innerText = this.localization("Gamepad");
2023-06-23 16:33:20 +00:00
aboutParent.appendChild(gamepad);
const keyboard = this.createElement("div");
keyboard.style = "text-align:center;width:50%;float:left;";
2023-07-18 15:13:12 +00:00
keyboard.innerText = this.localization("Keyboard");
2023-06-23 16:33:20 +00:00
aboutParent.appendChild(keyboard);
const headingPadding = this.createElement("div");
headingPadding.style = "clear:both;";
playerTitle.appendChild(gamepadTitle);
playerTitle.appendChild(leftPadding);
playerTitle.appendChild(aboutParent);
if ((this.touch || navigator.maxTouchPoints > 0) && i === 0) {
const vgp = this.createElement("div");
vgp.style = "width:25%;float:right;clear:none;padding:0;font-size: 11px;padding-left: 2.25rem;";
2023-08-26 23:57:23 +00:00
vgp.classList.add("ejs_control_row");
vgp.classList.add("ejs_cheat_row");
const input = this.createElement("input");
input.type = "checkbox";
input.checked = true;
input.value = "o";
input.id = "ejs_vp";
vgp.appendChild(input);
const label = this.createElement("label");
label.for = "ejs_vp";
label.innerText = "Virtual Gamepad";
vgp.appendChild(label);
label.addEventListener("click", (e) => {
input.checked = !input.checked;
this.changeSettingOption('virtual-gamepad', input.checked ? 'enabled' : "disabled");
})
this.on("start", (e) => {
if (this.settings["virtual-gamepad"] === "disabled") {
input.checked = false;
}
})
playerTitle.appendChild(vgp);
}
2023-06-23 16:33:20 +00:00
playerTitle.appendChild(headingPadding);
player.appendChild(playerTitle);
for (const buttonIdx in buttons) {
const k = buttons[buttonIdx].id;
const controlLabel = buttons[buttonIdx].label;
2023-06-23 16:33:20 +00:00
const buttonText = this.createElement("div");
buttonText.setAttribute("data-id", k);
buttonText.setAttribute("data-index", i);
buttonText.setAttribute("data-label", controlLabel);
2023-06-23 16:33:20 +00:00
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 = controlLabel+":";
2023-06-23 16:33:20 +00:00
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) {
let value = this.controls[i][k].value.toString();
if (value === " ") value = "space";
value = this.localization(value);
textBox2.value = value;
2023-06-29 16:34:34 +00:00
}
if (this.controls[i][k] && this.controls[i][k].value2 !== undefined && this.controls[i][k].value2 !== "") {
let value2 = this.controls[i][k].value2.toString();
2023-08-26 02:36:37 +00:00
if (value2.includes(":")) {
value2 = value2.split(":");
value2 = this.localization(value2[0]) + ":" + this.localization(value2[1])
} else if (!isNaN(value2)){
value2 = this.localization("BUTTON")+" "+this.localization(value2);
2023-08-26 02:36:37 +00:00
} else {
value2 = this.localization(value2);
}
textBox1.value = value2;
2023-06-29 16:34:34 +00:00
}
})
2023-06-23 16:33:20 +00:00
if (this.controls[i][k] && this.controls[i][k].value) {
let value = this.controls[i][k].value.toString();
if (value === " ") value = "space";
value = this.localization(value);
textBox2.value = value;
2023-06-23 16:33:20 +00:00
}
if (this.controls[i][k] && this.controls[i][k].value2) {
let value2 = this.controls[i][k].value2.toString();
2023-08-26 02:36:37 +00:00
if (value2.includes(":")) {
value2 = value2.split(":");
value2 = this.localization(value2[0]) + ":" + this.localization(value2[1])
} else if (!isNaN(value2)){
value2 = this.localization("BUTTON")+" "+this.localization(value2);
2023-08-26 02:36:37 +00:00
} else {
value2 = this.localization(value2);
}
textBox1.value = 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 = "[ " + controlLabel + " ]\n"+this.localization("Press 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',
'value2': 'BUTTON_2',
2023-06-23 16:33:20 +00:00
},
1: {
'value': 's',
'value2': 'BUTTON_4',
2023-06-23 16:33:20 +00:00
},
2: {
'value': 'v',
'value2': 'SELECT',
2023-06-23 16:33:20 +00:00
},
3: {
'value': 'enter',
'value2': 'START',
2023-06-23 16:33:20 +00:00
},
4: {
'value': 'arrowup',
'value2': 'DPAD_UP',
2023-06-23 16:33:20 +00:00
},
5: {
'value': 'arrowdown',
'value2': 'DPAD_DOWN',
2023-06-23 16:33:20 +00:00
},
6: {
'value': 'arrowleft',
'value2': 'DPAD_LEFT',
2023-06-23 16:33:20 +00:00
},
7: {
'value': 'arrowright',
'value2': 'DPAD_RIGHT',
2023-06-23 16:33:20 +00:00
},
8: {
'value': 'z',
'value2': 'BUTTON_1',
2023-06-23 16:33:20 +00:00
},
9: {
'value': 'a',
'value2': 'BUTTON_3',
2023-06-23 16:33:20 +00:00
},
10: {
'value': 'q',
2023-09-02 10:42:00 +00:00
'value2': 'LEFT_TOP_SHOULDER',
2023-06-23 16:33:20 +00:00
},
11: {
'value': 'e',
2023-09-02 10:42:00 +00:00
'value2': 'RIGHT_TOP_SHOULDER',
2023-06-23 16:33:20 +00:00
},
12: {
'value': 'e',
2023-09-02 10:42:00 +00:00
'value2': 'LEFT_BOTTOM_SHOULDER',
2023-06-23 16:33:20 +00:00
},
13: {
'value': 'w',
2023-09-02 10:42:00 +00:00
'value2': 'RIGHT_BOTTOM_SHOULDER',
},
14: {
'value2': 'LEFT_STICK',
},
15: {
'value2': 'RIGHT_STICK',
2023-06-23 16:33:20 +00:00
},
16: {
'value': 'h',
'value2': 'LEFT_STICK_X:+1',
2023-06-23 16:33:20 +00:00
},
17: {
'value': 'f',
'value2': 'LEFT_STICK_X:-1',
2023-06-23 16:33:20 +00:00
},
18: {
'value': 'g',
'value2': 'LEFT_STICK_Y:+1',
2023-06-23 16:33:20 +00:00
},
19: {
'value': 't',
'value2': 'LEFT_STICK_Y:-1',
},
20: {
'value': 'l',
'value2': 'RIGHT_STICK_X:+1',
},
21: {
'value': 'j',
'value2': 'RIGHT_STICK_X:-1',
},
22: {
'value': 'k',
'value2': 'RIGHT_STICK_Y:+1',
},
23: {
'value': 'i',
'value2': 'RIGHT_STICK_Y:-1',
2023-06-23 16:33:20 +00:00
},
24: {},
25: {},
2023-08-10 21:40:25 +00:00
26: {},
27: {},
28: {},
2023-08-11 16:20:11 +00:00
29: {},
2023-06-23 16:33:20 +00:00
},
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-08-11 16:20:11 +00:00
for (let j=0; j<30; 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 = parseInt(this.controlPopup.getAttribute("player-num"));
if (e.gamepadIndex !== player) return;
2023-06-30 01:02:55 +00:00
if (!this.controls[player][num]) {
this.controls[player][num] = {};
}
this.controls[player][num].value2 = e.label;
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-08-16 23:44:21 +00:00
if (e.gamepadIndex !== i) continue;
2023-08-11 16:20:11 +00:00
for (let j=0; j<30; j++) {
if (!this.controls[i][j] || this.controls[i][j].value2 === undefined) {
continue;
}
const controlValue = this.controls[i][j].value2;
if (['buttonup', 'buttondown'].includes(e.type) && (controlValue === e.label || controlValue === e.index)) {
2023-07-28 14:16:50 +00:00
this.gameManager.simulateInput(i, j, (e.type === 'buttonup' ? 0 : (special.includes(j) ? 0x7fff : 1)));
2023-06-30 01:02:55 +00:00
} else if (e.type === "axischanged") {
if (typeof controlValue === 'string' && controlValue.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) {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 16, 0x7fff * e.value);
this.gameManager.simulateInput(i, 17, 0);
2023-06-30 01:02:55 +00:00
} else {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 17, -0x7fff * e.value);
this.gameManager.simulateInput(i, 16, 0);
2023-06-30 01:02:55 +00:00
}
} else if (e.axis === 'LEFT_STICK_Y') {
if (e.value > 0) {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 18, 0x7fff * e.value);
this.gameManager.simulateInput(i, 19, 0);
2023-06-30 01:02:55 +00:00
} else {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 19, -0x7fff * e.value);
this.gameManager.simulateInput(i, 18, 0);
2023-06-30 01:02:55 +00:00
}
} else if (e.axis === 'RIGHT_STICK_X') {
if (e.value > 0) {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 20, 0x7fff * e.value);
this.gameManager.simulateInput(i, 21, 0);
2023-06-30 01:02:55 +00:00
} else {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 21, -0x7fff * e.value);
this.gameManager.simulateInput(i, 20, 0);
2023-06-30 01:02:55 +00:00
}
} else if (e.axis === 'RIGHT_STICK_Y') {
if (e.value > 0) {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 22, 0x7fff * e.value);
this.gameManager.simulateInput(i, 23, 0);
2023-06-30 01:02:55 +00:00
} else {
2023-08-16 23:44:21 +00:00
this.gameManager.simulateInput(i, 23, 0x7fff * e.value);
this.gameManager.simulateInput(i, 22, 0);
2023-06-30 01:02:55 +00:00
}
}
} else if (value === 0 || controlValue === e.label || controlValue === `${e.axis}:${value}`) {
2023-06-30 01:02:55 +00:00
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);
const speedControlButtons = [
{"type":"button","text":"Fast","id":"speed-fast","location":"center","left":-35,"top":50,"fontSize":15,"block":true,"input_value":27},
{"type":"button","text":"Slow","id":"speed-slow","location":"center","left":95,"top":50,"fontSize":15,"block":true,"input_value":29},
];
if (this.rewindEnabled) {
2023-08-12 15:44:40 +00:00
speedControlButtons.push({"type":"button","text":"Rewind","id":"speed-rewind","location":"center","left":30,"top":50,"fontSize":15,"block":true,"input_value":28});
}
let info;
if (this.config.VirtualGamepadSettings && function(set) {
if (!Array.isArray(set)) {
console.warn("Virtual 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;
} else if ("gba" === this.getControlScheme()) {
info = [
{"type":"button","text":"B","id":"b","location":"right","left":10,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"A","id":"a","location":"right","left":81,"top":40,"bold":true,"input_value":8},
{"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":-90,"bold":true,"block":true,"input_value":10},
{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-90,"bold":true,"block":true,"input_value":11}
];
info.push(...speedControlButtons);
} else if ("gb" === this.getControlScheme()) {
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}
];
info.push(...speedControlButtons);
} else if ('nes' === this.getControlScheme()) {
info = [
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"A","id":"a","location":"right","right":5,"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}
];
info.push(...speedControlButtons);
} else if ('n64' === this.getControlScheme()) {
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}
];
info.push(...speedControlButtons);
} else if ("nds" === this.getControlScheme()) {
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}
];
info.push(...speedControlButtons);
} else if ("snes" === this.getControlScheme()) {
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}
];
info.push(...speedControlButtons);
} else if (['segaMD', 'segaCD', 'sega32x'].includes(this.getControlScheme())) {
info = [
{"type":"button","text":"A","id":"a","location":"right","right":145,"top":70,"bold":true,"input_value":9},
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"C","id":"c","location":"right","right":5,"top":70,"bold":true,"input_value":8},
{"type":"button","text":"X","id":"x","location":"right","right":145,"top":0,"bold":true,"input_value":10},
{"type":"button","text":"Y","id":"y","location":"right","right":75,"top":0,"bold":true,"input_value":9},
{"type":"button","text":"Z","id":"z","location":"right","right":5,"top":0,"bold":true,"input_value":11},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"Mode","id":"mode","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2},
{"type":"button","text":"Start","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3}
];
info.push(...speedControlButtons);
} else if ("segaMS" === this.getControlScheme()) {
info = [
{"type":"button","text":"1","id":"button1","location":"right","left":10,"top":40,"bold":true,"input_value":0},
{"type":"button","text":"2","id":"button2","location":"right","left":81,"top":40,"bold":true,"input_value":8},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]}
];
info.push(...speedControlButtons);
} else if ("segaGG" === this.getControlScheme()) {
info = [
{"type":"button","text":"1","id":"button1","location":"right","left":10,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"2","id":"button2","location":"right","left":81,"top":40,"bold":true,"input_value":8},
{"type":"dpad","location":"left","left":"50%","top":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"Start","id":"start","location":"center","left":30,"fontSize":15,"block":true,"input_value":3}
];
info.push(...speedControlButtons);
} else if ("segaSaturn" === this.getControlScheme()) {
info = [
{"type":"button","text":"A","id":"a","location":"right","right":145,"top":70,"bold":true,"input_value":1},
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"C","id":"c","location":"right","right":5,"top":70,"bold":true,"input_value":8},
{"type":"button","text":"X","id":"x","location":"right","right":145,"top":0,"bold":true,"input_value":9},
{"type":"button","text":"Y","id":"y","location":"right","right":75,"top":0,"bold":true,"input_value":10},
{"type":"button","text":"Z","id":"z","location":"right","right":5,"top":0,"bold":true,"input_value":11},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"L","id":"l","location":"left","left":3,"top":-90,"bold":true,"block":true,"input_value":12},
{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-90,"bold":true,"block":true,"input_value":13},
{"type":"button","text":"Start","id":"start","location":"center","left":30,"fontSize":15,"block":true,"input_value":3}
];
info.push(...speedControlButtons);
} else if ("atari2600" === this.getControlScheme()) {
info = [
{"type":"button","text":"","id":"button1","location":"right","right":10,"top":70,"bold":true,"input_value":0},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"Reset","id":"reset","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.push(...speedControlButtons);
} else if ("atari7800" === this.getControlScheme()) {
info = [
{"type":"button","text":"1","id":"button1","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"2","id":"button2","location":"right","right":5,"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":"Reset","id":"reset","location":"center","left":-35,"fontSize":15,"block":true,"input_value":9},
{"type":"button","text":"Pause","id":"pause","location":"center","left":95,"fontSize":15,"block":true,"input_value":3},
{"type":"button","text":"Select","id":"select","location":"center","left":30,"fontSize":15,"block":true,"input_value":2},
];
info.push(...speedControlButtons);
} else if ("lynx" === this.getControlScheme()) {
info = [
{"type":"button","text":"B","id":"button1","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"A","id":"button2","location":"right","right":5,"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":"Opt 1","id":"option1","location":"center","left":-35,"fontSize":15,"block":true,"input_value":10},
{"type":"button","text":"Opt 2","id":"option2","location":"center","left":95,"fontSize":15,"block":true,"input_value":11},
{"type":"button","text":"Start","id":"start","location":"center","left":30,"fontSize":15,"block":true,"input_value":3}
];
info.push(...speedControlButtons);
} else if ("jaguar" === this.getControlScheme()) {
info = [
{"type":"button","text":"A","id":"a","location":"right","right":145,"top":70,"bold":true,"input_value":8},
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"C","id":"c","location":"right","right":5,"top":70,"bold":true,"input_value":1},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"Option","id":"start","location":"center","left":60,"fontSize":15,"block":true,"input_value":3},
{"type":"button","text":"Pause","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2}
];
info.push(...speedControlButtons);
} else if ("vb" === this.getControlScheme()) {
info = [
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":150,"bold":true,"input_value":0},
{"type":"button","text":"A","id":"a","location":"right","right":5,"top":150,"bold":true,"input_value":8},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"dpad","location":"right","left":"50%","right":"50%","joystickInput":false,"inputValues":[19,18,17,16]},
{"type":"button","text":"L","id":"l","location":"left","left":3,"top":-90,"bold":true,"block":true,"input_value":10},
{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-90,"bold":true,"block":true,"input_value":11},
{"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.push(...speedControlButtons);
} else if ("3do" === this.getControlScheme()) {
info = [
{"type":"button","text":"A","id":"a","location":"right","right":145,"top":70,"bold":true,"input_value":1},
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"C","id":"c","location":"right","right":5,"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":"L","id":"l","location":"left","left":3,"top":-90,"bold":true,"block":true,"input_value":10},
{"type":"button","text":"R","id":"r","location":"right","right":3,"top":-90,"bold":true,"block":true,"input_value":11},
{"type":"button","text":"X","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"bold":true,"input_value":2},
{"type":"button","text":"P","id":"start","location":"center","left":60,"fontSize":15,"block":true,"bold":true,"input_value":3}
];
info.push(...speedControlButtons);
} else if ("pce" === this.getControlScheme()) {
info = [
{"type":"button","text":"II","id":"ii","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"I","id":"i","location":"right","right":5,"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":"Run","id":"run","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.push(...speedControlButtons);
2023-08-30 20:16:17 +00:00
} else if ('ngp' === this.getControlScheme()) {
info = [
{"type":"button","text":"A","id":"a","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"B","id":"b","location":"right","right":5,"top":50,"bold":true,"input_value":8},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"Option","id":"option","location":"center","left":30,"fontSize":15,"block":true,"input_value":3}
];
info.push(...speedControlButtons);
2023-08-31 14:30:20 +00:00
} else if ('ws' === this.getControlScheme()) {
info = [
{"type":"button","text":"B","id":"b","location":"right","right":75,"top":150,"bold":true,"input_value":0},
{"type":"button","text":"A","id":"a","location":"right","right":5,"top":150,"bold":true,"input_value":8},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"dpad","location":"right","left":"50%","right":"50%","joystickInput":false,"inputValues":[13,12,10,11]},
{"type":"button","text":"Start","id":"start","location":"center","left":30,"fontSize":15,"block":true,"input_value":3},
];
info.push(...speedControlButtons);
} else if ('coleco' === this.getControlScheme()) {
info = [
{"type":"button","text":"L","id":"buttonLeft","location":"right","left":10,"top":40,"bold":true,"input_value":8},
{"type":"button","text":"R","id":"buttonRight","location":"right","left":81,"top":40,"bold":true,"input_value":0},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]}
];
info.push(...speedControlButtons);
2023-08-31 15:51:41 +00:00
} else if ('pcfx' === this.getControlScheme()) {
info = [
{"type":"button","text":"I","id":"i","location":"right","right":5,"top":70,"bold":true,"input_value":8},
{"type":"button","text":"II","id":"ii","location":"right","right":75,"top":70,"bold":true,"input_value":0},
{"type":"button","text":"III","id":"iii","location":"right","right":145,"top":70,"bold":true,"input_value":9},
{"type":"button","text":"IV","id":"iv","location":"right","right":5,"top":0,"bold":true,"input_value":1},
{"type":"button","text":"V","id":"v","location":"right","right":75,"top":0,"bold":true,"input_value":10},
{"type":"button","text":"VI","id":"vi","location":"right","right":145,"top":0,"bold":true,"input_value":11},
{"type":"dpad","location":"left","left":"50%","right":"50%","joystickInput":false,"inputValues":[4,5,6,7]},
{"type":"button","text":"Select","id":"select","location":"center","left":-5,"fontSize":15,"block":true,"input_value":2},
{"type":"button","text":"Run","id":"run","location":"center","left":60,"fontSize":15,"block":true,"input_value":3}
];
info.push(...speedControlButtons);
} 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.push(...speedControlButtons);
}
for (let i=0; i<info.length; i++) {
if (info[i].text) {
info[i].text = this.localization(info[i].text);
}
}
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-07-21 16:13:47 +00:00
if (this.virtualGamepad) {
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
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-21 16:13:47 +00:00
if (!this.handleSettingsResize) return;
2023-07-11 12:28:34 +00:00
this.handleSettingsResize();
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));
}
loadRewindEnabled() {
if (!window.localStorage) return;
let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings");
try {
coreSpecific = JSON.parse(coreSpecific);
if (!coreSpecific || !coreSpecific.settings) {
return false;
}
return coreSpecific.settings.rewindEnabled === 'enabled';
} catch (e) {
console.warn("Could not load previous settings", e);
return false;
}
}
2023-07-03 15:34:18 +00:00
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-18 16:10:21 +00:00
if (this.debug) 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-08-07 16:31:20 +00:00
} else if (option === "ff-ratio") {
if (this.isFastForward) this.gameManager.toggleFastForward(0);
2023-08-07 16:31:20 +00:00
if (value === "unlimited") {
this.gameManager.setFastForwardRatio(0);
} else if (!isNaN(value)) {
this.gameManager.setFastForwardRatio(parseFloat(value));
}
setTimeout(() => {
if (this.isFastForward) this.gameManager.toggleFastForward(1);
}, 10)
2023-08-08 14:04:13 +00:00
} else if (option === "fastForward") {
if (value === "enabled") {
this.isFastForward = true;
this.gameManager.toggleFastForward(1);
} else if (value === "disabled") {
this.isFastForward = false;
this.gameManager.toggleFastForward(0);
}
2023-08-11 16:20:11 +00:00
} else if (option === 'sm-ratio') {
if (this.isSlowMotion) this.gameManager.toggleSlowMotion(0);
this.gameManager.setSlowMotionRatio(parseFloat(value));
setTimeout(() => {
if (this.isSlowMotion) this.gameManager.toggleSlowMotion(1);
}, 10);
} else if (option === 'slowMotion') {
if (value === "enabled") {
this.isSlowMotion = true;
this.gameManager.toggleSlowMotion(1);
} else if (value === "disabled") {
this.isSlowMotion = false;
this.gameManager.toggleSlowMotion(0);
}
2023-08-10 21:40:25 +00:00
} else if (option === "rewind-granularity") {
if (this.rewindEnabled) {
this.gameManager.setRewindGranularity(parseInt(value));
}
2023-06-29 16:49:48 +00:00
}
2023-06-28 18:27:06 +00:00
this.gameManager.setVariable(option, value);
this.saveSettings();
2023-06-28 18:27:06 +00:00
}
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-08-08 18:04:42 +00:00
let needChange = false;
if (this.settingsMenu.style.display !== "") {
this.settingsMenu.style.opacity = "0";
this.settingsMenu.style.display = "";
needChange = true;
}
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-08-12 16:05:50 +00:00
this.settingsMenu.classList.toggle("ejs_settings_center_left", (x < width/2) && (width < 575));
this.settingsMenu.classList.toggle("ejs_settings_center_right", (x >= width/2) && (width < 575));
2023-08-08 18:04:42 +00:00
if (needChange) {
this.settingsMenu.style.display = "none";
this.settingsMenu.style.opacity = "";
}
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');
2023-08-07 16:31:20 +00:00
addToMenu(this.localization('Fast Forward Ratio'), 'ff-ratio', [
"1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0", "unlimited"
], "3.0");
2023-08-10 21:40:25 +00:00
2023-08-11 16:20:11 +00:00
addToMenu(this.localization('Slow Motion Ratio'), 'sm-ratio', [
"1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0"
], "3.0");
2023-08-08 14:04:13 +00:00
addToMenu(this.localization('Fast Forward'), 'fastForward', {
2023-08-11 16:20:11 +00:00
'enabled': this.localization("Enabled"),
'disabled': this.localization("Disabled")
}, "disabled");
addToMenu(this.localization('Slow Motion'), 'slowMotion', {
'enabled': this.localization("Enabled"),
'disabled': this.localization("Disabled")
2023-08-08 14:04:13 +00:00
}, "disabled");
addToMenu(this.localization('Rewind Enabled (requires restart)'), 'rewindEnabled', {
'enabled': this.localization("Enabled"),
'disabled': this.localization("Disabled")
}, 'disabled');
addToMenu(this.localization('Rewind Granularity'), 'rewind-granularity', [
'1', '3', '6', '12', '25', '50', '100'
], '6');
2023-07-03 17:03:00 +00:00
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');
}
let coreOpts;
try {
coreOpts = this.gameManager.getCoreOptions();
} catch(e){}
if (coreOpts) {
coreOpts.split('\n').forEach((line, index) => {
2023-07-05 00:32:00 +00:00
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], this.settingsLanguage);
2023-07-05 00:32:00 +00:00
}
addToMenu(this.localization(optionName, this.settingsLanguage),
2023-07-05 00:32:00 +00:00
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");
2023-07-18 15:13:12 +00:00
title.innerText = this.localization("Rooms");
2023-07-15 16:51:10 +00:00
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 = "";
2023-07-22 16:44:40 +00:00
if (!this.netplay || (this.netplay && !this.netplay.name)) {
2023-07-15 16:51:10 +00:00
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");
2023-07-18 15:13:12 +00:00
head.innerText = this.localization("Player Name");
2023-07-15 16:51:10 +00:00
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)";
2023-07-18 15:13:12 +00:00
submit.innerText = this.localization("Submit");
2023-07-15 16:51:10 +00:00
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)";
2023-07-18 15:13:12 +00:00
join.innerText = this.localization("Join");
2023-07-15 16:51:10 +00:00
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");
2023-07-18 15:13:12 +00:00
rnhead.innerText = this.localization("Room Name");
2023-07-16 22:49:33 +00:00
const rninput = this.createElement("input");
rninput.type = "text";
rninput.setAttribute("maxlength", 20);
const maxhead = this.createElement("strong");
2023-07-18 15:13:12 +00:00
maxhead.innerText = this.localization("Max Players");
2023-07-16 22:49:33 +00:00
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");
2023-07-18 15:13:12 +00:00
pwhead.innerText = this.localization("Password (optional)");
2023-07-16 22:49:33 +00:00
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";
2023-07-18 15:13:12 +00:00
submit.innerText = this.localization("Submit");
2023-07-16 22:49:33 +00:00
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";
2023-07-18 15:13:12 +00:00
close.innerText = this.localization("Close");
2023-07-16 22:49:33 +00:00
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) => {
2023-07-18 16:10:21 +00:00
if (this.debug) console.log(users);
2023-07-17 14:00:32 +00:00
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) {
2023-07-18 16:10:21 +00:00
if (this.debug) console.log("error: ", error);
2023-07-17 14:00:32 +00:00
return;
}
this.netplay.roomJoined(true, roomName, password, sessionid);
})
});
}
this.netplay.leaveRoom = () => {
2023-07-18 16:10:21 +00:00
if (this.debug) 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) {
2023-07-18 16:10:21 +00:00
if (this.debug) console.log("error: ", error);
2023-07-17 14:00:32 +00:00
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-18 16:10:21 +00:00
if (this.debug) console.log(this.netplay.extra);
2023-07-17 13:07:52 +00:00
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 = "";
2023-07-18 15:13:12 +00:00
this.netplay.passwordElem.innerText = this.localization("Password")+": "+password
2023-07-17 13:07:52 +00:00
} else {
this.netplay.passwordElem.style.display = "none";
}
2023-07-18 15:13:12 +00:00
this.netplay.createButton.innerText = this.localization("Leave Room");
2023-07-17 13:07:52 +00:00
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;
2023-07-18 15:13:12 +00:00
this.netplay.createButton.innerText = this.localization("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) => {
2023-07-18 16:10:21 +00:00
if (this.debug) console.log("loading:", loading);
2023-07-18 12:52:32 +00:00
}
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;
2023-07-18 16:10:21 +00:00
if (this.debug) 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 = "";
2023-07-21 15:05:55 +00:00
const getIndex = (desc, code) => {
for (let i=0; i<this.cheats.length; i++) {
if (this.cheats[i].desc === desc && this.cheats[i].code === code) return i;
}
}
2023-06-29 15:35:25 +00:00
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;
2023-07-21 15:05:55 +00:00
this.cheats[getIndex(desc, code)].checked = input.checked;
this.cheatChanged(input.checked, code, getIndex(desc, code));
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);
2023-07-21 15:05:55 +00:00
close.addEventListener("click", (e) => {
this.cheatChanged(false, code, getIndex(desc, code));
this.cheats.splice(getIndex(desc, code), 1);
row.remove();
})
2023-06-29 15:35:25 +00:00
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);
}
}
2023-08-12 14:50:45 +00:00
window.EmulatorJS = EmulatorJS;