diff --git a/app.py b/app.py index a02491e..1d2a14c 100644 --- a/app.py +++ b/app.py @@ -6,7 +6,6 @@ import json import sqlite3 import re import os -import urlparse from flask import Flask, g, jsonify, render_template, request, abort, redirect from ffmpy import FFmpeg diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 5d85708..472730c 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -288,6 +288,7 @@ var string = inputText.split("") var drawn = [] + var quoteOpened = false for(var i = 0; i < string.length; i++){ let symbol = string[i] @@ -314,7 +315,12 @@ }else if(symbol === "…"){ drawn.push({text: symbol, x: bold ? 9 : 0, y: 5, h: 25, rotate: true}) }else if(symbol === '"'){ - drawn.push({text: symbol, x: 0, y: 5, h: 20, rotate: true}) + if(quoteOpened){ + drawn.push({realText: symbol, text: "“", x: -25, y: 10, h: 20}) + }else{ + drawn.push({realText: symbol, text: "”", x: 12, y: 15, h: 20}) + } + quoteOpened = !quoteOpened }else if(r.comma.test(symbol)){ // Comma, full stop if(bold){ @@ -440,14 +446,17 @@ } var scaling = 1 + var strokeScaling = 1 var height = config.height - (ura ? 52 * mul : 0) if(height && drawnHeight > height){ scaling = height / drawnHeight if(config.align === "bottom"){ + strokeScaling = Math.max(0.6, height / drawnHeight) ctx.translate(40 * mul, 0) - ctx.scale(Math.max(0.6, height / drawnHeight), scaling) + ctx.scale(strokeScaling, scaling) ctx.translate(-40 * mul, 0) }else{ + strokeScaling = scaling ctx.scale(1, scaling) } if(config.selectable){ @@ -482,7 +491,7 @@ ctx.strokeStyle = config.outline ctx.lineWidth = config.outlineSize * mul if(config.align === "bottom"){ - ctx.lineWidth /= scaling + ctx.lineWidth /= strokeScaling } ctx.lineJoin = "round" ctx.miterLimit = 1 @@ -632,6 +641,9 @@ }else{ drawn.push({text: symbol, x: -3, y: 13, w: 13, scale: [1.2, 0.7]}) } + }else if(r.tilde.test(symbol)){ + // Hyphen, tilde + drawn.push({text: symbol === "~" ? "~" : symbol, x: 0, y: 0, w: 39}) }else if(r.en.test(symbol)){ // n-width drawn.push({text: symbol, x: 0, y: 0, w: 28}) diff --git a/public/src/js/controller.js b/public/src/js/controller.js index 91204a2..793997f 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -75,7 +75,9 @@ class Controller{ } var ms = this.game.elapsedTime - this.keyboard.checkMenuKeys() + if(this.game.musicFadeOut < 3){ + this.keyboard.checkMenuKeys() + } if(!this.game.isPaused()){ this.keyboard.checkGameKeys() diff --git a/public/src/js/debug.js b/public/src/js/debug.js index ae3877e..ca2cf51 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -20,7 +20,7 @@ class Debug{ this.moving = false pageEvents.add(window, ["mousedown", "mouseup", "blur"], this.stopMove.bind(this)) - pageEvents.add(window, "mousemove", this.onMove.bind(this)) + pageEvents.mouseAdd(this, this.onMove.bind(this)) pageEvents.add(this.titleDiv, "mousedown", this.startMove.bind(this)) pageEvents.add(this.minimiseDiv, "click", this.minimise.bind(this)) pageEvents.add(this.restartBtn, "click", this.restartSong.bind(this)) @@ -173,7 +173,8 @@ class Debug{ clean(){ this.offsetSlider.clean() - pageEvents.remove(window, ["mousedown", "mouseup", "mousemove", "blur"]) + pageEvents.remove(window, ["mousedown", "mouseup", "blur"]) + pageEvents.mouseRemove(this) pageEvents.remove(this.title, "mousedown") pageEvents.remove(this.minimiseDiv, "click") pageEvents.remove(this.restartBtn, "click") diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index f979240..a6c194b 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -24,9 +24,11 @@ this.tjaFiles = [] this.osuFiles = [] + this.assetFiles = {} var metaFiles = [] this.otherFiles = {} this.songs = [] + this.stylesheet = [] this.courseTypes = { "easy": 0, "normal": 1, @@ -53,6 +55,18 @@ this.categories[allStrings[i].categories[ja].toLowerCase()] = ja } } + this.assetSelectors = { + "bg-pattern-1": ".pattern-bg", + "bg_genre_0": "#song-select", + "title-screen": "#title-screen", + "dancing-don": "#loading-don", + "touch_drum": "#touch-drum-img", + "touch_fullscreen": "#touch-full-btn", + "touch_pause": "#touch-pause-btn", + "bg_stage_1": ".song-stage-1", + "bg_stage_2": ".song-stage-2", + "bg_stage_3": ".song-stage-3" + } for(var i = 0; i < files.length; i++){ var file = files[i] @@ -74,8 +88,12 @@ file: file, level: (level * 2) + (name === "genre.ini" ? 1 : 0) }) + }else if(path.indexOf("/taiko-web assets/") !== -1){ + if(!(name in this.assetFiles)){ + this.assetFiles[name] = file + } }else{ - this.otherFiles[file.webkitRelativePath.toLowerCase()] = file + this.otherFiles[path] = file } } @@ -94,6 +112,7 @@ this.osuFiles.forEach(fileObj => { songPromises.push(this.addOsu(fileObj)) }) + songPromises.push(this.addAssets()) Promise.all(songPromises).then(this.loaded.bind(this)) }) } @@ -188,6 +207,9 @@ if(meta.genre){ songObj.category = this.categories[meta.genre.toLowerCase()] || meta.genre } + if(meta.taikowebskin){ + songObj.song_skin = this.getSkin(dir, meta.taikowebskin) + } } if(!songObj.category){ songObj.category = category || this.getCategory(file) @@ -242,6 +264,67 @@ return promise } + addAssets(){ + return new Promise((resolve, reject) => { + var promises = [] + for(let name in this.assetFiles){ + let id = this.getFilename(name) + var file = this.assetFiles[name] + if(name === "vectors.json"){ + var reader = new FileReader() + promises.push(pageEvents.load(reader).then(() => response => { + vectors = JSON.parse(response) + })) + reader.readAsText(file) + } + if(assets.img.indexOf(name) !== -1){ + let image = document.createElement("img") + promises.push(pageEvents.load(image).then(() => { + if(id in this.assetSelectors){ + var selector = this.assetSelectors[id] + this.stylesheet.push(selector + '{background-image:url("' + image.src + '")}') + } + })) + image.id = name + image.src = URL.createObjectURL(file) + loader.assetsDiv.appendChild(image) + assets.image[id].parentNode.removeChild(assets.image[id]) + assets.image[id] = image + } + if(assets.audioSfx.indexOf(name) !== -1){ + assets.sounds[id].clean() + promises.push(this.loadSound(file, name, snd.sfxGain)) + } + if(assets.audioMusic.indexOf(name) !== -1){ + assets.sounds[id].clean() + promises.push(this.loadSound(file, name, snd.musicGain)) + } + if(assets.audioSfxLR.indexOf(name) !== -1){ + assets.sounds[id + "_p1"].clean() + assets.sounds[id + "_p2"].clean() + promises.push(this.loadSound(file, name, snd.sfxGain).then(sound => { + assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL) + assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR) + })) + } + if(assets.audioSfxLoud.indexOf(name) !== -1){ + assets.sounds[id].clean() + promises.push(this.loadSound(file, name, snd.sfxLoudGain)) + } + } + Promise.all(promises).then(resolve, reject) + }) + } + loadSound(file, name, gain){ + var id = this.getFilename(name) + return gain.load(file, true).then(sound => { + assets.sounds[id] = sound + }) + } + getFilename(name){ + return name.slice(0, name.lastIndexOf(".")) + } + getCategory(file){ var path = file.webkitRelativePath.toLowerCase().split("/") for(var i = path.length - 2; i >= 0; i--){ @@ -253,8 +336,55 @@ } } + getSkin(dir, config){ + var configArray = config.toLowerCase().split(",") + var configObj = {} + for(var i in configArray){ + var string = configArray[i].trim() + var space = string.indexOf(" ") + if(space !== -1){ + configObj[string.slice(0, space).trim()] = string.slice(space + 1).trim() + } + } + if(!configObj.dir){ + configObj.dir = "" + } + configObj.prefix = "custom " + var skinnable = ["song", "stage", "don"] + for(var i in skinnable){ + var skinName = skinnable[i] + var skinValue = configObj[skinName] + if(skinValue && skinValue !== "none"){ + var fileName = "bg_" + skinName + "_" + configObj.name + var skinPath = this.joinPath(dir, configObj.dir, fileName) + for(var j = 0; j < 2; j++){ + if(skinValue !== "static"){ + var suffix = (j === 0 ? "_a" : "_b") + ".png" + }else{ + var suffix = ".png" + } + var skinFull = this.normPath(skinPath + suffix) + if(skinFull in this.otherFiles){ + configObj[fileName + suffix] = this.otherFiles[skinFull] + }else{ + configObj[skinName] = null + } + if(skinValue === "static"){ + break + } + } + } + } + return configObj + } + loaded(){ this.songs = this.songs.filter(song => typeof song !== "undefined") + if(this.stylesheet.length){ + var style = document.createElement("style") + style.appendChild(document.createTextNode(this.stylesheet.join("\n"))) + document.head.appendChild(style) + } if(this.songs.length){ assets.songs = this.songs assets.customSongs = true @@ -274,6 +404,40 @@ } } + joinPath(){ + var resultPath = arguments[0] + for(var i = 1; i < arguments.length; i++){ + var pPath = arguments[i] + if(pPath && (pPath[0] === "/" || pPath[0] === "\\")){ + resultPath = pPath + }else{ + var lastChar = resultPath.slice(-1) + if(resultPath && (lastChar !== "/" || lastChar !== "\\")){ + resultPath = resultPath + "/" + } + resultPath = resultPath + pPath + } + } + return resultPath + } + normPath(path){ + path = path.replace(/\\/g, "/").toLowerCase() + while(path[0] === "/"){ + path = path.slice(1) + } + var comps = path.split("/") + for(var i = 0; i < comps.length; i++){ + if(comps[i] === "." || comps[i] === ""){ + comps.splice(i, 1) + i-- + }else if(i !== 0 && comps[i] === ".." && comps[i - 1] !== ".."){ + comps.splice(i - 1, 2) + i -= 2 + } + } + return comps.join("/") + } + clean(){ delete this.loaderDiv delete this.songs diff --git a/public/src/js/loader.js b/public/src/js/loader.js index 27beac8..c5badbd 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -243,7 +243,6 @@ class Loader{ clean(){ var fontDetectDiv = document.getElementById("fontdetectHelper") fontDetectDiv.parentNode.removeChild(fontDetectDiv) - delete this.assetsDiv delete this.loaderPercentage delete this.loaderProgress delete this.promises diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 91ff9e5..3669604 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -26,11 +26,13 @@ class LoadSong{ song.songStage = this.randInt(1, 3) song.donBg = this.randInt(1, 6) + var songObj = assets.songs.find(song => song.id === id) + if(song.songSkin && song.songSkin.name){ var imgLoad = [] for(var type in song.songSkin){ var value = song.songSkin[type] - if(type !== "name" && value && value !== "none"){ + if(["song", "stage", "don"].indexOf(type) !== -1 && value && value !== "none"){ var filename = "bg_" + type + "_" + song.songSkin.name if(value === "static"){ imgLoad.push({ @@ -57,28 +59,34 @@ class LoadSong{ } var skinBase = gameConfig.assets_baseurl + "song_skins/" for(var i = 0; i < imgLoad.length; i++){ + let filename = imgLoad[i].filename + let prefix = song.songSkin.prefix || "" + if((prefix + filename) in assets.image){ + continue + } let img = document.createElement("img") - if(this.touchEnabled && imgLoad[i].type === "song"){ + if(!songObj.music && this.touchEnabled && imgLoad[i].type === "song"){ img.crossOrigin = "Anonymous" } - let filename = imgLoad[i].filename let promise = pageEvents.load(img) if(imgLoad[i].type === "song"){ promises.push(promise.then(() => { - return this.scaleImg(img, filename) + return this.scaleImg(img, filename, prefix) })) }else{ promises.push(promise.then(() => { - assets.image[filename] = img + assets.image[prefix + filename] = img })) } - img.src = skinBase + filename + ".png" + if(songObj.music){ + img.src = URL.createObjectURL(song.songSkin[filename + ".png"]) + }else{ + img.src = skinBase + filename + ".png" + } } } promises.push(this.loadSongBg(id)) - var songObj = assets.songs.find(song => song.id === id) - promises.push(new Promise((resolve, reject) => { if(songObj.sound){ songObj.sound.gain = snd.musicGain @@ -136,7 +144,7 @@ class LoadSong{ img.crossOrigin = "Anonymous" } promises.push(pageEvents.load(img).then(() => { - return this.scaleImg(img, filenameAb) + return this.scaleImg(img, filenameAb, "") })) }else{ promises.push(pageEvents.load(img).then(() => { @@ -150,7 +158,7 @@ class LoadSong{ Promise.all(promises).then(resolve, reject) }) } - scaleImg(img, filename){ + scaleImg(img, filename, prefix){ return new Promise((resolve, reject) => { if(this.touchEnabled){ var canvas = document.createElement("canvas") @@ -163,7 +171,7 @@ class LoadSong{ var saveScaled = url => { let img2 = document.createElement("img") pageEvents.load(img2).then(() => { - assets.image[filename] = img2 + assets.image[prefix + filename] = img2 resolve() }, reject) img2.src = url @@ -176,7 +184,7 @@ class LoadSong{ saveScaled(canvas.toDataURL()) } }else{ - assets.image[filename] = img + assets.image[prefix + filename] = img resolve() } }) diff --git a/public/src/js/view.js b/public/src/js/view.js index 18458d0..81743da 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -999,6 +999,7 @@ var songSkinName = selectedSong.songSkin.name var supportsBlend = "mixBlendMode" in this.songBg.style var songLayers = [document.getElementById("layer1"), document.getElementById("layer2")] + var prefix = selectedSong.songSkin.prefix || "" if(selectedSong.category in this.categories){ var catId = this.categories[selectedSong.category].sort @@ -1009,19 +1010,19 @@ if(!selectedSong.songSkin.song){ var id = selectedSong.songBg this.songBg.classList.add("songbg-" + id) - this.setLayers(songLayers, "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend) + this.setLayers(songLayers, prefix + "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend) }else if(selectedSong.songSkin.song !== "none"){ var notStatic = selectedSong.songSkin.song !== "static" if(notStatic){ this.songBg.classList.add("songbg-" + selectedSong.songSkin.song) } - this.setLayers(songLayers, "bg_song_" + songSkinName + (notStatic ? "_" : ""), notStatic) + this.setLayers(songLayers, prefix + "bg_song_" + songSkinName + (notStatic ? "_" : ""), notStatic) } if(!selectedSong.songSkin.stage){ this.songStage.classList.add("song-stage-" + selectedSong.songStage) }else if(selectedSong.songSkin.stage !== "none"){ - this.setBgImage(this.songStage, assets.image["bg_stage_" + songSkinName].src) + this.setBgImage(this.songStage, assets.image[prefix + "bg_stage_" + songSkinName].src) } } setDonBg(){ @@ -1029,6 +1030,7 @@ var songSkinName = selectedSong.songSkin.name var donLayers = [] var filename = !selectedSong.songSkin.don && this.multiplayer === 2 ? "bg_don2_" : "bg_don_" + var prefix = selectedSong.songSkin.prefix || "" this.donBg = document.createElement("div") this.donBg.classList.add("donbg") @@ -1047,7 +1049,7 @@ var asset1, asset2 if(!selectedSong.songSkin.don){ this.donBg.classList.add("donbg-" + selectedSong.donBg) - this.setLayers(donLayers, filename + selectedSong.donBg, true) + this.setLayers(donLayers, prefix + filename + selectedSong.donBg, true) asset1 = filename + selectedSong.donBg + "a" asset2 = filename + selectedSong.donBg + "b" }else if(selectedSong.songSkin.don !== "none"){ @@ -1060,15 +1062,17 @@ asset1 = filename + songSkinName asset2 = filename + songSkinName } - this.setLayers(donLayers, filename + songSkinName + (notStatic ? "_" : ""), notStatic) + this.setLayers(donLayers, prefix + filename + songSkinName + (notStatic ? "_" : ""), notStatic) + }else{ + return } - var w1 = assets.image[asset1].width - var w2 = assets.image[asset2].width + var w1 = assets.image[prefix + asset1].width + var w2 = assets.image[prefix + asset2].width this.donBg.style.setProperty("--sw", w1 > w2 ? w1 : w2) this.donBg.style.setProperty("--sw1", w1) this.donBg.style.setProperty("--sw2", w2) - this.donBg.style.setProperty("--sh1", assets.image[asset1].height) - this.donBg.style.setProperty("--sh2", assets.image[asset2].height) + this.donBg.style.setProperty("--sh1", assets.image[prefix + asset1].height) + this.donBg.style.setProperty("--sh2", assets.image[prefix + asset2].height) } setDonBgHeight(){ this.donBg.style.setProperty("--h", getComputedStyle(this.donBg).height)