From 1db4eb6710a65973bcef9afafade5b85b09fb8e8 Mon Sep 17 00:00:00 2001 From: KatieFrogs <23621460+KatieFrogs@users.noreply.github.com> Date: Fri, 11 Feb 2022 17:28:22 +0300 Subject: [PATCH] ImportSongs: Add plugin support - Files with filenames that end with .taikoweb.js can be imported and run to add custom functionality to the game - The plugin file is a javascript module script that should have a class in the default export - Currently supported methods in the class: name (string), load, start, stop, unload (functions) - The class can be extended from the Patch class to add automatic patching of variables and functions - Here are some of the plugins I made: https://github.com/KatieFrogs/taiko-web-plugins --- public/src/js/about.js | 5 +- public/src/js/abstractfile.js | 25 ++- public/src/js/account.js | 5 +- public/src/js/assets.js | 3 +- public/src/js/autoscore.js | 5 +- public/src/js/canvasasset.js | 5 +- public/src/js/canvascache.js | 5 +- public/src/js/canvasdraw.js | 5 +- public/src/js/canvastest.js | 5 +- public/src/js/circle.js | 5 +- public/src/js/controller.js | 5 +- public/src/js/customsongs.js | 17 +- public/src/js/debug.js | 10 +- public/src/js/game.js | 5 +- public/src/js/gameinput.js | 5 +- public/src/js/gamepad.js | 5 +- public/src/js/gamerules.js | 5 +- public/src/js/gpicker.js | 5 +- public/src/js/idb.js | 9 +- public/src/js/importsongs.js | 66 ++++++- public/src/js/keyboard.js | 5 +- public/src/js/loader.js | 6 +- public/src/js/loadsong.js | 5 +- public/src/js/logo.js | 5 +- public/src/js/lyrics.js | 5 +- public/src/js/main.js | 1 + public/src/js/mekadon.js | 5 +- public/src/js/p2.js | 5 +- public/src/js/pageevents.js | 5 +- public/src/js/parseosu.js | 5 +- public/src/js/parsetja.js | 5 +- public/src/js/plugins.js | 342 ++++++++++++++++++++++++++++++++++ public/src/js/scoresheet.js | 5 +- public/src/js/scorestorage.js | 5 +- public/src/js/session.js | 5 +- public/src/js/settings.js | 284 +++++++++++++++------------- public/src/js/songselect.js | 50 +++-- public/src/js/soundbuffer.js | 5 +- public/src/js/strings.js | 28 +++ public/src/js/titlescreen.js | 5 +- public/src/js/tutorial.js | 5 +- public/src/js/view.js | 5 +- public/src/js/viewassets.js | 5 +- 43 files changed, 803 insertions(+), 193 deletions(-) create mode 100644 public/src/js/plugins.js diff --git a/public/src/js/about.js b/public/src/js/about.js index 5e3dc97..7611145 100644 --- a/public/src/js/about.js +++ b/public/src/js/about.js @@ -1,5 +1,8 @@ class About{ - constructor(touchEnabled){ + constructor(...args){ + this.init(...args) + } + init(touchEnabled){ this.touchEnabled = touchEnabled loader.changePage("about", true) cancelTouch = false diff --git a/public/src/js/abstractfile.js b/public/src/js/abstractfile.js index 44b4bbb..84b0526 100644 --- a/public/src/js/abstractfile.js +++ b/public/src/js/abstractfile.js @@ -20,7 +20,10 @@ function filePermission(file){ }) } class RemoteFile{ - constructor(url){ + constructor(...args){ + this.init(...args) + } + init(url){ this.url = url try{ this.path = new URL(url).pathname @@ -53,7 +56,10 @@ class RemoteFile{ } } class LocalFile{ - constructor(file, path){ + constructor(...args){ + this.init(...args) + } + init(file, path){ this.file = file this.path = path || file.webkitRelativePath this.url = this.path @@ -70,7 +76,10 @@ class LocalFile{ } } class FilesystemFile{ - constructor(file, path){ + constructor(...args){ + this.init(...args) + } + init(file, path){ this.file = file this.path = path this.url = this.path @@ -87,7 +96,10 @@ class FilesystemFile{ } } class GdriveFile{ - constructor(fileObj){ + constructor(...args){ + this.init(...args) + } + init(fileObj){ this.path = fileObj.path this.name = fileObj.name this.id = fileObj.id @@ -108,7 +120,10 @@ class GdriveFile{ } } class CachedFile{ - constructor(contents, oldFile){ + constructor(...args){ + this.init(...args) + } + init(contents, oldFile){ this.contents = contents this.oldFile = oldFile this.path = oldFile.path diff --git a/public/src/js/account.js b/public/src/js/account.js index f0383bd..d4b7754 100644 --- a/public/src/js/account.js +++ b/public/src/js/account.js @@ -1,5 +1,8 @@ class Account{ - constructor(touchEnabled){ + constructor(...args){ + this.init(...args) + } + init(touchEnabled){ this.touchEnabled = touchEnabled cancelTouch = false this.locked = false diff --git a/public/src/js/assets.js b/public/src/js/assets.js index f449487..ac246ba 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -36,7 +36,8 @@ var assets = { "lyrics.js", "customsongs.js", "abstractfile.js", - "idb.js" + "idb.js", + "plugins.js" ], "css": [ "main.css", diff --git a/public/src/js/autoscore.js b/public/src/js/autoscore.js index 45443f6..c1b6b54 100644 --- a/public/src/js/autoscore.js +++ b/public/src/js/autoscore.js @@ -1,5 +1,8 @@ class AutoScore { - constructor(difficulty, level, scoremode, circles) { + constructor(...args){ + this.init(...args) + } + init(difficulty, level, scoremode, circles) { this.scoremode = scoremode; this.circles = circles; this.basic_max_score_list = { diff --git a/public/src/js/canvasasset.js b/public/src/js/canvasasset.js index 9ef1b7c..03f6ec3 100644 --- a/public/src/js/canvasasset.js +++ b/public/src/js/canvasasset.js @@ -1,5 +1,8 @@ class CanvasAsset{ - constructor(view, layer, position){ + constructor(...args){ + this.init(...args) + } + init(view, layer, position){ this.ctx = view.ctx this.view = view this.position = position diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js index bf4f861..bb67c38 100644 --- a/public/src/js/canvascache.js +++ b/public/src/js/canvascache.js @@ -1,5 +1,8 @@ class CanvasCache{ - constructor(noSmoothing, w, h, scale){ + constructor(...args){ + this.init(...args) + } + init(noSmoothing, w, h, scale){ this.noSmoothing = noSmoothing if(w){ this.resize(w, h, scale) diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index b09531b..d341623 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -1,5 +1,8 @@ class CanvasDraw{ - constructor(noSmoothing){ + constructor(...args){ + this.init(...args) + } + init(noSmoothing){ this.diffStarPath = new Path2D(vectors.diffStar) this.longVowelMark = new Path2D(vectors.longVowelMark) diff --git a/public/src/js/canvastest.js b/public/src/js/canvastest.js index dd6e7a7..da20dd6 100644 --- a/public/src/js/canvastest.js +++ b/public/src/js/canvastest.js @@ -1,5 +1,8 @@ class CanvasTest{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ this.canvas = document.createElement("canvas") var pixelRatio = window.devicePixelRatio || 1 var width = innerWidth * pixelRatio diff --git a/public/src/js/circle.js b/public/src/js/circle.js index d971f84..3492f10 100644 --- a/public/src/js/circle.js +++ b/public/src/js/circle.js @@ -1,5 +1,8 @@ class Circle{ - constructor(config){ + constructor(...args){ + this.init(...args) + } + init(config){ this.id = config.id this.ms = config.start this.originalMS = this.ms diff --git a/public/src/js/controller.js b/public/src/js/controller.js index 9da4b1c..cb01da9 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -1,5 +1,8 @@ class Controller{ - constructor(selectedSong, songData, autoPlayEnabled, multiplayer, touchEnabled){ + constructor(...args){ + this.init(...args) + } + init(selectedSong, songData, autoPlayEnabled, multiplayer, touchEnabled){ this.selectedSong = selectedSong this.songData = songData this.autoPlayEnabled = autoPlayEnabled diff --git a/public/src/js/customsongs.js b/public/src/js/customsongs.js index 34ae66e..60f9471 100644 --- a/public/src/js/customsongs.js +++ b/public/src/js/customsongs.js @@ -1,5 +1,8 @@ class CustomSongs{ - constructor(touchEnabled, noPage){ + constructor(...args){ + this.init(...args) + } + init(touchEnabled, noPage){ this.loaderDiv = document.createElement("div") this.loaderDiv.innerHTML = assets.pages["loadsong"] var loadingText = this.loaderDiv.querySelector("#loading-text") @@ -151,8 +154,10 @@ class CustomSongs{ this.changeSelected(this.linkLocalFolder) if(typeof showDirectoryPicker === "function"){ return showDirectoryPicker().then(file => { - this.walkFilesystem(file).then(files => this.importLocal(files)).then(e => { - db.setItem("customFolder", [file]) + this.walkFilesystem(file).then(files => this.importLocal(files)).then(input => { + if(input){ + db.setItem("customFolder", [file]) + } }).catch(e => { if(e !== "cancel"){ return Promise.reject(e) @@ -217,8 +222,8 @@ class CustomSongs{ })) } } - Promise.all(dropPromises).then(() => this.importLocal(allFiles)).then(() => { - if(dbItems.length){ + Promise.all(dropPromises).then(() => this.importLocal(allFiles)).then(input => { + if(input && dbItems.length){ db.setItem("customFolder", dbItems) } }) @@ -265,6 +270,7 @@ class CustomSongs{ }else if(e !== "cancel"){ return Promise.reject(e) } + return false }) } gdriveFolder(event){ @@ -387,6 +393,7 @@ class CustomSongs{ new SongSelect("customSongs", false, this.touchEnabled) pageEvents.send("import-songs", length) }, 500) + return songs && songs.length } keyPressed(pressed, name){ if(!pressed || this.locked){ diff --git a/public/src/js/debug.js b/public/src/js/debug.js index a3a441f..4c9bf16 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -1,5 +1,8 @@ class Debug{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ if(!assets.pages["debug"]){ return } @@ -329,7 +332,10 @@ class Debug{ } } class InputSlider{ - constructor(sliderDiv, min, max, fixedPoint){ + constructor(...args){ + this.init(...args) + } + init(sliderDiv, min, max, fixedPoint){ this.fixedPoint = fixedPoint this.mul = Math.pow(10, fixedPoint) this.min = min * this.mul diff --git a/public/src/js/game.js b/public/src/js/game.js index 58664b4..c649936 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -1,5 +1,8 @@ class Game{ - constructor(controller, selectedSong, songData){ + constructor(...args){ + this.init(...args) + } + init(controller, selectedSong, songData){ this.controller = controller this.selectedSong = selectedSong this.songData = songData diff --git a/public/src/js/gameinput.js b/public/src/js/gameinput.js index bacdddd..e31502c 100644 --- a/public/src/js/gameinput.js +++ b/public/src/js/gameinput.js @@ -1,5 +1,8 @@ class GameInput{ - constructor(controller){ + constructor(...args){ + this.init(...args) + } + init(controller){ this.controller = controller this.game = this.controller.game diff --git a/public/src/js/gamepad.js b/public/src/js/gamepad.js index a4b3327..95581d7 100644 --- a/public/src/js/gamepad.js +++ b/public/src/js/gamepad.js @@ -1,5 +1,8 @@ class Gamepad{ - constructor(bindings, callback){ + constructor(...args){ + this.init(...args) + } + init(bindings, callback){ this.bindings = bindings this.callback = !!callback this.b = { diff --git a/public/src/js/gamerules.js b/public/src/js/gamerules.js index c9da2f9..79654f0 100644 --- a/public/src/js/gamerules.js +++ b/public/src/js/gamerules.js @@ -1,5 +1,8 @@ class GameRules{ - constructor(game){ + constructor(...args){ + this.init(...args) + } + init(game){ this.difficulty = game.controller.selectedSong.difficulty var frame = 1000 / 60 diff --git a/public/src/js/gpicker.js b/public/src/js/gpicker.js index 4e36279..f5fb1ab 100644 --- a/public/src/js/gpicker.js +++ b/public/src/js/gpicker.js @@ -1,5 +1,8 @@ class Gpicker{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ this.apiKey = gameConfig.google_credentials.api_key this.oauthClientId = gameConfig.google_credentials.oauth_client_id this.projectNumber = gameConfig.google_credentials.project_number diff --git a/public/src/js/idb.js b/public/src/js/idb.js index 6ca2640..a8b50db 100644 --- a/public/src/js/idb.js +++ b/public/src/js/idb.js @@ -1,9 +1,12 @@ class IDB{ - constructor(name, store){ + constructor(...args){ + this.init(...args) + } + init(name, store){ this.name = name this.store = store } - init(){ + start(){ if(this.db){ return Promise.resolve(this.db) } @@ -31,7 +34,7 @@ class IDB{ }) } transaction(method, ...args){ - return this.init().then(db => + return this.start().then(db => db.transaction(this.store, "readwrite").objectStore(this.store)[method](...args) ).then(this.promise.bind(this)) } diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 2e5b673..f6a053a 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -1,12 +1,19 @@ class ImportSongs{ - constructor(limited, otherFiles){ + constructor(...args){ + this.init(...args) + } + init(limited, otherFiles, noPlugins, pluginAmount){ this.limited = limited this.tjaFiles = [] this.osuFiles = [] this.assetFiles = {} + this.pluginFiles = [] this.otherFiles = otherFiles || {} + this.noPlugins = noPlugins + this.pluginAmount = pluginAmount this.songs = [] this.stylesheet = [] + this.plugins = [] this.songTitle = this.otherFiles.songTitle || {} this.uraRegex = /\s*[\((]裏[\))]$/ this.courseTypes = { @@ -77,11 +84,48 @@ if(!(name in this.assetFiles)){ this.assetFiles[name] = file } + }else if(name.endsWith(".taikoweb.js")){ + this.pluginFiles.push({ + file: file, + index: i + }) }else{ this.otherFiles[path] = file } } + if(!this.noPlugins && this.pluginFiles.length){ + var pluginPromises = [] + this.pluginFiles.forEach(fileObj => { + pluginPromises.push(this.addPlugin(fileObj).catch(e => console.warn(e))) + }) + return Promise.all(pluginPromises).then(() => { + var startPromises = [] + var pluginAmount = 0 + if(this.plugins.length && confirm(strings.plugins.warning.replace("%s", + strings.plugins.plugin[strings.plural.select(this.plugins.length)].replace("%s", + this.plugins.length.toString() + ) + ))){ + this.plugins.forEach(obj => { + var plugin = plugins.add(obj.data, obj.name) + if(plugin){ + pluginAmount++ + plugins.imported.push({ + name: plugin.name, + plugin: plugin + }) + startPromises.push(plugin.start()) + } + }) + } + return Promise.all(startPromises).then(() => { + var importSongs = new ImportSongs(this.limited, this.otherFiles, true, pluginAmount) + return importSongs.load(files) + }) + }) + } + var metaPromises = [] metaFiles.forEach(fileObj => { metaPromises.push(this.addMeta(fileObj)) @@ -468,6 +512,18 @@ return name.slice(0, name.lastIndexOf(".")) } + addPlugin(fileObj){ + var file = fileObj.file + var filePromise = file.read() + return filePromise.then(dataRaw => { + var name = file.name.slice(0, file.name.lastIndexOf(".taikoweb.js")) + this.plugins.push({ + name: name, + data: dataRaw + }) + }) + } + getCategory(file, exclude){ var path = file.path.toLowerCase().split("/") for(var i = path.length - 2; i >= 0; i--){ @@ -543,10 +599,12 @@ assets.otherFiles.songTitle = this.songTitle } return Promise.resolve(this.songs) - }else if(Object.keys(this.assetFiles).length){ - return Promise.resolve() }else{ - return Promise.reject("nosongs") + if(this.noPlugins && this.pluginAmount || Object.keys(this.assetFiles).length){ + return Promise.resolve() + }else{ + return Promise.reject("nosongs") + } } this.clean() } diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index c0816e7..8a93c88 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -1,5 +1,8 @@ class Keyboard{ - constructor(bindings, callback){ + constructor(...args){ + this.init(...args) + } + init(bindings, callback){ this.bindings = bindings this.callback = callback this.wildcard = false diff --git a/public/src/js/loader.js b/public/src/js/loader.js index 9053d99..7fa203d 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -1,5 +1,8 @@ class Loader{ - constructor(callback){ + constructor(...args){ + this.init(...args) + } + init(callback){ this.callback = callback this.loadedAssets = 0 this.assetsDiv = document.getElementById("assets") @@ -253,6 +256,7 @@ class Loader{ pageEvents.setKbd() scoreStorage = new ScoreStorage() db = new IDB("taiko", "store") + plugins = new Plugins() Promise.all(this.promises).then(() => { if(this.error){ diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 5410a6d..dc1fdf0 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -1,5 +1,8 @@ class LoadSong{ - constructor(selectedSong, autoPlayEnabled, multiplayer, touchEnabled){ + constructor(...args){ + this.init(...args) + } + init(selectedSong, autoPlayEnabled, multiplayer, touchEnabled){ this.selectedSong = selectedSong this.autoPlayEnabled = autoPlayEnabled this.multiplayer = multiplayer diff --git a/public/src/js/logo.js b/public/src/js/logo.js index e77f992..7a7e515 100644 --- a/public/src/js/logo.js +++ b/public/src/js/logo.js @@ -1,5 +1,8 @@ class Logo{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ this.canvas = document.getElementById("logo") this.ctx = this.canvas.getContext("2d") this.pathSvg = failedTests.indexOf("Path2D SVG") === -1 && vectors.logo1 diff --git a/public/src/js/lyrics.js b/public/src/js/lyrics.js index d4974b3..8459fc3 100644 --- a/public/src/js/lyrics.js +++ b/public/src/js/lyrics.js @@ -1,5 +1,8 @@ class Lyrics{ - constructor(file, songOffset, div, parsed){ + constructor(...args){ + this.init(...args) + } + init(file, songOffset, div, parsed){ this.div = div this.stroke = document.createElement("div") this.stroke.classList.add("stroke") diff --git a/public/src/js/main.js b/public/src/js/main.js index 2077fcd..2f8ed2b 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -91,6 +91,7 @@ var scoreStorage var account = {} var gpicker var db +var plugins pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){ diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index d8cf58d..e76abdc 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -1,5 +1,8 @@ class Mekadon{ - constructor(controller, game){ + constructor(...args){ + this.init(...args) + } + init(controller, game){ this.controller = controller this.game = game this.lr = false diff --git a/public/src/js/p2.js b/public/src/js/p2.js index b73690e..9f7d9aa 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -1,5 +1,8 @@ class P2Connection{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ this.closed = true this.lastMessages = {} this.otherConnected = false diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js index 7779a24..b6d2a39 100644 --- a/public/src/js/pageevents.js +++ b/public/src/js/pageevents.js @@ -1,5 +1,8 @@ class PageEvents{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ this.allEvents = new Map() this.keyListeners = new Map() this.mouseListeners = new Map() diff --git a/public/src/js/parseosu.js b/public/src/js/parseosu.js index f836cdf..e95c714 100644 --- a/public/src/js/parseosu.js +++ b/public/src/js/parseosu.js @@ -1,5 +1,8 @@ class ParseOsu{ - constructor(fileContent, difficulty, stars, offset, metaOnly){ + constructor(...args){ + this.init(...args) + } + init(fileContent, difficulty, stars, offset, metaOnly){ this.osu = { OFFSET: 0, MSPERBEAT: 1, diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js index 964a1d8..18bf061 100644 --- a/public/src/js/parsetja.js +++ b/public/src/js/parsetja.js @@ -1,5 +1,8 @@ class ParseTja{ - constructor(file, difficulty, stars, offset, metaOnly){ + constructor(...args){ + this.init(...args) + } + init(file, difficulty, stars, offset, metaOnly){ this.data = [] for(let line of file){ var indexComment = line.indexOf("//") diff --git a/public/src/js/plugins.js b/public/src/js/plugins.js new file mode 100644 index 0000000..e82faf8 --- /dev/null +++ b/public/src/js/plugins.js @@ -0,0 +1,342 @@ +class Plugins{ + constructor(...args){ + this.init(...args) + } + init(){ + this.imported = [] + this.allPlugins = [] + this.pluginMap = {} + this.hashes = [] + } + add(script, name){ + var hash = md5.base64(script.toString()) + if(this.hashes.indexOf(hash) !== -1){ + console.warn("Skip adding an already addded plugin: " + name) + return + } + name = name || "plugin" + var baseName = name + for(var i = 2; name in this.allPlugins; i++){ + name = baseName + i.toString() + } + var plugin = new PluginLoader(script, name, hash) + this.allPlugins.push({ + name: name, + plugin: plugin + }) + this.pluginMap[name] = plugin + this.hashes.push(hash) + return plugin + } + remove(name){ + var hash = this.pluginMap[name].hash + if(hash){ + var index = this.hashes.indexOf(hash) + if(index !== -1){ + this.hashes.splice(index, 1) + } + } + this.unload(name) + var index = this.imported.findIndex(obj => obj.name === name) + if(index !== -1){ + this.imported.splice(index, 1) + } + var index = this.allPlugins.findIndex(obj => obj.name === name) + if(index !== -1){ + this.allPlugins.splice(index, 1) + } + delete this.pluginMap[name] + } + load(name){ + this.pluginMap[name].load() + } + loadAll(){ + for(var i = 0; i < this.allPlugins.length; i++){ + this.allPlugins[i].plugin.load() + } + } + start(name){ + this.pluginMap[name].start() + } + startAll(){ + for(var i = 0; i < this.allPlugins.length; i++){ + this.allPlugins[i].plugin.start() + } + } + stop(name){ + this.pluginMap[name].stop() + } + stopAll(){ + for(var i = this.allPlugins.length; i--;){ + this.allPlugins[i].plugin.stop() + } + } + unload(name){ + this.pluginMap[name].unload() + } + unloadAll(){ + for(var i = this.allPlugins.length; i--;){ + this.allPlugins[i].plugin.unload() + } + } + unloadImported(){ + for(var i = this.imported.length; i--;){ + this.imported[i].plugin.unload() + } + } + + strFromFunc(func){ + var output = func.toString() + return output.slice(output.indexOf("{") + 1, output.lastIndexOf("}")) + } + argsFromFunc(func){ + var output = func.toString() + output = output.slice(0, output.indexOf("{")) + output = output.slice(output.indexOf("(") + 1, output.lastIndexOf(")")) + return output.split(",").map(str => str.trim()).filter(Boolean) + } + insertBefore(input, insertedText, searchString){ + var index = input.indexOf(searchString) + if(index === -1){ + throw new Error("searchString not found: " + searchString) + } + return input.slice(0, index) + insertedText + input.slice(index) + } + insertAfter(input, searchString, insertedText){ + var index = input.indexOf(searchString) + if(index === -1){ + throw new Error("searchString not found: " + searchString) + } + var length = searchString.length + return input.slice(0, index + length) + insertedText + input.slice(index + length) + } + strReplace(input, searchString, insertedText){ + var index = input.indexOf(searchString) + if(index === -1){ + throw new Error("searchString not found: " + searchString) + } + return input.slice(0, index) + insertedText + input.slice(index + searchString.length) + } + + getSettings(){ + var items = {} + for(var i = 0; i < this.allPlugins.length; i++){ + var obj = this.allPlugins[i] + var plugin = obj.plugin + items[obj.name] = { + name: plugin.module.name || obj.name, + type: "toggle", + default: true, + getItem: () => plugin.started, + setItem: value => { + if(plugin.started && !value){ + plugin.stop() + }else if(!plugin.started && value){ + plugin.start() + } + } + } + } + return items + } +} + +class PluginLoader{ + constructor(...args){ + this.init(...args) + } + init(script, name, hash){ + this.name = name + this.hash = hash + if(typeof script === "string"){ + this.url = URL.createObjectURL(new Blob([script], { + type: "application/javascript" + })) + }else{ + this.class = script + } + } + load(){ + if(this.loaded || !this.url && !this.class){ + return Promise.resolve() + }else{ + return (this.url ? import(this.url) : Promise.resolve({ + default: this.class + })).then(module => { + if(this.url){ + URL.revokeObjectURL(this.url) + delete this.url + }else{ + delete this.class + } + this.loaded = true + try{ + this.module = new module.default() + }catch(e){ + console.error(e) + this.error() + return + } + try{ + if(this.module.beforeLoad){ + this.module.beforeLoad(this) + } + if(this.module.load){ + this.module.load(this) + } + }catch(e){ + console.error(e) + this.error() + } + }) + } + } + start(){ + return this.load().then(() => { + if(!this.started && this.module){ + this.started = true + try{ + if(this.module.beforeStart){ + this.module.beforeStart() + } + if(this.module.start){ + this.module.start() + } + }catch(e){ + console.error(e) + this.error() + } + } + }) + } + stop(error){ + if(this.loaded && this.started){ + this.started = false + try{ + if(this.module.beforeStop){ + this.module.beforeStop() + } + if(this.module.stop){ + this.module.stop() + } + }catch(e){ + console.error(e) + if(!error){ + this.error() + } + } + } + } + unload(error){ + if(this.loaded){ + if(this.started){ + this.stop(error) + } + this.loaded = false + plugins.remove(this.name) + if(this.module){ + try{ + if(this.module.beforeUnload){ + this.module.beforeUnload() + } + if(this.module.unload){ + this.module.unload() + } + }catch(e){ + console.error(e) + } + delete this.module + } + } + } + error(){ + if(this.module && this.module.error){ + try{ + this.module.error() + }catch(e){ + console.error(e) + } + } + this.unload(true) + } +} + +class EditValue{ + constructor(...args){ + this.init(...args) + } + init(parent, name){ + if(name){ + this.original = parent[name] + this.name = [parent, name] + this.delete = !(name in parent) + }else{ + this.original = parent + } + } + load(callback){ + var output = callback(this.original) + if(typeof output === "undefined"){ + throw new Error("A value is expected to be returned") + } + this.edited = output + return this + } + start(){ + if(this.name){ + this.name[0][this.name[1]] = this.edited + } + return this.edited + } + stop(){ + if(this.name){ + if(this.delete){ + delete this.name[0][this.name[1]] + }else{ + this.name[0][this.name[1]] = this.original + } + } + return this.original + } + unload(){ + delete this.name + delete this.edited + delete this.original + } +} + +class EditFunction extends EditValue{ + load(callback){ + var output = callback(plugins.strFromFunc(this.original)) + if(typeof output === "undefined"){ + throw new Error("A value is expected to be returned") + } + var args = plugins.argsFromFunc(this.original) + this.edited = Function(...args, output) + return this + } +} + +class Patch{ + edits = [] + addEdits(...args){ + args.forEach(arg => this.edits.push(arg)) + } + beforeStart(){ + this.edits.forEach(edit => edit.start()) + } + beforeStop(){ + this.edits.forEach(edit => edit.stop()) + } + beforeUnload(){ + this.edits.forEach(edit => edit.unload()) + } + log(message){ + var name = this.name || "Plugin" + console.log( + "%c[" + name + "]%c " + message, + "font-weight: bold;", + "" + ) + } +} diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index d964560..04396c4 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -1,5 +1,8 @@ class Scoresheet{ - constructor(controller, results, multiplayer, touchEnabled){ + constructor(...args){ + this.init(...args) + } + init(controller, results, multiplayer, touchEnabled){ this.controller = controller this.resultsObj = results this.player = [multiplayer ? (p2.player === 1 ? 0 : 1) : 0] diff --git a/public/src/js/scorestorage.js b/public/src/js/scorestorage.js index 2415082..2d2ce2f 100644 --- a/public/src/js/scorestorage.js +++ b/public/src/js/scorestorage.js @@ -1,5 +1,8 @@ class ScoreStorage{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ this.scores = {} this.scoresP2 = {} this.requestP2 = new Set() diff --git a/public/src/js/session.js b/public/src/js/session.js index 2640551..ddd511e 100644 --- a/public/src/js/session.js +++ b/public/src/js/session.js @@ -1,5 +1,8 @@ class Session{ - constructor(touchEnabled){ + constructor(...args){ + this.init(...args) + } + init(touchEnabled){ this.touchEnabled = touchEnabled loader.changePage("session", true) this.endButton = this.getElement("view-end-button") diff --git a/public/src/js/settings.js b/public/src/js/settings.js index 1f7a9bb..711a57b 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -1,5 +1,8 @@ class Settings{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ var ios = /iPhone|iPad/.test(navigator.userAgent) var phone = /Android|iPhone|iPad/.test(navigator.userAgent) this.allLanguages = [] @@ -151,6 +154,7 @@ class Settings{ loader.screen.style.fontFamily = strings.font loader.screen.style.fontWeight = boldFonts ? "bold" : "" loader.screen.classList[boldFonts ? "add" : "remove"]("bold-fonts") + strings.plural = new Intl.PluralRules(lang.intl) if(!noEvent){ pageEvents.send("language-change", lang.id) } @@ -158,10 +162,15 @@ class Settings{ } class SettingsView{ - constructor(touchEnabled, tutorial, songId, toSetting){ + constructor(...args){ + this.init(...args) + } + init(touchEnabled, tutorial, songId, toSetting, settingsItems){ this.touchEnabled = touchEnabled this.tutorial = tutorial this.songId = songId + this.customSettings = !!settingsItems + this.settingsItems = settingsItems || settings.items loader.changePage("settings", tutorial) assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) @@ -233,8 +242,8 @@ class SettingsView{ var content = this.getElement("view-content") this.items = [] this.selected = 0 - for(let i in settings.items){ - var current = settings.items[i] + for(let i in this.settingsItems){ + var current = this.settingsItems[i] if( !touchEnabled && current.touch === true || touchEnabled && current.touch === false || @@ -246,7 +255,7 @@ class SettingsView{ settingBox.classList.add("setting-box") var nameDiv = document.createElement("div") nameDiv.classList.add("setting-name", "stroke-sub") - var name = strings.settings[i].name + var name = current.name || strings.settings[i].name this.setAltText(nameDiv, name) settingBox.appendChild(nameDiv) var valueDiv = document.createElement("div") @@ -277,101 +286,103 @@ class SettingsView{ }) this.addTouch(this.endButton, this.onEnd.bind(this)) - this.gamepadSettings = document.getElementById("settings-gamepad") - this.addTouch(this.gamepadSettings, event => { - if(event.target === event.currentTarget){ - this.gamepadBack() - } - }) - this.gamepadTitle = this.gamepadSettings.getElementsByClassName("view-title")[0] - this.gamepadEndButton = this.gamepadSettings.getElementsByClassName("view-end-button")[0] - this.addTouch(this.gamepadEndButton, event => this.gamepadBack(true)) - this.gamepadBox = this.gamepadSettings.getElementsByClassName("setting-box")[0] - this.addTouch(this.gamepadBox, event => this.gamepadSet(1)) - this.gamepadButtons = document.getElementById("gamepad-buttons") - this.gamepadValue = document.getElementById("gamepad-value") - - this.latencySettings = document.getElementById("settings-latency") - this.addTouch(this.latencySettings, event => { - if(event.target === event.currentTarget){ - this.latencyBack() - } - }) - this.latencyTitle = this.latencySettings.getElementsByClassName("view-title")[0] - this.latencyItems = [] - this.latencySelected = 0 - var latencyContent = this.latencySettings.getElementsByClassName("view-content")[0] - var latencyWindow = ["calibration", "audio", "video", "drumSounds"] - for(let i in latencyWindow){ - let current = latencyWindow[i] - var settingBox = document.createElement("div") - settingBox.classList.add("setting-box") - var nameDiv = document.createElement("div") - nameDiv.classList.add("setting-name", "stroke-sub") - var name = strings.settings.latency[current] - this.setAltText(nameDiv, name) - settingBox.appendChild(nameDiv) - let outputObject = { - id: current, - settingBox: settingBox, - nameDiv: nameDiv - } - if(current === "calibration"){ - nameDiv.style.width = "100%" - }else{ - var valueDiv = document.createElement("div") - valueDiv.classList.add("setting-value") - settingBox.appendChild(valueDiv) - var valueText = document.createTextNode("") - valueDiv.appendChild(valueText) - this.latencyGetValue(current, valueText) - if(current !== "drumSounds"){ - var buttons = document.createElement("div") - buttons.classList.add("latency-buttons") - var buttonMinus = document.createElement("span") - buttonMinus.innerText = "-" - buttons.appendChild(buttonMinus) - this.addTouchRepeat(buttonMinus, event => { - this.latencySetAdjust(outputObject, -1) - }) - var buttonPlus = document.createElement("span") - buttonPlus.innerText = "+" - buttons.appendChild(buttonPlus) - this.addTouchRepeat(buttonPlus, event => { - this.latencySetAdjust(outputObject, 1) - }) - valueDiv.appendChild(buttons) - } - } - latencyContent.appendChild(settingBox) - if(this.latencyItems.length === this.latencySelected){ - settingBox.classList.add("selected") - } - this.addTouch(settingBox, event => { - if(event.target.tagName !== "SPAN"){ - this.latencySetValue(current, event.type === "touchstart") + if(!this.customSettings){ + this.gamepadSettings = document.getElementById("settings-gamepad") + this.addTouch(this.gamepadSettings, event => { + if(event.target === event.currentTarget){ + this.gamepadBack() } }) - if(current !== "calibration"){ - outputObject.valueDiv = valueDiv - outputObject.valueText = valueText - outputObject.buttonMinus = buttonMinus - outputObject.buttonPlus = buttonPlus + this.gamepadTitle = this.gamepadSettings.getElementsByClassName("view-title")[0] + this.gamepadEndButton = this.gamepadSettings.getElementsByClassName("view-end-button")[0] + this.addTouch(this.gamepadEndButton, event => this.gamepadBack(true)) + this.gamepadBox = this.gamepadSettings.getElementsByClassName("setting-box")[0] + this.addTouch(this.gamepadBox, event => this.gamepadSet(1)) + this.gamepadButtons = document.getElementById("gamepad-buttons") + this.gamepadValue = document.getElementById("gamepad-value") + + this.latencySettings = document.getElementById("settings-latency") + this.addTouch(this.latencySettings, event => { + if(event.target === event.currentTarget){ + this.latencyBack() + } + }) + this.latencyTitle = this.latencySettings.getElementsByClassName("view-title")[0] + this.latencyItems = [] + this.latencySelected = 0 + var latencyContent = this.latencySettings.getElementsByClassName("view-content")[0] + var latencyWindow = ["calibration", "audio", "video", "drumSounds"] + for(let i in latencyWindow){ + let current = latencyWindow[i] + var settingBox = document.createElement("div") + settingBox.classList.add("setting-box") + var nameDiv = document.createElement("div") + nameDiv.classList.add("setting-name", "stroke-sub") + var name = strings.settings.latency[current] + this.setAltText(nameDiv, name) + settingBox.appendChild(nameDiv) + let outputObject = { + id: current, + settingBox: settingBox, + nameDiv: nameDiv + } + if(current === "calibration"){ + nameDiv.style.width = "100%" + }else{ + var valueDiv = document.createElement("div") + valueDiv.classList.add("setting-value") + settingBox.appendChild(valueDiv) + var valueText = document.createTextNode("") + valueDiv.appendChild(valueText) + this.latencyGetValue(current, valueText) + if(current !== "drumSounds"){ + var buttons = document.createElement("div") + buttons.classList.add("latency-buttons") + var buttonMinus = document.createElement("span") + buttonMinus.innerText = "-" + buttons.appendChild(buttonMinus) + this.addTouchRepeat(buttonMinus, event => { + this.latencySetAdjust(outputObject, -1) + }) + var buttonPlus = document.createElement("span") + buttonPlus.innerText = "+" + buttons.appendChild(buttonPlus) + this.addTouchRepeat(buttonPlus, event => { + this.latencySetAdjust(outputObject, 1) + }) + valueDiv.appendChild(buttons) + } + } + latencyContent.appendChild(settingBox) + if(this.latencyItems.length === this.latencySelected){ + settingBox.classList.add("selected") + } + this.addTouch(settingBox, event => { + if(event.target.tagName !== "SPAN"){ + this.latencySetValue(current, event.type === "touchstart") + } + }) + if(current !== "calibration"){ + outputObject.valueDiv = valueDiv + outputObject.valueText = valueText + outputObject.buttonMinus = buttonMinus + outputObject.buttonPlus = buttonPlus + } + this.latencyItems.push(outputObject) } - this.latencyItems.push(outputObject) + this.latencyDefaultButton = document.getElementById("latency-default") + this.latencyItems.push({ + id: "default", + settingBox: this.latencyDefaultButton + }) + this.addTouch(this.latencyDefaultButton, event => this.latencyDefault()) + this.latencyEndButton = this.latencySettings.getElementsByClassName("view-end-button")[0] + this.latencyItems.push({ + id: "back", + settingBox: this.latencyEndButton + }) + this.addTouch(this.latencyEndButton, event => this.latencyBack(true)) } - this.latencyDefaultButton = document.getElementById("latency-default") - this.latencyItems.push({ - id: "default", - settingBox: this.latencyDefaultButton - }) - this.addTouch(this.latencyDefaultButton, event => this.latencyDefault()) - this.latencyEndButton = this.latencySettings.getElementsByClassName("view-end-button")[0] - this.latencyItems.push({ - id: "back", - settingBox: this.latencyEndButton - }) - this.addTouch(this.latencyEndButton, event => this.latencyBack(true)) this.setStrings() @@ -432,8 +443,12 @@ class SettingsView{ pageEvents.remove(element, ["mousedown", "touchend"]) } getValue(name, valueDiv){ - var current = settings.items[name] - var value = settings.getItem(name) + var current = this.settingsItems[name] + if(current.getItem){ + var value = current.getItem() + }else{ + var value = settings.getItem(name) + } if(current.type === "language"){ value = allStrings[value].name + " (" + value + ")" }else if(current.type === "select" || current.type === "gamepad"){ @@ -471,8 +486,13 @@ class SettingsView{ valueDiv.innerText = value } setValue(name){ - var current = settings.items[name] - var value = settings.getItem(name) + var promise + var current = this.settingsItems[name] + if(current.getItem){ + var value = current.getItem() + }else{ + var value = settings.getItem(name) + } var selectedIndex = this.items.findIndex(item => item.id === name) var selected = this.items[selectedIndex] if(this.mode !== "settings"){ @@ -511,12 +531,18 @@ class SettingsView{ this.playSound("se_don") return } - settings.setItem(name, value) - this.getValue(name, this.items[this.selected].valueDiv) - this.playSound("se_ka") - if(current.type === "language"){ - this.setLang(allStrings[value]) + if(current.setItem){ + promise = current.setItem(value) + }else{ + settings.setItem(name, value) } + (promise || Promise.resolve()).then(() => { + this.getValue(name, this.items[this.selected].valueDiv) + this.playSound("se_ka") + if(current.type === "language"){ + this.setLang(allStrings[value]) + } + }) } keyPressed(pressed, name, event, repeat){ if(pressed){ @@ -627,7 +653,7 @@ class SettingsView{ } keyboardSet(){ var selected = this.items[this.selected] - var current = settings.items[selected.id] + var current = this.settingsItems[selected.id] selected.valueDiv.innerHTML = "" for(var i in current.default){ var keyDiv = document.createElement("div") @@ -665,7 +691,7 @@ class SettingsView{ return } var selected = this.items[this.selected] - var current = settings.items[selected.id] + var current = this.settingsItems[selected.id] if(diff){ this.gamepadSelected = this.mod(current.options.length, this.gamepadSelected + diff) this.playSound("se_ka") @@ -681,7 +707,7 @@ class SettingsView{ return } var selected = this.items[this.selected] - var current = settings.items[selected.id] + var current = this.settingsItems[selected.id] settings.setItem(selected.id, current.options[this.gamepadSelected]) this.getValue(selected.id, selected.valueDiv) this.playSound(confirm ? "se_don" : "se_cancel") @@ -693,7 +719,7 @@ class SettingsView{ return } var selected = this.items[this.selected] - var current = settings.items[selected.id] + var current = this.settingsItems[selected.id] this.latencySettings.style.display = "flex" } latencyGetValue(name, valueText){ @@ -801,7 +827,7 @@ class SettingsView{ return } var selected = this.items[this.selected] - var current = settings.items[selected.id] + var current = this.settingsItems[selected.id] this.getValue(selected.id, selected.valueDiv) this.playSound(confirm ? "se_don" : "se_cancel") this.latencySettings.style.display = "" @@ -821,10 +847,14 @@ class SettingsView{ return output } defaultSettings(){ + if(this.customSettings){ + plugins.unloadImported() + return this.onEnd() + } if(this.mode === "keyboard"){ this.keyboardBack(this.items[this.selected]) } - for(var i in settings.items){ + for(var i in this.settingsItems){ settings.setItem(i, null) } this.setLang(allStrings[settings.getItem("language")]) @@ -848,7 +878,7 @@ class SettingsView{ try{ localStorage.setItem("tutorial", "true") }catch(e){} - new SongSelect(this.tutorial ? false : "settings", false, this.touched, this.songId) + new SongSelect(this.tutorial ? false : this.customSettings ? "plugins" : "settings", false, this.touched, this.songId) } }, 500) } @@ -877,14 +907,16 @@ class SettingsView{ this.setStrings() } setStrings(){ - this.setAltText(this.viewTitle, strings.gameSettings) + this.setAltText(this.viewTitle, this.customSettings ? strings.plugins.title : strings.gameSettings) this.setAltText(this.endButton, strings.settings.ok) - this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name) - this.setAltText(this.gamepadEndButton, strings.settings.ok) - this.setAltText(this.latencyTitle, strings.settings.latency.name) - this.setAltText(this.latencyDefaultButton, strings.settings.default) - this.setAltText(this.latencyEndButton, strings.settings.ok) - this.setAltText(this.defaultButton, strings.settings.default) + if(!this.customSettings){ + this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name) + this.setAltText(this.gamepadEndButton, strings.settings.ok) + this.setAltText(this.latencyTitle, strings.settings.latency.name) + this.setAltText(this.latencyDefaultButton, strings.settings.default) + this.setAltText(this.latencyEndButton, strings.settings.ok) + } + this.setAltText(this.defaultButton, this.customSettings ? strings.plugins.unloadAll : strings.settings.default) } setAltText(element, text){ element.innerText = text @@ -941,12 +973,14 @@ class SettingsView{ if(this.defaultButton){ delete this.defaultButton } - this.removeTouch(this.gamepadSettings) - this.removeTouch(this.gamepadEndButton) - this.removeTouch(this.gamepadBox) - this.removeTouch(this.latencySettings) - this.removeTouch(this.latencyDefaultButton) - this.removeTouch(this.latencyEndButton) + if(!this.customSettings){ + this.removeTouch(this.gamepadSettings) + this.removeTouch(this.gamepadEndButton) + this.removeTouch(this.gamepadBox) + this.removeTouch(this.latencySettings) + this.removeTouch(this.latencyDefaultButton) + this.removeTouch(this.latencyEndButton) + } delete this.windowSymbol delete this.touchMove delete this.viewOuter diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 03028bb..cd3e4e0 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1,5 +1,8 @@ class SongSelect{ - constructor(fromTutorial, fadeIn, touchEnabled, songId, showWarning){ + constructor(...args){ + this.init(...args) + } + init(fromTutorial, fadeIn, touchEnabled, songId, showWarning){ this.touchEnabled = touchEnabled loader.changePage("songselect", false) @@ -55,6 +58,12 @@ class SongSelect{ border: ["#ffe7ef", "#d36aa2"], outline: "#d36aa2" }, + "plugins": { + sort: 0, + background: "#f6bba1", + border: ["#fde9df", "#ce7553"], + outline: "#ce7553" + }, "default": { sort: null, background: "#ececec", @@ -150,6 +159,14 @@ class SongSelect{ category: strings.random }) } + if(plugins.allPlugins.length){ + this.songs.push({ + title: strings.plugins.title, + skin: this.songSkin.plugins, + action: "plugins", + category: strings.random + }) + } this.songs.push({ title: strings.back, @@ -218,8 +235,12 @@ class SongSelect{ this.playedSounds = {} var songIdIndex = -1 + var newSelected = -1 if(fromTutorial){ - this.selectedSong = this.songs.findIndex(song => song.action === fromTutorial) + newSelected = this.songs.findIndex(song => song.action === fromTutorial) + } + if(newSelected !== -1){ + this.selectedSong = newSelected this.playBgm(true) }else{ if(songId){ @@ -481,18 +502,6 @@ class SongSelect{ } touchEnd(event){ event.preventDefault() - if(this.state.screen === "song" && this.redrawRunning){ - var currentSong = this.songs[this.selectedSong] - if(currentSong.action === "customSongs"){ - var x = event.changedTouches[0].pageX - this.canvas.offsetLeft - var y = event.changedTouches[0].pageY - this.canvas.offsetTop - var mouse = this.mouseOffset(x, y) - var moveBy = this.songSelMouse(mouse.x, mouse.y) - if(moveBy === 0){ - this.toCustomSongs() - } - } - } } mouseMove(event){ var mouse = this.mouseOffset(event.offsetX, event.offsetY) @@ -565,8 +574,8 @@ class SongSelect{ } diffSelMouse(x, y){ if(this.state.locked === 0){ - if(223 < x && x < 367 && 132 < y && y < 436){ - return Math.floor((x - 223) / ((367 - 223) / 2)) + if(223 < x && x < 223 + 72 * this.diffOptions.length && 132 < y && y < 436){ + return Math.floor((x - 223) / 72) }else if(this.songs[this.selectedSong].maker && this.songs[this.selectedSong].maker.id > 0 && this.songs[this.selectedSong].maker.url && x > 230 && x < 485 && y > 446 && y < 533) { return "maker" }else if(550 < x && x < 1050 && 109 < y && y < 538){ @@ -706,6 +715,8 @@ class SongSelect{ this.toSettings() }else if(currentSong.action === "customSongs"){ this.toCustomSongs() + }else if(currentSong.action === "plugins"){ + this.toPlugins() } } this.pointer(false) @@ -864,6 +875,13 @@ class SongSelect{ }, 500) } } + toPlugins(){ + this.playSound("se_don") + this.clean() + setTimeout(() => { + new SettingsView(this.touchEnabled, false, undefined, undefined, plugins.getSettings()) + }, 500) + } redraw(){ if(!this.redrawRunning){ diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js index b4d2e07..76b01bb 100644 --- a/public/src/js/soundbuffer.js +++ b/public/src/js/soundbuffer.js @@ -1,5 +1,8 @@ class SoundBuffer{ - constructor(){ + constructor(...args){ + this.init(...args) + } + init(){ var AudioContext = window.AudioContext || window.webkitAudioContext this.context = new AudioContext() this.audioDecoder = this.context.decodeAudioData.bind(this.context) diff --git a/public/src/js/strings.js b/public/src/js/strings.js index ea4380e..a9d946e 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -21,6 +21,13 @@ var translations = { tw: "Microsoft YaHei, sans-serif", ko: "Microsoft YaHei, sans-serif" }, + intl: { + ja: "ja", + en: "en-GB", + cn: "zh-Hans", + tw: "zh-Hant", + ko: "ko" + }, taikoWeb: { ja: "たいこウェブ", @@ -1278,6 +1285,27 @@ var translations = { en: "This function requires third party cookies.", tw: "此功能需要第三方 cookies。" } + }, + plugins: { + title: { + ja: null, + en: "Plugins" + }, + unloadAll: { + ja: null, + en: "Unload All" + }, + warning: { + ja: null, + en: "You are about to load %s. Plugins should only be loaded if you trust them. Continue?" + }, + plugin: { + ja: null, + en: { + one: "%s plugin", + other: "%s plugins" + } + } } } var allStrings = {} diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 89de402..45408d6 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -1,5 +1,8 @@ class Titlescreen{ - constructor(songId){ + constructor(...args){ + this.init(...args) + } + init(songId){ this.songId = songId db.getItem("customFolder").then(folder => this.customFolder = folder) diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index bb69082..5c96280 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -1,5 +1,8 @@ class Tutorial{ - constructor(fromSongSel, songId){ + constructor(...args){ + this.init(...args) + } + init(fromSongSel, songId){ this.fromSongSel = fromSongSel this.songId = songId loader.changePage("tutorial", true) diff --git a/public/src/js/view.js b/public/src/js/view.js index 686bdc0..45e97d1 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -1,5 +1,8 @@ class View{ - constructor(controller){ + constructor(...args){ + this.init(...args) + } + init(controller){ this.controller = controller this.canvas = document.getElementById("canvas") diff --git a/public/src/js/viewassets.js b/public/src/js/viewassets.js index 62ca25b..58dd7f8 100644 --- a/public/src/js/viewassets.js +++ b/public/src/js/viewassets.js @@ -1,5 +1,8 @@ class ViewAssets{ - constructor(view){ + constructor(...args){ + this.init(...args) + } + init(view){ this.view = view this.controller = this.view.controller this.allAssets = []