Compare commits
4 Commits
6d7be5c45c
...
Bang2
| Author | SHA1 | Date | |
|---|---|---|---|
| 271fc52e82 | |||
| 1038fc85b9 | |||
| 76a3d52098 | |||
| d6a1b6bd41 |
155
app.py
155
app.py
@@ -105,6 +105,9 @@ db.users.create_index('username', unique=True)
|
|||||||
db.songs.create_index('id', unique=True)
|
db.songs.create_index('id', unique=True)
|
||||||
db.songs.create_index('song_type')
|
db.songs.create_index('song_type')
|
||||||
db.scores.create_index('username')
|
db.scores.create_index('username')
|
||||||
|
db.leaderboards.create_index([('song_id', 1), ('difficulty', 1), ('month', 1), ('score_value', -1)])
|
||||||
|
db.leaderboards.create_index([('song_id', 1), ('difficulty', 1), ('username', 1), ('month', 1)], unique=True)
|
||||||
|
db.leaderboards.create_index('month')
|
||||||
|
|
||||||
|
|
||||||
class HashException(Exception):
|
class HashException(Exception):
|
||||||
@@ -746,6 +749,158 @@ def route_api_scores_get():
|
|||||||
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
|
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route(basedir + 'api/leaderboard/submit', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def route_api_leaderboard_submit():
|
||||||
|
data = request.get_json()
|
||||||
|
if not schema.validate(data, schema.leaderboard_submit):
|
||||||
|
return abort(400)
|
||||||
|
|
||||||
|
username = session.get('username')
|
||||||
|
user = db.users.find_one({'username': username})
|
||||||
|
if not user:
|
||||||
|
return api_error('user_not_found')
|
||||||
|
|
||||||
|
song_id = data.get('song_id')
|
||||||
|
difficulty = data.get('difficulty')
|
||||||
|
score_data = data.get('score')
|
||||||
|
|
||||||
|
# Validate difficulty
|
||||||
|
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
||||||
|
if difficulty not in valid_difficulties:
|
||||||
|
return api_error('invalid_difficulty')
|
||||||
|
|
||||||
|
# Get current month (YYYY-MM format)
|
||||||
|
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||||
|
|
||||||
|
# Check if user already has a record for this song/difficulty/month
|
||||||
|
existing = db.leaderboards.find_one({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'username': username,
|
||||||
|
'month': current_month
|
||||||
|
})
|
||||||
|
|
||||||
|
# Parse score (assuming it's in the same format as the scores collection)
|
||||||
|
try:
|
||||||
|
if isinstance(score_data, str):
|
||||||
|
import json as json_module
|
||||||
|
score_obj = json_module.loads(score_data)
|
||||||
|
else:
|
||||||
|
score_obj = score_data
|
||||||
|
|
||||||
|
score_value = int(score_obj.get('score', 0))
|
||||||
|
except:
|
||||||
|
return api_error('invalid_score_format')
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
# Update only if new score is higher
|
||||||
|
existing_score = existing.get('score_value', 0)
|
||||||
|
if score_value > existing_score:
|
||||||
|
db.leaderboards.update_one(
|
||||||
|
{'_id': existing['_id']},
|
||||||
|
{'$set': {
|
||||||
|
'score': score_data,
|
||||||
|
'score_value': score_value,
|
||||||
|
'display_name': user['display_name'],
|
||||||
|
'submitted_at': time.time()
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
return jsonify({'status': 'ok', 'message': 'score_updated'})
|
||||||
|
else:
|
||||||
|
return jsonify({'status': 'ok', 'message': 'score_not_higher'})
|
||||||
|
else:
|
||||||
|
# Check if this score would be in top 50
|
||||||
|
count = db.leaderboards.count_documents({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'month': current_month
|
||||||
|
})
|
||||||
|
|
||||||
|
if count >= 50:
|
||||||
|
# Find the 50th score
|
||||||
|
leaderboard = list(db.leaderboards.find({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'month': current_month
|
||||||
|
}).sort('score_value', -1).limit(50))
|
||||||
|
|
||||||
|
if len(leaderboard) >= 50:
|
||||||
|
last_score = leaderboard[49].get('score_value', 0)
|
||||||
|
if score_value <= last_score:
|
||||||
|
return jsonify({'status': 'ok', 'message': 'score_too_low'})
|
||||||
|
|
||||||
|
# Insert new record
|
||||||
|
db.leaderboards.insert_one({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'username': username,
|
||||||
|
'display_name': user['display_name'],
|
||||||
|
'score': score_data,
|
||||||
|
'score_value': score_value,
|
||||||
|
'submitted_at': time.time(),
|
||||||
|
'month': current_month
|
||||||
|
})
|
||||||
|
|
||||||
|
# Remove entries beyond 50th place
|
||||||
|
if count >= 50:
|
||||||
|
# Get all entries sorted by score
|
||||||
|
all_entries = list(db.leaderboards.find({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'month': current_month
|
||||||
|
}).sort('score_value', -1))
|
||||||
|
|
||||||
|
# Delete entries beyond 50
|
||||||
|
if len(all_entries) > 50:
|
||||||
|
for entry in all_entries[50:]:
|
||||||
|
db.leaderboards.delete_one({'_id': entry['_id']})
|
||||||
|
|
||||||
|
return jsonify({'status': 'ok', 'message': 'score_submitted'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route(basedir + 'api/leaderboard/get')
|
||||||
|
def route_api_leaderboard_get():
|
||||||
|
song_id = request.args.get('song_id', None)
|
||||||
|
difficulty = request.args.get('difficulty', None)
|
||||||
|
|
||||||
|
if not song_id or not difficulty:
|
||||||
|
return abort(400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
song_id = int(song_id)
|
||||||
|
except:
|
||||||
|
return abort(400)
|
||||||
|
|
||||||
|
# Validate difficulty
|
||||||
|
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
||||||
|
if difficulty not in valid_difficulties:
|
||||||
|
return abort(400)
|
||||||
|
|
||||||
|
# Get current month
|
||||||
|
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||||
|
|
||||||
|
# Get top 50 scores
|
||||||
|
leaderboard = list(db.leaderboards.find({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'month': current_month
|
||||||
|
}, {
|
||||||
|
'_id': False,
|
||||||
|
'username': True,
|
||||||
|
'display_name': True,
|
||||||
|
'score': True,
|
||||||
|
'score_value': True,
|
||||||
|
'submitted_at': True
|
||||||
|
}).sort('score_value', -1).limit(50))
|
||||||
|
|
||||||
|
# Add rank to each entry
|
||||||
|
for i, entry in enumerate(leaderboard):
|
||||||
|
entry['rank'] = i + 1
|
||||||
|
|
||||||
|
return jsonify({'status': 'ok', 'leaderboard': leaderboard, 'month': current_month})
|
||||||
|
|
||||||
|
|
||||||
@app.route(basedir + 'privacy')
|
@app.route(basedir + 'privacy')
|
||||||
def route_api_privacy():
|
def route_api_privacy():
|
||||||
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
||||||
|
|||||||
13
public/src/css/leaderboard.css
Normal file
13
public/src/css/leaderboard.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#leaderboard {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leaderboard-canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
390
public/src/js/leaderboard.js
Normal file
390
public/src/js/leaderboard.js
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
class Leaderboard {
|
||||||
|
constructor() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.canvas = document.getElementById("leaderboard-canvas")
|
||||||
|
if (!this.canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ctx = this.canvas.getContext("2d")
|
||||||
|
|
||||||
|
var resolution = settings.getItem("resolution")
|
||||||
|
var noSmoothing = resolution === "low" || resolution === "lowest"
|
||||||
|
if (noSmoothing) {
|
||||||
|
this.ctx.imageSmoothingEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.songId = null
|
||||||
|
this.difficulty = null
|
||||||
|
this.leaderboardData = []
|
||||||
|
this.currentMonth = ""
|
||||||
|
this.visible = false
|
||||||
|
|
||||||
|
this.draw = new CanvasDraw(noSmoothing)
|
||||||
|
|
||||||
|
// Keyboard controls
|
||||||
|
this.keyboard = new Keyboard({
|
||||||
|
confirm: ["enter", "escape", "don_l", "don_r"],
|
||||||
|
back: ["escape"],
|
||||||
|
left: ["left", "ka_l"],
|
||||||
|
right: ["right", "ka_r"]
|
||||||
|
}, this.keyPress.bind(this))
|
||||||
|
|
||||||
|
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.onClose.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPress(pressed, name) {
|
||||||
|
if (!pressed || !this.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (name === "confirm" || name === "back") {
|
||||||
|
this.close()
|
||||||
|
} else if (name === "left") {
|
||||||
|
this.changeDifficulty(-1)
|
||||||
|
} else if (name === "right") {
|
||||||
|
this.changeDifficulty(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async display(songId, difficulty) {
|
||||||
|
this.songId = songId
|
||||||
|
this.difficulty = difficulty
|
||||||
|
this.visible = true
|
||||||
|
|
||||||
|
loader.changePage("leaderboard", false)
|
||||||
|
|
||||||
|
// Fetch leaderboard data
|
||||||
|
await this.fetchLeaderboard()
|
||||||
|
|
||||||
|
// Start rendering
|
||||||
|
this.redrawRunning = true
|
||||||
|
this.redrawBind = this.redraw.bind(this)
|
||||||
|
this.redraw()
|
||||||
|
|
||||||
|
assets.sounds["se_don"].play()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchLeaderboard() {
|
||||||
|
try {
|
||||||
|
var response = await loader.ajax(
|
||||||
|
`${gameConfig.basedir || "/"}api/leaderboard/get?song_id=${this.songId}&difficulty=${this.difficulty}`
|
||||||
|
)
|
||||||
|
var data = JSON.parse(response)
|
||||||
|
if (data.status === "ok") {
|
||||||
|
this.leaderboardData = data.leaderboard || []
|
||||||
|
this.currentMonth = data.month || ""
|
||||||
|
} else {
|
||||||
|
this.leaderboardData = []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch leaderboard:", e)
|
||||||
|
this.leaderboardData = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDifficulty(direction) {
|
||||||
|
var difficulties = ["easy", "normal", "hard", "oni", "ura"]
|
||||||
|
var currentIndex = difficulties.indexOf(this.difficulty)
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newIndex = (currentIndex + direction + difficulties.length) % difficulties.length
|
||||||
|
this.difficulty = difficulties[newIndex]
|
||||||
|
|
||||||
|
this.fetchLeaderboard().then(() => {
|
||||||
|
assets.sounds["se_ka"].play()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(event) {
|
||||||
|
if (!this.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = this.canvas.getBoundingClientRect()
|
||||||
|
var x = (event.offsetX || event.touches[0].pageX - rect.left)
|
||||||
|
var y = (event.offsetY || event.touches[0].pageY - rect.top)
|
||||||
|
|
||||||
|
// Check if clicked outside modal - updated dimensions
|
||||||
|
var centerX = this.canvas.width / 2
|
||||||
|
var centerY = this.canvas.height / 2
|
||||||
|
var modalWidth = 880 // Updated from 800
|
||||||
|
var modalHeight = 640 // Updated from 600
|
||||||
|
|
||||||
|
if (x < centerX - modalWidth / 2 || x > centerX + modalWidth / 2 ||
|
||||||
|
y < centerY - modalHeight / 2 || y > centerY + modalHeight / 2) {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.visible = false
|
||||||
|
this.redrawRunning = false
|
||||||
|
|
||||||
|
if (this.keyboard) {
|
||||||
|
this.keyboard.clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.sounds["se_cancel"].play()
|
||||||
|
|
||||||
|
// Return to song select - get touchEnabled from global or default to false
|
||||||
|
setTimeout(() => {
|
||||||
|
var touchEnabled = typeof window.touchEnabled !== 'undefined' ? window.touchEnabled : false
|
||||||
|
new SongSelect(false, false, touchEnabled)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
redraw() {
|
||||||
|
if (!this.redrawRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(this.redrawBind)
|
||||||
|
|
||||||
|
var winW = innerWidth
|
||||||
|
var winH = innerHeight
|
||||||
|
var ratio = winH / 720
|
||||||
|
|
||||||
|
this.canvas.width = winW
|
||||||
|
this.canvas.height = winH
|
||||||
|
this.ctx.save()
|
||||||
|
|
||||||
|
// Draw semi-transparent background
|
||||||
|
this.ctx.fillStyle = "rgba(0, 0, 0, 0.8)"
|
||||||
|
this.ctx.fillRect(0, 0, winW, winH)
|
||||||
|
|
||||||
|
this.ctx.scale(ratio, ratio)
|
||||||
|
|
||||||
|
// Draw modal background with gradient
|
||||||
|
var modalX = 200
|
||||||
|
var modalY = 40
|
||||||
|
var modalW = 880
|
||||||
|
var modalH = 640
|
||||||
|
|
||||||
|
// Gradient background
|
||||||
|
var bgGradient = this.ctx.createLinearGradient(modalX, modalY, modalX, modalY + modalH)
|
||||||
|
bgGradient.addColorStop(0, "#ffffff")
|
||||||
|
bgGradient.addColorStop(1, "#f0f4f8")
|
||||||
|
this.ctx.fillStyle = bgGradient
|
||||||
|
this.ctx.shadowColor = "rgba(0, 0, 0, 0.3)"
|
||||||
|
this.ctx.shadowBlur = 30
|
||||||
|
this.ctx.shadowOffsetX = 0
|
||||||
|
this.ctx.shadowOffsetY = 10
|
||||||
|
this.ctx.fillRect(modalX, modalY, modalW, modalH)
|
||||||
|
this.ctx.shadowBlur = 0
|
||||||
|
|
||||||
|
// Modal border with gradient
|
||||||
|
var borderGradient = this.ctx.createLinearGradient(modalX, modalY, modalX + modalW, modalY + modalH)
|
||||||
|
borderGradient.addColorStop(0, "#4a90e2")
|
||||||
|
borderGradient.addColorStop(0.5, "#7b68ee")
|
||||||
|
borderGradient.addColorStop(1, "#ff6b9d")
|
||||||
|
this.ctx.strokeStyle = borderGradient
|
||||||
|
this.ctx.lineWidth = 6
|
||||||
|
this.ctx.strokeRect(modalX, modalY, modalW, modalH)
|
||||||
|
|
||||||
|
// Draw title with gradient
|
||||||
|
var titleGradient = this.ctx.createLinearGradient(0, 90, 0, 130)
|
||||||
|
titleGradient.addColorStop(0, "#4a90e2")
|
||||||
|
titleGradient.addColorStop(1, "#7b68ee")
|
||||||
|
this.ctx.fillStyle = titleGradient
|
||||||
|
this.ctx.font = "bold 48px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.shadowColor = "rgba(0, 0, 0, 0.2)"
|
||||||
|
this.ctx.shadowBlur = 5
|
||||||
|
this.ctx.shadowOffsetX = 2
|
||||||
|
this.ctx.shadowOffsetY = 2
|
||||||
|
this.ctx.fillText("🏆 排行榜 Leaderboard", 640, 100)
|
||||||
|
this.ctx.shadowBlur = 0
|
||||||
|
|
||||||
|
// Draw difficulty selector with improved styling
|
||||||
|
var diffX = 640
|
||||||
|
var diffY = 155
|
||||||
|
var difficulties = [
|
||||||
|
{ id: "easy", name: "簡単", color: "#00a0e9", glow: "#00d4ff" },
|
||||||
|
{ id: "normal", name: "普通", color: "#00a040", glow: "#00ff66" },
|
||||||
|
{ id: "hard", name: "難しい", color: "#ff8c00", glow: "#ffb347" },
|
||||||
|
{ id: "oni", name: "鬼", color: "#dc143c", glow: "#ff6b9d" },
|
||||||
|
{ id: "ura", name: "裏", color: "#9400d3", glow: "#da70d6" }
|
||||||
|
]
|
||||||
|
|
||||||
|
this.ctx.font = "bold 22px " + (strings.font || "sans-serif")
|
||||||
|
for (var i = 0; i < difficulties.length; i++) {
|
||||||
|
var diff = difficulties[i]
|
||||||
|
var x = diffX - 220 + i * 110
|
||||||
|
|
||||||
|
if (diff.id === this.difficulty) {
|
||||||
|
// Selected difficulty with glow effect
|
||||||
|
this.ctx.shadowColor = diff.glow
|
||||||
|
this.ctx.shadowBlur = 15
|
||||||
|
var gradient = this.ctx.createLinearGradient(x - 50, diffY - 30, x + 50, diffY + 20)
|
||||||
|
gradient.addColorStop(0, diff.color)
|
||||||
|
gradient.addColorStop(1, diff.glow)
|
||||||
|
this.ctx.fillStyle = gradient
|
||||||
|
this.ctx.fillRect(x - 50, diffY - 30, 100, 50)
|
||||||
|
this.ctx.shadowBlur = 0
|
||||||
|
this.ctx.fillStyle = "#ffffff"
|
||||||
|
this.ctx.shadowColor = "rgba(0, 0, 0, 0.5)"
|
||||||
|
this.ctx.shadowBlur = 3
|
||||||
|
} else {
|
||||||
|
// Unselected difficulty
|
||||||
|
this.ctx.strokeStyle = diff.color
|
||||||
|
this.ctx.lineWidth = 3
|
||||||
|
this.ctx.strokeRect(x - 50, diffY - 30, 100, 50)
|
||||||
|
this.ctx.fillStyle = diff.color
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText(diff.name, x, diffY)
|
||||||
|
this.ctx.shadowBlur = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw month info with style
|
||||||
|
this.ctx.fillStyle = "#666666"
|
||||||
|
this.ctx.font = "20px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("📅 当月排行 " + this.currentMonth, 640, 215)
|
||||||
|
|
||||||
|
// Header bar
|
||||||
|
var headerY = 250
|
||||||
|
var headerGradient = this.ctx.createLinearGradient(modalX, headerY, modalX, headerY + 35)
|
||||||
|
headerGradient.addColorStop(0, "#e8eef5")
|
||||||
|
headerGradient.addColorStop(1, "#d0dae8")
|
||||||
|
this.ctx.fillStyle = headerGradient
|
||||||
|
this.ctx.fillRect(modalX + 20, headerY, modalW - 40, 35)
|
||||||
|
|
||||||
|
this.ctx.fillStyle = "#333333"
|
||||||
|
this.ctx.font = "bold 18px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("排名", modalX + 80, headerY + 23)
|
||||||
|
this.ctx.textAlign = "left"
|
||||||
|
this.ctx.fillText("玩家", modalX + 140, headerY + 23)
|
||||||
|
this.ctx.textAlign = "right"
|
||||||
|
this.ctx.fillText("分数", modalX + modalW - 60, headerY + 23)
|
||||||
|
|
||||||
|
// Draw leaderboard entries
|
||||||
|
var startY = 295
|
||||||
|
var rowHeight = 38
|
||||||
|
|
||||||
|
this.ctx.font = "22px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "left"
|
||||||
|
|
||||||
|
if (this.leaderboardData.length === 0) {
|
||||||
|
this.ctx.fillStyle = "#999999"
|
||||||
|
this.ctx.font = "24px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("暂无排行数据", 640, startY + 120)
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < Math.min(this.leaderboardData.length, 15); i++) {
|
||||||
|
var entry = this.leaderboardData[i]
|
||||||
|
var y = startY + i * rowHeight
|
||||||
|
var rank = entry.rank
|
||||||
|
|
||||||
|
// Rank background color with gradient
|
||||||
|
var gradient
|
||||||
|
if (rank === 1) {
|
||||||
|
// Gold gradient for 1st place
|
||||||
|
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||||
|
gradient.addColorStop(0, "#ffd700")
|
||||||
|
gradient.addColorStop(1, "#ffed4e")
|
||||||
|
this.ctx.fillStyle = gradient
|
||||||
|
this.ctx.shadowColor = "#ffd700"
|
||||||
|
this.ctx.shadowBlur = 20
|
||||||
|
} else if (rank === 2) {
|
||||||
|
// Silver gradient for 2nd place
|
||||||
|
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||||
|
gradient.addColorStop(0, "#c0c0c0")
|
||||||
|
gradient.addColorStop(1, "#e8e8e8")
|
||||||
|
this.ctx.fillStyle = gradient
|
||||||
|
this.ctx.shadowColor = "#c0c0c0"
|
||||||
|
this.ctx.shadowBlur = 15
|
||||||
|
} else if (rank === 3) {
|
||||||
|
// Bronze gradient for 3rd place
|
||||||
|
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||||
|
gradient.addColorStop(0, "#cd7f32")
|
||||||
|
gradient.addColorStop(1, "#e9a86a")
|
||||||
|
this.ctx.fillStyle = gradient
|
||||||
|
this.ctx.shadowColor = "#cd7f32"
|
||||||
|
this.ctx.shadowBlur = 12
|
||||||
|
} else {
|
||||||
|
// Regular entries with subtle gradient
|
||||||
|
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||||
|
gradient.addColorStop(0, "#ffffff")
|
||||||
|
gradient.addColorStop(1, "#f8f9fa")
|
||||||
|
this.ctx.fillStyle = gradient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounded rectangle effect
|
||||||
|
this.ctx.fillRect(modalX + 20, y - 25, modalW - 40, 35)
|
||||||
|
this.ctx.shadowBlur = 0
|
||||||
|
|
||||||
|
// Border for entries
|
||||||
|
if (rank <= 3) {
|
||||||
|
this.ctx.strokeStyle = rank === 1 ? "#ffd700" : rank === 2 ? "#c0c0c0" : "#cd7f32"
|
||||||
|
this.ctx.lineWidth = 2
|
||||||
|
this.ctx.strokeRect(modalX + 20, y - 25, modalW - 40, 35)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rank with medal emoji for top 3
|
||||||
|
if (rank === 1) {
|
||||||
|
this.ctx.fillStyle = "#8b6914"
|
||||||
|
this.ctx.font = "bold 24px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("🥇", modalX + 80, y + 2)
|
||||||
|
} else if (rank === 2) {
|
||||||
|
this.ctx.fillStyle = "#5c5c5c"
|
||||||
|
this.ctx.font = "bold 24px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("🥈", modalX + 80, y + 2)
|
||||||
|
} else if (rank === 3) {
|
||||||
|
this.ctx.fillStyle = "#6d4423"
|
||||||
|
this.ctx.font = "bold 24px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("🥉", modalX + 80, y + 2)
|
||||||
|
} else {
|
||||||
|
this.ctx.fillStyle = rank <= 3 ? "#ffffff" : "#555555"
|
||||||
|
this.ctx.font = "bold 22px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("#" + rank, modalX + 80, y + 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display name
|
||||||
|
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#333333"
|
||||||
|
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "left"
|
||||||
|
var displayName = entry.display_name || entry.username
|
||||||
|
if (displayName.length > 18) {
|
||||||
|
displayName = displayName.substring(0, 18) + "..."
|
||||||
|
}
|
||||||
|
this.ctx.fillText(displayName, modalX + 140, y + 2)
|
||||||
|
|
||||||
|
// Score with formatting
|
||||||
|
var score = entry.score && entry.score.score ? entry.score.score : 0
|
||||||
|
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#555555"
|
||||||
|
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "right"
|
||||||
|
this.ctx.fillText(score.toLocaleString(), modalX + modalW - 60, y + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw close hint with icon
|
||||||
|
this.ctx.fillStyle = "#888888"
|
||||||
|
this.ctx.font = "18px " + (strings.font || "sans-serif")
|
||||||
|
this.ctx.textAlign = "center"
|
||||||
|
this.ctx.fillText("⌨️ 按ESC或点击外部关闭 Press ESC or click outside to close", 640, 665)
|
||||||
|
|
||||||
|
this.ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
|
if (this.keyboard) {
|
||||||
|
this.keyboard.clean()
|
||||||
|
}
|
||||||
|
if (this.redrawRunning) {
|
||||||
|
this.redrawRunning = false
|
||||||
|
}
|
||||||
|
pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
class Scoresheet{
|
class Scoresheet {
|
||||||
constructor(...args){
|
constructor(...args) {
|
||||||
this.init(...args)
|
this.init(...args)
|
||||||
}
|
}
|
||||||
init(controller, results, multiplayer, touchEnabled){
|
init(controller, results, multiplayer, touchEnabled) {
|
||||||
this.controller = controller
|
this.controller = controller
|
||||||
this.resultsObj = results
|
this.resultsObj = results
|
||||||
this.player = [multiplayer ? (p2.player === 1 ? 0 : 1) : 0]
|
this.player = [multiplayer ? (p2.player === 1 ? 0 : 1) : 0]
|
||||||
@@ -11,12 +11,12 @@ class Scoresheet{
|
|||||||
this.results[player0] = {}
|
this.results[player0] = {}
|
||||||
this.rules = []
|
this.rules = []
|
||||||
this.rules[player0] = this.controller.game.rules
|
this.rules[player0] = this.controller.game.rules
|
||||||
if(multiplayer){
|
if (multiplayer) {
|
||||||
this.player.push(p2.player === 2 ? 0 : 1)
|
this.player.push(p2.player === 2 ? 0 : 1)
|
||||||
this.results[this.player[1]] = p2.results
|
this.results[this.player[1]] = p2.results
|
||||||
this.rules[this.player[1]] = this.controller.syncWith.game.rules
|
this.rules[this.player[1]] = this.controller.syncWith.game.rules
|
||||||
}
|
}
|
||||||
for(var i in results){
|
for (var i in results) {
|
||||||
this.results[player0][i] = results[i] === null ? null : results[i].toString()
|
this.results[player0][i] = results[i] === null ? null : results[i].toString()
|
||||||
}
|
}
|
||||||
this.multiplayer = multiplayer
|
this.multiplayer = multiplayer
|
||||||
@@ -26,10 +26,10 @@ class Scoresheet{
|
|||||||
this.ctx = this.canvas.getContext("2d")
|
this.ctx = this.canvas.getContext("2d")
|
||||||
var resolution = settings.getItem("resolution")
|
var resolution = settings.getItem("resolution")
|
||||||
var noSmoothing = resolution === "low" || resolution === "lowest"
|
var noSmoothing = resolution === "low" || resolution === "lowest"
|
||||||
if(noSmoothing){
|
if (noSmoothing) {
|
||||||
this.ctx.imageSmoothingEnabled = false
|
this.ctx.imageSmoothingEnabled = false
|
||||||
}
|
}
|
||||||
if(resolution === "lowest"){
|
if (resolution === "lowest") {
|
||||||
this.canvas.style.imageRendering = "pixelated"
|
this.canvas.style.imageRendering = "pixelated"
|
||||||
}
|
}
|
||||||
this.game = document.getElementById("game")
|
this.game = document.getElementById("game")
|
||||||
@@ -78,12 +78,12 @@ class Scoresheet{
|
|||||||
assets.sounds["bgm_result"].playLoop(3, false, 0, 0.847, 17.689)
|
assets.sounds["bgm_result"].playLoop(3, false, 0, 0.847, 17.689)
|
||||||
|
|
||||||
this.session = p2.session
|
this.session = p2.session
|
||||||
if(this.session){
|
if (this.session) {
|
||||||
if(p2.getMessage("songsel")){
|
if (p2.getMessage("songsel")) {
|
||||||
this.toSongsel(true)
|
this.toSongsel(true)
|
||||||
}
|
}
|
||||||
pageEvents.add(p2, "message", response => {
|
pageEvents.add(p2, "message", response => {
|
||||||
if(response.type === "songsel"){
|
if (response.type === "songsel") {
|
||||||
this.toSongsel(true)
|
this.toSongsel(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -100,51 +100,51 @@ class Scoresheet{
|
|||||||
touchEvents: controller.view.touchEvents
|
touchEvents: controller.view.touchEvents
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
keyDown(pressed){
|
keyDown(pressed) {
|
||||||
if(pressed && this.redrawing){
|
if (pressed && this.redrawing) {
|
||||||
this.toNext()
|
this.toNext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mouseDown(event){
|
mouseDown(event) {
|
||||||
if(event.type === "touchstart"){
|
if (event.type === "touchstart") {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.canvas.style.cursor = ""
|
this.canvas.style.cursor = ""
|
||||||
this.state.pointerLocked = true
|
this.state.pointerLocked = true
|
||||||
}else{
|
} else {
|
||||||
this.state.pointerLocked = false
|
this.state.pointerLocked = false
|
||||||
if(event.which !== 1){
|
if (event.which !== 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.toNext()
|
this.toNext()
|
||||||
}
|
}
|
||||||
toNext(){
|
toNext() {
|
||||||
var elapsed = this.getMS() - this.state.screenMS
|
var elapsed = this.getMS() - this.state.screenMS
|
||||||
if(this.state.screen === "fadeIn" && elapsed >= this.state.startDelay){
|
if (this.state.screen === "fadeIn" && elapsed >= this.state.startDelay) {
|
||||||
this.toScoresShown()
|
this.toScoresShown()
|
||||||
}else if(this.state.screen === "scoresShown" && elapsed >= 1000){
|
} else if (this.state.screen === "scoresShown" && elapsed >= 1000) {
|
||||||
this.toSongsel()
|
this.toSongsel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toScoresShown(){
|
toScoresShown() {
|
||||||
if(!p2.session){
|
if (!p2.session) {
|
||||||
this.state.screen = "scoresShown"
|
this.state.screen = "scoresShown"
|
||||||
this.state.screenMS = this.getMS()
|
this.state.screenMS = this.getMS()
|
||||||
this.controller.playSound("neiro_1_don", 0, true)
|
this.controller.playSound("neiro_1_don", 0, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toSongsel(fromP2){
|
toSongsel(fromP2) {
|
||||||
if(!p2.session || fromP2){
|
if (!p2.session || fromP2) {
|
||||||
snd.musicGain.fadeOut(0.5)
|
snd.musicGain.fadeOut(0.5)
|
||||||
this.state.screen = "fadeOut"
|
this.state.screen = "fadeOut"
|
||||||
this.state.screenMS = this.getMS()
|
this.state.screenMS = this.getMS()
|
||||||
if(!fromP2){
|
if (!fromP2) {
|
||||||
this.controller.playSound("neiro_1_don", 0, true)
|
this.controller.playSound("neiro_1_don", 0, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startRedraw(){
|
startRedraw() {
|
||||||
this.redrawing = true
|
this.redrawing = true
|
||||||
requestAnimationFrame(this.redrawBind)
|
requestAnimationFrame(this.redrawBind)
|
||||||
this.winW = null
|
this.winW = null
|
||||||
@@ -152,7 +152,7 @@ class Scoresheet{
|
|||||||
|
|
||||||
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this))
|
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this))
|
||||||
|
|
||||||
if(!this.multiplayer){
|
if (!this.multiplayer) {
|
||||||
this.tetsuoHana = document.createElement("div")
|
this.tetsuoHana = document.createElement("div")
|
||||||
this.tetsuoHana.id = "tetsuohana"
|
this.tetsuoHana.id = "tetsuohana"
|
||||||
var flowersBg = "url('" + assets.image["results_flowers"].src + "')"
|
var flowersBg = "url('" + assets.image["results_flowers"].src + "')"
|
||||||
@@ -160,12 +160,12 @@ class Scoresheet{
|
|||||||
var tetsuoHanaBg = "url('" + assets.image["results_tetsuohana" + (debugObj.state === "closed" ? "" : "2")].src + "')"
|
var tetsuoHanaBg = "url('" + assets.image["results_tetsuohana" + (debugObj.state === "closed" ? "" : "2")].src + "')"
|
||||||
var id = ["flowers1", "flowers2", "mikoshi", "tetsuo", "hana"]
|
var id = ["flowers1", "flowers2", "mikoshi", "tetsuo", "hana"]
|
||||||
var bg = [flowersBg, flowersBg, mikoshiBg, tetsuoHanaBg, tetsuoHanaBg]
|
var bg = [flowersBg, flowersBg, mikoshiBg, tetsuoHanaBg, tetsuoHanaBg]
|
||||||
for(var i = 0; i < id.length; i++){
|
for (var i = 0; i < id.length; i++) {
|
||||||
if(id[i] === "mikoshi"){
|
if (id[i] === "mikoshi") {
|
||||||
var divOut = document.createElement("div")
|
var divOut = document.createElement("div")
|
||||||
divOut.id = id[i] + "-out"
|
divOut.id = id[i] + "-out"
|
||||||
this.tetsuoHana.appendChild(divOut)
|
this.tetsuoHana.appendChild(divOut)
|
||||||
}else{
|
} else {
|
||||||
var divOut = this.tetsuoHana
|
var divOut = this.tetsuoHana
|
||||||
}
|
}
|
||||||
var div = document.createElement("div")
|
var div = document.createElement("div")
|
||||||
@@ -180,16 +180,16 @@ class Scoresheet{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redraw(){
|
redraw() {
|
||||||
if(!this.redrawRunning){
|
if (!this.redrawRunning) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(this.redrawing){
|
if (this.redrawing) {
|
||||||
requestAnimationFrame(this.redrawBind)
|
requestAnimationFrame(this.redrawBind)
|
||||||
}
|
}
|
||||||
var ms = this.getMS()
|
var ms = this.getMS()
|
||||||
|
|
||||||
if(!this.redrawRunning){
|
if (!this.redrawRunning) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,11 +200,11 @@ class Scoresheet{
|
|||||||
var winH = lastHeight
|
var winH = lastHeight
|
||||||
this.pixelRatio = window.devicePixelRatio || 1
|
this.pixelRatio = window.devicePixelRatio || 1
|
||||||
var resolution = settings.getItem("resolution")
|
var resolution = settings.getItem("resolution")
|
||||||
if(resolution === "medium"){
|
if (resolution === "medium") {
|
||||||
this.pixelRatio *= 0.75
|
this.pixelRatio *= 0.75
|
||||||
}else if(resolution === "low"){
|
} else if (resolution === "low") {
|
||||||
this.pixelRatio *= 0.5
|
this.pixelRatio *= 0.5
|
||||||
}else if(resolution === "lowest"){
|
} else if (resolution === "lowest") {
|
||||||
this.pixelRatio *= 0.25
|
this.pixelRatio *= 0.25
|
||||||
}
|
}
|
||||||
winW *= this.pixelRatio
|
winW *= this.pixelRatio
|
||||||
@@ -213,8 +213,8 @@ class Scoresheet{
|
|||||||
var ratioY = winH / 720
|
var ratioY = winH / 720
|
||||||
var ratio = (ratioX < ratioY ? ratioX : ratioY)
|
var ratio = (ratioX < ratioY ? ratioX : ratioY)
|
||||||
|
|
||||||
if(this.redrawing){
|
if (this.redrawing) {
|
||||||
if(this.winW !== winW || this.winH !== winH){
|
if (this.winW !== winW || this.winH !== winH) {
|
||||||
this.canvas.width = Math.max(1, winW)
|
this.canvas.width = Math.max(1, winW)
|
||||||
this.canvas.height = Math.max(1, winH)
|
this.canvas.height = Math.max(1, winH)
|
||||||
ctx.scale(ratio, ratio)
|
ctx.scale(ratio, ratio)
|
||||||
@@ -224,37 +224,37 @@ class Scoresheet{
|
|||||||
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
|
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
|
||||||
this.nameplateCache.resize(274, 134, ratio + 0.2)
|
this.nameplateCache.resize(274, 134, ratio + 0.2)
|
||||||
|
|
||||||
if(!this.multiplayer){
|
if (!this.multiplayer) {
|
||||||
this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio)
|
this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio)
|
||||||
if(this.tetsuoHanaClass === "dance"){
|
if (this.tetsuoHanaClass === "dance") {
|
||||||
this.tetsuoHana.classList.remove("dance", "dance2")
|
this.tetsuoHana.classList.remove("dance", "dance2")
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
this.tetsuoHana.classList.add("dance2")
|
this.tetsuoHana.classList.add("dance2")
|
||||||
},50)
|
}, 50)
|
||||||
}else if(this.tetsuoHanaClass === "failed"){
|
} else if (this.tetsuoHanaClass === "failed") {
|
||||||
this.tetsuoHana.classList.remove("failed")
|
this.tetsuoHana.classList.remove("failed")
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
this.tetsuoHana.classList.add("failed")
|
this.tetsuoHana.classList.add("failed")
|
||||||
},50)
|
}, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if(!document.hasFocus() && this.state.screen === "scoresShown"){
|
} else if (!document.hasFocus() && this.state.screen === "scoresShown") {
|
||||||
if(this.state["countup0"]){
|
if (this.state["countup0"]) {
|
||||||
this.stopSound("se_results_countup", 0)
|
this.stopSound("se_results_countup", 0)
|
||||||
}
|
}
|
||||||
if(this.state["countup1"]){
|
if (this.state["countup1"]) {
|
||||||
this.stopSound("se_results_countup", 1)
|
this.stopSound("se_results_countup", 1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}else{
|
} else {
|
||||||
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
|
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
ctx.scale(ratio, ratio)
|
ctx.scale(ratio, ratio)
|
||||||
if(!this.canvasCache.canvas){
|
if (!this.canvasCache.canvas) {
|
||||||
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
|
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
|
||||||
}
|
}
|
||||||
if(!this.nameplateCache.canvas){
|
if (!this.nameplateCache.canvas) {
|
||||||
this.nameplateCache.resize(274, 67, ratio + 0.2)
|
this.nameplateCache.resize(274, 67, ratio + 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,14 +272,14 @@ class Scoresheet{
|
|||||||
|
|
||||||
var bgOffset = 0
|
var bgOffset = 0
|
||||||
var elapsed = ms - this.state.screenMS
|
var elapsed = ms - this.state.screenMS
|
||||||
if(this.state.screen === "fadeIn" && elapsed < 1000){
|
if (this.state.screen === "fadeIn" && elapsed < 1000) {
|
||||||
bgOffset = Math.min(1, this.draw.easeIn(1 - elapsed / 1000)) * (winH / 2)
|
bgOffset = Math.min(1, this.draw.easeIn(1 - elapsed / 1000)) * (winH / 2)
|
||||||
}
|
}
|
||||||
if((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved){
|
if ((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved) {
|
||||||
this.saveScore()
|
this.saveScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bgOffset){
|
if (bgOffset) {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.translate(0, -bgOffset)
|
ctx.translate(0, -bgOffset)
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ class Scoresheet{
|
|||||||
ctx.fillRect(0, winH / 2 - 12, winW, 12)
|
ctx.fillRect(0, winH / 2 - 12, winW, 12)
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.25)"
|
ctx.fillStyle = "rgba(0, 0, 0, 0.25)"
|
||||||
ctx.fillRect(0, winH / 2, winW, 20)
|
ctx.fillRect(0, winH / 2, winW, 20)
|
||||||
if(bgOffset !== 0){
|
if (bgOffset !== 0) {
|
||||||
ctx.fillStyle = "#000"
|
ctx.fillStyle = "#000"
|
||||||
ctx.fillRect(0, winH / 2 - 2, winW, 2)
|
ctx.fillRect(0, winH / 2 - 2, winW, 2)
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ class Scoresheet{
|
|||||||
ctx.fillStyle = "#bf2900"
|
ctx.fillStyle = "#bf2900"
|
||||||
ctx.fillRect(0, frameTop + 64, winW, 8)
|
ctx.fillRect(0, frameTop + 64, winW, 8)
|
||||||
|
|
||||||
if(bgOffset){
|
if (bgOffset) {
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.translate(0, bgOffset)
|
ctx.translate(0, bgOffset)
|
||||||
@@ -325,9 +325,9 @@ class Scoresheet{
|
|||||||
ctx.fillStyle = this.multiplayer ? "rgba(138, 245, 247, 0.5)" : "rgba(249, 163, 149, 0.5)"
|
ctx.fillStyle = this.multiplayer ? "rgba(138, 245, 247, 0.5)" : "rgba(249, 163, 149, 0.5)"
|
||||||
ctx.fillRect(0, winH / 2, winW, 12)
|
ctx.fillRect(0, winH / 2, winW, 12)
|
||||||
ctx.fillStyle = "#000"
|
ctx.fillStyle = "#000"
|
||||||
if(bgOffset === 0){
|
if (bgOffset === 0) {
|
||||||
ctx.fillRect(0, winH / 2 - 2, winW, 4)
|
ctx.fillRect(0, winH / 2 - 2, winW, 4)
|
||||||
}else{
|
} else {
|
||||||
ctx.fillRect(0, winH / 2, winW, 2)
|
ctx.fillRect(0, winH / 2, winW, 2)
|
||||||
}
|
}
|
||||||
ctx.fillStyle = this.multiplayer ? "#6bbec0" : "#fa4529"
|
ctx.fillStyle = this.multiplayer ? "#6bbec0" : "#fa4529"
|
||||||
@@ -337,46 +337,46 @@ class Scoresheet{
|
|||||||
ctx.fillStyle = this.multiplayer ? "#a8e0e0" : "#ff9b7a"
|
ctx.fillStyle = this.multiplayer ? "#a8e0e0" : "#ff9b7a"
|
||||||
ctx.fillRect(0, winH - frameTop - 66, winW, 2)
|
ctx.fillRect(0, winH - frameTop - 66, winW, 2)
|
||||||
|
|
||||||
if(bgOffset){
|
if (bgOffset) {
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.screen === "scoresShown" || this.state.screen === "fadeOut"){
|
if (this.state.screen === "scoresShown" || this.state.screen === "fadeOut") {
|
||||||
var elapsed = Infinity
|
var elapsed = Infinity
|
||||||
}else if(this.redrawing){
|
} else if (this.redrawing) {
|
||||||
var elapsed = ms - this.state.screenMS - this.state.startDelay
|
var elapsed = ms - this.state.screenMS - this.state.startDelay
|
||||||
}else{
|
} else {
|
||||||
var elapsed = 0
|
var elapsed = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules = this.controller.game.rules
|
var rules = this.controller.game.rules
|
||||||
var failedOffset = rules.clearReached(this.results[this.player[0]].gauge) ? 0 : -2000
|
var failedOffset = rules.clearReached(this.results[this.player[0]].gauge) ? 0 : -2000
|
||||||
if(players === 2 && failedOffset !== 0){
|
if (players === 2 && failedOffset !== 0) {
|
||||||
var p2results = this.results[this.player[1]]
|
var p2results = this.results[this.player[1]]
|
||||||
if(p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)){
|
if (p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)) {
|
||||||
failedOffset = 0
|
failedOffset = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(elapsed >= 3100 + failedOffset){
|
if (elapsed >= 3100 + failedOffset) {
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var clear = this.rules[p].clearReached(results.gauge)
|
var clear = this.rules[p].clearReached(results.gauge)
|
||||||
if(p === 1 || !this.multiplayer && clear){
|
if (p === 1 || !this.multiplayer && clear) {
|
||||||
ctx.translate(0, 290)
|
ctx.translate(0, 290)
|
||||||
}
|
}
|
||||||
if(clear){
|
if (clear) {
|
||||||
ctx.globalCompositeOperation = "lighter"
|
ctx.globalCompositeOperation = "lighter"
|
||||||
}
|
}
|
||||||
ctx.globalAlpha = Math.min(1, Math.max(0, (elapsed - (3100 + failedOffset)) / 500)) * 0.5
|
ctx.globalAlpha = Math.min(1, Math.max(0, (elapsed - (3100 + failedOffset)) / 500)) * 0.5
|
||||||
var grd = ctx.createLinearGradient(0, frameTop + 72, 0, frameTop + 368)
|
var grd = ctx.createLinearGradient(0, frameTop + 72, 0, frameTop + 368)
|
||||||
grd.addColorStop(0, "#000")
|
grd.addColorStop(0, "#000")
|
||||||
if(clear){
|
if (clear) {
|
||||||
grd.addColorStop(1, "#ffffba")
|
grd.addColorStop(1, "#ffffba")
|
||||||
}else{
|
} else {
|
||||||
grd.addColorStop(1, "transparent")
|
grd.addColorStop(1, "transparent")
|
||||||
}
|
}
|
||||||
ctx.fillStyle = grd
|
ctx.fillStyle = grd
|
||||||
@@ -385,10 +385,10 @@ class Scoresheet{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(elapsed >= 0){
|
if (elapsed >= 0) {
|
||||||
if(this.state.hasPointer === 0){
|
if (this.state.hasPointer === 0) {
|
||||||
this.state.hasPointer = 1
|
this.state.hasPointer = 1
|
||||||
if(!this.state.pointerLocked){
|
if (!this.state.pointerLocked) {
|
||||||
this.canvas.style.cursor = this.session ? "" : "pointer"
|
this.canvas.style.cursor = this.session ? "" : "pointer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,13 +416,13 @@ class Scoresheet{
|
|||||||
letterSpacing: strings.id === "en" ? 0 : 3,
|
letterSpacing: strings.id === "en" ? 0 : 3,
|
||||||
forceShadow: true
|
forceShadow: true
|
||||||
}, [
|
}, [
|
||||||
{x: -2, y: -2, outline: "#000", letterBorder: 22},
|
{ x: -2, y: -2, outline: "#000", letterBorder: 22 },
|
||||||
{},
|
{},
|
||||||
{x: 2, y: 2, shadow: [2, 2, 7]},
|
{ x: 2, y: 2, shadow: [2, 2, 7] },
|
||||||
{x: 2, y: 2, outline: "#ad1516", letterBorder: 10},
|
{ x: 2, y: 2, outline: "#ad1516", letterBorder: 10 },
|
||||||
{x: -2, y: -2, outline: "#ff797b"},
|
{ x: -2, y: -2, outline: "#ff797b" },
|
||||||
{outline: "#f70808"},
|
{ outline: "#f70808" },
|
||||||
{fill: "#fff", shadow: [-1, 1, 3, 1.5]}
|
{ fill: "#fff", shadow: [-1, 1, 3, 1.5] }
|
||||||
])
|
])
|
||||||
|
|
||||||
this.draw.layeredText({
|
this.draw.layeredText({
|
||||||
@@ -436,18 +436,18 @@ class Scoresheet{
|
|||||||
align: "right",
|
align: "right",
|
||||||
forceShadow: true
|
forceShadow: true
|
||||||
}, [
|
}, [
|
||||||
{outline: "#000", letterBorder: 10, shadow: [1, 1, 3]},
|
{ outline: "#000", letterBorder: 10, shadow: [1, 1, 3] },
|
||||||
{fill: "#fff"}
|
{ fill: "#fff" }
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.save()
|
ctx.save()
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if(p === 1){
|
if (p === 1) {
|
||||||
ctx.translate(0, p2Offset)
|
ctx.translate(0, p2Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,9 +470,9 @@ class Scoresheet{
|
|||||||
ctx.miterLimit = 10
|
ctx.miterLimit = 10
|
||||||
|
|
||||||
var defaultName = p === 0 ? strings.defaultName : strings.default2PName
|
var defaultName = p === 0 ? strings.defaultName : strings.default2PName
|
||||||
if(p === this.player[0]){
|
if (p === this.player[0]) {
|
||||||
var name = account.loggedIn ? account.displayName : defaultName
|
var name = account.loggedIn ? account.displayName : defaultName
|
||||||
}else{
|
} else {
|
||||||
var name = results.name || defaultName
|
var name = results.name || defaultName
|
||||||
}
|
}
|
||||||
this.nameplateCache.get({
|
this.nameplateCache.get({
|
||||||
@@ -493,7 +493,7 @@ class Scoresheet{
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if(this.controller.autoPlayEnabled){
|
if (this.controller.autoPlayEnabled) {
|
||||||
ctx.drawImage(assets.image["badge_auto"],
|
ctx.drawImage(assets.image["badge_auto"],
|
||||||
431, 311, 34, 34
|
431, 311, 34, 34
|
||||||
)
|
)
|
||||||
@@ -549,8 +549,8 @@ class Scoresheet{
|
|||||||
align: "right",
|
align: "right",
|
||||||
width: 36
|
width: 36
|
||||||
}, [
|
}, [
|
||||||
{fill: "#fff"},
|
{ fill: "#fff" },
|
||||||
{outline: "#000", letterBorder: 0.5}
|
{ outline: "#000", letterBorder: 0.5 }
|
||||||
])
|
])
|
||||||
|
|
||||||
this.draw.score({
|
this.draw.score({
|
||||||
@@ -590,8 +590,8 @@ class Scoresheet{
|
|||||||
width: 154,
|
width: 154,
|
||||||
letterSpacing: strings.id === "ja" ? 1 : 0
|
letterSpacing: strings.id === "ja" ? 1 : 0
|
||||||
}, [
|
}, [
|
||||||
{outline: "#000", letterBorder: 8},
|
{ outline: "#000", letterBorder: 8 },
|
||||||
{fill: grd}
|
{ fill: grd }
|
||||||
])
|
])
|
||||||
this.draw.layeredText({
|
this.draw.layeredText({
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -604,8 +604,8 @@ class Scoresheet{
|
|||||||
width: 154,
|
width: 154,
|
||||||
letterSpacing: strings.id === "ja" ? 4 : 0
|
letterSpacing: strings.id === "ja" ? 4 : 0
|
||||||
}, [
|
}, [
|
||||||
{outline: "#000", letterBorder: 8},
|
{ outline: "#000", letterBorder: 8 },
|
||||||
{fill: "#ffc700"}
|
{ fill: "#ffc700" }
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
@@ -613,15 +613,15 @@ class Scoresheet{
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.multiplayer){
|
if (!this.multiplayer) {
|
||||||
if(elapsed >= 400 && elapsed < 3100 + failedOffset){
|
if (elapsed >= 400 && elapsed < 3100 + failedOffset) {
|
||||||
if(this.tetsuoHanaClass !== "fadein"){
|
if (this.tetsuoHanaClass !== "fadein") {
|
||||||
this.tetsuoHana.classList.add("fadein")
|
this.tetsuoHana.classList.add("fadein")
|
||||||
this.tetsuoHanaClass = "fadein"
|
this.tetsuoHanaClass = "fadein"
|
||||||
}
|
}
|
||||||
}else if(elapsed >= 3100 + failedOffset){
|
} else if (elapsed >= 3100 + failedOffset) {
|
||||||
if(this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed"){
|
if (this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed") {
|
||||||
if(this.tetsuoHanaClass){
|
if (this.tetsuoHanaClass) {
|
||||||
this.tetsuoHana.classList.remove(this.tetsuoHanaClass)
|
this.tetsuoHana.classList.remove(this.tetsuoHanaClass)
|
||||||
}
|
}
|
||||||
this.tetsuoHanaClass = this.rules[this.player[0]].clearReached(this.results[this.player[0]].gauge) ? "dance" : "failed"
|
this.tetsuoHanaClass = this.rules[this.player[0]].clearReached(this.results[this.player[0]].gauge) ? "dance" : "failed"
|
||||||
@@ -630,19 +630,19 @@ class Scoresheet{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(elapsed >= 800){
|
if (elapsed >= 800) {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||||
this.draw.alpha(Math.min(1, (elapsed - 800) / 500), ctx, ctx => {
|
this.draw.alpha(Math.min(1, (elapsed - 800) / 500), ctx, ctx => {
|
||||||
ctx.scale(ratio, ratio)
|
ctx.scale(ratio, ratio)
|
||||||
ctx.translate(frameLeft, frameTop)
|
ctx.translate(frameLeft, frameTop)
|
||||||
|
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if(p === 1){
|
if (p === 1) {
|
||||||
ctx.translate(0, p2Offset)
|
ctx.translate(0, p2Offset)
|
||||||
}
|
}
|
||||||
var w = 712
|
var w = 712
|
||||||
@@ -670,50 +670,50 @@ class Scoresheet{
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(elapsed >= 1200){
|
if (elapsed >= 1200) {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||||
var noCrownResultWait = -2000;
|
var noCrownResultWait = -2000;
|
||||||
|
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var crownType = null
|
var crownType = null
|
||||||
if(this.rules[p].clearReached(results.gauge)){
|
if (this.rules[p].clearReached(results.gauge)) {
|
||||||
crownType = results.bad === "0" ? "gold" : "silver"
|
crownType = results.bad === "0" ? "gold" : "silver"
|
||||||
}
|
}
|
||||||
if(crownType !== null){
|
if (crownType !== null) {
|
||||||
noCrownResultWait = 0;
|
noCrownResultWait = 0;
|
||||||
var amount = Math.min(1, (elapsed - 1200) / 450)
|
var amount = Math.min(1, (elapsed - 1200) / 450)
|
||||||
this.draw.alpha(this.draw.easeIn(amount), ctx, ctx => {
|
this.draw.alpha(this.draw.easeIn(amount), ctx, ctx => {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.scale(ratio, ratio)
|
ctx.scale(ratio, ratio)
|
||||||
ctx.translate(frameLeft, frameTop)
|
ctx.translate(frameLeft, frameTop)
|
||||||
if(p === 1){
|
if (p === 1) {
|
||||||
ctx.translate(0, p2Offset)
|
ctx.translate(0, p2Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
var crownScale = 1
|
var crownScale = 1
|
||||||
var shine = 0
|
var shine = 0
|
||||||
if(amount < 1){
|
if (amount < 1) {
|
||||||
crownScale = 2.8 * (1 - amount) + 0.9
|
crownScale = 2.8 * (1 - amount) + 0.9
|
||||||
}else if(elapsed < 1850){
|
} else if (elapsed < 1850) {
|
||||||
crownScale = 0.9 + (elapsed - 1650) / 2000
|
crownScale = 0.9 + (elapsed - 1650) / 2000
|
||||||
}else if(elapsed < 2200){
|
} else if (elapsed < 2200) {
|
||||||
shine = (elapsed - 1850) / 175
|
shine = (elapsed - 1850) / 175
|
||||||
if(shine > 1){
|
if (shine > 1) {
|
||||||
shine = 2 - shine
|
shine = 2 - shine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(this.state.screen === "fadeIn" && elapsed >= 1200 && !this.state["fullcomboPlayed" + p]){
|
if (this.state.screen === "fadeIn" && elapsed >= 1200 && !this.state["fullcomboPlayed" + p]) {
|
||||||
this.state["fullcomboPlayed" + p] = true
|
this.state["fullcomboPlayed" + p] = true
|
||||||
if(crownType === "gold"){
|
if (crownType === "gold") {
|
||||||
this.playSound("v_results_fullcombo" + (p === 1 ? "2" : ""), p)
|
this.playSound("v_results_fullcombo" + (p === 1 ? "2" : ""), p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(this.state.screen === "fadeIn" && elapsed >= 1650 && !this.state["crownPlayed" + p]){
|
if (this.state.screen === "fadeIn" && elapsed >= 1650 && !this.state["crownPlayed" + p]) {
|
||||||
this.state["crownPlayed" + p] = true
|
this.state["crownPlayed" + p] = true
|
||||||
this.playSound("se_results_crown", p)
|
this.playSound("se_results_crown", p)
|
||||||
}
|
}
|
||||||
@@ -735,51 +735,51 @@ class Scoresheet{
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(elapsed >= 2400 + noCrownResultWait){
|
if (elapsed >= 2400 + noCrownResultWait) {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.translate(frameLeft, frameTop)
|
ctx.translate(frameLeft, frameTop)
|
||||||
|
|
||||||
var printNumbers = ["good", "ok", "bad", "maxCombo", "drumroll"]
|
var printNumbers = ["good", "ok", "bad", "maxCombo", "drumroll"]
|
||||||
if(!this.state["countupTime0"]){
|
if (!this.state["countupTime0"]) {
|
||||||
var times = {}
|
var times = {}
|
||||||
var lastTime = 0
|
var lastTime = 0
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame
|
var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame
|
||||||
if(currentTime > lastTime){
|
if (currentTime > lastTime) {
|
||||||
lastTime = currentTime
|
lastTime = currentTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(var i in printNumbers){
|
for (var i in printNumbers) {
|
||||||
var largestTime = 0
|
var largestTime = 0
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
times[printNumbers[i]] = lastTime + 500
|
times[printNumbers[i]] = lastTime + 500
|
||||||
var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
|
var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
|
||||||
if(currentTime > largestTime){
|
if (currentTime > largestTime) {
|
||||||
largestTime = currentTime
|
largestTime = currentTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastTime = largestTime
|
lastTime = largestTime
|
||||||
}
|
}
|
||||||
this.state.fadeInEnd = lastTime
|
this.state.fadeInEnd = lastTime
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
this.state["countupTime" + p] = times
|
this.state["countupTime" + p] = times
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var p = 0; p < players; p++){
|
for (var p = 0; p < players; p++) {
|
||||||
var results = this.results[p]
|
var results = this.results[p]
|
||||||
if(!results){
|
if (!results) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if(p === 1){
|
if (p === 1) {
|
||||||
ctx.translate(0, p2Offset)
|
ctx.translate(0, p2Offset)
|
||||||
}
|
}
|
||||||
ctx.save()
|
ctx.save()
|
||||||
@@ -795,24 +795,24 @@ class Scoresheet{
|
|||||||
ctx.fillStyle = "#fff"
|
ctx.fillStyle = "#fff"
|
||||||
ctx.strokeStyle = "#fff"
|
ctx.strokeStyle = "#fff"
|
||||||
ctx.lineWidth = 0.5
|
ctx.lineWidth = 0.5
|
||||||
for(var i = 0; i < points.length; i++){
|
for (var i = 0; i < points.length; i++) {
|
||||||
ctx.translate(-23.3 * scale, 0)
|
ctx.translate(-23.3 * scale, 0)
|
||||||
ctx.fillText(points[points.length - i - 1], 0, 0)
|
ctx.fillText(points[points.length - i - 1], 0, 0)
|
||||||
ctx.strokeText(points[points.length - i - 1], 0, 0)
|
ctx.strokeText(points[points.length - i - 1], 0, 0)
|
||||||
}
|
}
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
|
|
||||||
if(!this.state["countupTime" + p]){
|
if (!this.state["countupTime" + p]) {
|
||||||
var times = {}
|
var times = {}
|
||||||
var lastTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame + 1000
|
var lastTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame + 1000
|
||||||
for(var i in printNumbers){
|
for (var i in printNumbers) {
|
||||||
times[printNumbers[i]] = lastTime + 500
|
times[printNumbers[i]] = lastTime + 500
|
||||||
lastTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
|
lastTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
|
||||||
}
|
}
|
||||||
this.state["countupTime" + p] = times
|
this.state["countupTime" + p] = times
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var i in printNumbers){
|
for (var i in printNumbers) {
|
||||||
var start = this.state["countupTime" + p][printNumbers[i]]
|
var start = this.state["countupTime" + p][printNumbers[i]]
|
||||||
this.draw.layeredText({
|
this.draw.layeredText({
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -824,25 +824,25 @@ class Scoresheet{
|
|||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
align: "right"
|
align: "right"
|
||||||
}, [
|
}, [
|
||||||
{outline: "#000", letterBorder: 9},
|
{ outline: "#000", letterBorder: 9 },
|
||||||
{fill: "#fff"}
|
{ fill: "#fff" }
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.countupShown){
|
if (this.state.countupShown) {
|
||||||
if(!this.state["countup" + p]){
|
if (!this.state["countup" + p]) {
|
||||||
this.state["countup" + p] = true
|
this.state["countup" + p] = true
|
||||||
this.loopSound("se_results_countup", p, [0.1, false, 0, 0, 0.07])
|
this.loopSound("se_results_countup", p, [0.1, false, 0, 0, 0.07])
|
||||||
}
|
}
|
||||||
}else if(this.state["countup" + p]){
|
} else if (this.state["countup" + p]) {
|
||||||
this.state["countup" + p] = false
|
this.state["countup" + p] = false
|
||||||
this.stopSound("se_results_countup", p)
|
this.stopSound("se_results_countup", p)
|
||||||
if(this.state.screen === "fadeIn"){
|
if (this.state.screen === "fadeIn") {
|
||||||
this.playSound("neiro_1_don", p)
|
this.playSound("neiro_1_don", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.screen === "fadeIn" && elapsed >= this.state.fadeInEnd){
|
if (this.state.screen === "fadeIn" && elapsed >= this.state.fadeInEnd) {
|
||||||
this.state.screen = "scoresShown"
|
this.state.screen = "scoresShown"
|
||||||
this.state.screenMS = this.getMS()
|
this.state.screenMS = this.getMS()
|
||||||
}
|
}
|
||||||
@@ -850,28 +850,28 @@ class Scoresheet{
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.session && !this.state.scoreNext && this.state.screen === "scoresShown" && ms - this.state.screenMS >= 10000){
|
if (this.session && !this.state.scoreNext && this.state.screen === "scoresShown" && ms - this.state.screenMS >= 10000) {
|
||||||
this.state.scoreNext = true
|
this.state.scoreNext = true
|
||||||
if(p2.session){
|
if (p2.session) {
|
||||||
p2.send("songsel")
|
p2.send("songsel")
|
||||||
}else{
|
} else {
|
||||||
this.toSongsel(true)
|
this.toSongsel(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.screen === "fadeOut"){
|
if (this.state.screen === "fadeOut") {
|
||||||
if(this.state.hasPointer === 1){
|
if (this.state.hasPointer === 1) {
|
||||||
this.state.hasPointer = 2
|
this.state.hasPointer = 2
|
||||||
this.canvas.style.cursor = ""
|
this.canvas.style.cursor = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.fadeScreenBlack){
|
if (!this.fadeScreenBlack) {
|
||||||
this.fadeScreenBlack = true
|
this.fadeScreenBlack = true
|
||||||
this.fadeScreen.style.backgroundColor = "#000"
|
this.fadeScreen.style.backgroundColor = "#000"
|
||||||
}
|
}
|
||||||
var elapsed = ms - this.state.screenMS
|
var elapsed = ms - this.state.screenMS
|
||||||
|
|
||||||
if(elapsed >= 1000){
|
if (elapsed >= 1000) {
|
||||||
this.clean()
|
this.clean()
|
||||||
this.controller.songSelection(true, this.showWarning)
|
this.controller.songSelection(true, this.showWarning)
|
||||||
}
|
}
|
||||||
@@ -880,80 +880,106 @@ class Scoresheet{
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
getNumber(score, start, elapsed){
|
getNumber(score, start, elapsed) {
|
||||||
var numberPos = Math.floor((elapsed - start) / this.frame)
|
var numberPos = Math.floor((elapsed - start) / this.frame)
|
||||||
if(numberPos < 0){
|
if (numberPos < 0) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var output = ""
|
var output = ""
|
||||||
for(var i = 0; i < score.length; i++){
|
for (var i = 0; i < score.length; i++) {
|
||||||
if(numberPos < 30 * (i + 1)){
|
if (numberPos < 30 * (i + 1)) {
|
||||||
this.state.countupShown = true
|
this.state.countupShown = true
|
||||||
return this.numbers[numberPos % 30] + output
|
return this.numbers[numberPos % 30] + output
|
||||||
}else{
|
} else {
|
||||||
output = score[score.length - i - 1] + output
|
output = score[score.length - i - 1] + output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
getSound(id, p){
|
getSound(id, p) {
|
||||||
return assets.sounds[id + (this.multiplayer ? "_p" + (p + 1) : "")]
|
return assets.sounds[id + (this.multiplayer ? "_p" + (p + 1) : "")]
|
||||||
}
|
}
|
||||||
playSound(id, p){
|
playSound(id, p) {
|
||||||
this.getSound(id, p).play()
|
this.getSound(id, p).play()
|
||||||
}
|
}
|
||||||
loopSound(id, p, args){
|
loopSound(id, p, args) {
|
||||||
this.getSound(id, p).playLoop(...args)
|
this.getSound(id, p).playLoop(...args)
|
||||||
}
|
}
|
||||||
stopSound(id, p){
|
stopSound(id, p) {
|
||||||
this.getSound(id, p).stop()
|
this.getSound(id, p).stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
mod(length, index){
|
mod(length, index) {
|
||||||
return ((index % length) + length) % length
|
return ((index % length) + length) % length
|
||||||
}
|
}
|
||||||
|
|
||||||
getMS(){
|
getMS() {
|
||||||
return Date.now()
|
return Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
saveScore(){
|
saveScore() {
|
||||||
if(this.controller.saveScore){
|
if (this.controller.saveScore) {
|
||||||
if(this.resultsObj.points < 0){
|
if (this.resultsObj.points < 0) {
|
||||||
this.resultsObj.points = 0
|
this.resultsObj.points = 0
|
||||||
}
|
}
|
||||||
var title = this.controller.selectedSong.originalTitle
|
var title = this.controller.selectedSong.originalTitle
|
||||||
var hash = this.controller.selectedSong.hash
|
var hash = this.controller.selectedSong.hash
|
||||||
var difficulty = this.resultsObj.difficulty
|
var difficulty = this.resultsObj.difficulty
|
||||||
|
var songId = this.controller.selectedSong.id
|
||||||
var oldScore = scoreStorage.get(hash, difficulty, true)
|
var oldScore = scoreStorage.get(hash, difficulty, true)
|
||||||
var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge)
|
var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge)
|
||||||
var crown = ""
|
var crown = ""
|
||||||
if(clearReached){
|
if (clearReached) {
|
||||||
crown = this.resultsObj.bad === 0 ? "gold" : "silver"
|
crown = this.resultsObj.bad === 0 ? "gold" : "silver"
|
||||||
}
|
}
|
||||||
if(!oldScore || oldScore.points <= this.resultsObj.points){
|
if (!oldScore || oldScore.points <= this.resultsObj.points) {
|
||||||
if(oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)){
|
if (oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)) {
|
||||||
crown = oldScore.crown
|
crown = oldScore.crown
|
||||||
}
|
}
|
||||||
this.resultsObj.crown = crown
|
this.resultsObj.crown = crown
|
||||||
delete this.resultsObj.title
|
delete this.resultsObj.title
|
||||||
delete this.resultsObj.difficulty
|
delete this.resultsObj.difficulty
|
||||||
delete this.resultsObj.gauge
|
delete this.resultsObj.gauge
|
||||||
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => {
|
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).then(() => {
|
||||||
this.showWarning = {name: "scoreSaveFailed"}
|
// Auto-submit to leaderboard if logged in and has song ID
|
||||||
|
this.submitToLeaderboard(songId, difficulty, this.resultsObj)
|
||||||
|
}).catch(() => {
|
||||||
|
this.showWarning = { name: "scoreSaveFailed" }
|
||||||
})
|
})
|
||||||
}else if(oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)){
|
} else if (oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)) {
|
||||||
oldScore.crown = crown
|
oldScore.crown = crown
|
||||||
scoreStorage.add(hash, difficulty, oldScore, true, title).catch(() => {
|
scoreStorage.add(hash, difficulty, oldScore, true, title).catch(() => {
|
||||||
this.showWarning = {name: "scoreSaveFailed"}
|
this.showWarning = { name: "scoreSaveFailed" }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.scoreSaved = true
|
this.scoreSaved = true
|
||||||
}
|
}
|
||||||
|
|
||||||
clean(){
|
submitToLeaderboard(songId, difficulty, scoreObj) {
|
||||||
|
// Only submit if user is logged in and song has valid ID
|
||||||
|
if (!account.loggedIn || !songId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.getCsrfToken().then(token => {
|
||||||
|
var request = new XMLHttpRequest()
|
||||||
|
request.open("POST", "api/leaderboard/submit")
|
||||||
|
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
|
||||||
|
request.setRequestHeader("X-CSRFToken", token)
|
||||||
|
request.send(JSON.stringify({
|
||||||
|
song_id: songId,
|
||||||
|
difficulty: difficulty,
|
||||||
|
score: scoreObj
|
||||||
|
}))
|
||||||
|
}).catch(() => {
|
||||||
|
// Silently fail - leaderboard submission is optional
|
||||||
|
console.log("Leaderboard submission failed")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
this.keyboard.clean()
|
this.keyboard.clean()
|
||||||
this.gamepad.clean()
|
this.gamepad.clean()
|
||||||
this.draw.clean()
|
this.draw.clean()
|
||||||
@@ -962,13 +988,13 @@ class Scoresheet{
|
|||||||
snd.buffer.loadSettings()
|
snd.buffer.loadSettings()
|
||||||
this.redrawRunning = false
|
this.redrawRunning = false
|
||||||
pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
|
pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
|
||||||
if(this.touchEnabled){
|
if (this.touchEnabled) {
|
||||||
pageEvents.remove(document.getElementById("touch-full-btn"), "touchend")
|
pageEvents.remove(document.getElementById("touch-full-btn"), "touchend")
|
||||||
}
|
}
|
||||||
if(this.session){
|
if (this.session) {
|
||||||
pageEvents.remove(p2, "message")
|
pageEvents.remove(p2, "message")
|
||||||
}
|
}
|
||||||
if(!this.multiplayer){
|
if (!this.multiplayer) {
|
||||||
delete this.tetsuoHana
|
delete this.tetsuoHana
|
||||||
}
|
}
|
||||||
delete this.ctx
|
delete this.ctx
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
3
public/src/views/leaderboard.html
Normal file
3
public/src/views/leaderboard.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div id="leaderboard">
|
||||||
|
<canvas id="leaderboard-canvas"></canvas>
|
||||||
|
</div>
|
||||||
11
schema.py
11
schema.py
@@ -80,3 +80,14 @@ scores_save = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leaderboard_submit = {
|
||||||
|
'$schema': 'http://json-schema.org/schema#',
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'song_id': {'type': 'number'},
|
||||||
|
'difficulty': {'type': 'string'},
|
||||||
|
'score': {'type': 'object'}
|
||||||
|
},
|
||||||
|
'required': ['song_id', 'difficulty', 'score']
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>太鼓ウェブ - Taiko Web | (゚∀゚)</title>
|
<title>太鼓ウェブ - Taiko Web | (゚∀゚)</title>
|
||||||
<link rel="icon" href="{{config.assets_baseurl}}img/favicon.png" type="image/png">
|
<link rel="icon" href="{{config.assets_baseurl}}img/favicon.png" type="image/png">
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
<meta name="description" content="2025年最新の無料オンラインゲームの パソコンとスマホのブラウザ向けの太鼓の達人シミュレータ 🥁 Taiko no Tatsujin rhythm game simulator for desktop and mobile browsers">
|
<meta name="description"
|
||||||
<meta name="keywords" content="taiko no tatsujin, taiko, don chan, online, rhythm, browser, html5, game, for browsers, pc, arcade, emulator, free, download, website, 太鼓の達人, 太鼓ウェブ, 太鼓之達人, 太鼓達人, 太鼓网页, 网页版, 太鼓網頁, 網頁版, 태고의 달인, 태고 웹">
|
content="2026年最新の無料オンラインゲームの パソコンとスマホのブラウザ向けの太鼓の達人シミュレータ 🥁 Taiko no Tatsujin rhythm game simulator for desktop and mobile browsers">
|
||||||
|
<meta name="keywords"
|
||||||
|
content="taiko no tatsujin, taiko, don chan, online, rhythm, browser, html5, game, for browsers, pc, arcade, emulator, free, download, website, 太鼓の達人, 太鼓ウェブ, 太鼓之達人, 太鼓達人, 太鼓网页, 网页版, 太鼓網頁, 網頁版, 태고의 달인, 태고 웹">
|
||||||
<meta name="robots" content="notranslate">
|
<meta name="robots" content="notranslate">
|
||||||
<meta name="robots" content="noimageindex">
|
<meta name="robots" content="noimageindex">
|
||||||
<meta name="color-scheme" content="only light">
|
<meta name="color-scheme" content="only light">
|
||||||
|
|
||||||
<link rel="canonical" href="https://taikoapp.uk/" />
|
<link rel="canonical" href="https://taikoapp.uk/" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="src/css/loader.css?{{version.commit_short}}">
|
<link rel="stylesheet" href="src/css/loader.css?{{version.commit_short}}">
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
@@ -24,14 +27,17 @@
|
|||||||
<script src="src/js/lib/jszip.js"></script>
|
<script src="src/js/lib/jszip.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="assets"></div>
|
<div id="assets"></div>
|
||||||
<div id="screen" class="pattern-bg"></div>
|
<div id="screen" class="pattern-bg"></div>
|
||||||
<div data-nosnippet id="version">
|
<div data-nosnippet id="version">
|
||||||
{% if version.version and version.commit_short and version.commit %}
|
{% if version.version and version.commit_short and version.commit %}
|
||||||
<a href="{{version.url}}commit/{{version.commit}}" target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="vLightNova 1.0.0">vLightNova 1.0.0</a>
|
<a href="{{version.url}}commit/{{version.commit}}" target="_blank" rel="noopener" id="version-link"
|
||||||
|
class="stroke-sub" alt="vLightNova 1.1.0">vLightNova 1.1.0</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="vLightNova 1.0.0">vLightNova 1.0.0</a>
|
<a target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="vLightNova 1.1.0">vLightNova
|
||||||
|
1.1.0</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<script src="src/js/browsersupport.js?{{version.commit_short}}"></script>
|
<script src="src/js/browsersupport.js?{{version.commit_short}}"></script>
|
||||||
@@ -43,4 +49,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user