mirror of
https://github.com/jiojciojsioe3/a3cjroijsiojiorj.git
synced 2024-12-26 03:06:14 +08:00
1756 lines
44 KiB
JavaScript
1756 lines
44 KiB
JavaScript
class View{
|
|
constructor(controller){
|
|
this.controller = controller
|
|
|
|
this.canvas = document.getElementById("canvas")
|
|
this.ctx = this.canvas.getContext("2d")
|
|
|
|
this.cursor = document.getElementById("cursor")
|
|
this.gameDiv = document.getElementById("game")
|
|
this.songBg = document.getElementById("songbg")
|
|
this.songStage = document.getElementById("song-stage")
|
|
|
|
this.portraitClass = false
|
|
this.touchp2Class = false
|
|
this.darkDonBg = false
|
|
|
|
this.pauseOptions = strings.pauseOptions
|
|
this.categories = {
|
|
"J-POP": {
|
|
sort: 0,
|
|
infoFill: "#004d68"
|
|
},
|
|
"アニメ": {
|
|
sort: 1,
|
|
infoFill: "#9c4002"
|
|
},
|
|
"ボーカロイド™曲": {
|
|
sort: 2,
|
|
infoFill: "#546184"
|
|
},
|
|
"バラエティ": {
|
|
sort: 3,
|
|
infoFill: "#3c6800"
|
|
},
|
|
"クラシック": {
|
|
sort: 4,
|
|
infoFill: "#865800"
|
|
},
|
|
"ゲームミュージック": {
|
|
sort: 5,
|
|
infoFill: "#4f2886"
|
|
},
|
|
"ナムコオリジナル": {
|
|
sort: 6,
|
|
infoFill: "#961e00"
|
|
},
|
|
"default": {
|
|
sort: 7,
|
|
infoFill: "#656565"
|
|
}
|
|
}
|
|
this.difficulty = {
|
|
"easy": 0,
|
|
"normal": 1,
|
|
"hard": 2,
|
|
"oni": 3,
|
|
"ura": 4
|
|
}
|
|
|
|
this.currentScore = {
|
|
ms: -Infinity,
|
|
type: 0
|
|
}
|
|
this.noteFace = {
|
|
small: 0,
|
|
big: 3
|
|
}
|
|
this.state = {
|
|
pausePos: 0,
|
|
moveMS: 0,
|
|
moveHover: null,
|
|
hasPointer: false
|
|
}
|
|
this.nextBeat = 0
|
|
this.gogoTime = 0
|
|
this.drumroll = []
|
|
|
|
this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval
|
|
this.font = strings.font
|
|
|
|
this.draw = new CanvasDraw()
|
|
this.assets = new ViewAssets(this)
|
|
|
|
this.titleCache = new CanvasCache()
|
|
this.comboCache = new CanvasCache()
|
|
this.pauseCache = new CanvasCache()
|
|
|
|
this.multiplayer = this.controller.multiplayer
|
|
|
|
this.touchEnabled = this.controller.touchEnabled
|
|
this.touch = -Infinity
|
|
|
|
if(this.multiplayer !== 2){
|
|
|
|
if(this.controller.touchEnabled){
|
|
this.touchDrumDiv = document.getElementById("touch-drum")
|
|
this.touchDrumImg = document.getElementById("touch-drum-img")
|
|
|
|
if(this.controller.autoPlayEnabled){
|
|
this.touchDrumDiv.style.display = "none"
|
|
}
|
|
pageEvents.add(this.canvas, "touchstart", this.ontouch.bind(this))
|
|
|
|
this.gameDiv.classList.add("touch-visible")
|
|
document.getElementById("version").classList.add("version-hide")
|
|
|
|
this.touchFullBtn = document.getElementById("touch-full-btn")
|
|
pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen)
|
|
if(!fullScreenSupported){
|
|
this.touchFullBtn.style.display = "none"
|
|
}
|
|
|
|
this.touchPauseBtn = document.getElementById("touch-pause-btn")
|
|
pageEvents.add(this.touchPauseBtn, "touchend", () => {
|
|
this.controller.togglePause()
|
|
})
|
|
if(this.multiplayer){
|
|
this.touchPauseBtn.style.display = "none"
|
|
}
|
|
}
|
|
}
|
|
if(this.multiplayer){
|
|
this.gameDiv.classList.add("multiplayer")
|
|
}else{
|
|
pageEvents.add(this.canvas, "mousedown", this.onmousedown.bind(this))
|
|
}
|
|
}
|
|
run(){
|
|
if(this.multiplayer !== 2){
|
|
this.setBackground()
|
|
}
|
|
this.setDonBg()
|
|
|
|
this.lastMousemove = this.controller.game.getAccurateTime()
|
|
pageEvents.mouseAdd(this, this.onmousemove.bind(this))
|
|
|
|
this.refresh()
|
|
}
|
|
refresh(){
|
|
var ctx = this.ctx
|
|
|
|
var winW = innerWidth
|
|
var winH = lastHeight
|
|
|
|
if(winW / 32 > winH / 9){
|
|
winW = winH / 9 * 32
|
|
}
|
|
|
|
this.portrait = winW < winH
|
|
var touchMultiplayer = this.touchEnabled && this.multiplayer && !this.portrait
|
|
|
|
this.pixelRatio = window.devicePixelRatio || 1
|
|
winW *= this.pixelRatio
|
|
winH *= this.pixelRatio
|
|
if(this.portrait){
|
|
var ratioX = winW / 720
|
|
var ratioY = winH / 1280
|
|
}else{
|
|
var ratioX = winW / 1280
|
|
var ratioY = winH / 720
|
|
}
|
|
var ratio = (ratioX < ratioY ? ratioX : ratioY)
|
|
|
|
if(this.winW !== winW || this.winH !== winH){
|
|
this.winW = winW
|
|
this.winH = winH
|
|
this.ratio = ratio
|
|
|
|
if(this.multiplayer !== 2){
|
|
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"
|
|
|
|
this.titleCache.resize(640, 90, ratio)
|
|
}
|
|
if(!this.multiplayer){
|
|
this.pauseCache.resize(81 * this.pauseOptions.length * 2, 464, ratio)
|
|
}
|
|
this.fillComboCache()
|
|
this.setDonBgHeight()
|
|
}else if(this.controller.game.paused && !document.hasFocus()){
|
|
return
|
|
}else if(this.multiplayer !== 2){
|
|
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
|
|
}
|
|
winW /= ratio
|
|
winH /= ratio
|
|
if(!this.controller.game.paused){
|
|
this.ms = this.controller.game.getAccurateTime()
|
|
}
|
|
var ms = this.ms
|
|
|
|
if(this.portrait){
|
|
var frameTop = winH / 2 - 1280 / 2
|
|
var frameLeft = winW / 2 - 720 / 2
|
|
}else{
|
|
var frameTop = winH / 2 - 720 / 2
|
|
var frameLeft = winW / 2 - 1280 / 2
|
|
}
|
|
if(this.multiplayer === 2){
|
|
frameTop += this.multiplayer === 2 ? 165 : 176
|
|
}
|
|
if(touchMultiplayer){
|
|
if(!this.touchp2Class){
|
|
this.touchp2Class = true
|
|
this.gameDiv.classList.add("touchp2")
|
|
this.setDonBgHeight()
|
|
}
|
|
frameTop -= 90
|
|
}else if(this.touchp2Class){
|
|
this.touchp2Class = false
|
|
this.gameDiv.classList.remove("touchp2")
|
|
this.setDonBgHeight()
|
|
}
|
|
|
|
ctx.save()
|
|
ctx.translate(0, frameTop)
|
|
|
|
this.drawGogoTime()
|
|
|
|
if(!touchMultiplayer || this.multiplayer === 1 && frameTop >= 0){
|
|
this.assets.drawAssets("background")
|
|
}
|
|
|
|
if(this.multiplayer !== 2){
|
|
this.titleCache.get({
|
|
ctx: ctx,
|
|
x: winW - (touchMultiplayer && fullScreenSupported ? 750 : 650),
|
|
y: touchMultiplayer ? 75 : 10,
|
|
w: 640,
|
|
h: 90,
|
|
id: "title"
|
|
}, ctx => {
|
|
var selectedSong = this.controller.selectedSong
|
|
|
|
this.draw.layeredText({
|
|
ctx: ctx,
|
|
text: selectedSong.title,
|
|
fontSize: 40,
|
|
fontFamily: this.font,
|
|
x: 620,
|
|
y: 20,
|
|
width: 600,
|
|
align: "right"
|
|
}, [
|
|
{outline: "#000", letterBorder: 10},
|
|
{fill: "#fff"}
|
|
])
|
|
|
|
if(selectedSong.category){
|
|
var _w = 142
|
|
var _h = 22
|
|
var _x = 628 - _w
|
|
var _y = 88 - _h
|
|
if(selectedSong.category in this.categories){
|
|
ctx.fillStyle = this.categories[selectedSong.category].infoFill
|
|
}else{
|
|
ctx.fillStyle = this.categories.default.infoFill
|
|
}
|
|
this.draw.roundedRect({
|
|
ctx: ctx,
|
|
x: _x, y: _y,
|
|
w: _w, h: _h,
|
|
radius: 11
|
|
})
|
|
ctx.fill()
|
|
|
|
if(selectedSong.category in strings.categories){
|
|
var categoryName = strings.categories[selectedSong.category]
|
|
}else{
|
|
var categoryName = selectedSong.category
|
|
}
|
|
this.draw.layeredText({
|
|
ctx: ctx,
|
|
text: categoryName,
|
|
fontSize: 15,
|
|
fontFamily: this.font,
|
|
align: "center",
|
|
baseline: "middle",
|
|
x: _x + _w / 2,
|
|
y: _y + _h / 2,
|
|
width: 122
|
|
}, [
|
|
{fill: "#fff"}
|
|
])
|
|
}
|
|
})
|
|
}
|
|
|
|
var score = this.controller.getGlobalScore()
|
|
var gaugePercent = Math.round(score.gauge / 2) / 50
|
|
|
|
if(this.multiplayer === 2){
|
|
var scoreImg = "bg_score_p2"
|
|
var scoreFill = "#6bbec0"
|
|
}else{
|
|
var scoreImg = "bg_score_p1"
|
|
var scoreFill = "#fa4529"
|
|
}
|
|
|
|
if(this.portrait){
|
|
// Portrait
|
|
|
|
if(!this.portraitClass){
|
|
this.portraitClass = true
|
|
this.gameDiv.classList.add("portrait")
|
|
this.setDonBgHeight()
|
|
}
|
|
|
|
this.slotPos = {
|
|
x: 66,
|
|
y: frameTop + 375,
|
|
size: 100,
|
|
paddingLeft: 0
|
|
}
|
|
this.scorePos = {x: 363, y: frameTop + (this.multiplayer === 2 ? 520 : 227)}
|
|
|
|
var animPos = {
|
|
x1: this.slotPos.x + 13,
|
|
y1: this.slotPos.y + (this.multiplayer === 2 ? 27 : -27),
|
|
x2: winW - 38,
|
|
y2: frameTop + (this.multiplayer === 2 ? 484 : 293)
|
|
}
|
|
var taikoPos = {
|
|
x: 19,
|
|
y: frameTop + (this.multiplayer === 2 ? 464 : 184),
|
|
w: 111,
|
|
h: 130
|
|
}
|
|
|
|
ctx.fillStyle = "#000"
|
|
ctx.fillRect(
|
|
0,
|
|
this.multiplayer === 2 ? 306 : 288,
|
|
winW,
|
|
this.multiplayer === 1 ? 184 : 183
|
|
)
|
|
ctx.beginPath()
|
|
if(this.multiplayer === 2){
|
|
ctx.moveTo(0, 467)
|
|
ctx.lineTo(384, 467)
|
|
ctx.lineTo(384, 512)
|
|
ctx.lineTo(184, 560)
|
|
ctx.lineTo(0, 560)
|
|
}else{
|
|
ctx.moveTo(0, 217)
|
|
ctx.lineTo(184, 217)
|
|
ctx.lineTo(384, 265)
|
|
ctx.lineTo(384, 309)
|
|
ctx.lineTo(0, 309)
|
|
}
|
|
ctx.fill()
|
|
|
|
// Left side
|
|
ctx.fillStyle = scoreFill
|
|
var leftSide = (ctx, mul) => {
|
|
ctx.beginPath()
|
|
if(this.multiplayer === 2){
|
|
ctx.moveTo(0, 468 * mul)
|
|
ctx.lineTo(380 * mul, 468 * mul)
|
|
ctx.lineTo(380 * mul, 512 * mul)
|
|
ctx.lineTo(184 * mul, 556 * mul)
|
|
ctx.lineTo(0, 556 * mul)
|
|
}else{
|
|
ctx.moveTo(0, 221 * mul)
|
|
ctx.lineTo(184 * mul, 221 * mul)
|
|
ctx.lineTo(380 * mul, 265 * mul)
|
|
ctx.lineTo(380 * mul, 309 * mul)
|
|
ctx.lineTo(0, 309 * mul)
|
|
}
|
|
}
|
|
leftSide(ctx, 1)
|
|
ctx.fill()
|
|
ctx.globalAlpha = 0.5
|
|
this.draw.pattern({
|
|
ctx: ctx,
|
|
img: assets.image[scoreImg],
|
|
shape: leftSide,
|
|
dx: 0,
|
|
dy: 45,
|
|
scale: 1.55
|
|
})
|
|
ctx.globalAlpha = 1
|
|
|
|
// Score background
|
|
ctx.fillStyle = "#000"
|
|
ctx.beginPath()
|
|
if(this.multiplayer === 2){
|
|
this.draw.roundedCorner(ctx, 184, 512, 20, 0)
|
|
ctx.lineTo(384, 512)
|
|
this.draw.roundedCorner(ctx, 384, 560, 12, 2)
|
|
ctx.lineTo(184, 560)
|
|
}else{
|
|
ctx.moveTo(184, 217)
|
|
this.draw.roundedCorner(ctx, 384, 217, 12, 1)
|
|
ctx.lineTo(384, 265)
|
|
this.draw.roundedCorner(ctx, 184, 265, 20, 3)
|
|
}
|
|
ctx.fill()
|
|
|
|
// Difficulty
|
|
ctx.drawImage(assets.image["difficulty"],
|
|
0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
|
|
168, 143,
|
|
126, this.multiplayer === 2 ? 497 : 228,
|
|
62, 53
|
|
)
|
|
|
|
// Badges
|
|
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
|
|
this.ctx.drawImage(assets.image["badge_auto"],
|
|
183,
|
|
this.multiplayer === 2 ? 490 : 265,
|
|
23,
|
|
23
|
|
)
|
|
}
|
|
|
|
// Gauge
|
|
ctx.fillStyle = "#000"
|
|
ctx.beginPath()
|
|
var gaugeX = winW - 788 * 0.7 - 32
|
|
if(this.multiplayer === 2){
|
|
ctx.moveTo(gaugeX, 464)
|
|
ctx.lineTo(winW, 464)
|
|
ctx.lineTo(winW, 489)
|
|
this.draw.roundedCorner(ctx, gaugeX, 489, 12, 3)
|
|
}else{
|
|
this.draw.roundedCorner(ctx, gaugeX, 288, 12, 0)
|
|
ctx.lineTo(winW, 288)
|
|
ctx.lineTo(winW, 314)
|
|
ctx.lineTo(gaugeX, 314)
|
|
}
|
|
ctx.fill()
|
|
this.draw.gauge({
|
|
ctx: ctx,
|
|
x: winW,
|
|
y: this.multiplayer === 2 ? 468 : 273,
|
|
clear: 25 / 50,
|
|
percentage: gaugePercent,
|
|
font: this.font,
|
|
scale: 0.7,
|
|
multiplayer: this.multiplayer === 2,
|
|
blue: this.multiplayer === 2
|
|
})
|
|
this.draw.soul({
|
|
ctx: ctx,
|
|
x: winW - 40,
|
|
y: this.multiplayer === 2 ? 484 : 293,
|
|
scale: 0.75,
|
|
cleared: gaugePercent - 1 / 50 >= 25 / 50
|
|
})
|
|
|
|
// Note bar
|
|
ctx.fillStyle = "#2c2a2c"
|
|
ctx.fillRect(0, 314, winW, 122)
|
|
ctx.fillStyle = "#847f84"
|
|
ctx.fillRect(0, 440, winW, 24)
|
|
|
|
}else{
|
|
// Landscape
|
|
|
|
if(this.portraitClass){
|
|
this.portraitClass = false
|
|
this.gameDiv.classList.remove("portrait")
|
|
this.setDonBgHeight()
|
|
}
|
|
|
|
this.slotPos = {
|
|
x: 413,
|
|
y: frameTop + 257,
|
|
size: 106,
|
|
paddingLeft: 332
|
|
}
|
|
this.scorePos = {
|
|
x: 155,
|
|
y: frameTop + (this.multiplayer === 2 ? 318 : 193)
|
|
}
|
|
|
|
var animPos = {
|
|
x1: this.slotPos.x + 14,
|
|
y1: this.slotPos.y + (this.multiplayer === 2 ? 29 : -29),
|
|
x2: winW - 55,
|
|
y2: frameTop + (this.multiplayer === 2 ? 378 : 165)
|
|
}
|
|
var taikoPos = {x: 179, y: frameTop + 190, w: 138, h: 162}
|
|
|
|
ctx.fillStyle = "#000"
|
|
ctx.fillRect(
|
|
0,
|
|
184,
|
|
winW,
|
|
this.multiplayer === 1 ? 177 : 176
|
|
)
|
|
ctx.beginPath()
|
|
if(this.multiplayer === 2){
|
|
ctx.moveTo(328, 351)
|
|
ctx.lineTo(winW, 351)
|
|
ctx.lineTo(winW, 385)
|
|
this.draw.roundedCorner(ctx, 328, 385, 10, 3)
|
|
}else{
|
|
ctx.moveTo(328, 192)
|
|
this.draw.roundedCorner(ctx, 328, 158, 10, 0)
|
|
ctx.lineTo(winW, 158)
|
|
ctx.lineTo(winW, 192)
|
|
}
|
|
ctx.fill()
|
|
|
|
// Gauge
|
|
this.draw.gauge({
|
|
ctx: ctx,
|
|
x: winW,
|
|
y: this.multiplayer === 2 ? 357 : 135,
|
|
clear: 25 / 50,
|
|
percentage: gaugePercent,
|
|
font: this.font,
|
|
multiplayer: this.multiplayer === 2,
|
|
blue: this.multiplayer === 2
|
|
})
|
|
this.draw.soul({
|
|
ctx: ctx,
|
|
x: winW - 57,
|
|
y: this.multiplayer === 2 ? 378 : 165,
|
|
cleared: gaugePercent - 1 / 50 >= 25 / 50
|
|
})
|
|
|
|
// Note bar
|
|
ctx.fillStyle = "#2c2a2c"
|
|
ctx.fillRect(332, 192, winW - 332, 130)
|
|
ctx.fillStyle = "#847f84"
|
|
ctx.fillRect(332, 326, winW - 332, 26)
|
|
|
|
// Left side
|
|
ctx.fillStyle = scoreFill
|
|
ctx.fillRect(0, 192, 328, 160)
|
|
ctx.globalAlpha = 0.5
|
|
this.draw.pattern({
|
|
ctx: ctx,
|
|
img: assets.image[scoreImg],
|
|
x: 0,
|
|
y: 192,
|
|
w: 328,
|
|
h: 160,
|
|
dx: 0,
|
|
dy: 45,
|
|
scale: 1.55
|
|
})
|
|
ctx.globalAlpha = 1
|
|
|
|
// Difficulty
|
|
ctx.drawImage(assets.image["difficulty"],
|
|
0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
|
|
168, 143,
|
|
16, this.multiplayer === 2 ? 194 : 232,
|
|
141, 120
|
|
)
|
|
var diff = this.controller.selectedSong.difficulty
|
|
var text = strings[diff === "ura" ? "oni" : diff]
|
|
ctx.font = this.draw.bold(this.font) + "20px " + this.font
|
|
ctx.textAlign = "center"
|
|
ctx.textBaseline = "bottom"
|
|
ctx.strokeStyle = "#000"
|
|
ctx.fillStyle = "#fff"
|
|
ctx.lineWidth = 7
|
|
ctx.miterLimit = 1
|
|
ctx.strokeText(text, 87, this.multiplayer === 2 ? 310 : 348)
|
|
ctx.fillText(text, 87, this.multiplayer === 2 ? 310 : 348)
|
|
ctx.miterLimit = 10
|
|
|
|
// Badges
|
|
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
|
|
this.ctx.drawImage(assets.image["badge_auto"],
|
|
125, 235, 34, 34
|
|
)
|
|
}
|
|
|
|
// Score background
|
|
ctx.fillStyle = "#000"
|
|
ctx.beginPath()
|
|
if(this.multiplayer === 2){
|
|
ctx.moveTo(0, 312)
|
|
this.draw.roundedCorner(ctx, 176, 312, 20, 1)
|
|
ctx.lineTo(176, 353)
|
|
ctx.lineTo(0, 353)
|
|
}else{
|
|
ctx.moveTo(0, 191)
|
|
ctx.lineTo(176, 191)
|
|
this.draw.roundedCorner(ctx, 176, 232, 20, 2)
|
|
ctx.lineTo(0, 232)
|
|
}
|
|
ctx.fill()
|
|
}
|
|
|
|
ctx.restore()
|
|
|
|
animPos.w = animPos.x2 - animPos.x1
|
|
animPos.h = animPos.y1 - animPos.y2
|
|
this.animateBezier = [{
|
|
// 427, 228
|
|
x: animPos.x1,
|
|
y: animPos.y1
|
|
}, {
|
|
// 560, 10
|
|
x: animPos.x1 + animPos.w / 6,
|
|
y: animPos.y1 - animPos.h * (this.multiplayer === 2 ? 2.5 : 3.5)
|
|
}, {
|
|
// 940, -150
|
|
x: animPos.x2 - animPos.w / 3,
|
|
y: animPos.y2 - animPos.h * (this.multiplayer === 2 ? 3.5 : 5)
|
|
}, {
|
|
// 1225, 165
|
|
x: animPos.x2,
|
|
y: animPos.y2
|
|
}]
|
|
|
|
var touchTop = frameTop + (touchMultiplayer ? 135 : 0)
|
|
this.touchDrum = (() => {
|
|
var sw = 842
|
|
var sh = 340
|
|
var x = 0
|
|
var y = this.portrait ? touchTop + 477 : touchTop + 365
|
|
var paddingTop = 13
|
|
var w = winW
|
|
var maxH = winH - y
|
|
var h = maxH - paddingTop
|
|
if(w / h >= sw / sh){
|
|
w = h / sh * sw
|
|
x = (winW - w) / 2
|
|
y += paddingTop
|
|
}else{
|
|
h = w / sw * sh
|
|
y = y + (maxH - h)
|
|
}
|
|
return {
|
|
x: x, y: y, w: w, h: h
|
|
}
|
|
})()
|
|
this.touchCircle = {
|
|
x: winW / 2,
|
|
y: winH + this.touchDrum.h * 0.1,
|
|
rx: this.touchDrum.w / 2 - this.touchDrum.h * 0.03,
|
|
ry: this.touchDrum.h * 1.07
|
|
}
|
|
|
|
if(this.multiplayer !== 2){
|
|
this.mouseIdle()
|
|
this.drawTouch()
|
|
}
|
|
|
|
// Score
|
|
ctx.save()
|
|
ctx.font = "30px TnT, Meiryo, sans-serif"
|
|
ctx.fillStyle = "#fff"
|
|
ctx.strokeStyle = "#fff"
|
|
ctx.lineWidth = 0.3
|
|
ctx.textAlign = "center"
|
|
ctx.textBaseline = "top"
|
|
var glyph = 29
|
|
var pointsText = score.points.toString().split("")
|
|
ctx.translate(this.scorePos.x, this.scorePos.y)
|
|
ctx.scale(0.7, 1)
|
|
for(var i in pointsText){
|
|
var x = glyph * (i - pointsText.length + 1)
|
|
ctx.strokeText(pointsText[i], x, 0)
|
|
ctx.fillText(pointsText[i], x, 0)
|
|
}
|
|
ctx.restore()
|
|
|
|
// Bar pressed keys
|
|
var keyTime = this.controller.getKeyTime()
|
|
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
|
|
var padding = this.slotPos.paddingLeft
|
|
var mul = this.slotPos.size / 106
|
|
var barY = this.slotPos.y - 65 * mul
|
|
var barH = 130 * mul
|
|
|
|
if(this.gogoTime || ms <= this.gogoTimeStarted + 100){
|
|
var grd = ctx.createLinearGradient(padding, 0, winW, 0)
|
|
grd.addColorStop(0, "#512a2c")
|
|
grd.addColorStop(0.46, "#6f2a2d")
|
|
grd.addColorStop(0.76, "#8a4763")
|
|
grd.addColorStop(1, "#2c2a2c")
|
|
ctx.fillStyle = grd
|
|
if(!this.touchEnabled){
|
|
var alpha = Math.min(100, ms - this.gogoTimeStarted) / 100
|
|
if(!this.gogoTime){
|
|
alpha = 1 - alpha
|
|
}
|
|
ctx.globalAlpha = alpha
|
|
}
|
|
ctx.fillRect(padding, barY, winW - padding, barH)
|
|
}
|
|
if(keyTime[sound] > ms - 130){
|
|
var gradients = {
|
|
"don": "255, 0, 0",
|
|
"ka": "0, 170, 255"
|
|
}
|
|
var yellow = "255, 231, 0"
|
|
var currentGradient = gradients[sound]
|
|
ctx.globalCompositeOperation = "lighter"
|
|
do{
|
|
var grd = ctx.createLinearGradient(padding, 0, winW, 0)
|
|
grd.addColorStop(0, "rgb(" + currentGradient + ")")
|
|
grd.addColorStop(1, "rgba(" + currentGradient + ", 0)")
|
|
ctx.fillStyle = grd
|
|
ctx.globalAlpha = (1 - (ms - keyTime[sound]) / 130) / 5
|
|
ctx.fillRect(padding, barY, winW - padding, barH)
|
|
}while(this.currentScore.ms > ms - 130 && currentGradient !== yellow && (currentGradient = yellow))
|
|
ctx.globalCompositeOperation = "source-over"
|
|
}
|
|
ctx.globalAlpha = 1
|
|
|
|
// Taiko
|
|
ctx.drawImage(assets.image["taiko"],
|
|
0, 0, 138, 162,
|
|
taikoPos.x, taikoPos.y, taikoPos.w, taikoPos.h
|
|
)
|
|
|
|
// Taiko pressed keys
|
|
var kbd = this.controller.getBindings()
|
|
var keys = ["ka_l", "ka_r", "don_l", "don_r"]
|
|
|
|
for(var i = 0; i < keys.length; i++){
|
|
var keyMS = ms - keyTime[kbd[keys[i]]]
|
|
if(keyMS < 130){
|
|
if(keyMS > 70 && !this.touchEnabled){
|
|
ctx.globalAlpha = this.draw.easeOut(1 - (keyMS - 70) / 60)
|
|
}
|
|
ctx.drawImage(assets.image["taiko"],
|
|
0, 162 * (i + 1), 138, 162,
|
|
taikoPos.x, taikoPos.y, taikoPos.w, taikoPos.h
|
|
)
|
|
}
|
|
}
|
|
ctx.globalAlpha = 1
|
|
|
|
// Combo
|
|
var scoreMS = ms - this.currentScore.ms
|
|
|
|
var comboCount = this.controller.getCombo()
|
|
if(comboCount >= 10){
|
|
var comboText = comboCount.toString().split("")
|
|
var mul = this.portrait ? 0.8 : 1
|
|
var comboX = taikoPos.x + taikoPos.w / 2
|
|
var comboY = taikoPos.y + taikoPos.h * 0.09
|
|
var comboScale = 0
|
|
if(this.currentScore !== 0 && scoreMS < 100){
|
|
comboScale = this.draw.fade(scoreMS / 100)
|
|
}
|
|
var glyphW = 51
|
|
var glyphH = 64
|
|
var letterSpacing = (comboText.length >= 4 ? 38 : 42) * mul
|
|
var orange = comboCount >= 100 ? "1" : "0"
|
|
|
|
var w = glyphW * mul
|
|
var h = glyphH * mul * (1 + comboScale / 8)
|
|
|
|
for(var i in comboText){
|
|
var textX = comboX + letterSpacing * (i - (comboText.length - 1) / 2)
|
|
this.comboCache.get({
|
|
ctx: ctx,
|
|
x: textX - w / 2,
|
|
y: comboY + glyphH * mul - h,
|
|
w: w,
|
|
h: h,
|
|
id: orange + "combo" + comboText[i]
|
|
})
|
|
}
|
|
|
|
var fontSize = 24 * mul
|
|
var comboTextY = taikoPos.y + taikoPos.h * 0.63
|
|
if(orange === "1"){
|
|
var grd = ctx.createLinearGradient(
|
|
0,
|
|
comboTextY - fontSize * 0.6,
|
|
0,
|
|
comboTextY + fontSize * 0.1
|
|
)
|
|
grd.addColorStop(0, "#ff2000")
|
|
grd.addColorStop(0.5, "#ffc321")
|
|
grd.addColorStop(1, "#ffedb7")
|
|
ctx.fillStyle = grd
|
|
}else{
|
|
ctx.fillStyle = "#fff"
|
|
}
|
|
ctx.font = this.draw.bold(this.font) + fontSize + "px " + this.font
|
|
ctx.lineWidth = 7 * mul
|
|
ctx.textAlign = "center"
|
|
ctx.miterLimit = 1
|
|
ctx.strokeText(strings.combo, comboX, comboTextY)
|
|
ctx.miterLimit = 10
|
|
ctx.fillText(strings.combo, comboX, comboTextY)
|
|
}
|
|
|
|
// Slot
|
|
this.draw.slot(ctx, this.slotPos.x, this.slotPos.y, this.slotPos.size)
|
|
|
|
// Measures
|
|
ctx.save()
|
|
ctx.beginPath()
|
|
ctx.rect(this.slotPos.paddingLeft, 0, winW - this.slotPos.paddingLeft, winH)
|
|
ctx.clip()
|
|
this.drawMeasures()
|
|
ctx.restore()
|
|
|
|
// Go go time fire
|
|
this.assets.drawAssets("bar")
|
|
|
|
// Hit notes shadow
|
|
if(scoreMS < 300 && this.currentScore.type){
|
|
var fadeOut = scoreMS > 120 && !this.touchEnabled
|
|
if(fadeOut){
|
|
ctx.globalAlpha = 1 - (scoreMS - 120) / 180
|
|
}
|
|
var scoreId = this.currentScore.type === 230 ? 0 : 1
|
|
if(this.currentScore.bigNote){
|
|
scoreId += 2
|
|
}
|
|
ctx.drawImage(assets.image["notes_hit"],
|
|
0, 128 * scoreId, 128, 128,
|
|
this.slotPos.x - 64, this.slotPos.y - 64,
|
|
128, 128
|
|
)
|
|
if(fadeOut){
|
|
ctx.globalAlpha = 1
|
|
}
|
|
}
|
|
|
|
// Future notes
|
|
this.updateNoteFaces()
|
|
ctx.save()
|
|
ctx.beginPath()
|
|
ctx.rect(this.slotPos.paddingLeft, 0, winW - this.slotPos.paddingLeft, winH)
|
|
ctx.clip()
|
|
|
|
this.drawCircles(this.controller.getCircles())
|
|
ctx.restore()
|
|
|
|
// Hit notes explosion
|
|
this.assets.drawAssets("notes")
|
|
|
|
// Good, OK, Bad
|
|
if(scoreMS < 300){
|
|
var mul = this.slotPos.size / 106
|
|
var scores = {
|
|
"0": "bad",
|
|
"230": "ok",
|
|
"450": "good"
|
|
}
|
|
var yOffset = scoreMS < 70 ? scoreMS * (13 / 70) : 0
|
|
var fadeOut = scoreMS > 250 && !this.touchEnabled
|
|
if(fadeOut){
|
|
ctx.globalAlpha = 1 - (scoreMS - 250) / 50
|
|
}
|
|
this.draw.score({
|
|
ctx: ctx,
|
|
score: scores[this.currentScore.type],
|
|
x: this.slotPos.x,
|
|
y: this.slotPos.y - 98 * mul - yOffset,
|
|
scale: 1.35 * mul,
|
|
align: "center"
|
|
})
|
|
if(fadeOut){
|
|
ctx.globalAlpha = 1
|
|
}
|
|
}
|
|
|
|
// Animating notes
|
|
this.drawAnimatedCircles(this.controller.getCircles())
|
|
this.drawAnimatedCircles(this.drumroll)
|
|
|
|
// Go-go time fireworks
|
|
if(!this.touchEnabled && !this.portrait && !this.multiplayer){
|
|
this.assets.drawAssets("foreground")
|
|
}
|
|
|
|
// Pause screen
|
|
if(!this.multiplayer && this.controller.game.paused){
|
|
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
|
|
ctx.fillRect(0, 0, winW, winH)
|
|
|
|
ctx.save()
|
|
if(this.portrait){
|
|
ctx.translate(frameLeft - 242, frameTop + 308)
|
|
var pauseScale = 720 / 766
|
|
ctx.scale(pauseScale, pauseScale)
|
|
}else{
|
|
ctx.translate(frameLeft, frameTop)
|
|
}
|
|
|
|
var pauseRect = (ctx, mul) => {
|
|
this.draw.roundedRect({
|
|
ctx: ctx,
|
|
x: 269 * mul,
|
|
y: 93 * mul,
|
|
w: 742 * mul,
|
|
h: 494 * mul,
|
|
radius: 17 * mul
|
|
})
|
|
}
|
|
pauseRect(ctx, 1)
|
|
ctx.strokeStyle = "#fff"
|
|
ctx.lineWidth = 24
|
|
ctx.stroke()
|
|
ctx.strokeStyle = "#000"
|
|
ctx.lineWidth = 12
|
|
ctx.stroke()
|
|
this.draw.pattern({
|
|
ctx: ctx,
|
|
img: assets.image["bg_pause"],
|
|
shape: pauseRect,
|
|
dx: 68,
|
|
dy: 11
|
|
})
|
|
|
|
ctx.drawImage(assets.image["mimizu"],
|
|
313, 247, 136, 315
|
|
)
|
|
|
|
var _y = 108
|
|
var _w = 80
|
|
var _h = 464
|
|
for(var i = 0; i < this.pauseOptions.length; i++){
|
|
var _x = 520 + 110 * i
|
|
if(this.state.moveHover !== null){
|
|
var selected = i === this.state.moveHover
|
|
}else{
|
|
var selected = i === this.state.pausePos
|
|
}
|
|
if(selected){
|
|
ctx.fillStyle = "#ffb447"
|
|
this.draw.roundedRect({
|
|
ctx: ctx,
|
|
x: _x - _w / 2,
|
|
y: _y,
|
|
w: _w,
|
|
h: _h,
|
|
radius: 30
|
|
})
|
|
ctx.fill()
|
|
}
|
|
this.pauseCache.get({
|
|
ctx: ctx,
|
|
x: _x - _w / 2,
|
|
y: _y,
|
|
w: _w,
|
|
h: _h,
|
|
id: this.pauseOptions[i] + (selected ? "1" : "0")
|
|
}, ctx => {
|
|
var textConfig = {
|
|
ctx: ctx,
|
|
text: this.pauseOptions[i],
|
|
x: _w / 2,
|
|
y: 18,
|
|
width: _w,
|
|
height: _h - 54,
|
|
fontSize: 40,
|
|
fontFamily: this.font,
|
|
letterSpacing: -1
|
|
}
|
|
if(selected){
|
|
textConfig.fill = "#fff"
|
|
textConfig.outline = "#000"
|
|
textConfig.outlineSize = 10
|
|
}else{
|
|
textConfig.fill = "#000"
|
|
}
|
|
this.draw.verticalText(textConfig)
|
|
})
|
|
|
|
var highlight = 0
|
|
if(this.state.moveHover === i){
|
|
highlight = 2
|
|
}else if(selected){
|
|
highlight = 1
|
|
}
|
|
if(highlight){
|
|
this.draw.highlight({
|
|
ctx: ctx,
|
|
x: _x - _w / 2 - 3.5,
|
|
y: _y - 3.5,
|
|
w: _w + 7,
|
|
h: _h + 7,
|
|
animate: highlight === 1,
|
|
animateMS: this.state.moveMS,
|
|
opacity: highlight === 2 ? 0.8 : 1,
|
|
radius: 30
|
|
})
|
|
}
|
|
}
|
|
|
|
ctx.restore()
|
|
}
|
|
}
|
|
setBackground(){
|
|
var selectedSong = this.controller.selectedSong
|
|
var songSkinName = selectedSong.songSkin.name
|
|
var supportsBlend = "mixBlendMode" in this.songBg.style
|
|
var songLayers = [document.getElementById("layer1"), document.getElementById("layer2")]
|
|
var prefix = selectedSong.songSkin.prefix || ""
|
|
|
|
if(selectedSong.category in this.categories){
|
|
var catId = this.categories[selectedSong.category].sort
|
|
}else{
|
|
var catId = this.categories.default.sort
|
|
}
|
|
|
|
if(!selectedSong.songSkin.song){
|
|
var id = selectedSong.songBg
|
|
this.songBg.classList.add("songbg-" + id)
|
|
this.setLayers(songLayers, prefix + "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend)
|
|
}else if(selectedSong.songSkin.song !== "none"){
|
|
var notStatic = selectedSong.songSkin.song !== "static"
|
|
if(notStatic){
|
|
this.songBg.classList.add("songbg-" + selectedSong.songSkin.song)
|
|
}
|
|
this.setLayers(songLayers, prefix + "bg_song_" + songSkinName + (notStatic ? "_" : ""), notStatic)
|
|
}
|
|
|
|
if(!selectedSong.songSkin.stage){
|
|
this.songStage.classList.add("song-stage-" + selectedSong.songStage)
|
|
}else if(selectedSong.songSkin.stage !== "none"){
|
|
this.setBgImage(this.songStage, assets.image[prefix + "bg_stage_" + songSkinName].src)
|
|
}
|
|
}
|
|
setDonBg(){
|
|
var selectedSong = this.controller.selectedSong
|
|
var songSkinName = selectedSong.songSkin.name
|
|
var donLayers = []
|
|
var filename = !selectedSong.songSkin.don && this.multiplayer === 2 ? "bg_don2_" : "bg_don_"
|
|
var prefix = selectedSong.songSkin.prefix || ""
|
|
|
|
this.donBg = document.createElement("div")
|
|
this.donBg.classList.add("donbg")
|
|
if(this.multiplayer === 2){
|
|
this.donBg.classList.add("donbg-bottom")
|
|
}
|
|
for(var layer = 1; layer <= 3; layer++){
|
|
var donLayer = document.createElement("div")
|
|
donLayer.classList.add("donlayer" + layer)
|
|
this.donBg.appendChild(donLayer)
|
|
if(layer !== 3){
|
|
donLayers.push(donLayer)
|
|
}
|
|
}
|
|
this.songBg.parentNode.insertBefore(this.donBg, this.songBg)
|
|
var asset1, asset2
|
|
if(!selectedSong.songSkin.don){
|
|
this.donBg.classList.add("donbg-" + selectedSong.donBg)
|
|
this.setLayers(donLayers, prefix + filename + selectedSong.donBg, true)
|
|
asset1 = filename + selectedSong.donBg + "a"
|
|
asset2 = filename + selectedSong.donBg + "b"
|
|
}else if(selectedSong.songSkin.don !== "none"){
|
|
var notStatic = selectedSong.songSkin.don !== "static"
|
|
if(notStatic){
|
|
this.donBg.classList.add("donbg-" + selectedSong.songSkin.don)
|
|
asset1 = filename + songSkinName + "_a"
|
|
asset2 = filename + songSkinName + "_b"
|
|
}else{
|
|
asset1 = filename + songSkinName
|
|
asset2 = filename + songSkinName
|
|
}
|
|
this.setLayers(donLayers, prefix + filename + songSkinName + (notStatic ? "_" : ""), notStatic)
|
|
}else{
|
|
return
|
|
}
|
|
var w1 = assets.image[prefix + asset1].width
|
|
var w2 = assets.image[prefix + asset2].width
|
|
this.donBg.style.setProperty("--sw", w1 > w2 ? w1 : w2)
|
|
this.donBg.style.setProperty("--sw1", w1)
|
|
this.donBg.style.setProperty("--sw2", w2)
|
|
this.donBg.style.setProperty("--sh1", assets.image[prefix + asset1].height)
|
|
this.donBg.style.setProperty("--sh2", assets.image[prefix + asset2].height)
|
|
}
|
|
setDonBgHeight(){
|
|
this.donBg.style.setProperty("--h", getComputedStyle(this.donBg).height)
|
|
}
|
|
setLayers(elements, file, ab){
|
|
if(ab){
|
|
this.setBgImage(elements[0], assets.image[file + "a"].src)
|
|
this.setBgImage(elements[1], assets.image[file + "b"].src)
|
|
}else{
|
|
this.setBgImage(elements[0], assets.image[file].src)
|
|
}
|
|
}
|
|
setBgImage(element, url){
|
|
element.style.backgroundImage = "url('" + url + "')"
|
|
}
|
|
|
|
drawMeasures(){
|
|
var measures = this.controller.parsedSongData.measures
|
|
var ms = this.getMS()
|
|
var mul = this.slotPos.size / 106
|
|
var distanceForCircle = this.winW / this.ratio - this.slotPos.x
|
|
var measureY = this.slotPos.y - 65 * mul
|
|
var measureH = 130 * mul
|
|
|
|
measures.forEach(measure => {
|
|
var timeForDistance = this.posToMs(distanceForCircle, measure.speed)
|
|
var startingTime = measure.ms - timeForDistance
|
|
var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed)
|
|
if(ms >= startingTime && ms <= finishTime){
|
|
var measureX = this.slotPos.x + this.msToPos(measure.ms - ms, measure.speed)
|
|
this.ctx.strokeStyle = "#bdbdbd"
|
|
this.ctx.lineWidth = 3
|
|
this.ctx.beginPath()
|
|
this.ctx.moveTo(measureX, measureY)
|
|
this.ctx.lineTo(measureX, measureY + measureH)
|
|
this.ctx.stroke()
|
|
}
|
|
})
|
|
}
|
|
updateNoteFaces(){
|
|
var ms = this.getMS()
|
|
while(ms >= this.nextBeat){
|
|
this.nextBeat += this.beatInterval
|
|
if(this.controller.getCombo() >= 50){
|
|
var face = Math.floor(ms / this.beatInterval) % 2
|
|
this.noteFace = {
|
|
small: face,
|
|
big: face + 2
|
|
}
|
|
}else{
|
|
this.noteFace = {
|
|
small: 0,
|
|
big: 3
|
|
}
|
|
}
|
|
}
|
|
}
|
|
drawCircles(circles){
|
|
var distanceForCircle = this.winW / this.ratio - this.slotPos.x
|
|
var ms = this.getMS()
|
|
|
|
for(var i = circles.length; i--;){
|
|
var circle = circles[i]
|
|
var speed = circle.getSpeed()
|
|
|
|
var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed)
|
|
var startingTime = circle.getMS() - timeForDistance
|
|
var finishTime = circle.getEndTime() + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed)
|
|
|
|
if(circle.getPlayed() <= 0 || circle.getScore() === 0){
|
|
if(ms >= startingTime && ms <= finishTime && circle.getPlayed() !== -1){
|
|
this.drawCircle(circle)
|
|
}
|
|
}else if(!circle.isAnimated()){
|
|
// Start animation to gauge
|
|
circle.animate(ms)
|
|
}
|
|
if(ms >= circle.ms && !circle.gogoChecked){
|
|
if(this.gogoTime != circle.gogoTime){
|
|
this.toggleGogoTime(circle)
|
|
}
|
|
circle.gogoChecked = true
|
|
}
|
|
}
|
|
}
|
|
drawAnimatedCircles(circles){
|
|
var ms = this.getMS()
|
|
|
|
for(var i = 0; i < circles.length; i++){
|
|
var circle = circles[i]
|
|
|
|
if(circle.isAnimated()){
|
|
|
|
var animT = circle.getAnimT()
|
|
if(ms < animT + 490){
|
|
|
|
if(circle.fixedPos){
|
|
circle.fixedPos = false
|
|
circle.animT = ms
|
|
animT = ms
|
|
}
|
|
var animPoint = (ms - animT) / 490
|
|
var bezierPoint = this.calcBezierPoint(this.draw.easeOut(animPoint), this.animateBezier)
|
|
this.drawCircle(circle, {x: bezierPoint.x, y: bezierPoint.y})
|
|
|
|
}else if(ms < animT + 810){
|
|
var pos = this.animateBezier[3]
|
|
this.drawCircle(circle, pos, (ms - animT - 490) / 160)
|
|
}else{
|
|
circle.endAnimation()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
calcBezierPoint(t, data){
|
|
var at = 1 - t
|
|
data = data.slice()
|
|
|
|
for(var i = 1; i < data.length; i++){
|
|
for(var k = 0; k < data.length - i; k++){
|
|
data[k] = {
|
|
x: data[k].x * at + data[k + 1].x * t,
|
|
y: data[k].y * at + data[k + 1].y * t
|
|
}
|
|
}
|
|
}
|
|
return data[0]
|
|
}
|
|
drawCircle(circle, circlePos, fade){
|
|
var ctx = this.ctx
|
|
var mul = this.slotPos.size / 106
|
|
|
|
var bigCircleSize = 106 * mul / 2
|
|
var circleSize = 70 * mul / 2
|
|
var lyricsSize = 20 * mul
|
|
|
|
var fill, size, faceID
|
|
var type = circle.getType()
|
|
var ms = this.getMS()
|
|
var circleMs = circle.getMS()
|
|
var endTime = circle.getEndTime()
|
|
var animated = circle.isAnimated()
|
|
var speed = circle.getSpeed()
|
|
var played = circle.getPlayed()
|
|
var drumroll = 0
|
|
var endX = 0
|
|
|
|
if(!circlePos){
|
|
circlePos = {
|
|
x: this.slotPos.x + this.msToPos(circleMs - ms, speed),
|
|
y: this.slotPos.y
|
|
}
|
|
}
|
|
if(animated){
|
|
var noteFace = {
|
|
small: 0,
|
|
big: 3
|
|
}
|
|
}else{
|
|
var noteFace = this.noteFace
|
|
}
|
|
if(type === "don" || type === "daiDon" && played === 1){
|
|
fill = "#f34728"
|
|
size = circleSize
|
|
faceID = noteFace.small
|
|
}else if(type === "ka" || type === "daiKa" && played === 1){
|
|
fill = "#65bdbb"
|
|
size = circleSize
|
|
faceID = noteFace.small
|
|
}else if(type === "daiDon"){
|
|
fill = "#f34728"
|
|
size = bigCircleSize
|
|
faceID = noteFace.big
|
|
}else if(type === "daiKa"){
|
|
fill = "#65bdbb"
|
|
size = bigCircleSize
|
|
faceID = noteFace.big
|
|
}else if(type === "balloon"){
|
|
if(animated){
|
|
fill = "#f34728"
|
|
size = bigCircleSize * 0.8
|
|
faceID = noteFace.big
|
|
}else{
|
|
fill = "#f87700"
|
|
size = circleSize
|
|
faceID = noteFace.small
|
|
var h = size * 1.8
|
|
if(circleMs < ms && ms <= endTime){
|
|
circlePos.x = this.slotPos.x
|
|
}else if(ms > endTime){
|
|
circlePos.x = this.slotPos.x + this.msToPos(endTime - ms, speed)
|
|
}
|
|
ctx.drawImage(assets.image["balloon"],
|
|
circlePos.x + size - 4,
|
|
circlePos.y - h / 2 + 2,
|
|
h / 61 * 115,
|
|
h
|
|
)
|
|
}
|
|
}else if(type === "drumroll" || type === "daiDrumroll"){
|
|
fill = "#f3b500"
|
|
if(type == "drumroll"){
|
|
size = circleSize
|
|
faceID = noteFace.small
|
|
}else{
|
|
size = bigCircleSize
|
|
faceID = noteFace.big
|
|
}
|
|
endX = this.msToPos(endTime - circleMs, speed)
|
|
drumroll = endX > 50 ? 2 : 1
|
|
|
|
ctx.fillStyle = fill
|
|
ctx.strokeStyle = "#000"
|
|
ctx.lineWidth = 3
|
|
ctx.beginPath()
|
|
ctx.moveTo(circlePos.x, circlePos.y - size + 1.5)
|
|
ctx.arc(circlePos.x + endX, circlePos.y, size - 1.5, Math.PI / -2, Math.PI / 2)
|
|
ctx.lineTo(circlePos.x, circlePos.y + size - 1.5)
|
|
ctx.fill()
|
|
ctx.stroke()
|
|
}
|
|
if(!fade || fade < 1){
|
|
// Main circle
|
|
ctx.fillStyle = fill
|
|
ctx.beginPath()
|
|
ctx.arc(circlePos.x, circlePos.y, size - 1, 0, Math.PI * 2)
|
|
ctx.fill()
|
|
// Face on circle
|
|
var drawSize = size
|
|
if(faceID < 2){
|
|
drawSize *= bigCircleSize / circleSize
|
|
}
|
|
ctx.drawImage(assets.image[drumroll ? "notes_drumroll" : "notes"],
|
|
0, 172 * faceID,
|
|
172, 172,
|
|
circlePos.x - drawSize - 4,
|
|
circlePos.y - drawSize - 4,
|
|
drawSize * 2 + 8,
|
|
drawSize * 2 + 8
|
|
)
|
|
}
|
|
if(fade && !this.touchEnabled){
|
|
ctx.globalAlpha = this.draw.easeOut(fade < 1 ? fade : 2 - fade)
|
|
ctx.fillStyle = "#fff"
|
|
ctx.beginPath()
|
|
ctx.arc(circlePos.x, circlePos.y, size - 1, 0, Math.PI * 2)
|
|
ctx.fill()
|
|
ctx.globalAlpha = 1
|
|
}
|
|
if(!circle.isAnimated()){
|
|
// Text
|
|
var text = circle.getText()
|
|
var textX = circlePos.x
|
|
var textY = circlePos.y + 83 * mul
|
|
ctx.font = lyricsSize + "px Kozuka, Microsoft YaHei, sans-serif"
|
|
ctx.textBaseline = "middle"
|
|
ctx.textAlign = "center"
|
|
|
|
if(drumroll === 2){
|
|
var longText = text.split("ー")
|
|
text = longText[0]
|
|
var text0Width = ctx.measureText(longText[0]).width
|
|
var text1Width = ctx.measureText(longText[1]).width
|
|
}
|
|
|
|
ctx.fillStyle = "#fff"
|
|
ctx.strokeStyle = "#000"
|
|
ctx.lineWidth = 5
|
|
ctx.strokeText(text, textX, textY)
|
|
|
|
if(drumroll === 2){
|
|
ctx.strokeText(longText[1], textX + endX, textY)
|
|
|
|
ctx.lineWidth = 4
|
|
var x1 = textX + text0Width / 2
|
|
var x2 = textX + endX - text1Width / 2
|
|
ctx.beginPath()
|
|
ctx.moveTo(x1, textY - 2)
|
|
ctx.lineTo(x2, textY - 2)
|
|
ctx.lineTo(x2, textY + 1)
|
|
ctx.lineTo(x1, textY + 1)
|
|
ctx.closePath()
|
|
ctx.stroke()
|
|
ctx.fill()
|
|
}
|
|
|
|
ctx.strokeStyle = "#fff"
|
|
ctx.lineWidth = 0.5
|
|
|
|
ctx.strokeText(text, textX, textY)
|
|
ctx.fillText(text, textX, textY)
|
|
|
|
if(drumroll === 2){
|
|
ctx.strokeText(longText[1], textX + endX, textY)
|
|
ctx.fillText(longText[1], textX + endX, textY)
|
|
}
|
|
}
|
|
}
|
|
fillComboCache(){
|
|
var fontSize = 58
|
|
var letterSpacing = fontSize * 0.67
|
|
var glyphW = 50
|
|
var glyphH = 64
|
|
var textX = 5
|
|
var textY = 5
|
|
var letterBorder = fontSize * 0.15
|
|
|
|
this.comboCache.resize((glyphW + 1) * 20, glyphH + 1, this.ratio)
|
|
for(var orange = 0; orange < 2; orange++){
|
|
for(var i = 0; i < 10; i++){
|
|
this.comboCache.set({
|
|
w: glyphW,
|
|
h: glyphH,
|
|
id: orange + "combo" + i
|
|
}, ctx => {
|
|
ctx.scale(0.9, 1)
|
|
if(orange){
|
|
var grd = ctx.createLinearGradient(
|
|
(glyphW - glyphH) / 2,
|
|
0,
|
|
(glyphW + glyphH) / 2,
|
|
glyphH
|
|
)
|
|
grd.addColorStop(0.3, "#ff2000")
|
|
grd.addColorStop(0.5, "#ffc321")
|
|
grd.addColorStop(0.6, "#ffedb7")
|
|
grd.addColorStop(0.8, "#ffffce")
|
|
var fill = grd
|
|
}else{
|
|
var fill = "#fff"
|
|
}
|
|
this.draw.layeredText({
|
|
ctx: ctx,
|
|
text: i.toString(),
|
|
fontSize: fontSize,
|
|
fontFamily: "TnT, Meiryo, sans-serif",
|
|
x: textX,
|
|
y: textY
|
|
}, [
|
|
{x: -2, y: -1, outline: "#000", letterBorder: letterBorder},
|
|
{x: 3.5, y: 1.5},
|
|
{x: 3, y: 1},
|
|
{},
|
|
{x: -2, y: -1, fill: "#fff"},
|
|
{x: 3.5, y: 1.5, fill: fill},
|
|
{x: 3, y: 1, fill: "rgba(0, 0, 0, 0.5)"},
|
|
{fill: fill}
|
|
])
|
|
})
|
|
}
|
|
}
|
|
this.globalAlpha = 0
|
|
this.comboCache.get({
|
|
ctx: this.ctx,
|
|
x: 0,
|
|
y: 0,
|
|
w: 54,
|
|
h: 77,
|
|
id: "combo0"
|
|
})
|
|
this.globalAlpha = 1
|
|
}
|
|
toggleGogoTime(circle){
|
|
this.gogoTime = circle.gogoTime
|
|
this.gogoTimeStarted = circle.ms
|
|
|
|
if(this.gogoTime){
|
|
this.assets.fireworks.forEach(fireworksAsset => {
|
|
fireworksAsset.setAnimation("normal")
|
|
fireworksAsset.setAnimationStart(circle.ms)
|
|
var length = fireworksAsset.getAnimationLength("normal")
|
|
fireworksAsset.setAnimationEnd(length, () => {
|
|
fireworksAsset.setAnimation(false)
|
|
})
|
|
})
|
|
this.assets.fire.setAnimation("normal")
|
|
var don = this.assets.don
|
|
don.setAnimation("gogostart")
|
|
var length = don.getAnimationLength("gogo")
|
|
don.setUpdateSpeed(4 / length)
|
|
var start = circle.ms - (circle.ms % this.beatInterval)
|
|
don.setAnimationStart(start)
|
|
var length = don.getAnimationLength("gogostart")
|
|
don.setAnimationEnd(length, don.normalAnimation)
|
|
}
|
|
}
|
|
drawGogoTime(){
|
|
var ms = this.getMS()
|
|
|
|
if(this.gogoTime){
|
|
var circles = this.controller.parsedSongData.circles
|
|
var lastCircle = circles[circles.length - 1]
|
|
var endTime = lastCircle.getEndTime() + 3000
|
|
if(ms >= endTime){
|
|
this.toggleGogoTime({
|
|
gogoTime: 0,
|
|
ms: endTime
|
|
})
|
|
}
|
|
}else{
|
|
var animation = this.assets.don.getAnimation()
|
|
var gauge = this.controller.getGlobalScore().gauge
|
|
var cleared = Math.round(gauge / 2) - 1 >= 25
|
|
if(animation === "gogo" || cleared && animation === "normal" || !cleared && animation === "clear"){
|
|
this.assets.don.normalAnimation()
|
|
}
|
|
if(ms >= this.gogoTimeStarted + 100){
|
|
this.assets.fire.setAnimation(false)
|
|
}
|
|
}
|
|
}
|
|
updateCombo(combo){
|
|
var don = this.assets.don
|
|
var animation = don.getAnimation()
|
|
if(
|
|
combo > 0
|
|
&& combo % 10 === 0
|
|
&& animation !== "10combo"
|
|
&& animation !== "gogostart"
|
|
&& animation !== "gogo"
|
|
){
|
|
don.setAnimation("10combo")
|
|
var ms = this.getMS()
|
|
don.setAnimationStart(ms)
|
|
var length = don.getAnimationLength("normal")
|
|
don.setUpdateSpeed(4 / length)
|
|
var length = don.getAnimationLength("10combo")
|
|
don.setAnimationEnd(length, don.normalAnimation)
|
|
}
|
|
}
|
|
displayScore(score, notPlayed, bigNote){
|
|
if(!notPlayed){
|
|
this.currentScore.ms = this.getMS()
|
|
this.currentScore.type = score
|
|
this.currentScore.bigNote = bigNote
|
|
|
|
if(score > 0){
|
|
var explosion = this.assets.explosion
|
|
explosion.type = (bigNote ? 0 : 2) + (score === 450 ? 0 : 1)
|
|
explosion.setAnimation("normal")
|
|
explosion.setAnimationStart(this.getMS())
|
|
explosion.setAnimationEnd(bigNote ? 14 : 7, () => {
|
|
explosion.setAnimation(false)
|
|
})
|
|
}
|
|
this.setDarkBg(score === 0)
|
|
}else{
|
|
this.setDarkBg(true)
|
|
}
|
|
}
|
|
setDarkBg(miss){
|
|
if(!miss && this.darkDonBg){
|
|
this.darkDonBg = false
|
|
this.donBg.classList.remove("donbg-dark")
|
|
}else if(miss && !this.darkDonBg){
|
|
this.darkDonBg = true
|
|
this.donBg.classList.add("donbg-dark")
|
|
}
|
|
}
|
|
posToMs(pos, speed){
|
|
var circleSize = 70 * this.slotPos.size / 106 / 2
|
|
return 140 / circleSize * pos / speed
|
|
}
|
|
msToPos(ms, speed){
|
|
var circleSize = 70 * this.slotPos.size / 106 / 2
|
|
return speed / (140 / circleSize) * ms
|
|
}
|
|
drawTouch(){
|
|
if(this.touchEnabled){
|
|
var ms = this.getMS()
|
|
var mul = this.ratio / this.pixelRatio
|
|
|
|
var drumWidth = this.touchDrum.w * mul
|
|
var drumHeight = this.touchDrum.h * mul
|
|
if(drumHeight !== this.touchDrumHeight || drumWidth !== this.touchDrumWidth){
|
|
this.touchDrumWidth = drumWidth
|
|
this.touchDrumHeight = drumHeight
|
|
this.touchDrumDiv.style.width = drumWidth + "px"
|
|
this.touchDrumDiv.style.height = drumHeight + "px"
|
|
}
|
|
if(this.touch > ms - 100){
|
|
if(!this.drumPadding){
|
|
this.drumPadding = true
|
|
this.touchDrumImg.style.backgroundPositionY = "7px"
|
|
}
|
|
}else if(this.drumPadding){
|
|
this.drumPadding = false
|
|
this.touchDrumImg.style.backgroundPositionY = ""
|
|
}
|
|
}
|
|
}
|
|
ontouch(event){
|
|
if(!("changedTouches" in event)){
|
|
event.changedTouches = [event]
|
|
}
|
|
for(var i = 0; i < event.changedTouches.length; i++){
|
|
var touch = event.changedTouches[i]
|
|
event.preventDefault()
|
|
if(this.controller.game.paused){
|
|
var mouse = this.mouseOffset(touch.pageX, touch.pageY)
|
|
var moveTo = this.pauseMouse(mouse.x, mouse.y)
|
|
if(moveTo !== null){
|
|
this.pauseConfirm(moveTo)
|
|
}
|
|
}else if(!this.controller.autoPlayEnabled){
|
|
var pageX = touch.pageX * this.pixelRatio
|
|
var pageY = touch.pageY * this.pixelRatio
|
|
|
|
var c = this.touchCircle
|
|
var pi = Math.PI
|
|
var inPath = () => this.ctx.isPointInPath(pageX, pageY)
|
|
|
|
this.ctx.beginPath()
|
|
this.ctx.ellipse(c.x, c.y, c.rx, c.ry, 0, pi, 0)
|
|
|
|
if(inPath()){
|
|
if(pageX < this.winW / 2){
|
|
this.touchNote("don_l")
|
|
}else{
|
|
this.touchNote("don_r")
|
|
}
|
|
}else{
|
|
if(pageX < this.winW / 2){
|
|
this.touchNote("ka_l")
|
|
}else{
|
|
this.touchNote("ka_r")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
touchNote(note){
|
|
var keyboard = this.controller.keyboard
|
|
var kbd = keyboard.getBindings()
|
|
var ms = this.controller.game.getAccurateTime()
|
|
this.touch = ms
|
|
keyboard.setKey(kbd[note], false)
|
|
keyboard.setKey(kbd[note], true, ms)
|
|
}
|
|
mod(length, index){
|
|
return ((index % length) + length) % length
|
|
}
|
|
pauseMove(pos, absolute){
|
|
if(absolute){
|
|
this.state.pausePos = pos
|
|
}else{
|
|
this.state.pausePos = this.mod(this.pauseOptions.length, this.state.pausePos + pos)
|
|
}
|
|
this.state.moveMS = Date.now() - (absolute ? 0 : 500)
|
|
this.state.moveHover = null
|
|
}
|
|
pauseConfirm(pos){
|
|
if(typeof pos === "undefined"){
|
|
pos = this.state.pausePos
|
|
}
|
|
switch(pos){
|
|
case 1:
|
|
assets.sounds["don"].play()
|
|
return this.controller.restartSong()
|
|
case 2:
|
|
assets.sounds["don"].play()
|
|
return this.controller.songSelection()
|
|
default:
|
|
return this.controller.togglePause()
|
|
}
|
|
}
|
|
onmousedown(event){
|
|
if(this.controller.game.paused){
|
|
if(event.which !== 1){
|
|
return
|
|
}
|
|
var mouse = this.mouseOffset(event.offsetX, event.offsetY)
|
|
var moveTo = this.pauseMouse(mouse.x, mouse.y)
|
|
if(moveTo !== null){
|
|
this.pauseConfirm(moveTo)
|
|
}
|
|
}
|
|
}
|
|
onmousemove(event){
|
|
this.lastMousemove = this.getMS()
|
|
this.cursorHidden = false
|
|
|
|
if(!this.multiplayer && this.controller.game.paused){
|
|
var mouse = this.mouseOffset(event.offsetX, event.offsetY)
|
|
var moveTo = this.pauseMouse(mouse.x, mouse.y)
|
|
if(moveTo === null && this.state.moveHover === this.state.pausePos){
|
|
this.state.moveMS = Date.now() - 500
|
|
}
|
|
this.state.moveHover = moveTo
|
|
this.pointer(moveTo !== null)
|
|
}
|
|
}
|
|
mouseOffset(offsetX, offsetY){
|
|
return {
|
|
x: (offsetX * this.pixelRatio - this.winW / 2) / this.ratio + (this.portrait ? 720 : 1280) / 2,
|
|
y: (offsetY * this.pixelRatio - this.winH / 2) / this.ratio + (this.portrait ? 1280 : 720) / 2
|
|
}
|
|
}
|
|
pointer(enabled){
|
|
if(!this.canvas){
|
|
return
|
|
}
|
|
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
|
|
}
|
|
}
|
|
pauseMouse(x, y){
|
|
if(this.portrait){
|
|
var pauseScale = 766 / 720
|
|
x = x * pauseScale + 257
|
|
y = y * pauseScale - 328
|
|
}
|
|
if(104 <= y && y <= 575 && 465 <= x && x <= 465 + 110 * this.pauseOptions.length){
|
|
return Math.floor((x - 465) / 110)
|
|
}
|
|
return null
|
|
}
|
|
mouseIdle(){
|
|
var lastMouse = pageEvents.getMouse()
|
|
if(lastMouse && !this.cursorHidden){
|
|
if(this.getMS() >= this.lastMousemove + 2000){
|
|
this.cursor.style.top = lastMouse.clientY + "px"
|
|
this.cursor.style.left = lastMouse.clientX + "px"
|
|
this.cursor.style.pointerEvents = "auto"
|
|
this.cursorHidden = true
|
|
}else{
|
|
this.cursor.style.top = ""
|
|
this.cursor.style.left = ""
|
|
this.cursor.style.pointerEvents = ""
|
|
}
|
|
}
|
|
}
|
|
changeBeatInterval(beatMS){
|
|
this.beatInterval = beatMS
|
|
this.assets.changeBeatInterval(beatMS)
|
|
}
|
|
getMS(){
|
|
return this.ms
|
|
}
|
|
clean(){
|
|
this.draw.clean()
|
|
this.assets.clean()
|
|
this.titleCache.clean()
|
|
this.comboCache.clean()
|
|
|
|
if(this.multiplayer !== 2){
|
|
if(this.touchEnabled){
|
|
pageEvents.remove(this.canvas, "touchstart")
|
|
pageEvents.remove(this.touchPauseBtn, "touchend")
|
|
this.gameDiv.classList.add("touch-results")
|
|
document.getElementById("version").classList.remove("version-hide")
|
|
this.touchDrumDiv.parentNode.removeChild(this.touchDrumDiv)
|
|
delete this.touchDrumDiv
|
|
delete this.touchDrumImg
|
|
delete this.touchFullBtn
|
|
delete this.touchPauseBtn
|
|
}
|
|
}
|
|
if(!this.multiplayer){
|
|
pageEvents.remove(this.canvas, "mousedown")
|
|
this.songBg.parentNode.removeChild(this.songBg)
|
|
this.songStage.parentNode.removeChild(this.songStage)
|
|
this.donBg.parentNode.removeChild(this.donBg)
|
|
delete this.donBg
|
|
delete this.songBg
|
|
delete this.songStage
|
|
}
|
|
pageEvents.mouseRemove(this)
|
|
|
|
delete this.pauseMenu
|
|
delete this.cursor
|
|
delete this.gameDiv
|
|
delete this.canvas
|
|
delete this.ctx
|
|
}
|
|
}
|