diff --git a/public/index.html b/public/index.html index 7adba58..742ea9a 100644 --- a/public/index.html +++ b/public/index.html @@ -19,7 +19,6 @@ - @@ -47,6 +46,7 @@ + diff --git a/public/src/css/scoresheet.css b/public/src/css/scoresheet.css deleted file mode 100644 index f6fa362..0000000 --- a/public/src/css/scoresheet.css +++ /dev/null @@ -1,207 +0,0 @@ -.scoresheet{ - width:100%; - height:100%; - color:black; - font-family: TnT; - background: url("/assets/img/bg-pattern-2.png"); - position: absolute; -} - -.scoresheet h2{ - position:absolute; - top:1%; - left:1%; - font-size: 7vmin; - margin:0; - color: white; -} - -.result-window{ - width:70%; - margin:auto; -} - -.scoresheet button{ - height: 50%; - min-width:20%; - position: absolute; - display: inline-block; - cursor: pointer; - border:5px solid #ae7a26; - background: rgb(255, 255, 255); - color: black; - font-family: TnT; - font-size: 5vmin; - border-radius: 10px; - outline: none; - top:10%; - white-space: nowrap; -} - -.scoresheet .replay{ - left:1%; -} - -.scoresheet .song-select{ - left:23%; -} - -.scoresheet button:hover, -.scoresheet .bottom-part:not(:hover) button.selected{ - border-color:#fa5d3a; - color:white; - background:#0c6577; -} - -.scoresheet .result-bar{ - max-width: 120vh; - height: 71vh; - min-height: 200px; - display:flex; - flex-direction: column; - justify-content: center; - align-items: flex-end; - position: absolute; - left: 0; - right: 0; - margin: auto; -} - -.scoresheet .score-cont{ - position:relative; - right:1%; - width:60%; - height:80%; - background:rgba(255,255,255,0.7); - border-radius:15px; - margin: 10px; - max-height: 33vh; -} - -.scoresheet .score-hp-bar-bg{ - position: relative; - margin-top:2%; - margin-left:5%; - background: url("/assets/img/hp-bar-bg.png"); - background-size: contain; - background-repeat: no-repeat; - -} - -.scoresheet .score-hp-bar-colour{ - position:absolute; - padding: 0; -} - -.scoresheet .score-hp-bar-colour img{ - position:absolute; - height: 100%; - width: 100%; - margin:0; - padding:0; -} - -.scoresheet .score-points{ - min-width:30%; - height:18%; - background:black; - border:5px solid #ae7a26; - border-radius: 10px; - position:absolute; - bottom:5%; - left:5%; - color: white; - font-size: 5vmin; - text-align: right; - padding: .3% 1%; - white-space: nowrap; -} - -.scoresheet .score-details{ - position: absolute; - right:5%; - width:70%; - height:50%; - color:white; -} - -.scoresheet .score-details th, -.scoresheet .score-details td{ - font-size: 3vmin; - font-weight: normal; - white-space: nowrap; -} - -.scoresheet .score-details td{ - text-align: right; -} - -.scoresheet .value{ - width: 25%; -} - -.scoresheet .bottom-part{ - position: fixed; - width:100%; - height:19vh; - bottom:0; - -webkit-box-shadow: inset 0px 10px 20px -5px #ee6d46; - -moz-box-shadow: inset 0px 10px 20px -5px #ee6d46; - box-shadow: inset 0px 10px 20px -5px #ee6d46; - border-top:10px outset #b6361d; - box-sizing: border-box; -} - -.scoresheet .score-mark{ - position: absolute; - top: 0; - bottom: 0; - right: 105%; - height: 40%; - margin: auto; -} - -.scoresheet .gradient-overlay{ - position:absolute; - width:100%; - height:100%; - background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(255,165,100,0.64) 62%, rgba(255,165,100,0.65) 63%); - -} - -.scoresheet .top-part{ - width:100%; - height:10vh; - background:#e84019; - border-bottom:5px solid #b23111; - box-sizing: border-box; -} - -.header-great, -.header-fail{ - color: transparent; -} -.header-great::after, -.header-fail::after{ - content: attr(alt); - color: transparent; - -webkit-background-clip: text; - background-clip: text; - position: absolute; - width: 100%; - left: 0; -} -.header-great::after{ - background-image: linear-gradient(0deg, #f00 10%, #fe0 70%); -} -.header-fail::after{ - background-image: linear-gradient(0deg, #00b3df 30%, #6a62f9 90%); - -} -.scoresheet .stroke-sub{ - position:relative; - z-index:1; -} -.scoresheet .stroke-sub::before{ - left:auto; -} \ No newline at end of file diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 2539c5a..04414dd 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -1,6 +1,5 @@ var assets = { "img": [ - "background.png", "title-screen.png", "logo-big.png", "don-0.png", @@ -18,11 +17,7 @@ var assets = { "score-230.png", "score-450.png", "dancing-don.gif", - "scoresheet.jpg", "bg-pattern-1.png", - "bg-pattern-2.png", - "ranking-S.png", - "ranking-X.png", "muzu_easy.png", "muzu_normal.png", "muzu_hard.png", @@ -41,7 +36,9 @@ var assets = { "bg_genre_4.png", "bg_genre_5.png", "bg_genre_6.png", - "bg_genre_7.png" + "bg_genre_7.png", + "bg_score_p1.png", + "bg_score_p2.png" ], "audioSfx": [ "start.wav", @@ -86,7 +83,7 @@ var assets = { "title.ogg", "pause.wav", "cancel.wav", - "results.wav", + "results.ogg", "diffsel.wav", "gamefullcombo.wav", @@ -110,7 +107,6 @@ var assets = { "views": [ "game.html", "loadsong.html", - "scoresheet.html", "songselect.html", "titlescreen.html", "tutorial.html" diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js new file mode 100644 index 0000000..a625af8 --- /dev/null +++ b/public/src/js/canvasdraw.js @@ -0,0 +1,762 @@ +class CanvasDraw{ + constructor(){ + this.diffStarPath = 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.diffPath = { + good: new Path2D("m12 17c4 3 9 7 10 9 0 0 1 3-1 3C19 29 9 18 9 18m6 2c3 0 3-3 3-3 2-1 5 1 4 3-1 1-2 2-5 3m-1 0C13 26 4 29 1 29 0 29 0 26 0 26 0 24 2 24 2 24V13l5-1v4l8-1c1 0 1-3 1-3 0 0-9 1-14 1V8L7 7v4.5L15 11C16 11 16 8 16 8 16 7 2 9 2 9-1 10 0 5 1 5h10l6-1c3 0 4 2 4 6 0 3-1 7-1 7L7 19v4.5c4 0 7-2.5 7-2.5M9 6C8 4 8 1 8 1c0 0 4-1 6 0 0 0 0 3 1 5"), + ok: new Path2D("m4 10c0 0 3-1 7-1 4 0 3 8 2 11-1 2-3 1-3 1-1-1 1-7 0-8-1-1-4-1-6 0m8 6c-1 1.2-7 1-7 1v-3c0 0 6 0 7-1M2 10c1-2 3 0 3 0 0 0 0 4 1 9-2 3-4 2-4 0zM21 5v19c0 1-2 3-3 3-1 0-5-4-5-4 0-1 4-1 4-1V5M1 2C12 2 17.9 0 20 0 23 0 25 3 21 5 11.7 6 9 6 5 6 0 7-1 2 1 2Z"), + bad: new Path2D("m13 7c8 0 10 9 10 9 1 4-6 3-8 0 0-1 4 0 2.5-3 0 0-2.5-4-4.5 0M16 6 3 18c-2 2-4 1-4 0 0-1 8-8 9-12m6 0c1 8 0 18 0 18-0.1 1-2 3-3 3-1 0-5-4-5-4 0-1 4-1 4-1 0 0-1-8 0-16M2 7C1 7 1 2 2 2 10 2 21 0 22 1 22 1 24 2 24 4 24 7 2 7 2 7Z") + } + + this.crownPath = new Path2D("m82 21c0-4 3-6 5.5-6 2.5 0 5.5 2 5.5 6 0 4-3 6-5.5 6C85 27 82 25 82 21ZM41.5 6c0-4 3-6 5.5-6 2.5 0 5.5 2 5.5 6 0 4-3 6-5.5 6-2.5 0-5.5-2-5.5-6zM1 21C1 17 4 15 6.5 15 9 15 12 17 12 21 12 25 9 27 6.5 27 4 27 1 25 1 21Zm12 46h68l2 11H11ZM13 62 5 18 29 34 47 6 65 34 89 18 81 62Z") + + 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: /[トド]/, + en: /[ceghknsuxyzceghknsuxyz]/, + em: /[mwmw]/, + emCap: /[MWMW]/, + rWidth: /[abdfIjo-rtvabdfIjo-rtv]/, + lWidth: /[ilil!!]/, + uppercaseDigit: /[A-ZA-Z0-90-9]/ + } + + this.tmpCanvas = document.createElement("canvas") + this.tmpCtx = this.tmpCanvas.getContext("2d") + } + + roundedRect(config){ + var ctx = config.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) + } + + songFrame(config){ + var ctx = config.ctx + var x = config.x + var y = config.y + var w = config.width + var h = config.height + var border = config.border + var innerBorder = config.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.borderStyle[1] + ctx.fillRect(_x, _y, _w, _h) + ctx.fillStyle = config.borderStyle[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.highlight({ + ctx: ctx, + x: x, + y: y, + w: w, + h: h, + animate: config.highlight === 2, + animateMS: config.animateMS, + opacity: config.highlight === 1 ? 0.8 : 1 + }) + } + + ctx.restore() + } + + highlight(config){ + var ctx = config.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.roundedRect({ + ctx: ctx, + 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() - config.animateMS) % 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 + } + easeIn(pos){ + return 1 - Math.cos(Math.PI / 2 * pos) + } + + verticalText(config){ + var ctx = config.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: 8, h: 37}) + }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 = config.outlineSize * 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() + } + + layeredText(config, layers){ + var ctx = config.ctx + var mul = config.fontSize / 40 + ctx.save() + + var string = config.text.split("") + if(config.align === "right"){ + string.reverse() + } + 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(symbol === "™"){ + drawn.push({text: symbol, x: -2, y: 0, w: 20, scale: [0.6, 0.5]}) + }else if(symbol === " "){ + drawn.push({text: symbol, x: 0, y: 0, w: 10}) + }else if(r.en.test(symbol)){ + // n-width + drawn.push({text: symbol, x: 0, y: 0, w: 28, scale: [1, 0.95]}) + }else if(r.em.test(symbol)){ + // m-width + drawn.push({text: symbol, x: 0, y: 0, w: 38, scale: [1, 0.95]}) + }else if(r.rWidth.test(symbol)){ + // r-width + drawn.push({text: symbol, x: 0, y: 0, w: 24, scale: [1, 0.95]}) + }else if(r.lWidth.test(symbol)){ + // l-width + drawn.push({text: symbol, x: 0, y: -1, w: 12, scale: [1, 0.95]}) + }else if(r.emCap.test(symbol)){ + // m-width uppercase + drawn.push({text: symbol, x: 0, y: -2, w: 38}) + }else if(r.uppercaseDigit.test(symbol)){ + // Latin script uppercase, digits + drawn.push({text: symbol, x: 0, y: -2, 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.shadowOffsetX = layer.shadow[0] + ctx.shadowOffsetY = layer.shadow[1] + ctx.shadowBlur = layer.shadow[2] + ctx.shadowColor = "rgba(0, 0, 0, " + (1 / (layer.shadow[3] || 2)) + ")" + } + 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) + var isLatin = r.latin.test(symbol.text) + + if(config.align === "center"){ + currentX -= drawnWidth / 2 + }else if(config.align === "right"){ + currentX = -offsetX + symbol.x + (layer.x || 0) - symbol.w / 2 + } + if(symbol.scale || isLatin){ + saved = true + ctx.save() + ctx.translate(currentX, currentY) + if(symbol.scale){ + ctx.scale(symbol.scale[0], symbol.scale[1]) + ctx.lineWidth /= symbol.scale[0] + } + currentX = 0 + currentY = 0 + } + if(isLatin){ + if(action === "strokeText"){ + ctx.lineWidth *= 1.05 + ctx.strokeText(symbol.text, currentX, currentY) + }else{ + ctx.lineWidth *= 0.05 + ctx.strokeStyle = ctx.fillStyle + ctx.strokeText(symbol.text, currentX, currentY) + } + } + ctx[action](symbol.text, currentX, currentY) + if(saved){ + ctx.restore() + } + offsetX += symbol.w * mul + } + if(layer.shadow){ + ctx.restore() + } + } + ctx.restore() + } + + diffIcon(config){ + var ctx = config.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() + } + + diffOptionsIcon(config){ + var ctx = config.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() + } + + diffCursor(config){ + var ctx = config.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.layeredText({ + ctx: ctx, + text: config.two ? "2P" : "1P", + fontSize: 43, + fontFamily: config.font, + x: textX, + y: 26, + width: 54, + letterSpacing: -4 + }, [ + {outline: "#fff", letterBorder: 11}, + {fill: "#000"} + ]) + + ctx.restore() + } + + diffStar(config){ + var ctx = config.ctx + ctx.save() + ctx.fillStyle = config.songSel ? "#fff" : "#f72568" + if(config.songSel){ + ctx.shadowColor = "#fff" + ctx.shadowBlur = 10 + ctx.translate(config.x - 9, config.y - 9) + }else{ + ctx.translate(config.x - 10.5, config.y - 9.5) + ctx.scale(1.1, 1.1) + } + ctx.fill(this.diffStarPath) + ctx.restore() + } + + pattern(config){ + var ctx = config.ctx + ctx.save() + var mul = config.scale || 1 + + if(mul !== 1){ + ctx.scale(1 / mul, 1 / mul) + } + ctx.fillStyle = ctx.createPattern(config.img, "repeat") + ctx.beginPath() + ctx.rect(config.x * mul, config.y * mul, config.w * mul, config.h * mul) + ctx.translate(config.dx, config.dy) + ctx.fill() + + ctx.restore() + } + + score(config){ + var ctx = config.ctx + ctx.save() + + ctx.translate(config.x, config.y) + if(config.scale){ + ctx.scale(config.scale, config.scale) + } + ctx.strokeStyle = "#000" + ctx.lineWidth = 7 + if(config.score === "good"){ + var grd = ctx.createLinearGradient(0, 0, 0, 29) + grd.addColorStop(0.3, "#f7fb00") + grd.addColorStop(0.9, "#ff4900") + ctx.fillStyle = grd + ctx.stroke(this.diffPath.good) + ctx.fill(this.diffPath.good) + }else if(config.score === "ok"){ + ctx.fillStyle = "#fff" + ctx.stroke(this.diffPath.ok) + ctx.fill(this.diffPath.ok) + }else if(config.score === "bad"){ + var grd = ctx.createLinearGradient(0, 0, 0, 27) + grd.addColorStop(0.1, "#6B5DFF") + grd.addColorStop(0.7, "#00AEDE") + ctx.fillStyle = grd + ctx.stroke(this.diffPath.bad) + ctx.fill(this.diffPath.bad) + ctx.translate(26, 0) + ctx.stroke(this.diffPath.ok) + ctx.fill(this.diffPath.ok) + } + ctx.restore() + } + + crown(config){ + var ctx = config.ctx + ctx.save() + + ctx.translate(config.x, config.y) + if(config.scale){ + ctx.scale(config.scale, config.scale) + } + ctx.translate(-47, -39) + ctx.miterLimit = 1.7 + + ctx.save() + ctx.strokeStyle = "#fff" + ctx.lineWidth = 35 + ctx.filter = "blur(1.5px)" + ctx.stroke(this.crownPath) + ctx.restore() + + if(config.shine){ + ctx.strokeStyle = "#fff" + ctx.lineWidth = 18 + ctx.stroke(this.crownPath) + ctx.globalAlpha = 1 - config.shine + } + + ctx.strokeStyle = "#000" + ctx.lineWidth = 18 + ctx.stroke(this.crownPath) + + if(config.shine){ + ctx.globalAlpha = 1 + ctx.fillStyle = "#fff" + ctx.fill(this.crownPath) + ctx.globalAlpha = 1 - config.shine + } + + var grd = ctx.createLinearGradient(0, 0, 94, 0) + if(config.type === "gold"){ + grd.addColorStop(0, "#ffffc5") + grd.addColorStop(0.23, "#ffff44") + grd.addColorStop(0.53, "#efbd12") + grd.addColorStop(0.83, "#ffff44") + grd.addColorStop(1, "#efbd12") + }else if(config.type === "silver"){ + grd.addColorStop(0, "#d6efef") + grd.addColorStop(0.23, "#bddfde") + grd.addColorStop(0.53, "#97c1c0") + grd.addColorStop(0.83, "#bddfde") + grd.addColorStop(1, "#97c1c0") + } + ctx.fillStyle = grd + ctx.fill(this.crownPath) + + ctx.restore() + } + + alpha(amount, ctx, callback){ + if(amount >= 1){ + return callback(ctx) + }else if(amount >= 0){ + this.tmpCanvas.width = ctx.canvas.width + this.tmpCanvas.height = ctx.canvas.height + callback(this.tmpCtx) + ctx.save() + ctx.globalAlpha = amount + ctx.drawImage(this.tmpCanvas, 0, 0) + ctx.restore() + } + } + + getMS(){ + return +new Date + } +} diff --git a/public/src/js/controller.js b/public/src/js/controller.js index e276f9d..2ddf853 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -100,6 +100,10 @@ class Controller{ this.view.refresh() } this.keyboard.checkMenuKeys() + + if(this.scoresheet){ + this.scoresheet.redraw() + } } } togglePauseMenu(){ @@ -109,10 +113,10 @@ class Controller{ gameEnded(){ var score = this.getGlobalScore() var vp - if(score.fail === 0){ + if(score.bad === 0){ vp = "fullcombo" this.playSoundMeka("fullcombo", 1.350) - }else if(score.hp >= 50){ + }else if(score.gauge >= 50){ vp = "clear" }else{ vp = "fail" @@ -120,17 +124,18 @@ class Controller{ assets.sounds["game" + vp].play() } displayResults(){ - this.clean() if(this.multiplayer !== 2){ - new Scoresheet(this, this.getGlobalScore(), this.multiplayer) + this.scoresheet = new Scoresheet(this, this.getGlobalScore(), this.multiplayer) } } displayScore(score, notPlayed){ this.view.displayScore(score, notPlayed) } - songSelection(){ - this.clean() - new SongSelect() + songSelection(fadeIn){ + if(!fadeIn){ + this.clean() + } + new SongSelect(false, fadeIn) } restartSong(){ this.clean() diff --git a/public/src/js/game.js b/public/src/js/game.js index d537390..6a9714c 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -6,15 +6,17 @@ class Game{ this.elapsedTime = {} this.currentCircle = 0 this.combo = 0 + this.rules = new GameRules(this) this.globalScore = { points: 0, - great: 0, good: 0, - fail: 0, + ok: 0, + bad: 0, maxCombo: 0, drumroll: 0, - hp: 0, - song: selectedSong.title + gauge: 0, + title: selectedSong.title, + difficulty: this.rules.difficulty } this.HPGain = 100 / this.songData.circles.filter(circle => { var type = circle.getType() @@ -28,7 +30,6 @@ class Game{ this.fadeOutStarted = false this.currentTimingPoint = 0 this.offsetTime = 0 - this.rules = new GameRules(this) assets.songs.forEach(song => { if(song.id == selectedSong.folder){ @@ -291,6 +292,11 @@ class Game{ }else if(this.musicFadeOut === 2 && (ms >= started + 8600 && ms >= this.controller.mainAsset.duration * 1000 + 250)){ this.controller.displayResults() this.musicFadeOut++ + }else if(this.musicFadeOut === 3 && (ms >= started + 9600 && ms >= this.controller.mainAsset.duration * 1000 + 1250)){ + if(this.controller.scoresheet){ + this.controller.scoresheet.startRedraw() + } + this.controller.clean() } } } @@ -394,22 +400,22 @@ class Game{ // Circle score switch(score){ case 450: - this.globalScore.great++ - break - case 230: this.globalScore.good++ break + case 230: + this.globalScore.ok++ + break case 0: - this.globalScore.fail++ + this.globalScore.bad++ break } - // HP Update + // Gauge update if(score !== 0){ - this.globalScore.hp += this.HPGain - }else if(this.globalScore.hp - this.HPGain > 0){ - this.globalScore.hp -= this.HPGain + this.globalScore.gauge += this.HPGain + }else if(this.globalScore.gauge - this.HPGain > 0){ + this.globalScore.gauge -= this.HPGain }else{ - this.globalScore.hp = 0 + this.globalScore.gauge = 0 } // Points update score += Math.max(0, Math.floor((Math.min(this.combo, 100) - 1) / 10) * 100) diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 02a89cf..cf7e216 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -1,139 +1,530 @@ class Scoresheet{ - constructor(controller, score, multiplayer){ + constructor(controller, results, multiplayer){ this.controller = controller - this.score = score + this.results = results this.multiplayer = multiplayer - loader.changePage("scoresheet") - this.run() - } - setResults(score, scoreCont){ - this.positionning(scoreCont) - var scoreMark = this.elem("score-mark", scoreCont) - var scoreHpBarColour = this.elem("score-hp-bar-colour", scoreCont) - if(score.fail == 0){ - var mark = "gold" - }else if (score.hp >= 50){ - var mark = "silver" + this.canvas = document.getElementById("canvas") + this.ctx = this.canvas.getContext("2d") + + this.font = "TnT" + this.state = { + screen: "fadeIn", + screenMS: this.getMS(), + startDelay: 3300, + hasPointer: 0 } - scoreHpBarColour.dataset.hp = score.hp - var imgW = score.hp * scoreHpBarColour.offsetWidth / 100 - var imgH = scoreHpBarColour.offsetHeight - scoreHpBarColour.getElementsByTagName("img")[0].style.clip = "rect(0, " + imgW + "px, " + imgH + "px, 0)" + this.draw = new CanvasDraw() - if(mark == "gold"){ - scoreMark.src = "/assets/img/ranking-X.png" - }else if(mark == "silver"){ - scoreMark.src = "/assets/img/ranking-S.png" - }else{ - scoreMark.parentNode.removeChild(scoreMark) - } - this.altText(this.elem("score-points", scoreCont), score.points + "点") - this.altText(this.elem("nb-great", scoreCont), score.great) - this.altText(this.elem("nb-good", scoreCont), score.good) - this.altText(this.elem("nb-fail", scoreCont), score.fail) - this.altText(this.elem("max-combo", scoreCont), score.maxCombo) - this.altText(this.elem("nb-drumroll", scoreCont), score.drumroll) - - pageEvents.add(window, "resize", () => { - this.positionning(scoreCont) - }) - } - elem(className, parent){ - return parent.getElementsByClassName(className)[0] - } - text(string){ - return document.createTextNode(string) - } - altText(element, string){ - element.appendChild(this.text(string)) - element.setAttribute("alt", string) - } - positionning(scoreCont){ - var scoreHpBarBg = this.elem("score-hp-bar-bg", scoreCont) - var scoreHpBarColour = this.elem("score-hp-bar-colour", scoreCont) - - var scoreBarW = scoreCont.offsetWidth * 0.9 - var bgW = scoreBarW - var bgH = 51 / 703 * scoreBarW - - scoreHpBarBg.style.width = bgW + "px" - scoreHpBarBg.style.height = bgH + "px" - var bgX = scoreHpBarBg.offsetLeft - var bgY = scoreHpBarBg.offsetTop - - scoreHpBarColour.style.left = (bgW * 0.008) + "px" - scoreHpBarColour.style.top = (bgH * 0.15) + "px" - scoreHpBarColour.style.width = (bgW - bgW * 0.08) + "px" - scoreHpBarColour.style.height = (bgH - bgH * 0.25) + "px" - - var imgW = scoreHpBarColour.dataset.hp * scoreHpBarColour.offsetWidth / 100 - var imgH = scoreHpBarColour.offsetHeight - scoreHpBarColour.getElementsByTagName("img")[0].style.clip = "rect(0, " + imgW + "px, " + imgH + "px, 0)" - } - run(){ - this.scoresheet = document.getElementsByClassName("scoresheet")[0] - var scoreCont = this.elem("score-cont", this.scoresheet) - var scoreContHtml = scoreCont.innerHTML - assets.sounds["results"].play() - assets.sounds["bgm_result"].playLoop(0.1, false, 0, 0.847, 17.689) - - this.setResults(this.score, scoreCont) - this.altText(this.elem("result-song", this.scoresheet), this.score.song) - - this.songSelect = this.elem("song-select", this.scoresheet) - this.replay = this.elem("replay", this.scoresheet) - pageEvents.once(this.songSelect, "click").then(() => { - this.clean() - assets.sounds["don"].play() - this.controller.songSelection() - }) - pageEvents.once(this.elem("replay", this.scoresheet), "click").then(() => { - this.clean() - assets.sounds["don"].play() - this.controller.restartSong() - }) - pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) this.gamepad = new Gamepad({ - "13": ["b", "start"], - "37": ["l", "r", "lb", "lt", "rb", "rt"] - }, (pressed, key) => { - if(pressed){ - this.keyDown(false, key) - } + "13": ["a", "b", "start"] }) - if(this.multiplayer && p2.results){ - var scoreCont2 = document.createElement("div") - scoreCont2.classList.add("score-cont") - scoreCont2.innerHTML = scoreContHtml - scoreCont.parentNode.appendChild(scoreCont2) - this.setResults(p2.results, scoreCont2) - } + this.redrawRunning = true + this.redrawBind = this.redraw.bind(this) + this.redraw() + pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) + pageEvents.add(this.canvas, "mousedown", this.mouseDown.bind(this)) + + assets.sounds["results"].play() + assets.sounds["bgm_result"].playLoop(3, false, 0, 0.847, 17.689) } keyDown(event, code){ if(!code){ + if(event.repeat){ + return + } code = event.keyCode } - var selected = this.elem("selected", this.scoresheet) - if(code == 13 || code == 32 || code == 86 || code == 66){ + var key = { + confirm: code == 13 || code == 32 || code == 86 || code == 66, // Enter, Space, V, B - selected.click() - }else if(code == 37 || code == 39 || code == 67 || code == 78){ - // Left, Right, C, N - assets.sounds["ka"].play() - selected.classList.remove("selected") - var next = selected.nextElementSibling - if(!next){ - next = selected.previousElementSibling - } - next.classList.add("selected") + cancel: code == 27 || code == 8 + // Esc, Backspace + } + if(key.cancel && event){ + event.preventDefault() + } + if(key.confirm || key.cancel){ + this.toNext() } } + mouseDown(event){ + if(event.which !== 1){ + return + } + this.toNext() + } + toNext(){ + var ms = this.getMS() + var elapsed = ms - this.state.screenMS - this.state.startDelay + if(this.state.screen === "fadeIn"){ + if(elapsed >= 3400){ + snd.musicGain.fadeOut(0.5) + this.state.screen = "fadeOut" + this.state.screenMS = ms + assets.sounds["don"].play() + }else if(elapsed >= 0 && elapsed <= 2400){ + this.state.screenMS = ms - 2400 - this.state.startDelay + assets.sounds["don"].play() + } + } + } + + startRedraw(){ + this.redrawing = true + requestAnimationFrame(this.redrawBind) + this.winW = null + this.winH = null + } + + redraw(){ + if(!this.redrawRunning){ + return + } + if(this.redrawing){ + requestAnimationFrame(this.redrawBind) + } + var ms = this.getMS() + + this.gamepad.play((pressed, keyCode) => { + if(pressed){ + this.keyDown(false, keyCode) + } + }) + + if(!this.redrawRunning){ + return + } + + var ctx = this.ctx + ctx.save() + + var winW = innerWidth + var winH = innerHeight + 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.redrawing){ + 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() && ms - this.state.screenMS - this.state.startDelay > 2400){ + return + }else{ + ctx.clearRect(0, 0, winW / ratio, winH / ratio) + } + }else{ + ctx.scale(ratio, 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 players = this.multiplayer && p2.results ? 2 : 1 + var p2Offset = 298 + + var bgOffset = 0 + var elapsed = ms - this.state.screenMS + if(this.state.screen === "fadeIn" && elapsed < 1000){ + bgOffset = Math.min(1, this.draw.easeIn(1 - elapsed / 1000)) * (winH / 2) + } + + if(bgOffset){ + ctx.save() + ctx.translate(0, -bgOffset) + } + this.draw.pattern({ + ctx: ctx, + img: assets.image["bg_score_p1"], + x: 0, + y: 0, + w: winW, + h: winH / 2, + dx: frameLeft - 35, + dy: frameTop + 17 + }) + ctx.fillStyle = "rgba(127, 28, 12, 0.5)" + ctx.fillRect(0, winH / 2 - 12, winW, 12) + ctx.fillStyle = "#000" + ctx.fillRect(0, winH / 2 - 2, winW, 3) + ctx.fillStyle = "#fa4529" + ctx.fillRect(0, 0, winW, frameTop + 64) + ctx.fillStyle = "#bf2900" + ctx.fillRect(0, frameTop + 64, winW, 8) + + if(bgOffset){ + ctx.restore() + ctx.save() + ctx.translate(0, bgOffset) + } + + this.draw.pattern({ + ctx: ctx, + img: assets.image[this.multiplayer ? "bg_score_p2" : "bg_score_p1"], + x: 0, + y: winH / 2, + w: winW, + h: winH / 2, + dx: frameLeft - 35, + dy: frameTop - 17 + }) + ctx.fillStyle = this.multiplayer ? "rgba(138, 245, 247, 0.5)" : "rgba(249, 163, 149, 0.5)" + ctx.fillRect(0, winH / 2, winW, 12) + ctx.fillStyle = "#000" + ctx.fillRect(0, winH / 2 - 1, winW, 3) + ctx.fillStyle = this.multiplayer ? "#6bbec0" : "#fa4529" + ctx.fillRect(0, winH - frameTop - 64, winW, frameTop + 64) + ctx.fillStyle = this.multiplayer ? "rgba(160, 228, 229, 0.8)" : "rgba(255, 144, 116, 0.8)" + ctx.fillRect(0, winH - frameTop - 72, winW, 7) + ctx.fillStyle = this.multiplayer ? "#a8e0e0" : "#ff9b7a" + ctx.fillRect(0, winH - frameTop - 66, winW, 2) + + if(bgOffset){ + ctx.restore() + } + + if(this.state.screen === "fadeOut"){ + var elapsed = 2400 + }else{ + var elapsed = ms - this.state.screenMS - this.state.startDelay + } + + if(elapsed >= 0){ + if(this.state.hasPointer === 0){ + this.state.hasPointer = 1 + this.canvas.style.cursor = "pointer" + } + ctx.save() + ctx.setTransform(1, 0, 0, 1, 0, 0) + this.draw.alpha(Math.min(1, elapsed / 400), ctx, ctx => { + ctx.scale(ratio, ratio) + ctx.translate(frameLeft, frameTop) + + this.draw.layeredText({ + ctx: ctx, + text: "成績発表", + fontSize: 48, + fontFamily: this.font, + x: 23, + y: 15, + letterSpacing: 3 + }, [ + {x: -2, y: -2, outline: "#000", letterBorder: 22}, + {}, + {x: 2, y: 2, shadow: [2, 2, 7]}, + {x: 2, y: 2, outline: "#ad1516", letterBorder: 10}, + {x: -2, y: -2, outline: "#ff797b"}, + {outline: "#f70808"}, + {fill: "#fff", shadow: [-1, 1, 3, 1.5]} + ]) + + this.draw.layeredText({ + ctx: ctx, + text: this.results.title, + fontSize: 40, + fontFamily: this.font, + x: 1257, + y: 20, + align: "right" + }, [ + {outline: "#000", letterBorder: 10, shadow: [1, 1, 3]}, + {fill: "#fff"} + ]) + + ctx.save() + for(var p = 0; p < players; p++){ + var results = this.results + if(p === 1){ + results = p2.results + ctx.translate(0, p2Offset) + } + + var imgScale = 1.35 + ctx.drawImage(assets.image["muzu_" + results.difficulty], + 276, 150, imgScale * 176, imgScale * 120 + ) + + this.draw.roundedRect({ + ctx: ctx, + x: 532, + y: 98, + w: 728, + h: 232, + radius: 30, + }) + ctx.fillStyle = p === 1 ? "rgba(195, 228, 229, 0.8)" : "rgba(255, 224, 216, 0.8)" + ctx.fill() + this.draw.roundedRect({ + ctx: ctx, + x: 556, + y: 237, + w: 254, + h: 70, + radius: 15, + }) + ctx.fillStyle = "#000" + ctx.fill() + this.draw.roundedRect({ + ctx: ctx, + x: 559, + y: 240, + w: 248, + h: 64, + radius: 14, + }) + ctx.fillStyle = "#eec954" + ctx.fill() + this.draw.roundedRect({ + ctx: ctx, + x: 567, + y: 248, + w: 232, + h: 48, + radius: 6, + }) + ctx.fillStyle = "#000" + ctx.fill() + ctx.font = "36px " + this.font + ctx.textAlign = "right" + ctx.fillStyle = "#fff" + ctx.strokeStyle = "#000" + ctx.lineWidth = 0.5 + ctx.fillText("点", 788, 284) + ctx.strokeText("点", 788, 284) + + this.draw.score({ + ctx: ctx, + score: "good", + x: 823, + y: 192 + }) + this.draw.score({ + ctx: ctx, + score: "ok", + x: 823, + y: 233 + }) + this.draw.score({ + ctx: ctx, + score: "bad", + x: 823, + y: 273 + }) + + ctx.textAlign = "right" + var grd = ctx.createLinearGradient(0, 0, 0, 30) + grd.addColorStop(0.2, "#ff4900") + grd.addColorStop(0.9, "#f7fb00") + this.draw.layeredText({ + ctx: ctx, + text: "最大コンボ数", + x: 1150, + y: 193, + fontSize: 29, + fontFamily: this.font, + align: "right", + width: 216, + letterSpacing: 1 + }, [ + {outline: "#000", letterBorder: 8}, + {fill: grd} + ]) + this.draw.layeredText({ + ctx: ctx, + text: "連打数", + x: 1150, + y: 233, + fontSize: 29, + fontFamily: this.font, + letterSpacing: 4, + align: "right" + }, [ + {outline: "#000", letterBorder: 8}, + {fill: "#ffc700"} + ]) + } + ctx.restore() + }) + ctx.restore() + } + + if(elapsed >= 800){ + ctx.save() + ctx.translate(frameLeft, frameTop) + + ctx.globalAlpha = Math.min(1, (elapsed - 800) / 500) + + for(var p = 0; p < players; p++){ + var results = this.results + if(p === 1){ + results = p2.results + ctx.translate(0, p2Offset) + } + ctx.drawImage(assets.image["hp-bar-bg"], + 552, 120, 688, 48 + ) + var gauge = results.gauge / 100 + if(gauge > 0){ + ctx.drawImage(assets.image["hp-bar-colour"], + 0, 0, 650 * gauge, 40, + 557, 127, 635 * gauge, 37, + ) + } + } + ctx.restore() + } + + if(elapsed >= 1200){ + ctx.save() + ctx.setTransform(1, 0, 0, 1, 0, 0) + + for(var p = 0; p < players; p++){ + var results = this.results + if(p === 1){ + results = p2.results + } + var crownType = null + if(results.bad === 0){ + crownType = "gold" + }else if(results.gauge >= 50){ + crownType = "silver" + } + if(crownType !== null){ + var amount = Math.min(1, (elapsed - 1200) / 450) + this.draw.alpha(this.draw.easeIn(amount), ctx, ctx => { + ctx.save() + ctx.scale(ratio, ratio) + ctx.translate(frameLeft, frameTop) + if(p === 1){ + ctx.translate(0, p2Offset) + } + + var crownScale = 1 + var shine = 0 + if(amount < 1){ + crownScale = 2.8 * (1 - amount) + 0.9 + }else if(elapsed < 1850){ + crownScale = 0.9 + (elapsed - 1650) / 2000 + }else if(elapsed < 2200){ + shine = (elapsed - 1850) / 175 + if(shine > 1){ + shine = 2 - shine + } + } + this.draw.crown({ + ctx: ctx, + type: crownType, + x: 395, + y: 218, + scale: crownScale, + shine: shine + }) + + ctx.restore() + }) + } + } + ctx.restore() + } + + if(elapsed >= 2400){ + ctx.save() + ctx.translate(frameLeft, frameTop) + + for(var p = 0; p < players; p++){ + var results = this.results + if(p === 1){ + results = p2.results + ctx.translate(0, p2Offset) + } + ctx.save() + var points = results.points.toString() + var scale = 1.3 + ctx.font = "36px " + this.font + ctx.translate(760, 286) + ctx.scale(1 / scale, 1 * 1.1) + ctx.textAlign = "center" + ctx.fillStyle = "#fff" + ctx.strokeStyle = "#fff" + ctx.lineWidth = 0.5 + for(var i = 0; i < points.length; i++){ + ctx.translate(-23 * scale, 0) + ctx.fillText(points[points.length - i - 1], 0, 0) + ctx.strokeText(points[points.length - i - 1], 0, 0) + } + ctx.restore() + + var printNumbers = ["good", "ok", "bad", "maxCombo", "drumroll"] + for(var i in printNumbers){ + this.draw.layeredText({ + ctx: ctx, + text: results[printNumbers[i]].toString(), + x: 971 + 270 * Math.floor(i / 3), + y: 194 + (40 * (i % 3)), + fontSize: 27, + fontFamily: this.font, + letterSpacing: 4, + align: "right" + }, [ + {outline: "#000", letterBorder: 9}, + {fill: "#fff"} + ]) + } + } + ctx.restore() + } + + if(this.state.screen === "fadeOut"){ + ctx.save() + if(this.state.hasPointer === 1){ + this.state.hasPointer = 2 + this.canvas.style.cursor = "" + } + + var elapsed = ms - this.state.screenMS + ctx.globalAlpha = Math.max(0, Math.min(1, elapsed / 1000)) + ctx.fillStyle = "#000" + ctx.fillRect(0, 0, winW, winH) + + ctx.restore() + + if(elapsed >= 1000){ + this.clean() + this.controller.songSelection(true) + } + } + + ctx.restore() + } + + mod(length, index){ + return ((index % length) + length) % length + } + + getMS(){ + return +new Date + } + clean(){ - this.gamepad.clean() assets.sounds["bgm_result"].stop() + snd.musicGain.fadeIn() + this.redrawRunning = false pageEvents.keyRemove(this, "all") - pageEvents.remove(window, "resize") + pageEvents.remove(this.canvas, "mousedown") + delete this.ctx + delete this.canvas } } diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index ce73015..75dbfe1 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1,5 +1,5 @@ class SongSelect{ - constructor(fromTutorial){ + constructor(fromTutorial, fadeIn){ loader.changePage("songselect") this.canvas = document.getElementById("song-sel-canvas") this.ctx = this.canvas.getContext("2d") @@ -131,50 +131,8 @@ class SongSelect{ 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.draw = new CanvasDraw() this.difficulty = ["かんたん", "ふつう", "むずかしい", "おに"] this.difficultyId = ["easy", "normal", "hard", "oni"] @@ -205,12 +163,13 @@ class SongSelect{ this.previewId = 0 this.state = { - screen: fromTutorial ? "song" : "title", + screen: fromTutorial ? "song" : (fadeIn ? "titleFadeIn" : "title"), screenMS: this.getMS(), move: 0, moveMS: 0, moveHover: null, - locked: true + locked: true, + hasPointer: false } this.songSelecting = { speed: 800, @@ -253,7 +212,7 @@ class SongSelect{ ctrl: event.ctrlKey } } - if(code === "ctrl" && code === "shift"){ + if(code === "ctrl" || code === "shift"){ return } var key = { @@ -323,6 +282,7 @@ class SongSelect{ } mouseMove(event){ var mouse = this.mouseOffset(event) + var moveTo = null 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){ @@ -336,6 +296,7 @@ class SongSelect{ } this.state.moveHover = moveTo } + this.pointer(moveTo !== null) } mouseOffset(event){ return { @@ -343,6 +304,15 @@ class SongSelect{ y: (event.offsetY * this.pixelRatio - this.winH / 2) / this.ratio + 720 / 2 } } + pointer(enabled){ + if(enabled && this.state.hasPointer === false){ + this.canvas.style.cursor = "pointer" + this.state.hasPointer = true + }else if(!enabled && this.state.hasPointer === true){ + this.canvas.style.cursor = "" + this.state.hasPointer = false + } + } songSelMouse(x, y){ if(this.state.locked === 0 && this.songAsset.marginTop <= y && y <= this.songAsset.marginTop + this.songAsset.height){ @@ -402,6 +372,7 @@ class SongSelect{ for(var i = 0; i < Math.abs(moveBy) - 1; i++){ assets.sounds["ka"].play((resize + i * soundsDelay) / 1000) } + this.pointer(false) } } moveToDiff(moveBy){ @@ -440,6 +411,7 @@ class SongSelect{ }else if(currentSong.action === "tutorial"){ this.toTutorial() } + this.pointer(false) } } toSongSelect(){ @@ -519,8 +491,6 @@ class SongSelect{ 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 @@ -535,6 +505,7 @@ class SongSelect{ this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px" }else if(!document.hasFocus()){ + this.pointer(false) return }else{ ctx.clearRect(0, 0, winW / ratio, winH / ratio) @@ -553,7 +524,7 @@ class SongSelect{ var screen = this.state.screen var selectedWidth = this.songAsset.width - if(screen === "title"){ + if(screen === "title" || screen === "titleFadeIn"){ if(ms > this.state.screenMS + 1000){ this.state.screen = "song" this.state.screenMS = ms + (ms - this.state.screenMS - 1000) @@ -563,10 +534,15 @@ class SongSelect{ }else{ this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS - 1000) } + if(ms > this.state.screenMS + 500){ + this.state.screen = "title" + screen = "title" + } } - if(screen === "title" || screen === "song"){ - this.drawLayeredText({ + if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ + this.draw.layeredText({ + ctx: ctx, text: "曲をえらぶ", fontSize: 48, fontFamily: this.font, @@ -576,25 +552,26 @@ class SongSelect{ }, [ {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"}, + {x: 2, y: 2, shadow: [3, 3, 3]}, + {x: 2, y: 2, outline: "#ad1516", letterBorder: 10}, + {x: -2, y: -2, outline: "#ff797b"}, {outline: "#f70808"}, - {fill: "#fff"} + {fill: "#fff", shadow: [-1, 1, 3, 1.5]} ]) var category = this.songs[this.selectedSong].category if(category){ - this.drawLayeredText({ + this.draw.layeredText({ + ctx: ctx, text: category, fontSize: 40, fontFamily: this.font, x: winW / 2, y: frameTop + 38, width: 255, - center: true + align: "center" }, [ - {outline: this.songs[this.selectedSong].skin.outline, letterBorder: 12, shadow: true}, + {outline: this.songs[this.selectedSong].skin.outline, letterBorder: 12, shadow: [3, 3, 3]}, {fill: "#fff"} ]) } @@ -659,13 +636,13 @@ class SongSelect{ if(this.previewing !== null){ this.endPreview() } - }else if(screen !== "title" && ms > this.state.moveMS + 100){ + }else if(screen !== "title" && screen !== "titleFadeIn" && ms > this.state.moveMS + 100){ if(this.previewing !== this.selectedSong && "id" in this.songs[this.selectedSong]){ this.startPreview() } } - if(screen === "title" || screen === "song"){ + if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ for(var i = this.selectedSong - 1; ; i--){ var highlight = 0 if(i - this.selectedSong === this.state.moveHover){ @@ -677,6 +654,7 @@ class SongSelect{ break } this.drawClosedSong({ + ctx: ctx, x: _x, y: songTop, song: this.songs[index], @@ -695,6 +673,7 @@ class SongSelect{ break } this.drawClosedSong({ + ctx: ctx, x: _x, y: songTop, song: this.songs[index], @@ -712,7 +691,7 @@ class SongSelect{ highlight = 1 } var selectedSkin = this.songSkin.selected - if(screen === "title" || this.state.locked === 3){ + if(screen === "title" || screen === "titleFadeIn" || this.state.locked === 3){ selectedSkin = currentSong.skin highlight = 2 }else if(songSelMoving){ @@ -726,22 +705,27 @@ class SongSelect{ highlight = 0 } - this.drawSongFrame({ + this.draw.songFrame({ + ctx: ctx, x: winW / 2 - selectedWidth / 2 + xOffset, y: songTop + this.songAsset.height - selectedHeight, width: selectedWidth, height: selectedHeight, + border: this.songAsset.border, + innerBorder: this.songAsset.innerBorder, background: selectedSkin.background, - border: selectedSkin.border, + borderStyle: selectedSkin.border, highlight: highlight, noCrop: screen === "difficulty", + animateMS: this.state.moveMS, innerContent: (x, y, w, h) => { ctx.strokeStyle = "#000" - if(screen === "title" || screen === "song"){ + if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ var opened = ((selectedWidth - this.songAsset.width) / (this.songAsset.selectedWidth - this.songAsset.width)) var songSel = true }else{ - this.drawLayeredText({ + this.draw.layeredText({ + ctx: ctx, text: "むずかしさをえらぶ", fontSize: 46, fontFamily: this.font, @@ -750,12 +734,12 @@ class SongSelect{ width: 280 }, [ {x: -2, y: -2, outline: "#000", letterBorder: 23}, - {shadow: true}, + {shadow: [3, 3, 3]}, {x: 2, y: 2}, - {x: -2, y: -2, outline: "#ff797b", letterBorder: 12}, - {x: 2, y: 2, outline: "#ad1516"}, + {x: 2, y: 2, outline: "#ad1516", letterBorder: 10}, + {x: -2, y: -2, outline: "#ff797b"}, {outline: "#f70808"}, - {fill: "#fff"} + {fill: "#fff", shadow: [-1, 1, 3, 1.5]} ]) var opened = 1 var songSel = false @@ -763,7 +747,8 @@ class SongSelect{ var _y = y + 67 ctx.fillStyle = "#efb058" ctx.lineWidth = 5 - this.drawRoundedRect({ + this.draw.roundedRect({ + ctx: ctx, x: _x - 28, y: _y, w: 56, @@ -776,9 +761,14 @@ class SongSelect{ ctx.beginPath() ctx.arc(_x, _y + 28, 20, 0, Math.PI * 2) ctx.fill() - this.drawDiffOptionsIcon({x: _x, y: _y + 28}) + this.draw.diffOptionsIcon({ + ctx: ctx, + x: _x, + y: _y + 28 + }) - this.drawVerticalText({ + this.draw.verticalText({ + ctx: ctx, text: "もどる", x: _x, y: _y + 57, @@ -786,6 +776,7 @@ class SongSelect{ height: 220, fill: "#fff", outline: "#000", + outlineSize: this.songAsset.letterBorder, letterBorder: 4, fontSize: 28, fontFamily: this.font, @@ -798,17 +789,21 @@ class SongSelect{ highlight = 1 } if(highlight){ - this.drawHighlight({ + this.draw.highlight({ + ctx: ctx, x: _x - 32, y: _y - 3, w: 64, h: 304, animate: highlight === 1, + animateMS: this.state.moveMS, opacity: highlight === 2 ? 0.8 : 1, radius: 24 }) if(this.selectedDiff === 0){ - this.drawDiffCursor({ + this.draw.diffCursor({ + ctx: ctx, + font: this.font, x: _x, y: _y - 45 }) @@ -825,7 +820,8 @@ class SongSelect{ ctx.arc(_x, _y + 22, 22, -Math.PI, 0) ctx.arc(_x, _y + 266, 22, 0, Math.PI) ctx.fill() - this.drawDiffIcon({ + this.draw.diffIcon({ + ctx: ctx, diff: i, x: _x, y: _y - 8, @@ -835,7 +831,8 @@ class SongSelect{ }else{ var _x = x + 402 + i * 100 var _y = y + 87 - this.drawDiffIcon({ + this.draw.diffIcon({ + ctx: ctx, diff: i, x: _x, y: _y - 12, @@ -851,7 +848,8 @@ class SongSelect{ ctx.lineWidth = 2.5 ctx.fillRect(_x - 28, _y + 19, 56, 351) ctx.strokeRect(_x - 28, _y + 19, 56, 351) - this.drawDiffIcon({ + this.draw.diffIcon({ + ctx: ctx, diff: i, x: _x, y: _y - 12, @@ -859,7 +857,8 @@ class SongSelect{ border: 4.5 }) } - this.drawVerticalText({ + this.draw.verticalText({ + ctx: ctx, text: this.difficulty[i], x: _x, y: songSel ? _y + 10 : _y + 23, @@ -881,22 +880,18 @@ class SongSelect{ 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() + this.draw.diffStar({ + ctx: ctx, + songSel: songSel, + x: _x, + y: yPos + }) } } if(i === currentSong.p2Cursor){ - this.drawDiffCursor({ + this.draw.diffCursor({ + ctx: ctx, + font: this.font, x: _x, y: _y - (songSel ? 45 : 65), two: true, @@ -913,19 +908,23 @@ class SongSelect{ highlight = 1 } if(currentDiff === i){ - this.drawDiffCursor({ + this.draw.diffCursor({ + ctx: ctx, + font: this.font, x: _x, y: _y - 65, side: currentSong.p2Cursor === currentDiff }) } if(highlight){ - this.drawHighlight({ + this.draw.highlight({ + ctx: ctx, x: _x - 32, y: _y + 14, w: 64, h: 362, animate: highlight === 1, + animateMS: this.state.moveMS, opacity: highlight === 2 ? 0.8 : 1 }) } @@ -938,7 +937,8 @@ class SongSelect{ ctx.globalAlpha = 1 var textX = Math.max(w - 37, w / 2) var textY = opened * 12 + (1 - opened) * 7 - this.drawVerticalText({ + this.draw.verticalText({ + ctx: ctx, text: currentSong.title, x: x + textX, y: y + textY, @@ -946,6 +946,7 @@ class SongSelect{ height: h - 35, fill: "#fff", outline: selectedSkin.outline, + outlineSize: this.songAsset.letterBorder, fontSize: 40, fontFamily: this.font }) @@ -953,7 +954,8 @@ class SongSelect{ }) if(songSelMoving){ - this.drawHighlight({ + this.draw.highlight({ + ctx: ctx, x: winW / 2 - selectedWidth / 2, y: songTop, w: selectedWidth, @@ -961,32 +963,34 @@ class SongSelect{ opacity: 0.8 }) } - } - - 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) + + if(screen === "titleFadeIn"){ + ctx.save() + + var elapsed = ms - this.state.screenMS + ctx.globalAlpha = Math.max(0, 1 - elapsed / 500) + ctx.fillStyle = "#000" + ctx.fillRect(0, 0, winW, winH) + + ctx.restore() + } } drawClosedSong(config){ + var ctx = config.ctx + config.width = this.songAsset.width config.height = this.songAsset.height + config.border = this.songAsset.border + config.innerBorder = this.songAsset.innerBorder config.background = config.song.skin.background - config.border = config.song.skin.border + config.borderStyle = config.song.skin.border config.outline = config.song.skin.outline config.text = config.song.title + config.animateMS = this.state.moveMS config.innerContent = (x, y, w, h) => { - this.drawVerticalText({ + this.draw.verticalText({ + ctx: ctx, text: config.text, x: x + w / 2, y: y + 7, @@ -994,13 +998,16 @@ class SongSelect{ height: h - 35, fill: "#fff", outline: config.outline, + outlineSize: this.songAsset.letterBorder, fontSize: 40, fontFamily: this.font }) } - this.drawSongFrame(config) + this.draw.songFrame(config) if(config.song.p2Cursor){ - this.drawDiffCursor({ + this.draw.diffCursor({ + ctx: ctx, + font: this.font, x: config.x + 48, y: config.y - 27, two: true, @@ -1010,496 +1017,6 @@ class SongSelect{ } } - 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(symbol === "™"){ - drawn.push({text: symbol, x: -2, y: 0, w: 20, scale: [0.6, 0.5]}) - }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.lineWidth /= symbol.scale[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 diff --git a/public/src/js/view.js b/public/src/js/view.js index c0f91e2..8e93dfe 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -90,13 +90,13 @@ class View{ var docW = document.body.offsetWidth var docH = document.body.offsetHeight this.canvas.rescale() - if(this.controller.multiplayer == 2){ + if(this.controller.multiplayer === 2){ docH = docH / 3 * 2 } this.canvas.resize(docW, docH) this.winW = this.canvas.scaledWidth this.winH = this.canvas.scaledHeight - if(this.controller.multiplayer == 2){ + if(this.controller.multiplayer === 2){ this.winH = this.winH / 2 * 3 } this.barY = 0.25 * this.winH @@ -227,8 +227,8 @@ class View{ getHP(){ var circles = this.controller.getCircles() var currentCircle = this.controller.getCurrentCircle() - var hp = this.controller.getGlobalScore().hp - var width = Math.floor(hp * 650 / 1000) * 10 + var gauge = this.controller.getGlobalScore().gauge + var width = Math.floor(gauge * 650 / 1000) * 10 return { imgW: width, canvasW: width / 650 * this.HPBarColMaxW @@ -769,7 +769,7 @@ class View{ } }else{ var animation = this.assets.don.getAnimation() - if(animation === "gogo" || this.controller.getGlobalScore().hp >= 50 && animation === "normal"){ + if(animation === "gogo" || this.controller.getGlobalScore().gauge >= 50 && animation === "normal"){ this.assets.don.normalAnimation() } if(ms >= this.gogoTimeStarted + 100){ @@ -817,6 +817,10 @@ class View{ } clean(){ pageEvents.mouseRemove(this) + if(this.controller.multiplayer === 2){ + this.canvas.canvas.parentNode.removeChild(this.canvas.canvas) + } + this.cursor.parentNode.removeChild(this.cursor) delete this.pauseMenu delete this.cursor delete this.canvas diff --git a/public/src/js/viewassets.js b/public/src/js/viewassets.js index 5d6a82b..0e8592a 100644 --- a/public/src/js/viewassets.js +++ b/public/src/js/viewassets.js @@ -42,7 +42,7 @@ class ViewAssets{ var length = this.don.getAnimationLength("gogo") this.don.setUpdateSpeed(this.beatInterval / (length / 4)) this.don.setAnimation("gogo") - }else if(this.controller.getGlobalScore().hp >= 50){ + }else if(this.controller.getGlobalScore().gauge >= 50){ this.don.setAnimationStart(0) var length = this.don.getAnimationLength("clear") this.don.setUpdateSpeed(this.beatInterval / (length / 2)) diff --git a/public/src/views/scoresheet.html b/public/src/views/scoresheet.html deleted file mode 100644 index 95372dc..0000000 --- a/public/src/views/scoresheet.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
-

成績発表

-

-
-
-
- -
-
- -
-
-
- - - - - - - - - - - - - - - - -
最大コンボ数
連打数
不可
-
-
-
-
- - -
-