From 71e180c7d7167d315414643b2edab5b13082e122 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Wed, 26 Sep 2018 21:30:57 +0300 Subject: [PATCH] Changed song selection screen --- app.py | 25 +- public/index.html | 1 - public/src/css/game.css | 7 +- public/src/css/main.css | 8 + public/src/css/songselect.css | 167 --- public/src/js/assets.js | 2 - public/src/js/controller.js | 1 - public/src/js/gamepad.js | 42 +- public/src/js/keyboard.js | 79 +- public/src/js/songselect.js | 1708 ++++++++++++++++++++++++++---- public/src/js/titlescreen.js | 18 +- public/src/js/tutorial.js | 13 +- public/src/js/view.js | 12 - public/src/js/viewassets.js | 1 - public/src/views/game.html | 2 +- public/src/views/songselect.html | 5 +- 16 files changed, 1663 insertions(+), 428 deletions(-) delete mode 100644 public/src/css/songselect.css diff --git a/app.py b/app.py index b0f28f2..4ee9267 100644 --- a/app.py +++ b/app.py @@ -69,6 +69,11 @@ def close_connection(exception): @app.route('/api/songs') def route_api_songs(): songs = query_db('select * from songs where enabled = 1') + raw_categories = query_db('select * from categories') + categories = {} + def_category = {'title': None, 'title_en': None} + for cat in raw_categories: + categories[cat[0]] = {'title': cat[1], 'title_en': cat[2]} songs_out = [] for song in songs: osus = [osu for osu in os.listdir('public/songs/%s' % song[0]) if osu in ['easy.osu', 'normal.osu', 'hard.osu', 'oni.osu']] @@ -77,13 +82,19 @@ def route_api_songs(): preview = int(get_osu_key(osud, 'General', 'PreviewTime', 0)) else: preview = 0 - - songs_out.append( - {'id': song[0], 'title': song[1], 'title_en': song[2], 'stars': { - 'easy': song[3], 'normal': song[4], - 'hard': song[5], 'oni': song[6] - }, 'preview': preview} - ) + category_out = categories[song[8]] if song[8] in categories else def_category + + songs_out.append({ + 'id': song[0], + 'title': song[1], + 'title_en': song[2], + 'stars': [ + song[3], song[4], song[5], song[6] + ], + 'preview': preview, + 'category': category_out['title'], + 'category_en': category_out['title_en'] + }) return jsonify(songs_out) diff --git a/public/index.html b/public/index.html index b34e7a9..7adba58 100644 --- a/public/index.html +++ b/public/index.html @@ -19,7 +19,6 @@ - diff --git a/public/src/css/game.css b/public/src/css/game.css index 7f73bbe..e058886 100644 --- a/public/src/css/game.css +++ b/public/src/css/game.css @@ -33,11 +33,14 @@ font-size: 3.5vmin; border-radius: 1.5vmin; } -#pause-menu button:hover{ - border-color:#fa5d3a; +#pause-menu button:hover, +#pause-menu button.selected{ color:white; background:#0c6577; } +#pause-menu button:hover{ + border-color:#fa5d3a; +} #cursor{ position: fixed; width: 1px; diff --git a/public/src/css/main.css b/public/src/css/main.css index 405920a..98635e8 100644 --- a/public/src/css/main.css +++ b/public/src/css/main.css @@ -188,3 +188,11 @@ kbd{ #tutorial-end-button{ font-size: 22pt; } +#song-sel-canvas{ + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; +} diff --git a/public/src/css/songselect.css b/public/src/css/songselect.css deleted file mode 100644 index 5da8c5f..0000000 --- a/public/src/css/songselect.css +++ /dev/null @@ -1,167 +0,0 @@ -@keyframes bgscroll{ - from{ - background-position: 0 0; - } - to{ - background-position: -30vmin 0; - } -} -#song-select{ - width: 100%; - height: 100%; - background: url("/assets/img/bg-pattern-1.png"); - background-size: 30vmin; - animation: bgscroll 8s infinite linear; - white-space: nowrap; -} -#song-container{ - width: 98%; - height: 80%; - padding: 5% 1% 1% 1%; - text-align: center; -} -ul li{ - list-style: none; -} -.difficulties{ - position: absolute; - left: 0; - display: block; - width: 303px; - height: 100%; - opacity: 0; - transition: opacity 0.1s; -} -.song.opened .difficulties{ - opacity: 1; - transition: opacity 0.1s 0.2s; -} -.song-title-char{ - text-align: center; - width: 45px; - display: block; -} -.song-title-char::before{ - content: attr(alt); - position: absolute; - -webkit-text-stroke: 0.25em #000; - z-index: -1; -} -.song-title{ - float: right; - width: 45px; - height: 100%; - padding: 10px 2px; - word-wrap: break-word; - font-size: 22pt; - color: white; - position: relative; - z-index: 1; - line-height: 28px; -} -.song-title-space{ - line-height: 25px; -} -.song{ - font-size: 14pt; - width: 50px; - margin-right: 15px; - height: 100%; - color: black; - display: inline-block; - background: rgba(255, 220, 47, 0.90); - border: 7px outset #f4ae00; - box-shadow: 2px 2px 10px black; - overflow: hidden; - cursor: pointer; - position: relative; - transition: width 0.3s; -} -.song:not(.opened):hover{ - background: rgba(255, 233, 125, 0.90); -} -.opened{ - width: 375px; -} -.difficulty{ - display: none; - cursor: pointer; - width: 35px; - height: 70%; - border-radius: 5px; - display: inline-block; - margin: 5px; - float: left; - background: white; - border: 10px solid #ae7a26; - position: relative; -} -.difficulty .diffname{ - word-wrap: break-word; - width: 20px; - display: block; - margin: auto; - margin-top: 10px; - font-size: 20pt; - margin-left: 6px; - white-space: normal; -} -.difficulty .stars{ - position: absolute; - color: #f12b69; - margin-left: -17px; - width: 100%; - bottom: 10px; -} -.difficulty:hover{ - border-color: #fa5d3a; - color: white; - background: #0c6577; -} -.difficulty:hover .diffname{ - -webkit-text-stroke-width: 1px; - -webkit-text-stroke-color: black; -} -.difficulty:hover .stars{ - color: white; -} -.song.p2:not(.opened)::after, -.difficulty.p2::after{ - content: "P2"; - display: block; - position: absolute; - bottom: 0; - width: 100%; -} -#songsel-help{ - float: right; - background: rgba(255, 255, 255, 0.5); - color: black; - padding: 15px; - margin: 10px; - font-size: 18px; - border: 3px black solid; - border-radius: 50px; - cursor: pointer; -} -.songsel-title-song, -.songsel-title-difficulty{ - position: absolute; - left: -300px; - opacity: 0; - margin: 20px; - color: #fff; - font-size: 7vmin; - z-index: 1; - transition: left 0s 0.2s, opacity 0.2s; -} -#song-select.difficulty-select .songsel-title-difficulty{ - left: 0; - opacity: 1; - transition: left 0.4s 0.2s, opacity 0.4s 0.2s; -} -#song-select:not(.difficulty-select) .songsel-title-song{ - left: 0; - opacity: 1; - transition: left 0.4s 0.2s, opacity 0.4s 0.2s; -} diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 9d8603b..347d3f4 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -32,7 +32,6 @@ var assets = { "don_anim_gogo.png", "don_anim_gogostart.png", "don_anim_clear.png", - "don_anim_endclear.png", "fire_anim.png", "fireworks_anim.png" ], @@ -92,7 +91,6 @@ var assets = { "renda.ogg" ], "audioMusic": [ - "bgm_songsel.ogg", "bgm_result.ogg", "bgm_setsume.ogg" ], diff --git a/public/src/js/controller.js b/public/src/js/controller.js index 85e4b94..e276f9d 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -107,7 +107,6 @@ class Controller{ this.view.togglePauseMenu() } gameEnded(){ - this.view.gameEnded() var score = this.getGlobalScore() var vp if(score.fail === 0){ diff --git a/public/src/js/gamepad.js b/public/src/js/gamepad.js index ceb0a98..aa4f23a 100644 --- a/public/src/js/gamepad.js +++ b/public/src/js/gamepad.js @@ -1,16 +1,6 @@ class Gamepad{ - constructor(keyboard){ - this.keyboard = keyboard - this.game = this.keyboard.controller.game - - var kbd = keyboard.getBindings() - this.gameBtn = {} - this.gameBtn[kbd["don_l"]] = ["u", "d", "l", "r"] - this.gameBtn[kbd["don_r"]] = ["a", "b", "x", "y"] - this.gameBtn[kbd["ka_l"]] = ["lb", "lt"] - this.gameBtn[kbd["ka_r"]] = ["rb", "rt"] - this.menuBtn = {} - this.menuBtn[kbd["pause"]] = ["start"] + constructor(bindings, callback){ + this.bindings = bindings this.b = { "a": 0, "b": 1, @@ -31,15 +21,19 @@ class Gamepad{ "guide": 16 } this.btn = {} + if(callback){ + this.interval = setInterval(() => { + this.play(callback) + }, 100) + } } - play(menuPlay){ - var ms = this.game.getAccurateTime() + play(callback){ if("getGamepads" in navigator){ var gamepads = navigator.getGamepads() }else{ return } - var bindings = menuPlay ? this.menuBtn : this.gameBtn + var bindings = this.bindings for(var i = 0; i < gamepads.length; i++){ if(gamepads[i]){ this.toRelease = {} @@ -51,7 +45,7 @@ class Gamepad{ for(var bind in bindings){ for(var name in bindings[bind]){ if(btnName === this.b[bindings[bind][name]]){ - this.checkButton(gamepads, btnName, bind, ms) + this.checkButton(gamepads, btnName, bind, callback) break buttonSearch } } @@ -62,7 +56,7 @@ class Gamepad{ } } } - checkButton(gamepads, btnName, keyCode, ms){ + checkButton(gamepads, btnName, keyCode, callback){ var button = false for(var i = 0; i < gamepads.length; i++){ @@ -77,7 +71,6 @@ class Gamepad{ } } - var keys = this.keyboard.getKeys() var pressed = !this.btn[btnName] && button var released = this.btn[btnName] && !button @@ -88,19 +81,18 @@ class Gamepad{ } if(pressed){ - if(keys[keyCode]){ - this.keyboard.setKey(keyCode, false) - } - this.keyboard.setKey(keyCode, true, ms) - - }else if(!button && keys[keyCode]){ + callback(true, keyCode) + }else if(!button){ if(released){ this.toRelease[keyCode + "released"] = true } this.toRelease[keyCode]-- if(this.toRelease[keyCode] === 0 && this.toRelease[keyCode + "released"]){ - this.keyboard.setKey(keyCode, false) + callback(false, keyCode) } } } + clean(){ + clearInterval(this.interval) + } } diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 36d799a..73ca1dd 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -9,7 +9,10 @@ class Keyboard{ "ka_l": 67, // C "ka_r": 78, // N "pause": 81, // Q - "back": 8 // Backspace + "back": 8, // Backspace + "previous": 38, // Up + "next": 40, // Down + "confirm": 13 // Enter } this.keys = {} this.waitKeyupScore = {} @@ -19,7 +22,24 @@ class Keyboard{ "don": -Infinity, "ka": -Infinity } - this.gamepad = new Gamepad(this) + + var gameBtn = {} + gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r"] + gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y"] + gameBtn[this.kbd["ka_l"]] = ["lb", "lt"] + gameBtn[this.kbd["ka_r"]] = ["rb", "rt"] + this.gamepad = new Gamepad(gameBtn) + + var menuBtn = { + "cancel": ["a"], + } + menuBtn[this.kbd["confirm"]] = ["b"] + menuBtn[this.kbd["previous"]] = ["u", "l", "lb", "lt"], + menuBtn[this.kbd["next"]] = ["d", "r", "rb", "rt"] + menuBtn[this.kbd["pause"]] = ["start"] + this.gamepadMenu = new Gamepad(menuBtn) + + pageEvents.keyAdd(this, "all", "both", event => { if(event.keyCode === 8){ // Disable back navigation when pressing backspace @@ -48,7 +68,17 @@ class Keyboard{ } checkGameKeys(){ if(!this.controller.autoPlayEnabled){ - this.gamepad.play() + var ms = this.game.getAccurateTime() + this.gamepad.play((pressed, keyCode) => { + if(pressed){ + if(this.keys[keyCode]){ + this.setKey(keyCode, false) + } + this.setKey(keyCode, true, ms) + }else{ + this.setKey(keyCode, false) + } + }) } this.checkKeySound(this.kbd["don_l"], "don") this.checkKeySound(this.kbd["don_r"], "don") @@ -57,10 +87,51 @@ class Keyboard{ } checkMenuKeys(){ if(!this.controller.multiplayer){ - this.gamepad.play(true) + var moveMenu = 0 + var ms = this.game.getAccurateTime() + this.gamepadMenu.play((pressed, keyCode) => { + if(pressed){ + if(this.game.isPaused()){ + if(keyCode === "cancel"){ + return setTimeout(() => { + this.controller.togglePauseMenu() + }, 200) + } + } + if(this.keys[keyCode]){ + this.setKey(keyCode, false) + } + this.setKey(keyCode, true, ms) + }else{ + this.setKey(keyCode, false) + } + }) this.checkKey(this.kbd["pause"], "menu", () => { this.controller.togglePauseMenu() }) + if(this.game.isPaused()){ + this.checkKey(this.kbd["previous"], "menu", () => { + moveMenu = -1 + }) + this.checkKey(this.kbd["next"], "menu", () => { + moveMenu = 1 + }) + this.checkKey(this.kbd["confirm"], "menu", () => { + setTimeout(() => { + document.getElementsByClassName("selected")[0].click() + }, 200) + }) + } + if(moveMenu){ + assets.sounds["ka"].play() + var selected = document.getElementsByClassName("selected")[0] + selected.classList.remove("selected") + var next = selected[(moveMenu === 1 ? "next" : "previous") + "ElementSibling"] + if(!next){ + next = selected.parentNode[(moveMenu === 1 ? "first" : "last") + "ElementChild"] + } + next.classList.add("selected") + } } if(this.controller.multiplayer !== 2){ this.checkKey(this.kbd["back"], "menu", () => { diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 4b37b67..dda0259 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1,206 +1,1523 @@ class SongSelect{ - constructor(){ - this.songs - this.selectedSong = { - "title": "", - "folder": "", - "difficulty": "" - } - this.previewId = 0 - this.diffNames={ - "easy": "かんたん", - "normal": "ふつう", - "hard": "むずかしい", - "oni": "おに" - } + constructor(fromTutorial){ loader.changePage("songselect") - this.run() - } - startPreview(id, prvtime, switchedTo){ - this.endPreview() - var startLoad = +new Date - var currentId = this.previewId - if(!switchedTo){ - snd.musicGain.fadeOut(0.4) + this.canvas = document.getElementById("song-sel-canvas") + this.ctx = this.canvas.getContext("2d") + + this.songSkin = { + "selected": { + background: "#ffdb2c", + border: ["#fff4b5", "#ffa600"], + outline: "#000" + }, + "back": { + background: "#efb058", + border: ["#ffe7bd", "#c68229"], + outline: "#ad7723" + }, + "random": { + background: "#fa91ff", + border: ["#ffdfff", "#b068b2"], + outline: "#b221bb" + }, + "tutorial": { + background: "#9afbe1", + border: ["#d6ffff", "#6bae9c"], + outline: "#31ae94" + }, + "J-POP": { + sort: 0, + background: "#219fbb", + border: ["#7ec3d3", "#0b6773"], + outline: "#005058" + }, + "アニメ": { + sort: 1, + background: "#ff9700", + border: ["#ffdb8c", "#e75500"], + outline: "#9c4100" + }, + "ボーカロイド": { + sort: 2, + background: "#def2ef", + border: ["#f7fbff", "#79919f"], + outline: "#5a6584" + }, + "バラエティ": { + sort: 3, + background: "#8fd321", + border: ["#f7fbff", "#587d0b"], + outline: "#374c00" + }, + "クラシック": { + sort: 4, + background: "#d1a016", + border: ["#e7cf6b", "#9a6b00"], + outline: "#734d00" + }, + "ゲームミュージック": { + sort: 5, + background: "#9c72c0", + border: ["#bda2ce", "#63407e"], + outline: "#4b1c74" + }, + "ナムコオリジナル": { + sort: 6, + background: "#ff5716", + border: ["#ffa66b", "#b53000"], + outline: "#9c2000" + }, + "default": { + sort: 7, + background: "#ececec", + border: ["#fbfbfb", "#8b8b8b"], + outline: "#656565" + } } - var songObj = assets.songs.find(song => song.id == id) - if(songObj.sound){ - this.preview = songObj.sound - this.preview.gain = snd.previewGain - this.previewLoaded(startLoad, prvtime, switchedTo) + this.font = "DFPKanTeiRyu-XB" + + this.songs = [] + for(let song of assets.songs){ + this.songs.push({ + id: song.id, + title: song.title, + skin: this.songSkin[song.category || "default"], + stars: song.stars, + category: song.category, + preview: song.preview || 0 + }) + } + this.songs.sort((a, b) => { + var sortA = this.songSkin[a.category || "default"].sort + var sortB = this.songSkin[b.category || "default"].sort + if(sortA === sortB){ + return a.id > b.id ? 1 : -1 + }else{ + return sortA > sortB ? 1 : -1 + } + }) + this.songs.push({ + title: "もどる", + skin: this.songSkin.back, + action: "back" + }) + this.songs.push({ + title: "ランダムに曲をえらぶ", + skin: this.songSkin.random, + action: "random" + }) + this.songs.push({ + title: "あそびかた説明", + skin: this.songSkin.tutorial, + action: "tutorial" + }) + this.songs.push({ + title: "もどる", + skin: this.songSkin.back, + action: "back" + }) + + this.songAsset = { + marginTop: 90, + marginLeft: 18, + width: 82, + selectedWidth: 382, + height: 452, + border: 6, + innerBorder: 8, + letterBorder: 12 + } + this.diffStar = new Path2D("M3 17 5 11 0 6h6l3-6 3 6h6l-5 5 2 6-6-3") + this.longVowelMark = new Path2D("m1 5c2 3 1 17 .5 25 0 5 6 5 6.5 0C9 22 9 6 7 3 4-2-1 2 1 5") + + this.diffIconPath = [[{w: 40, h: 33},{ + fill: "#ff2803", + d: new Path2D("m27 10c9-9 21 9 5 11 10 9-6 18-12 7C14 39-2 30 8 21-8 19 4 1 13 10 6-4 34-3 27 10Z") + },{ + fill: "#ffb910", + noStroke: true, + d: new Path2D("m12 15c5 1 7 0 8-4 1 4 3 5 8 4-4 3-4 5-2 8-4-4-8-4-12 0 2.2-3 2-5-2-8") + }],[{w: 48, h: 31},{ + fill: "#8daf51", + d: new Path2D("m24 0c-3 0-4 3-5 6-2 6-2 11 0 17 0 0 1 4 5 8 4-4 5-8 5-8C31 17 31 12 29 6 28 3 27 0 24 0M37 2c4 3 7 6 8 8 2 4 3 6 2 13C43 21 39 18 39 18 35 15 32 12 30 8 27 0 32-2 37 2M11 2C7 5 4 8 3 10 1 14 0 16 1 23 5 21 9 18 9 18 13 15 16 12 18 8 21 0 16-2 11 2") + }],[{w: 56, h: 37},{ + fill: "#784439", + d: new Path2D("m26 34v-2c-10 1-12 0-12-7 4-3 8-5 14-5 6 0 10 2 14 5 0 7-2 8-12 7V34Z") + },{ + fill: "#000", + noStroke: true, + d: new Path2D("m18 19v9h8v-9m4 9h8v-9h-8") + },{ + fill: "#414b2b", + d: new Path2D("M8 26C3 26-3 21 2 11 6 5 11 4 18 10c0-6 4-10 10-10 6 0 10 4 10 10 7-6 12-5 16 1 5 10-1 15-6 15-5 0-10-7-20-7-10 0-15 7-20 7") + }],[{w: 29, h: 27},{ + fill: "#db1885", + d: new Path2D("m18 9c1 3 4 4 7 3 0 4 1 11 4 16H0c3-5 4-12 4-16 3 1 6 0 7-3z") + },{ + fill: "#fff", + d: new Path2D("m6 0.5-2 11c4 1.5 6-0.5 6.5-3zm17 0-4.5 8C19 11 21 13 25 11.5ZM5.5 17.5C4.5 23.5 9 25 11 22Zm18 0L18 22c2 3 6.5 1.5 5.5-4.5z") + }]] + this.regex = { + comma: /[,.]/, + ideographicComma: /[、。]/, + apostrophe: /['']/, + brackets: /[\((\))「」『』]/, + tilde: /[\--~~]/, + tall: /[bbddffh-lh-ltt0-90-9]/, + uppercase: /[A-ZA-Z!!]/, + lowercase: /[a-za-z・]/, + latin: /[A-ZA-Z!!a-za-z・]/, + smallHiragana: /[ぁぃぅぇぉっゃゅょァィゥェォッャュョ]/, + hiragana: /[\u3040-\u30ff]/, + todo: /[トド]/ + } + + this.difficulty = ["かんたん", "ふつう", "むずかしい", "おに"] + this.difficultyId = ["easy", "normal", "hard", "oni"] + + this.selectedSong = 0 + this.selectedDiff = 0 + if(fromTutorial){ + this.selectedSong = this.songs.findIndex(song => song.action === "tutorial") }else{ - snd.previewGain.load("/songs/" + id + "/main.mp3").then(sound => { - if(currentId == this.previewId){ - songObj.sound = sound - this.preview = sound - this.previewLoaded(startLoad, prvtime, switchedTo) + if("selectedSong" in localStorage){ + this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length) + } + assets.sounds["song-select"].play() + } + if("selectedDiff" in localStorage){ + this.selectedDiff = Math.min(Math.max(0, localStorage["selectedDiff"] |0), 4) + } + + this.previewId = 0 + this.state = { + screen: fromTutorial ? "song" : "title", + screenMS: this.getMS(), + move: 0, + moveMS: 0, + moveHover: null, + locked: true + } + this.songSelecting = { + speed: 800, + resize: 0.3, + scrollDelay: 0.1 + } + + this.startPreview(true) + + this.pressedKeys = {} + this.gamepad = new Gamepad({ + "13": ["b", "start"], + "8": ["a"], + "37": ["l", "lb", "lt"], + "39": ["r", "rb", "rt"] + }) + + this.startP2() + this.redrawRunning = true + this.redrawBind = this.redraw.bind(this) + this.redraw() + pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) + pageEvents.add(this.canvas, "mousemove", this.mouseMove.bind(this)) + pageEvents.add(this.canvas, "mousedown", this.mouseDown.bind(this)) + } + + keyDown(event, code){ + if(!code){ + code = event.keyCode + } + var key = { + confirm: code == 13 || code == 32 || code == 86 || code == 66, + // Enter, Space, V, B + cancel: code == 27 || code == 8, + // Esc, Backspace + left: code == 37 || code == 67, + // Left, C + right: code == 39 || code == 78 + // Right, N + } + if(key.cancel && event){ + event.preventDefault() + } + if(this.state.screen === "song"){ + if(key.confirm){ + this.toSelectDifficulty() + }else if(key.cancel){ + this.toTitleScreen() + }else if(key.left){ + this.moveToSong(-1) + }else if(key.right){ + this.moveToSong(1) + } + }else if(this.state.screen === "difficulty"){ + if(key.confirm){ + if(this.selectedDiff === 0){ + this.toSongSelect() + }else{ + this.toLoadSong(this.selectedDiff - 1, event.shiftKey, event.ctrlKey) } + }else if(key.cancel){ + this.toSongSelect() + }else if(key.left){ + this.moveToDiff(-1) + }else if(key.right){ + this.moveToDiff(1) + } + } + } + + mouseDown(event){ + if(event.which !== 1){ + return + } + var mouse = this.mouseOffset(event) + if(this.state.screen === "song"){ + var moveBy = this.songSelMouse(mouse.x, mouse.y) + if(moveBy === 0){ + this.toSelectDifficulty() + }else if(moveBy !== null){ + this.moveToSong(moveBy) + } + }else if(this.state.screen === "difficulty"){ + var moveBy = this.diffSelMouse(mouse.x, mouse.y) + if( + moveBy === 0 + || mouse.x < 55 || mouse.x > 967 + || mouse.y < 40 || mouse.y > 540 + ){ + this.toSongSelect() + }else if(moveBy !== null){ + this.toLoadSong(moveBy - 1, event.shiftKey, event.ctrlKey) + } + } + } + mouseMove(event){ + var mouse = this.mouseOffset(event) + if(this.state.screen === "song"){ + var moveTo = this.songSelMouse(mouse.x, mouse.y) + if(moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].stars){ + this.state.moveMS = this.getMS() - this.songSelecting.speed + } + this.state.moveHover = moveTo + }else if(this.state.screen === "difficulty"){ + var moveTo = this.diffSelMouse(mouse.x, mouse.y) + if(moveTo === null && this.state.moveHover === this.selectedDiff){ + this.state.moveMS = this.getMS() - 1000 + } + this.state.moveHover = moveTo + } + } + mouseOffset(event){ + return { + x: (event.offsetX * this.pixelRatio - this.winW / 2) / this.ratio + 1024 / 2, + y: (event.offsetY * this.pixelRatio - this.winH / 2) / this.ratio + 720 / 2 + } + } + + songSelMouse(x, y){ + if(this.state.locked === 0 && this.songAsset.marginTop <= y && y <= this.songAsset.marginTop + this.songAsset.height){ + x -= 1024 / 2 + var dir = x > 0 ? 1 : -1 + x = Math.abs(x) + var selectedWidth = this.songAsset.selectedWidth + if(!this.songs[this.selectedSong].stars){ + selectedWidth = this.songAsset.width + } + var moveBy = Math.ceil((x - selectedWidth / 2 - this.songAsset.marginLeft / 2) / (this.songAsset.width + this.songAsset.marginLeft)) * dir + if(moveBy / dir > 0){ + return moveBy + }else{ + return 0 + } + } + return null + } + diffSelMouse(x, y){ + if(this.state.locked === 0){ + if(100 < x && x < 160 && 120 < y && y < 420){ + return 0 + }else if(434 < x && x < 810 && 95 < y && y < 524){ + var moveBy = Math.floor((x - 434) / ((810 - 434) / 4)) + 1 + var currentSong = this.songs[this.selectedSong] + if(currentSong.stars[moveBy - 1]){ + return moveBy + } + } + } + return null + } + + moveToSong(moveBy){ + if(this.state.locked !== 1){ + var ms = this.getMS() + if(this.songs[this.selectedSong].stars && this.state.locked === 0){ + this.state.moveMS = ms + }else{ + this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + } + this.state.move = moveBy + this.state.lastMove = moveBy + this.state.locked = 1 + this.state.moveHover = null + + var lastMoveMul = Math.pow(Math.abs(moveBy), 1 / 4) + var changeSpeed = this.songSelecting.speed * lastMoveMul + var resize = changeSpeed * this.songSelecting.resize / lastMoveMul + var scrollDelay = changeSpeed * this.songSelecting.scrollDelay + var resize2 = changeSpeed - resize + var scroll = resize2 - resize - scrollDelay * 2 + + var soundsDelay = Math.abs((scroll + resize) / moveBy) + + for(var i = 0; i < Math.abs(moveBy) - 1; i++){ + assets.sounds["ka"].play((resize + i * soundsDelay) / 1000) + } + } + } + moveToDiff(moveBy){ + if(this.state.locked !== 1){ + this.state.move = moveBy + this.state.moveMS = this.getMS() - 500 + this.state.locked = 1 + assets.sounds["ka"].play() + } + } + toSelectDifficulty(){ + if(this.state.locked === 0){ + var currentSong = this.songs[this.selectedSong] + if(currentSong.stars){ + this.state.screen = "difficulty" + this.state.screenMS = this.getMS() + this.state.locked = true + this.state.moveHover = null + + assets.sounds["don"].play() + assets.sounds["song-select"].stop() + assets.sounds["diffsel"].play(0.3) + }else if(currentSong.action === "back"){ + this.clean() + this.toTitleScreen() + }else if(currentSong.action === "random"){ + assets.sounds["don"].play() + this.state.locked = true + do{ + var i = Math.floor(Math.random() * this.songs.length) + }while(!this.songs[i].stars) + var moveBy = i - this.selectedSong + setTimeout(() => { + this.moveToSong(moveBy) + }, 200) + }else if(currentSong.action === "tutorial"){ + this.toTutorial() + } + } + } + toSongSelect(){ + if(this.state.locked !== 1){ + this.state.screen = "song" + this.state.screenMS = this.getMS() + this.state.locked = true + this.state.moveHover = null + + assets.sounds["diffsel"].stop() + assets.sounds["cancel"].play() + } + } + toLoadSong(difficulty, shift, ctrl){ + this.clean() + var selectedSong = this.songs[this.selectedSong] + assets.sounds["diffsel"].stop() + assets.sounds["don"].play() + + localStorage["selectedSong"] = this.selectedSong + localStorage["selectedDiff"] = this.selectedDiff + + new loadSong({ + "title": selectedSong.title, + "folder": selectedSong.id, + "difficulty": this.difficultyId[difficulty] + }, shift, ctrl) + } + toTitleScreen(){ + assets.sounds["cancel"].play() + this.clean() + setTimeout(() => { + new Titlescreen() + }, 500) + } + toTutorial(){ + assets.sounds["don"].play() + this.clean() + setTimeout(() => { + new Tutorial(true) + }, 500) + } + + redraw(){ + if(!this.redrawRunning){ + return + } + requestAnimationFrame(this.redrawBind) + var ms = this.getMS() + + this.gamepad.play((pressed, keyCode) => { + if(pressed){ + if(!this.pressedKeys[keyCode]){ + this.pressedKeys[keyCode] = ms + 300 + this.keyDown(false, keyCode) + } + }else{ + this.pressedKeys[keyCode] = 0 + } + }) + for(var key in this.pressedKeys){ + if(this.pressedKeys[key]){ + if(ms >= this.pressedKeys[key] + 100){ + this.keyDown(false, key) + this.pressedKeys[key] = ms + } + } + } + + if(!this.redrawRunning){ + return + } + + var ctx = this.ctx + var winW = innerWidth + var winH = innerHeight + if(winW / 32 > winH / 9){ + winW = winH / 9 * 32 + }else if(winH / 9 > winW / 16){ + winH = winW / 16 * 9 + } + this.pixelRatio = window.devicePixelRatio || 1 + winW *= this.pixelRatio + winH *= this.pixelRatio + var ratioX = winW / 1280 + var ratioY = winH / 720 + var ratio = (ratioX < ratioY ? ratioX : ratioY) + if(this.winW !== winW || this.winH !== winH){ + this.canvas.width = winW + this.canvas.height = winH + ctx.scale(ratio, ratio) + this.canvas.style.width = (winW / this.pixelRatio) + "px" + this.canvas.style.height = (winH / this.pixelRatio) + "px" + }else if(!document.hasFocus()){ + return + }else{ + ctx.clearRect(0, 0, winW / ratio, winH / ratio) + } + this.winW = winW + this.winH = winH + this.ratio = ratio + winW /= ratio + winH /= ratio + + var frameTop = winH / 2 - 720 / 2 + var frameLeft = winW / 2 - 1280 / 2 + var songTop = frameTop + this.songAsset.marginTop + var xOffset = 0 + var songSelMoving = false + var screen = this.state.screen + var selectedWidth = this.songAsset.width + + if(screen === "title"){ + if(ms > this.state.screenMS + 1000){ + this.state.screen = "song" + this.state.screenMS = ms + (ms - this.state.screenMS - 1000) + this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS) + this.state.locked = 3 + this.state.lastMove = 1 + }else{ + this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS - 1000) + } + } + + if(screen === "title" || screen === "song"){ + this.drawLayeredText({ + text: "曲をえらぶ", + fontSize: 48, + fontFamily: this.font, + x: frameLeft + 53, + y: frameTop + 30, + letterSpacing: 2 + }, [ + {x: -2, y: -2, outline: "#000", letterBorder: 22}, + {}, + {x: 2, y: 2, shadow: true}, + {x: -2, y: -2, outline: "#ff797b", letterBorder: 10}, + {x: 2, y: 2, outline: "#ad1516"}, + {outline: "#f70808"}, + {fill: "#fff"} + ]) + + var category = this.songs[this.selectedSong].category + if(category){ + this.drawLayeredText({ + text: category, + fontSize: 40, + fontFamily: this.font, + x: winW / 2, + y: frameTop + 38, + width: 255, + center: true + }, [ + {outline: this.songs[this.selectedSong].skin.outline, letterBorder: 12, shadow: true}, + {fill: "#fff"} + ]) + } + } + + if(screen === "song"){ + if(this.songs[this.selectedSong].stars){ + selectedWidth = this.songAsset.selectedWidth + } + + var lastMoveMul = Math.pow(Math.abs(this.state.lastMove), 1 / 4) + var changeSpeed = this.songSelecting.speed * lastMoveMul + var resize = changeSpeed * this.songSelecting.resize / lastMoveMul + var scrollDelay = changeSpeed * this.songSelecting.scrollDelay + var resize2 = changeSpeed - resize + var scroll = resize2 - resize - scrollDelay * 2 + var elapsed = ms - this.state.moveMS + if(this.state.move && ms > this.state.moveMS + resize2 - scrollDelay){ + assets.sounds["ka"].play() + this.selectedSong = this.mod(this.songs.length, this.selectedSong + this.state.move) + this.state.move = 0 + this.state.locked = 2 + } + if(this.state.moveMS && ms < this.state.moveMS + changeSpeed){ + xOffset = Math.min(scroll, Math.max(0, elapsed - resize - scrollDelay)) / scroll * (this.songAsset.width + this.songAsset.marginLeft) + xOffset *= -this.state.move + if(elapsed < resize){ + selectedWidth = this.songAsset.width + (((resize - elapsed) / resize) * (selectedWidth - this.songAsset.width)) + }else if(elapsed > resize2){ + this.state.locked = 1 + selectedWidth = this.songAsset.width + ((elapsed - resize2) / resize * (selectedWidth - this.songAsset.width)) + }else{ + songSelMoving = true + selectedWidth = this.songAsset.width + } + }else{ + this.state.locked = 0 + } + }else if(screen === "difficulty"){ + var currentSong = this.songs[this.selectedSong] + if(this.state.locked){ + this.state.locked = 0 + } + if(this.state.move){ + do{ + this.selectedDiff = this.mod(5, this.selectedDiff + this.state.move) + }while(this.selectedDiff !== 0 && !currentSong.stars[this.selectedDiff - 1]) + this.state.move = 0 + }else if(!currentSong.stars[this.selectedDiff - 1]){ + this.selectedDiff = 0 + } + } + + if(songSelMoving){ + if(this.previewing){ + this.endPreview() + } + }else if(screen !== "title"){ + if(this.previewing !== this.selectedSong){ + this.startPreview() + } + } + + if(screen === "title" || screen === "song"){ + for(var i = this.selectedSong - 1; ; i--){ + var highlight = 0 + if(i - this.selectedSong === this.state.moveHover){ + highlight = 1 + } + var index = this.mod(this.songs.length, i) + var _x = winW / 2 - (this.selectedSong - i) * (this.songAsset.width + this.songAsset.marginLeft) - selectedWidth / 2 + xOffset + if(_x + this.songAsset.width + this.songAsset.marginLeft < 0){ + break + } + this.drawClosedSong({ + x: _x, + y: songTop, + song: this.songs[index], + highlight: highlight + }) + } + for(var i = this.selectedSong + 1; ; i++){ + var highlight = 0 + if(i - this.selectedSong === this.state.moveHover){ + highlight = 1 + } + var index = this.mod(this.songs.length, i) + var currentSong = this.songs[index] + var _x = winW / 2 + (i - this.selectedSong - 1) * (this.songAsset.width + this.songAsset.marginLeft) + this.songAsset.marginLeft + selectedWidth / 2 + xOffset + if(_x > winW){ + break + } + this.drawClosedSong({ + x: _x, + y: songTop, + song: this.songs[index], + highlight: highlight + }) + } + } + + var currentSong = this.songs[this.selectedSong] + var highlight = 0 + if(!currentSong.stars){ + highlight = 2 + } + if(this.state.moveHover === 0){ + highlight = 1 + } + var selectedSkin = this.songSkin.selected + if(screen === "title" || this.state.locked === 3){ + selectedSkin = currentSong.skin + highlight = 2 + }else if(songSelMoving){ + selectedSkin = currentSong.skin + highlight = 0 + } + var selectedHeight = this.songAsset.height + if(screen === "difficulty"){ + selectedWidth = 912 + selectedHeight = 502 + highlight = 0 + } + + this.drawSongFrame({ + x: winW / 2 - selectedWidth / 2 + xOffset, + y: songTop + this.songAsset.height - selectedHeight, + width: selectedWidth, + height: selectedHeight, + background: selectedSkin.background, + border: selectedSkin.border, + highlight: highlight, + noCrop: screen === "difficulty", + innerContent: (x, y, w, h) => { + ctx.strokeStyle = "#000" + if(screen === "title" || screen === "song"){ + var opened = ((selectedWidth - this.songAsset.width) / (this.songAsset.selectedWidth - this.songAsset.width)) + var songSel = true + }else{ + this.drawLayeredText({ + text: "むずかしさをえらぶ", + fontSize: 46, + fontFamily: this.font, + x: x - 144, + y: y - 24, + width: 280 + }, [ + {x: -2, y: -2, outline: "#000", letterBorder: 23}, + {shadow: true}, + {x: 2, y: 2}, + {x: -2, y: -2, outline: "#ff797b", letterBorder: 12}, + {x: 2, y: 2, outline: "#ad1516"}, + {outline: "#f70808"}, + {fill: "#fff"} + ]) + var opened = 1 + var songSel = false + var _x = x + 62 + var _y = y + 67 + ctx.fillStyle = "#efb058" + ctx.lineWidth = 5 + this.drawRoundedRect({ + x: _x - 28, + y: _y, + w: 56, + h: 298, + radius: 24 + }) + ctx.fill() + ctx.stroke() + ctx.fillStyle = "#f7d39c" + ctx.beginPath() + ctx.arc(_x, _y + 28, 20, 0, Math.PI * 2) + ctx.fill() + this.drawDiffOptionsIcon({x: _x, y: _y + 28}) + + this.drawVerticalText({ + text: "もどる", + x: _x, + y: _y + 57, + width: 56, + height: 220, + fill: "#fff", + outline: "#000", + letterBorder: 4, + fontSize: 28, + fontFamily: this.font, + letterSpacing: 4 + }) + var highlight = 0 + if(this.state.moveHover === 0){ + highlight = 2 + }else if(this.selectedDiff === 0){ + highlight = 1 + } + if(highlight){ + this.drawHighlight({ + x: _x - 32, + y: _y - 3, + w: 64, + h: 304, + animate: highlight === 1, + opacity: highlight === 2 ? 0.8 : 1, + radius: 24 + }) + this.drawDiffCursor({ + x: _x, + y: _y - 45 + }) + } + } + for(var i = 0; currentSong.stars && i < 4; i++){ + if(currentSong.stars[i]){ + if(songSel){ + var _x = x + 33 + i * 60 + var _y = y + 120 + ctx.fillStyle = "#ff9f18" + ctx.beginPath() + ctx.arc(_x, _y + 22, 22, -Math.PI, 0) + ctx.arc(_x, _y + 266, 22, 0, Math.PI) + ctx.fill() + this.drawDiffIcon({ + diff: i, + x: _x, + y: _y - 8, + scale: 1, + border: 6 + }) + }else{ + var _x = x + 402 + i * 100 + var _y = y + 87 + this.drawDiffIcon({ + diff: i, + x: _x, + y: _y - 12, + scale: 1.4, + border: 6.5, + noFill: true + }) + ctx.fillStyle = "#aa7023" + ctx.lineWidth = 4.5 + ctx.fillRect(_x - 35.5, _y + 2, 71, 380) + ctx.strokeRect(_x - 35.5, _y + 2, 71, 380) + ctx.fillStyle = "#fff" + ctx.lineWidth = 2.5 + ctx.fillRect(_x - 28, _y + 19, 56, 351) + ctx.strokeRect(_x - 28, _y + 19, 56, 351) + this.drawDiffIcon({ + diff: i, + x: _x, + y: _y - 12, + scale: 1.4, + border: 4.5 + }) + } + this.drawVerticalText({ + text: this.difficulty[i], + x: _x, + y: songSel ? _y + 10 : _y + 23, + width: songSel ? 44 : 56, + height: songSel ? (i === 1 ? 66 : 88) : (i === 0 ? 130 : i === 1 ? 110 : 135), + fill: "#000", + fontSize: songSel ? 25 : (i === 2 ? 45 : 40), + fontFamily: this.font + }) + for(var j = 0; j < 10; j++){ + if(songSel){ + var yPos = _y + 113 + j * 17 + }else{ + var yPos = _y + 178 + j * 19.5 + } + if(10 - j > currentSong.stars[i]){ + ctx.fillStyle = songSel ? "#e97526" : "#e7e7e7" + ctx.beginPath() + ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2) + ctx.fill() + }else{ + ctx.save() + ctx.fillStyle = songSel ? "#fff" : "#f72568" + if(songSel){ + ctx.shadowColor = "#fff" + ctx.shadowBlur = 10 + ctx.translate(_x - 9, yPos - 9) + }else{ + ctx.translate(_x - 10.5, yPos - 9.5) + ctx.scale(1.1, 1.1) + } + ctx.fill(this.diffStar) + ctx.restore() + } + } + if(i === currentSong.p2Cursor){ + this.drawDiffCursor({ + x: _x, + y: _y - (songSel ? 45 : 65), + two: true, + side: songSel ? false : (currentSong.p2Cursor === this.selectedDiff - 1), + scale: songSel ? 0.7 : 1 + }) + } + if(!songSel){ + var highlight = 0 + var currentDiff = this.selectedDiff - 1 + if(this.state.moveHover - 1 === i){ + highlight = 2 + }else if(currentDiff === i){ + highlight = 1 + } + if(currentDiff === i){ + this.drawDiffCursor({ + x: _x, + y: _y - 65, + side: currentSong.p2Cursor === currentDiff + }) + } + if(highlight){ + this.drawHighlight({ + x: _x - 32, + y: _y + 14, + w: 64, + h: 362, + animate: highlight === 1, + opacity: highlight === 2 ? 0.8 : 1 + }) + } + } + } + } + ctx.globalAlpha = 1 - Math.max(0, opened - 0.5) * 2 + ctx.fillStyle = selectedSkin.background + ctx.fillRect(x,y,w,h) + ctx.globalAlpha = 1 + var textX = Math.max(w - 37, w / 2) + var textY = opened * 12 + (1 - opened) * 7 + this.drawVerticalText({ + text: currentSong.title, + x: x + textX, + y: y + textY, + width: w, + height: h - 35, + fill: "#fff", + outline: selectedSkin.outline, + fontSize: 40, + fontFamily: this.font + }) + } + }) + + if(songSelMoving){ + this.drawHighlight({ + x: winW / 2 - selectedWidth / 2, + y: songTop, + w: selectedWidth, + h: selectedHeight, + opacity: 0.8 }) } } - previewLoaded(startLoad, prvtime, switchedTo){ - var endLoad = +new Date + + drawRoundedRect(config){ + var ctx = this.ctx + var x = config.x + var y = config.y + var w = config.w + var h = config.h + var r = config.radius + ctx.beginPath() + ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5) + ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, 0) + ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2) + ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI) + ctx.lineTo(x, y + r) + } + + drawClosedSong(config){ + config.width = this.songAsset.width + config.height = this.songAsset.height + config.background = config.song.skin.background + config.border = config.song.skin.border + config.outline = config.song.skin.outline + config.text = config.song.title + config.innerContent = (x, y, w, h) => { + this.drawVerticalText({ + text: config.text, + x: x + w / 2, + y: y + 7, + width: w, + height: h - 35, + fill: "#fff", + outline: config.outline, + fontSize: 40, + fontFamily: this.font + }) + } + this.drawSongFrame(config) + if(config.song.p2Cursor){ + this.drawDiffCursor({ + x: config.x + 48, + y: config.y - 27, + two: true, + scale: 1, + side: true + }) + } + } + + drawSongFrame(config){ + var ctx = this.ctx + var x = config.x + var y = config.y + var w = config.width + var h = config.height + var border = this.songAsset.border + var innerBorder = this.songAsset.innerBorder + var allBorders = border + innerBorder + var innerX = x + allBorders + var innerY = y + allBorders + var innerW = w - allBorders * 2 + var innerH = h - allBorders * 2 + + ctx.save() + + ctx.shadowColor = "rgba(0, 0, 0, 0.5)" + ctx.shadowBlur = 10 + ctx.shadowOffsetX = 5 + ctx.shadowOffsetY = 5 + ctx.fillStyle = "#000" + ctx.fillRect(x, y, w, h) + + ctx.restore() + ctx.save() + + { + let _x = x + border + let _y = y + border + let _w = w - border * 2 + let _h = h - border * 2 + ctx.fillStyle = config.border[1] + ctx.fillRect(_x, _y, _w, _h) + ctx.fillStyle = config.border[0] + ctx.beginPath() + ctx.moveTo(_x, _y) + ctx.lineTo(_x + _w, _y) + ctx.lineTo(_x + _w - innerBorder, _y + innerBorder) + ctx.lineTo(_x + innerBorder, _y + _h - innerBorder) + ctx.lineTo(_x, _y + _h) + ctx.fill() + } + ctx.fillStyle = config.background + ctx.fillRect(innerX, innerY, innerW, innerH) + + ctx.save() + + ctx.strokeStyle = "rgba(255, 255, 255, 0.3)" + ctx.lineWidth = 3 + ctx.strokeRect(innerX, innerY, innerW, innerH) + if(!config.noCrop){ + ctx.beginPath() + ctx.rect(innerX, innerY, innerW, innerH) + ctx.clip() + } + + config.innerContent(innerX, innerY, innerW, innerH) + + ctx.restore() + + if(config.highlight){ + this.drawHighlight({ + x: x, + y: y, + w: w, + h: h, + animate: config.highlight === 2, + opacity: config.highlight === 1 ? 0.8 : 1 + }) + } + + ctx.restore() + } + + drawHighlight(config){ + var ctx = this.ctx + ctx.save() + var _x = config.x + 3.5 + var _y = config.y + 3.5 + var _w = config.w - 7 + var _h = config.h - 7 + var rect = () => { + if(config.radius){ + this.drawRoundedRect({x: _x, y: _y, w: _w, h: _h, radius: config.radius}) + ctx.stroke() + }else{ + ctx.strokeRect(_x, _y, _w, _h) + } + } + if(config.animate){ + ctx.globalAlpha = this.fade((this.getMS() - this.state.moveMS) % 2000 / 2000) + }else if(config.opacity){ + ctx.globalAlpha = config.opacity + } + ctx.strokeStyle = "rgba(255, 249, 1, 0.45)" + ctx.lineWidth = 14 + rect() + ctx.strokeStyle = "rgba(255, 249, 1, .8)" + ctx.lineWidth = 8 + rect() + ctx.strokeStyle = "#fff" + ctx.lineWidth = 6 + rect() + + ctx.restore() + } + fade(pos){ + if(pos < 0.5){ + pos = 1 - pos + } + return (1 - Math.cos(Math.PI * pos * 2)) / 2 + } + + drawVerticalText(config){ + var ctx = this.ctx + var inputText = config.text + var mul = config.fontSize / 40 + var ura = false + + if(inputText.endsWith(" (裏)")){ + inputText = inputText.slice(0, -4) + ura = true + }else if(inputText.endsWith("(裏)")){ + inputText = inputText.slice(0, -3) + ura = true + } + var string = inputText.split("") + var drawn = [] + + var r = this.regex + for(let symbol of string){ + if(symbol === " "){ + // Space + drawn.push({text: symbol, x: 0, y: 0, h: 18}) + }else if(symbol === "ー"){ + // Long-vowel mark + drawn.push({svg: this.longVowelMark, x: -4, y: 5, h: 33, scale: [mul, mul]}) + }else if(r.comma.test(symbol)){ + // Comma, full stop + drawn.push({text: symbol, x: 16, y: -7, h: 0, scale: [1.2, 0.7]}) + }else if(r.ideographicComma.test(symbol)){ + // Ideographic comma, full stop + drawn.push({text: symbol, x: 16, y: -16, h: 18}) + }else if(r.apostrophe.test(symbol)){ + // Apostrophe + drawn.push({text: ",", x: 20, y: -39, h: 0, scale: [1.2, 0.7]}) + }else if(r.brackets.test(symbol)){ + // Rotated brackets + drawn.push({text: symbol, x: 0, y: -5, h: 25, rotate: true}) + }else if(r.tilde.test(symbol)){ + // Rotated hyphen, tilde + if(symbol === "~"){ + symbol = "~" + } + drawn.push({text: symbol, x: 0, y: 2, h: 35, rotate: true}) + }else if(r.tall.test(symbol)){ + // Tall latin script lowercase, numbers + drawn.push({text: symbol, x: 0, y: 4, h: 34, scale: [1.05, 0.9]}) + }else if(r.uppercase.test(symbol)){ + // Latin script upper case + drawn.push({text: symbol, x: 0, y: 1, h: 31.5}) + }else if(r.lowercase.test(symbol)){ + // Latin script lower case + drawn.push({text: symbol, x: 0, y: -1, h: 28, scale: [1.05, 0.9]}) + }else if(r.smallHiragana.test(symbol)){ + // Small hiragana, small katakana + drawn.push({text: symbol, x: 0, y: -8, h: 25, right: true}) + }else if(r.hiragana.test(symbol)){ + // Hiragana, katakana + drawn.push({text: symbol, x: 0, y: 5, h: 38, right: r.todo.test(symbol)}) + }else{ + // Kanji, other + drawn.push({text: symbol, x: 0, y: 3, h: 39, right: true}) + } + } + + var drawnHeight = 0 + for(let symbol of drawn){ + if(config.letterSpacing){ + symbol.h += config.letterSpacing + } + drawnHeight += symbol.h * mul + } + + ctx.save() + ctx.translate(config.x, config.y) + + var scale = 1 + if(config.height){ + var height = config.height - (ura ? 52 * mul : 0) + if(drawnHeight > height){ + scale = height / drawnHeight + ctx.scale(1, scale) + } + } + + if(ura){ + // Circled ura + drawn.push({text: "裏", x: 0, y: 18, h: 52, ura: true, scale: [1, 1 / scale]}) + } + + var actions = [] + if(config.outline){ + actions.push("stroke") + } + if(config.fill){ + actions.push("fill") + } + for(let action of actions){ + ctx.font = config.fontSize + "px " + config.fontFamily + ctx.textBaseline = "top" + if(action === "stroke"){ + ctx.strokeStyle = config.outline + ctx.lineWidth = this.songAsset.letterBorder * mul + ctx.lineJoin = "round" + ctx.miterLimit = 1 + }else if(action === "fill"){ + ctx.fillStyle = config.fill + } + var offsetY = 0 + + for(let symbol of drawn){ + var saved = false + var currentX = symbol.x + if(symbol.right){ + currentX += 20 * mul + } + var currentY = offsetY + symbol.y * mul + if(symbol.rotate || symbol.scale || symbol.svg || symbol.ura){ + saved = true + ctx.save() + + if(symbol.rotate){ + ctx.translate(currentX + 20 * mul, currentY + 20 * mul) + ctx.rotate(Math.PI / 2) + }else{ + ctx.translate(currentX, currentY) + } + if(symbol.scale){ + ctx.scale(symbol.scale[0], symbol.scale[1]) + ctx.lineWidth = ctx.lineWidth / symbol.scale[0] + } + currentX = 0 + currentY = 0 + } + if(symbol.svg){ + ctx[action](symbol.svg) + }else{ + if(symbol.right){ + ctx.textAlign = "right" + }else{ + ctx.textAlign = "center" + } + if(symbol.ura){ + ctx.font = (30 * mul) + "px Meiryo" + ctx.textBaseline = "center" + ctx.beginPath() + ctx.arc(currentX, currentY + (21.5 * mul), (18 * mul), 0, Math.PI * 2) + if(action === "stroke"){ + ctx.fillStyle = config.outline + ctx.fill() + }else if(action === "fill"){ + ctx.strokeStyle = config.fill + ctx.lineWidth = 2.5 * mul + ctx.fillText(symbol.text, currentX, currentY) + } + ctx.stroke() + }else{ + ctx[action + "Text"](symbol.text, currentX, currentY) + } + } + offsetY += symbol.h * mul + if(saved){ + ctx.restore() + } + } + } + ctx.restore() + } + + drawLayeredText(config, layers){ + var ctx = this.ctx + var mul = config.fontSize / 40 + ctx.save() + + var string = config.text.split("") + var drawn = [] + + var r = this.regex + for(let symbol of string){ + if(symbol === "-"){ + drawn.push({text: symbol, x: -4, y: 0, w: 28, scale: [0.8, 1]}) + }else if(r.latin.test(symbol)){ + // Latin script + drawn.push({text: symbol, x: 0, y: 0, w: 32}) + }else if(r.smallHiragana.test(symbol)){ + // Small hiragana, small katakana + drawn.push({text: symbol, x: 0, y: 0, w: 30}) + }else if(r.hiragana.test(symbol)){ + // Hiragana, katakana + drawn.push({text: symbol, x: 0, y: 0, w: 35}) + }else{ + drawn.push({text: symbol, x: 0, y: 0, w: 39}) + } + } + + var drawnWidth = 0 + for(let symbol of drawn){ + if(config.letterSpacing){ + symbol.w += config.letterSpacing + } + drawnWidth += symbol.w + } + + ctx.translate(config.x, config.y) + var scale = 1 + if(config.width && drawnWidth > config.width){ + scale = config.width / drawnWidth + ctx.scale(scale, 1) + } + ctx.font = config.fontSize + "px " + config.fontFamily + ctx.textBaseline = "top" + ctx.textAlign = "center" + + for(let layer of layers){ + var action = "strokeText" + if(layer.outline){ + ctx.strokeStyle = layer.outline + ctx.lineJoin = "round" + ctx.miterLimit = 1 + } + if(layer.letterBorder){ + ctx.lineWidth = layer.letterBorder + } + if(layer.fill){ + ctx.fillStyle = layer.fill + action = "fillText" + } + if(layer.shadow){ + ctx.save() + ctx.shadowColor = "rgba(0, 0, 0, 0.5)" + ctx.shadowBlur = 3 + ctx.shadowOffsetX = 3 + ctx.shadowOffsetY = 3 + } + var offsetX = 0 + for(let symbol of drawn){ + var saved = false + var currentX = offsetX + symbol.x + (layer.x || 0) + symbol.w / 2 + var currentY = symbol.y + (layer.y || 0) + if(config.center){ + currentX -= drawnWidth / 2 + } + if(symbol.scale){ + saved = true + ctx.save() + ctx.translate(currentX, currentY) + ctx.scale(symbol.scale[0], symbol.scale[1]) + currentX = 0 + currentY = 0 + } + ctx[action](symbol.text, currentX, currentY) + if(saved){ + ctx.restore() + } + offsetX += symbol.w * mul + } + if(layer.shadow){ + ctx.restore() + } + } + ctx.restore() + } + + drawDiffIcon(config){ + var ctx = this.ctx + var scale = config.scale + ctx.save() + ctx.lineWidth = config.border + ctx.strokeStyle = "#000" + var icon = this.diffIconPath[config.diff] + ctx.translate(config.x - icon[0].w * scale / 2, config.y - icon[0].h * scale / 2) + ctx.scale(scale, scale) + for(var i = 1; i < icon.length; i++){ + if(!icon[i].noStroke){ + ctx.stroke(icon[i].d) + } + } + if(!config.noFill){ + for(var i = 1; i < icon.length; i++){ + ctx.fillStyle = icon[i].fill + ctx.fill(icon[i].d) + } + } + ctx.restore() + } + + drawDiffOptionsIcon(config){ + var ctx = this.ctx + ctx.save() + ctx.translate(config.x - 21, config.y - 21) + + var drawLine = y => { + ctx.beginPath() + ctx.moveTo(12, y) + ctx.arc(20.5, 25, 8.5, Math.PI, Math.PI * 2, true) + ctx.lineTo(29, 18) + ctx.stroke() + } + var drawTriangle = noFill => { + ctx.beginPath() + ctx.moveTo(29, 5) + ctx.lineTo(21, 19) + ctx.lineTo(37, 19) + ctx.closePath() + if(!noFill){ + ctx.fill() + } + } + ctx.strokeStyle = "#000" + ctx.lineWidth = 12 + drawLine(9) + ctx.lineWidth = 5 + drawTriangle(true) + ctx.stroke() + ctx.lineWidth = 7 + ctx.fillStyle = "#fff" + ctx.strokeStyle = "#fff" + drawLine(11) + drawTriangle() + ctx.translate(-1.5, -0.5) + ctx.fillStyle = "#23a6e1" + ctx.strokeStyle = "#23a6e1" + ctx.globalCompositeOperation = "darken" + drawLine(11) + drawTriangle() + + ctx.restore() + } + + drawDiffCursor(config){ + var ctx = this.ctx + ctx.save() + if(config.scale){ + ctx.translate(config.x, config.y) + ctx.scale(config.scale, config.scale) + ctx.translate(-48, -64) + }else{ + ctx.translate(config.x - 48, config.y - 64) + } + + ctx.fillStyle = config.two ? "#65cdcd" : "#ff411c" + ctx.strokeStyle = "#000" + ctx.lineWidth = 6 + ctx.beginPath() + if(!config.side){ + var textX = config.two ? 20 : 17 + ctx.moveTo(48, 120) + ctx.arc(48, 48.5, 45, Math.PI * 0.58, Math.PI * 0.42) + }else if(config.two){ + var textX = 70 + ctx.moveTo(56, 115) + ctx.arc(98, 48.5, 45, Math.PI * 0.75, Math.PI * 0.59) + }else{ + var textX = -33 + ctx.moveTo(39, 115) + ctx.arc(-2, 48.5, 45, Math.PI * 0.41, Math.PI * 0.25) + } + ctx.closePath() + ctx.fill() + ctx.stroke() + this.drawLayeredText({ + text: config.two ? "2P" : "1P", + fontSize: 43, + fontFamily: this.font, + x: textX, + y: 26, + width: 54, + letterSpacing: -4 + }, [ + {outline: "#fff", letterBorder: 11}, + {fill: "#000"} + ]) + + ctx.restore() + } + + startPreview(loadOnly){ + var currentSong = this.songs[this.selectedSong] + var id = currentSong.id + var prvTime = currentSong.preview + this.endPreview() + + if("id" in currentSong){ + var startLoad = this.getMS() + if(loadOnly){ + var currentId = null + }else{ + var currentId = this.previewId + this.previewing = this.selectedSong + } + var songObj = assets.songs.find(song => song.id == id) + + if(songObj.sound){ + if(!loadOnly){ + this.preview = songObj.sound + this.preview.gain = snd.previewGain + this.previewLoaded(startLoad, prvTime) + } + }else{ + snd.previewGain.load("/songs/" + id + "/main.mp3").then(sound => { + if(currentId === this.previewId){ + songObj.sound = sound + this.preview = sound + this.previewLoaded(startLoad, prvTime) + } + }) + } + } + } + previewLoaded(startLoad, prvtime){ + var endLoad = this.getMS() var difference = endLoad - startLoad - var minDelay = switchedTo ? 300 : 1000 + var minDelay = 300 var delay = minDelay - Math.min(minDelay, difference) this.preview.playLoop(delay / 1000, false, prvtime / 1000) } endPreview() { this.previewId++ + this.previewing = null if(this.preview){ this.preview.stop() } } - run(){ - this.createCode() - this.startP2() - - this.songselHelp = document.getElementById("songsel-help") - pageEvents.once(this.songselHelp, "click").then(() => { - this.clean() - assets.sounds["don"].play() - new Tutorial() - }) - this.diffElements = document.getElementsByClassName("difficulty") - for(let difficulty of this.diffElements){ - pageEvents.once(difficulty, "click").then(this.onDifficulty.bind(this)) - } - this.songElements = document.getElementsByClassName("song") - for(let song of this.songElements){ - pageEvents.add(song, "click", this.onSong.bind(this)) - } - this.songSelect = document.getElementById("song-select") - } - onDifficulty(event){ - this.clean() - var target = event.currentTarget - var song = target.parentNode.parentNode - assets.sounds["don"].play() - - this.selectedSong.difficulty = target.classList[1] - this.selectedSong.title = song.dataset.title - this.selectedSong.folder = song.dataset.songId - - new loadSong(this.selectedSong, event.shiftKey, event.ctrlKey) - } - onSong(event){ - var target = event.currentTarget - var opened = document.getElementsByClassName("opened")[0] - if(!opened){ - this.startPreview(target.dataset.songId, target.dataset.preview) - assets.sounds["don"].play() - assets.sounds["song-select"].stop() - assets.sounds["diffsel"].play(0.3) - target.classList.add("opened") - this.songSelect.classList.add("difficulty-select") - }else if(opened == target){ - this.endPreview() - snd.musicGain.fadeIn(0.4) - assets.sounds["diffsel"].stop() - assets.sounds["cancel"].play() - assets.sounds["song-select"].play(0.3) - opened.classList.remove("opened") - this.songSelect.classList.remove("difficulty-select") - }else{ - this.startPreview(target.dataset.songId, target.dataset.preview, true) - assets.sounds["ka"].play() - opened.classList.remove("opened") - target.classList.add("opened") - } - } - createCode(){ - assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506) - assets.sounds["song-select"].play(0.2) - var songElements = [0] - - assets.songs.forEach(song => { - var songTitle = song.title - var charElements = [0] - var diffElements = [0] - - for(var charIndex = 0; charIndex < songTitle.length; charIndex++){ - var ch = songTitle.charAt(charIndex) - var cl = "song-title-char" - if(ch == " "){ - ch = "\xa0" - cl += " song-title-space" - }else if(songTitle.charAt(charIndex + 1) == "'"){ - ch = ch + "'" - cl += " song-title-apos" - charIndex++ - } - charElements.push( - ["span", { - class: cl, - alt: ch - }, ch] - ) - } - for(var diff in this.diffNames){ - var diffName = diff - var diffLevel = song.stars[diff] - if (!diffLevel) { - continue - } - var starsDisplay = [0] - for(var star = 1; star <= diffLevel; star++){ - starsDisplay.push("\u2605") - starsDisplay.push(["br"]) - } - var diffTxt = this.diffNames[diffName] - diffElements.push( - ["li", { - class: "difficulty " + diffName - }, - ["span", { - class: "diffname" - }, diffTxt], - ["span", { - class: "stars" - }, starsDisplay] - ] - ) - } - songElements.push( - ["div", { - id: "song-" + song.id, - class: "song", - "data-title": songTitle, - "data-song-id": song.id, - "data-preview": song.preview - }, - ["div", { - class: /^[\x00-\xFF]*$/.test(songTitle) ? "song-title alpha-title" : "song-title" - }, charElements], - ["ul", { - class: "difficulties" - }, diffElements] - ] - ) - }) - element( - document.getElementById("song-container"), - songElements - ) - } + onusers(response){ - var oldP2Elements = document.getElementsByClassName("p2") - for(var i = oldP2Elements.length; i--;){ - oldP2Elements[i].classList.remove("p2") - } + this.songs.forEach(song => { + song.p2Cursor = null + }) if(response){ response.forEach(idDiff => { var id = idDiff.id |0 var diff = idDiff.diff - if(diff in this.diffNames){ - var idElement = document.getElementById("song-" + id) - if(idElement){ - idElement.classList.add("p2") - var diffElement = idElement.getElementsByClassName("difficulty " + diff)[0] - if(diffElement){ - diffElement.classList.add("p2") - } - } + var diffId = this.difficultyId.indexOf(diff) + if(diffId >= 0){ + var currentSong = this.songs.find(song => song.id === id) + currentSong.p2Cursor = diffId } }) } @@ -216,23 +1533,22 @@ class SongSelect{ p2.open() } } + + mod(length, index){ + return ((index % length) + length) % length + } + + getMS(){ + return +new Date + } + clean(){ - assets.sounds["bgm_songsel"].stop() - assets.sounds["song-select"].stop() - assets.sounds["diffsel"].stop() + this.redrawRunning = false this.endPreview() - snd.musicGain.fadeIn() - pageEvents.remove(p2, "message") - for(let difficulty of this.diffElements){ - pageEvents.remove(difficulty, "click") - } - delete this.diffElements - for(let song of this.songElements){ - pageEvents.remove(song, "click") - } - delete this.songElements - pageEvents.remove(this.songselHelp, "click") - delete this.songselHelp - delete this.songSelect + pageEvents.keyRemove(this, "all") + pageEvents.remove(this.canvas, "mousemove") + pageEvents.remove(this.canvas, "mousedown") + delete this.ctx + delete this.canvas } } diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 4354acb..905d8d4 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -2,13 +2,24 @@ class Titlescreen{ constructor(){ loader.changePage("titlescreen") this.titleScreen = document.getElementById("title-screen") - pageEvents.keyOnce(this, 13, "down").then(this.goNext.bind(this)) - pageEvents.once(this.titleScreen, "click").then(this.goNext.bind(this)) + pageEvents.keyOnce(this, 13, "down").then(this.onPressed.bind(this)) + pageEvents.once(this.titleScreen, "click").then(this.onPressed.bind(this)) assets.sounds["title"].play() + this.gamepad = new Gamepad({ + "start": ["b", "x", "y", "start"], + "a": ["a"] + }, (pressed, key) => { + if(pressed){ + this.onPressed() + } + }) } - goNext(){ + onPressed(){ this.clean() assets.sounds["don"].play() + setTimeout(this.goNext.bind(this), 500) + } + goNext(){ if(localStorage.getItem("tutorial") !== "true"){ new Tutorial() } else { @@ -16,6 +27,7 @@ class Titlescreen{ } } clean(){ + this.gamepad.clean() assets.sounds["title"].stop() pageEvents.keyRemove(this, 13) pageEvents.remove(this.titleScreen, "click") diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index f2ef292..5589c81 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -1,19 +1,28 @@ class Tutorial{ - constructor(){ + constructor(fromSongSel){ + this.fromSongSel = fromSongSel loader.changePage("tutorial") assets.sounds["bgm_setsume"].playLoop(0.1, false, 0, 1.054, 16.054) this.endButton = document.getElementById("tutorial-end-button") pageEvents.once(this.endButton, "click").then(this.onEnd.bind(this)) + pageEvents.keyOnce(this, 13, "down").then(this.onEnd.bind(this)) + this.gamepad = new Gamepad({ + "confirm": ["start", "b"] + }, this.onEnd.bind(this)) } onEnd(){ this.clean() assets.sounds["don"].play() localStorage.setItem("tutorial", "true") - new SongSelect() + setTimeout(() => { + new SongSelect(this.fromSongSel) + }, 500) } clean(){ + this.gamepad.clean() assets.sounds["bgm_setsume"].stop() pageEvents.remove(this.endButton, "click") + pageEvents.keyRemove(this, 13) delete this.endButton } } diff --git a/public/src/js/view.js b/public/src/js/view.js index 7a71f41..b84af10 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -777,18 +777,6 @@ class View{ don.setAnimationEnd(ms + length * don.speed, don.normalAnimation) } } - gameEnded(){ - if(this.controller.getGlobalScore().hp >= 50){ - var don = this.assets.don - don.setAnimation("endclear") - var ms = this.controller.getElapsedTime().ms - don.setAnimationStart(ms) - var length = don.getAnimationLength("normal") - don.setUpdateSpeed(this.beatInterval / (length / 4)) - var length = don.getAnimationLength("endclear") - don.setAnimationEnd(ms + length * don.speed, don.normalAnimation) - } - } onmousemove(event){ this.lastMousemove = this.controller.getElapsedTime().ms this.cursorHidden = false diff --git a/public/src/js/viewassets.js b/public/src/js/viewassets.js index 00063e8..5d6a82b 100644 --- a/public/src/js/viewassets.js +++ b/public/src/js/viewassets.js @@ -55,7 +55,6 @@ class ViewAssets{ } } this.don.addFrames("clear", 30, "don_anim_clear") - this.don.addFrames("endclear", 22, "don_anim_endclear") this.don.normalAnimation() this.fire = this.createAsset("bar", frame => { var imgw = 360 diff --git a/public/src/views/game.html b/public/src/views/game.html index 327f597..fe5ef6f 100644 --- a/public/src/views/game.html +++ b/public/src/views/game.html @@ -3,7 +3,7 @@
- +
diff --git a/public/src/views/songselect.html b/public/src/views/songselect.html index b99b5d1..ed1e45e 100644 --- a/public/src/views/songselect.html +++ b/public/src/views/songselect.html @@ -1,6 +1,3 @@
-

曲をえらぶ

-

むずかしさをえらぶ

-
?
-
+