Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e52baf2555 | |||
| 45d6b1d9de | |||
| bb3ad834b2 | |||
| addd9d90f0 | |||
| 69b92b34d8 | |||
| 9935d70e31 | |||
| 3f7ff13ef7 | |||
| 0706f99427 | |||
| 9bd2b21d44 | |||
| b15752e051 | |||
| 84d15b70c6 |
17
app.py
17
app.py
@@ -789,7 +789,13 @@ def route_api_leaderboard_submit():
|
|||||||
else:
|
else:
|
||||||
score_obj = score_data
|
score_obj = score_data
|
||||||
|
|
||||||
score_value = int(score_obj.get('score', 0))
|
# Check for 'score' first, then 'points'
|
||||||
|
# Frontend usually sends 'points'
|
||||||
|
score_val = score_obj.get('score')
|
||||||
|
if score_val is None:
|
||||||
|
score_val = score_obj.get('points')
|
||||||
|
|
||||||
|
score_value = int(score_val or 0)
|
||||||
except:
|
except:
|
||||||
return api_error('invalid_score_format')
|
return api_error('invalid_score_format')
|
||||||
|
|
||||||
@@ -858,7 +864,6 @@ def route_api_leaderboard_submit():
|
|||||||
|
|
||||||
return jsonify({'status': 'ok', 'message': 'score_submitted'})
|
return jsonify({'status': 'ok', 'message': 'score_submitted'})
|
||||||
|
|
||||||
|
|
||||||
@app.route(basedir + 'api/leaderboard/get')
|
@app.route(basedir + 'api/leaderboard/get')
|
||||||
def route_api_leaderboard_get():
|
def route_api_leaderboard_get():
|
||||||
song_id = request.args.get('song_id', None)
|
song_id = request.args.get('song_id', None)
|
||||||
@@ -867,10 +872,13 @@ def route_api_leaderboard_get():
|
|||||||
if not song_id or not difficulty:
|
if not song_id or not difficulty:
|
||||||
return abort(400)
|
return abort(400)
|
||||||
|
|
||||||
|
# Accept both numeric IDs and hash strings
|
||||||
|
# Try to convert to int if possible, otherwise use as string
|
||||||
try:
|
try:
|
||||||
song_id = int(song_id)
|
song_id = int(song_id)
|
||||||
except:
|
except (ValueError, TypeError):
|
||||||
return abort(400)
|
# Keep as string (hash ID)
|
||||||
|
pass
|
||||||
|
|
||||||
# Validate difficulty
|
# Validate difficulty
|
||||||
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
||||||
@@ -901,6 +909,7 @@ def route_api_leaderboard_get():
|
|||||||
return jsonify({'status': 'ok', 'leaderboard': leaderboard, 'month': current_month})
|
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')))
|
||||||
|
|||||||
201
local_debug.py
Normal file
201
local_debug.py
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# Mock DB and objects
|
||||||
|
class MockCollection:
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
def find_one(self, query):
|
||||||
|
for item in self.data:
|
||||||
|
match = True
|
||||||
|
for k, v in query.items():
|
||||||
|
if item.get(k) != v:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
return item
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find(self, query, projection=None):
|
||||||
|
results = []
|
||||||
|
for item in self.data:
|
||||||
|
match = True
|
||||||
|
for k, v in query.items():
|
||||||
|
if item.get(k) != v:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
if projection:
|
||||||
|
filtered = {k: v for k, v in item.items() if projection.get(k, False)}
|
||||||
|
if projection.get('_id') is None or projection.get('_id') is True:
|
||||||
|
filtered['_id'] = item.get('_id')
|
||||||
|
results.append(filtered)
|
||||||
|
else:
|
||||||
|
results.append(item)
|
||||||
|
return MockCursor(results)
|
||||||
|
|
||||||
|
def insert_one(self, doc):
|
||||||
|
doc['_id'] = len(self.data) + 1
|
||||||
|
print(f"[{self.name}] Inserted: {doc}")
|
||||||
|
self.data.append(doc)
|
||||||
|
|
||||||
|
def count_documents(self, query):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
|
class MockCursor:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
def sort(self, key, direction):
|
||||||
|
self.data.sort(key=lambda x: x.get(key, 0), reverse=(direction < 0))
|
||||||
|
return self
|
||||||
|
def limit(self, n):
|
||||||
|
self.data = self.data[:n]
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
class MockDB:
|
||||||
|
def __init__(self):
|
||||||
|
self.users = MockCollection('users')
|
||||||
|
self.leaderboards = MockCollection('leaderboards')
|
||||||
|
|
||||||
|
db = MockDB()
|
||||||
|
db.users.insert_one({'username': 'testuser', 'display_name': 'Test User'})
|
||||||
|
|
||||||
|
# --- Logic from app.py rewritten for standalone test ---
|
||||||
|
|
||||||
|
def logic_submit(data, username):
|
||||||
|
print(f"\n--- Submitting {data.get('song_id')} ---")
|
||||||
|
user = db.users.find_one({'username': username})
|
||||||
|
if not user:
|
||||||
|
return {'error': 'user_not_found'}
|
||||||
|
|
||||||
|
song_id = data.get('song_id')
|
||||||
|
difficulty = data.get('difficulty')
|
||||||
|
score_data = data.get('score')
|
||||||
|
|
||||||
|
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||||
|
|
||||||
|
existing = db.leaderboards.find_one({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'username': username,
|
||||||
|
'month': current_month
|
||||||
|
})
|
||||||
|
|
||||||
|
# Patched Logic from app.py
|
||||||
|
try:
|
||||||
|
if isinstance(score_data, str):
|
||||||
|
score_obj = json.loads(score_data)
|
||||||
|
else:
|
||||||
|
score_obj = score_data
|
||||||
|
|
||||||
|
# Check for 'score' first, then 'points'
|
||||||
|
score_val = score_obj.get('score')
|
||||||
|
if score_val is None:
|
||||||
|
score_val = score_obj.get('points')
|
||||||
|
|
||||||
|
score_value = int(score_val or 0)
|
||||||
|
print(f" Parsed Score Value: {score_value}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error parsing score: {e}")
|
||||||
|
return {'error': 'invalid_score_format'}
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
print(" Existing record found (simulated update).")
|
||||||
|
else:
|
||||||
|
print(" Inserting 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
|
||||||
|
})
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
def logic_get(args):
|
||||||
|
print(f"\n--- Getting {args.get('song_id')} ---")
|
||||||
|
song_id = args.get('song_id', None)
|
||||||
|
difficulty = args.get('difficulty', None)
|
||||||
|
|
||||||
|
if not song_id or not difficulty:
|
||||||
|
return {'error': 'missing args'}
|
||||||
|
|
||||||
|
try:
|
||||||
|
song_id = int(song_id)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||||
|
|
||||||
|
leaderboard = list(db.leaderboards.find({
|
||||||
|
'song_id': song_id,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'month': current_month
|
||||||
|
}, {
|
||||||
|
'_id': False,
|
||||||
|
'username': True,
|
||||||
|
'score_value': True
|
||||||
|
}).sort('score_value', -1).limit(50))
|
||||||
|
|
||||||
|
return {'status': 'ok', 'leaderboard': leaderboard}
|
||||||
|
|
||||||
|
# --- Test Cases ---
|
||||||
|
|
||||||
|
# Case 1: Hash ID
|
||||||
|
print("=== TEST CASE 1: Hash ID ===")
|
||||||
|
logic_submit({
|
||||||
|
'song_id': "hash123",
|
||||||
|
'difficulty': 'oni',
|
||||||
|
'score': {'score': 1000000}
|
||||||
|
}, 'testuser')
|
||||||
|
res1 = logic_get({'song_id': "hash123", 'difficulty': 'oni'})
|
||||||
|
if len(res1['leaderboard']) > 0: print("✅ Success")
|
||||||
|
else: print("❌ Failed")
|
||||||
|
|
||||||
|
# Case 2: Numeric ID
|
||||||
|
print("\n=== TEST CASE 2: Numeric ID ===")
|
||||||
|
logic_submit({
|
||||||
|
'song_id': 123,
|
||||||
|
'difficulty': 'oni',
|
||||||
|
'score': {'score': 2000000}
|
||||||
|
}, 'testuser')
|
||||||
|
res2 = logic_get({'song_id': "123", 'difficulty': 'oni'})
|
||||||
|
if len(res2['leaderboard']) > 0: print("✅ Success")
|
||||||
|
else: print("❌ Failed")
|
||||||
|
|
||||||
|
# Case 3: Points Only (Frontend Payload Simulation)
|
||||||
|
print("\n=== TEST CASE 3: Points Only (Frontend Payload) ===")
|
||||||
|
# This simulates what the JS 'this.resultsObj' looks like (it has 'points', not 'score')
|
||||||
|
logic_submit({
|
||||||
|
'song_id': "points_song",
|
||||||
|
'difficulty': 'hard',
|
||||||
|
'score': {'points': 345678, 'bad': 0, 'good': 100} # No 'score' key
|
||||||
|
}, 'testuser')
|
||||||
|
|
||||||
|
res3 = logic_get({'song_id': "points_song", 'difficulty': 'hard'})
|
||||||
|
passed = False
|
||||||
|
if len(res3.get('leaderboard', [])) > 0:
|
||||||
|
val = res3['leaderboard'][0].get('score_value')
|
||||||
|
if val == 345678:
|
||||||
|
print(f"✅ SUCCESS: Correctly parsed 'points' -> {val}")
|
||||||
|
passed = True
|
||||||
|
else:
|
||||||
|
print(f"❌ FAILED: Score value mismatch. Expected 345678, got {val}")
|
||||||
|
else:
|
||||||
|
print("❌ FAILED: No record found")
|
||||||
|
|
||||||
|
if passed:
|
||||||
|
print("\n---------------------------------------------------")
|
||||||
|
print("🎉 ALL LOCAL TESTS PASSED. BACKEND LOGIC VERIFIED.")
|
||||||
|
print("---------------------------------------------------")
|
||||||
|
else:
|
||||||
|
print("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||||
|
print("🛑 TESTS FAILED. CHECK LOGS.")
|
||||||
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||||
@@ -7,6 +7,7 @@ var assets = {
|
|||||||
"titlescreen.js",
|
"titlescreen.js",
|
||||||
"scoresheet.js",
|
"scoresheet.js",
|
||||||
"songselect.js",
|
"songselect.js",
|
||||||
|
"leaderboard.js",
|
||||||
"keyboard.js",
|
"keyboard.js",
|
||||||
"gameinput.js",
|
"gameinput.js",
|
||||||
"game.js",
|
"game.js",
|
||||||
@@ -140,6 +141,7 @@ var assets = {
|
|||||||
},
|
},
|
||||||
"views": [
|
"views": [
|
||||||
"game.html",
|
"game.html",
|
||||||
|
"leaderboard.html",
|
||||||
"loadsong.html",
|
"loadsong.html",
|
||||||
"songselect.html",
|
"songselect.html",
|
||||||
"titlescreen.html",
|
"titlescreen.html",
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
class Leaderboard {
|
class Leaderboard {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.init()
|
this.canvas = null
|
||||||
|
this.ctx = null
|
||||||
|
this.songId = null
|
||||||
|
this.difficulty = null
|
||||||
|
this.leaderboardData = []
|
||||||
|
this.currentMonth = ""
|
||||||
|
this.visible = false
|
||||||
|
this.draw = null
|
||||||
|
this.keyboard = null
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.canvas = document.getElementById("leaderboard-canvas")
|
this.canvas = document.getElementById("leaderboard-canvas")
|
||||||
if (!this.canvas) {
|
if (!this.canvas) {
|
||||||
return
|
console.error("Leaderboard canvas not found!")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
this.ctx = this.canvas.getContext("2d")
|
this.ctx = this.canvas.getContext("2d")
|
||||||
|
|
||||||
@@ -16,12 +25,6 @@ class Leaderboard {
|
|||||||
this.ctx.imageSmoothingEnabled = false
|
this.ctx.imageSmoothingEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.songId = null
|
|
||||||
this.difficulty = null
|
|
||||||
this.leaderboardData = []
|
|
||||||
this.currentMonth = ""
|
|
||||||
this.visible = false
|
|
||||||
|
|
||||||
this.draw = new CanvasDraw(noSmoothing)
|
this.draw = new CanvasDraw(noSmoothing)
|
||||||
|
|
||||||
// Keyboard controls
|
// Keyboard controls
|
||||||
@@ -33,6 +36,7 @@ class Leaderboard {
|
|||||||
}, this.keyPress.bind(this))
|
}, this.keyPress.bind(this))
|
||||||
|
|
||||||
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.onClose.bind(this))
|
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.onClose.bind(this))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPress(pressed, name) {
|
keyPress(pressed, name) {
|
||||||
@@ -55,6 +59,12 @@ class Leaderboard {
|
|||||||
|
|
||||||
loader.changePage("leaderboard", false)
|
loader.changePage("leaderboard", false)
|
||||||
|
|
||||||
|
// Initialize after page is loaded
|
||||||
|
if (!this.init()) {
|
||||||
|
console.error("Failed to initialize leaderboard")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch leaderboard data
|
// Fetch leaderboard data
|
||||||
await this.fetchLeaderboard()
|
await this.fetchLeaderboard()
|
||||||
|
|
||||||
@@ -67,10 +77,18 @@ class Leaderboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetchLeaderboard() {
|
async fetchLeaderboard() {
|
||||||
|
// Validate songId exists
|
||||||
|
if (!this.songId) {
|
||||||
|
console.error("Missing song ID for leaderboard")
|
||||||
|
this.leaderboardData = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var response = await loader.ajax(
|
var response = await loader.ajax(
|
||||||
`${gameConfig.basedir || "/"}api/leaderboard/get?song_id=${this.songId}&difficulty=${this.difficulty}`
|
`${gameConfig.basedir || "/"}api/leaderboard/get?song_id=${encodeURIComponent(this.songId)}&difficulty=${this.difficulty}`
|
||||||
)
|
)
|
||||||
|
|
||||||
var data = JSON.parse(response)
|
var data = JSON.parse(response)
|
||||||
if (data.status === "ok") {
|
if (data.status === "ok") {
|
||||||
this.leaderboardData = data.leaderboard || []
|
this.leaderboardData = data.leaderboard || []
|
||||||
@@ -84,6 +102,7 @@ class Leaderboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
changeDifficulty(direction) {
|
changeDifficulty(direction) {
|
||||||
var difficulties = ["easy", "normal", "hard", "oni", "ura"]
|
var difficulties = ["easy", "normal", "hard", "oni", "ura"]
|
||||||
var currentIndex = difficulties.indexOf(this.difficulty)
|
var currentIndex = difficulties.indexOf(this.difficulty)
|
||||||
@@ -361,7 +380,8 @@ class Leaderboard {
|
|||||||
this.ctx.fillText(displayName, modalX + 140, y + 2)
|
this.ctx.fillText(displayName, modalX + 140, y + 2)
|
||||||
|
|
||||||
// Score with formatting
|
// Score with formatting
|
||||||
var score = entry.score && entry.score.score ? entry.score.score : 0
|
// Score with formatting
|
||||||
|
var score = entry.score_value !== undefined ? entry.score_value : (entry.score && entry.score.score ? entry.score.score : 0)
|
||||||
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#555555"
|
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#555555"
|
||||||
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
|
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
|
||||||
this.ctx.textAlign = "right"
|
this.ctx.textAlign = "right"
|
||||||
|
|||||||
@@ -178,8 +178,94 @@ class Scoresheet {
|
|||||||
}
|
}
|
||||||
this.game.appendChild(this.tetsuoHana)
|
this.game.appendChild(this.tetsuoHana)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add leaderboard submit button if user is logged in
|
||||||
|
if (account.loggedIn && !this.multiplayer) {
|
||||||
|
this.leaderboardBtn = document.createElement("div")
|
||||||
|
this.leaderboardBtn.id = "leaderboard-submit-btn"
|
||||||
|
this.leaderboardBtn.innerHTML = "🏆 提交排行榜<br><small>Submit to Leaderboard</small>"
|
||||||
|
this.leaderboardBtn.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 15px 25px;
|
||||||
|
background: linear-gradient(135deg, #ff6b9d, #c44db3);
|
||||||
|
color: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: ${strings.font || "sans-serif"};
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||||
|
border: 3px solid #fff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
`
|
||||||
|
this.leaderboardBtn.onmouseover = () => {
|
||||||
|
if (!this.leaderboardSubmitted) {
|
||||||
|
this.leaderboardBtn.style.transform = "scale(1.05)"
|
||||||
|
this.leaderboardBtn.style.boxShadow = "0 6px 20px rgba(0,0,0,0.4)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.leaderboardBtn.onmouseout = () => {
|
||||||
|
this.leaderboardBtn.style.transform = "scale(1)"
|
||||||
|
this.leaderboardBtn.style.boxShadow = "0 4px 15px rgba(0,0,0,0.3)"
|
||||||
|
}
|
||||||
|
this.leaderboardBtn.onclick = () => this.onLeaderboardBtnClick()
|
||||||
|
this.game.appendChild(this.leaderboardBtn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLeaderboardBtnClick() {
|
||||||
|
if (this.leaderboardSubmitted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: This is CRITICAL for cases where saveScore() failed or was skipped
|
||||||
|
if (!this.leaderboardData) {
|
||||||
|
console.warn("Leaderboard data missing, attempting to reconstruct...")
|
||||||
|
var song = this.controller.selectedSong
|
||||||
|
var results = this.resultsObj
|
||||||
|
|
||||||
|
if (song && results) {
|
||||||
|
var songId = song.id || song.hash
|
||||||
|
|
||||||
|
if (songId) {
|
||||||
|
this.leaderboardData = {
|
||||||
|
songId: songId,
|
||||||
|
difficulty: results.difficulty,
|
||||||
|
scoreObj: Object.assign({ score: results.points }, results)
|
||||||
|
}
|
||||||
|
// Clean up scoreObj
|
||||||
|
if (this.leaderboardData.scoreObj) {
|
||||||
|
delete this.leaderboardData.scoreObj.title
|
||||||
|
delete this.leaderboardData.scoreObj.difficulty
|
||||||
|
delete this.leaderboardData.scoreObj.gauge
|
||||||
|
}
|
||||||
|
console.log("Leaderboard data reconstructed:", this.leaderboardData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.leaderboardData || !this.leaderboardData.songId) {
|
||||||
|
this.showLeaderboardNotification("no_song_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable button and show submitting state
|
||||||
|
this.leaderboardBtn.innerHTML = "⏳ 提交中..."
|
||||||
|
this.leaderboardBtn.style.background = "linear-gradient(135deg, #888, #666)"
|
||||||
|
this.leaderboardBtn.style.cursor = "default"
|
||||||
|
|
||||||
|
this.submitToLeaderboard(
|
||||||
|
this.leaderboardData.songId,
|
||||||
|
this.leaderboardData.difficulty,
|
||||||
|
this.leaderboardData.scoreObj
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
redraw() {
|
redraw() {
|
||||||
if (!this.redrawRunning) {
|
if (!this.redrawRunning) {
|
||||||
return
|
return
|
||||||
@@ -926,13 +1012,24 @@ class Scoresheet {
|
|||||||
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
|
// Use id if available, otherwise use hash
|
||||||
|
var songId = this.controller.selectedSong.id || hash
|
||||||
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store data for manual leaderboard submission
|
||||||
|
this.leaderboardData = {
|
||||||
|
songId: songId,
|
||||||
|
difficulty: difficulty,
|
||||||
|
scoreObj: Object.assign({ score: this.resultsObj.points }, this.resultsObj)
|
||||||
|
}
|
||||||
|
this.leaderboardSubmitted = false
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@@ -941,10 +1038,7 @@ class Scoresheet {
|
|||||||
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).then(() => {
|
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => {
|
||||||
// Auto-submit to leaderboard if logged in and has song ID
|
|
||||||
this.submitToLeaderboard(songId, difficulty, this.resultsObj)
|
|
||||||
}).catch(() => {
|
|
||||||
this.showWarning = { name: "scoreSaveFailed" }
|
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)) {
|
||||||
@@ -957,28 +1051,130 @@ class Scoresheet {
|
|||||||
this.scoreSaved = true
|
this.scoreSaved = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
submitToLeaderboard(songId, difficulty, scoreObj) {
|
submitToLeaderboard(songId, difficulty, scoreObj) {
|
||||||
// Only submit if user is logged in and song has valid ID
|
// Only submit if user is logged in and song has an ID
|
||||||
if (!account.loggedIn || !songId) {
|
if (!account.loggedIn || !songId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var self = this
|
||||||
loader.getCsrfToken().then(token => {
|
loader.getCsrfToken().then(token => {
|
||||||
var request = new XMLHttpRequest()
|
var request = new XMLHttpRequest()
|
||||||
request.open("POST", "api/leaderboard/submit")
|
request.open("POST", "api/leaderboard/submit")
|
||||||
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
|
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
|
||||||
request.setRequestHeader("X-CSRFToken", token)
|
request.setRequestHeader("X-CSRFToken", token)
|
||||||
|
|
||||||
|
request.onload = function () {
|
||||||
|
if (request.status === 200) {
|
||||||
|
try {
|
||||||
|
var response = JSON.parse(request.responseText)
|
||||||
|
if (response.status === "ok") {
|
||||||
|
self.leaderboardSubmitted = true
|
||||||
|
self.showLeaderboardNotification(response.message)
|
||||||
|
// Update button to show success
|
||||||
|
if (self.leaderboardBtn) {
|
||||||
|
self.leaderboardBtn.innerHTML = "✅ 已提交<br><small>Submitted!</small>"
|
||||||
|
self.leaderboardBtn.style.background = "linear-gradient(135deg, #4CAF50, #45a049)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show error
|
||||||
|
self.showLeaderboardNotification("error")
|
||||||
|
if (self.leaderboardBtn) {
|
||||||
|
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||||
|
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse leaderboard response:", e)
|
||||||
|
self.showLeaderboardNotification("error")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.showLeaderboardNotification("error")
|
||||||
|
if (self.leaderboardBtn) {
|
||||||
|
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||||
|
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onerror = function () {
|
||||||
|
self.showLeaderboardNotification("error")
|
||||||
|
if (self.leaderboardBtn) {
|
||||||
|
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||||
|
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
request.send(JSON.stringify({
|
request.send(JSON.stringify({
|
||||||
song_id: songId,
|
song_id: songId,
|
||||||
difficulty: difficulty,
|
difficulty: difficulty,
|
||||||
score: scoreObj
|
score: scoreObj
|
||||||
}))
|
}))
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Silently fail - leaderboard submission is optional
|
|
||||||
console.log("Leaderboard submission failed")
|
console.log("Leaderboard submission failed")
|
||||||
|
this.showLeaderboardNotification("error")
|
||||||
|
if (this.leaderboardBtn) {
|
||||||
|
this.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||||
|
this.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
showLeaderboardNotification(message) {
|
||||||
|
var notification = document.createElement("div")
|
||||||
|
notification.className = "leaderboard-notification"
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 15px 25px;
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #7b68ee);
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: ${strings.font || "sans-serif"};
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100px);
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
`
|
||||||
|
|
||||||
|
var text = ""
|
||||||
|
switch (message) {
|
||||||
|
case "score_submitted": text = "🏆 成绩已提交到排行榜!"; break
|
||||||
|
case "score_updated": text = "🎉 排行榜成绩已更新!"; break
|
||||||
|
case "score_not_higher": text = "📊 已有更高成绩"; break
|
||||||
|
case "score_too_low": text = "未进入排行榜前50"; break
|
||||||
|
case "error": text = "❌ 提交失败,请重试"; break
|
||||||
|
case "no_song_id": text = "❌ 无法提交:歌曲ID缺失"; break
|
||||||
|
default: text = "排行榜已更新"
|
||||||
|
}
|
||||||
|
notification.innerText = text
|
||||||
|
|
||||||
|
document.body.appendChild(notification)
|
||||||
|
|
||||||
|
// Trigger animation
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.opacity = "1"
|
||||||
|
notification.style.transform = "translateX(0)"
|
||||||
|
}, 10)
|
||||||
|
|
||||||
|
// Remove after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.opacity = "0"
|
||||||
|
notification.style.transform = "translateX(100px)"
|
||||||
|
setTimeout(() => {
|
||||||
|
if (notification.parentNode) {
|
||||||
|
notification.parentNode.removeChild(notification)
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
clean() {
|
clean() {
|
||||||
this.keyboard.clean()
|
this.keyboard.clean()
|
||||||
this.gamepad.clean()
|
this.gamepad.clean()
|
||||||
@@ -997,10 +1193,17 @@ class Scoresheet {
|
|||||||
if (!this.multiplayer) {
|
if (!this.multiplayer) {
|
||||||
delete this.tetsuoHana
|
delete this.tetsuoHana
|
||||||
}
|
}
|
||||||
|
// Clean up leaderboard button
|
||||||
|
if (this.leaderboardBtn && this.leaderboardBtn.parentNode) {
|
||||||
|
this.leaderboardBtn.parentNode.removeChild(this.leaderboardBtn)
|
||||||
|
}
|
||||||
|
delete this.leaderboardBtn
|
||||||
|
delete this.leaderboardData
|
||||||
delete this.ctx
|
delete this.ctx
|
||||||
delete this.canvas
|
delete this.canvas
|
||||||
delete this.fadeScreen
|
delete this.fadeScreen
|
||||||
delete this.results
|
delete this.results
|
||||||
delete this.rules
|
delete this.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
class Search{
|
class Search {
|
||||||
constructor(...args){
|
constructor(...args) {
|
||||||
this.init(...args)
|
this.init(...args)
|
||||||
}
|
}
|
||||||
init(songSelect){
|
init(songSelect) {
|
||||||
this.songSelect = songSelect
|
this.songSelect = songSelect
|
||||||
this.opened = false
|
this.opened = false
|
||||||
this.enabled = true
|
this.enabled = true
|
||||||
|
|
||||||
this.style = document.createElement("style")
|
this.style = document.createElement("style")
|
||||||
var css = []
|
var css = []
|
||||||
for(var i in this.songSelect.songSkin){
|
for (var i in this.songSelect.songSkin) {
|
||||||
var skin = this.songSelect.songSkin[i]
|
var skin = this.songSelect.songSkin[i]
|
||||||
if("id" in skin || i === "default"){
|
if ("id" in skin || i === "default") {
|
||||||
var id = "id" in skin ? ("cat" + skin.id) : i
|
var id = "id" in skin ? ("cat" + skin.id) : i
|
||||||
|
|
||||||
css.push(loader.cssRuleset({
|
css.push(loader.cssRuleset({
|
||||||
@@ -33,7 +33,7 @@ class Search{
|
|||||||
loader.screen.appendChild(this.style)
|
loader.screen.appendChild(this.style)
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeString(string){
|
normalizeString(string) {
|
||||||
string = string
|
string = string
|
||||||
.replace('’', '\'').replace('“', '"').replace('”', '"')
|
.replace('’', '\'').replace('“', '"').replace('”', '"')
|
||||||
.replace('。', '.').replace(',', ',').replace('、', ',')
|
.replace('。', '.').replace(',', ',').replace('、', ',')
|
||||||
@@ -45,28 +45,28 @@ class Search{
|
|||||||
return string.normalize("NFKD").replace(/[\u0300-\u036f]/g, "")
|
return string.normalize("NFKD").replace(/[\u0300-\u036f]/g, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
perform(query){
|
perform(query) {
|
||||||
var results = []
|
var results = []
|
||||||
var filters = {}
|
var filters = {}
|
||||||
|
|
||||||
var querySplit = query.split(" ").filter(word => {
|
var querySplit = query.split(" ").filter(word => {
|
||||||
if(word.length > 0){
|
if (word.length > 0) {
|
||||||
var parts = word.toLowerCase().split(":")
|
var parts = word.toLowerCase().split(":")
|
||||||
if(parts.length > 1){
|
if (parts.length > 1) {
|
||||||
switch(parts[0]){
|
switch (parts[0]) {
|
||||||
case "easy":
|
case "easy":
|
||||||
case "normal":
|
case "normal":
|
||||||
case "hard":
|
case "hard":
|
||||||
case "oni":
|
case "oni":
|
||||||
case "ura":
|
case "ura":
|
||||||
var range = this.parseRange(parts[1])
|
var range = this.parseRange(parts[1])
|
||||||
if(range){
|
if (range) {
|
||||||
filters[parts[0]] = range
|
filters[parts[0]] = range
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "extreme":
|
case "extreme":
|
||||||
var range = this.parseRange(parts[1])
|
var range = this.parseRange(parts[1])
|
||||||
if(range){
|
if (range) {
|
||||||
filters.oni = this.parseRange(parts[1])
|
filters.oni = this.parseRange(parts[1])
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -97,57 +97,57 @@ class Search{
|
|||||||
var totalFilters = Object.keys(filters).length
|
var totalFilters = Object.keys(filters).length
|
||||||
var random = false
|
var random = false
|
||||||
var allResults = false
|
var allResults = false
|
||||||
for(var i = 0; i < assets.songs.length; i++){
|
for (var i = 0; i < assets.songs.length; i++) {
|
||||||
var song = assets.songs[i]
|
var song = assets.songs[i]
|
||||||
var passedFilters = 0
|
var passedFilters = 0
|
||||||
|
|
||||||
Object.keys(filters).forEach(filter => {
|
Object.keys(filters).forEach(filter => {
|
||||||
var value = filters[filter]
|
var value = filters[filter]
|
||||||
switch(filter){
|
switch (filter) {
|
||||||
case "easy":
|
case "easy":
|
||||||
case "normal":
|
case "normal":
|
||||||
case "hard":
|
case "hard":
|
||||||
case "oni":
|
case "oni":
|
||||||
case "ura":
|
case "ura":
|
||||||
if(song.courses[filter] && song.courses[filter].stars >= value.min && song.courses[filter].stars <= value.max){
|
if (song.courses[filter] && song.courses[filter].stars >= value.min && song.courses[filter].stars <= value.max) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "clear":
|
case "clear":
|
||||||
case "silver":
|
case "silver":
|
||||||
case "gold":
|
case "gold":
|
||||||
if(value === "any"){
|
if (value === "any") {
|
||||||
var score = scoreStorage.scores[song.hash]
|
var score = scoreStorage.scores[song.hash]
|
||||||
scoreStorage.difficulty.forEach(difficulty => {
|
scoreStorage.difficulty.forEach(difficulty => {
|
||||||
if(score && score[difficulty] && score[difficulty].crown && (filter === "clear" || score[difficulty].crown === filter)){
|
if (score && score[difficulty] && score[difficulty].crown && (filter === "clear" || score[difficulty].crown === filter)) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
var score = scoreStorage.scores[song.hash]
|
var score = scoreStorage.scores[song.hash]
|
||||||
if(score && score[value] && score[value].crown && (filter === "clear" || score[value].crown === filter)){
|
if (score && score[value] && score[value].crown && (filter === "clear" || score[value].crown === filter)) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "played":
|
case "played":
|
||||||
var score = scoreStorage.scores[song.hash]
|
var score = scoreStorage.scores[song.hash]
|
||||||
if((value === "yes" && score) || (value === "no" && !score)){
|
if ((value === "yes" && score) || (value === "no" && !score)) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "lyrics":
|
case "lyrics":
|
||||||
if((value === "yes" && song.lyrics) || (value === "no" && !song.lyrics)){
|
if ((value === "yes" && song.lyrics) || (value === "no" && !song.lyrics)) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "creative":
|
case "creative":
|
||||||
if((value === "yes" && song.maker) || (value === "no" && !song.maker)){
|
if ((value === "yes" && song.maker) || (value === "no" && !song.maker)) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "maker":
|
case "maker":
|
||||||
if(song.maker && song.maker.name.toLowerCase().includes(value.toLowerCase())){
|
if (song.maker && song.maker.name.toLowerCase().includes(value.toLowerCase())) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -155,24 +155,24 @@ class Search{
|
|||||||
var cat = assets.categories.find(cat => cat.id === song.category_id)
|
var cat = assets.categories.find(cat => cat.id === song.category_id)
|
||||||
var aliases = cat.aliases ? cat.aliases.concat([cat.title]) : [cat.title]
|
var aliases = cat.aliases ? cat.aliases.concat([cat.title]) : [cat.title]
|
||||||
|
|
||||||
if(aliases.find(alias => alias.toLowerCase() === value.toLowerCase())){
|
if (aliases.find(alias => alias.toLowerCase() === value.toLowerCase())) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "diverge":
|
case "diverge":
|
||||||
var branch = Object.values(song.courses).find(course => course && course.branch)
|
var branch = Object.values(song.courses).find(course => course && course.branch)
|
||||||
if((value === "yes" && branch) || (value === "no" && !branch)){
|
if ((value === "yes" && branch) || (value === "no" && !branch)) {
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "random":
|
case "random":
|
||||||
if(value === "yes" || value === "no"){
|
if (value === "yes" || value === "no") {
|
||||||
random = value === "yes"
|
random = value === "yes"
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "all":
|
case "all":
|
||||||
if(value === "yes" || value === "no"){
|
if (value === "yes" || value === "no") {
|
||||||
allResults = value === "yes"
|
allResults = value === "yes"
|
||||||
passedFilters++
|
passedFilters++
|
||||||
}
|
}
|
||||||
@@ -180,86 +180,86 @@ class Search{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if(passedFilters === totalFilters){
|
if (passedFilters === totalFilters) {
|
||||||
results.push(song)
|
results.push(song)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxResults = allResults ? Infinity : (totalFilters > 0 && !query ? 100 : 50)
|
var maxResults = allResults ? Infinity : (totalFilters > 0 && !query ? 100 : 50)
|
||||||
|
|
||||||
if(query){
|
if (query) {
|
||||||
results = fuzzysort.go(query, results, {
|
results = fuzzysort.go(query, results, {
|
||||||
keys: ["titlePrepared", "subtitlePrepared"],
|
keys: ["titlePrepared", "subtitlePrepared"],
|
||||||
allowTypo: true,
|
allowTypo: true,
|
||||||
limit: maxResults,
|
limit: maxResults,
|
||||||
scoreFn: a => {
|
scoreFn: a => {
|
||||||
if(a[0]){
|
if (a[0]) {
|
||||||
var score0 = a[0].score
|
var score0 = a[0].score
|
||||||
a[0].ranges = this.indexesToRanges(a[0].indexes)
|
a[0].ranges = this.indexesToRanges(a[0].indexes)
|
||||||
if(a[0].indexes.length > 1){
|
if (a[0].indexes.length > 1) {
|
||||||
var rangeAmount = a[0].ranges.length
|
var rangeAmount = a[0].ranges.length
|
||||||
var lastIdx = -3
|
var lastIdx = -3
|
||||||
a[0].ranges.forEach(range => {
|
a[0].ranges.forEach(range => {
|
||||||
if(range[0] - lastIdx <= 2){
|
if (range[0] - lastIdx <= 2) {
|
||||||
rangeAmount--
|
rangeAmount--
|
||||||
score0 -= 1000
|
score0 -= 1000
|
||||||
}
|
}
|
||||||
lastIdx = range[1]
|
lastIdx = range[1]
|
||||||
})
|
})
|
||||||
var index = a[0].target.toLowerCase().indexOf(query)
|
var index = a[0].target.toLowerCase().indexOf(query)
|
||||||
if(index !== -1){
|
if (index !== -1) {
|
||||||
a[0].ranges = [[index, index + query.length - 1]]
|
a[0].ranges = [[index, index + query.length - 1]]
|
||||||
}else if(rangeAmount > a[0].indexes.length / 2){
|
} else if (rangeAmount > a[0].indexes.length / 2) {
|
||||||
score0 = -Infinity
|
score0 = -Infinity
|
||||||
a[0].ranges = null
|
a[0].ranges = null
|
||||||
}else if(rangeAmount !== 1){
|
} else if (rangeAmount !== 1) {
|
||||||
score0 -= 9000
|
score0 -= 9000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(a[1]){
|
if (a[1]) {
|
||||||
var score1 = a[1].score - 1000
|
var score1 = a[1].score - 1000
|
||||||
a[1].ranges = this.indexesToRanges(a[1].indexes)
|
a[1].ranges = this.indexesToRanges(a[1].indexes)
|
||||||
if(a[1].indexes.length > 1){
|
if (a[1].indexes.length > 1) {
|
||||||
var rangeAmount = a[1].ranges.length
|
var rangeAmount = a[1].ranges.length
|
||||||
var lastIdx = -3
|
var lastIdx = -3
|
||||||
a[1].ranges.forEach(range => {
|
a[1].ranges.forEach(range => {
|
||||||
if(range[0] - lastIdx <= 2){
|
if (range[0] - lastIdx <= 2) {
|
||||||
rangeAmount--
|
rangeAmount--
|
||||||
score1 -= 1000
|
score1 -= 1000
|
||||||
}
|
}
|
||||||
lastIdx = range[1]
|
lastIdx = range[1]
|
||||||
})
|
})
|
||||||
var index = a[1].target.indexOf(query)
|
var index = a[1].target.indexOf(query)
|
||||||
if(index !== -1){
|
if (index !== -1) {
|
||||||
a[1].ranges = [[index, index + query.length - 1]]
|
a[1].ranges = [[index, index + query.length - 1]]
|
||||||
}else if(rangeAmount > a[1].indexes.length / 2){
|
} else if (rangeAmount > a[1].indexes.length / 2) {
|
||||||
score1 = -Infinity
|
score1 = -Infinity
|
||||||
a[1].ranges = null
|
a[1].ranges = null
|
||||||
}else if(rangeAmount !== 1){
|
} else if (rangeAmount !== 1) {
|
||||||
score1 -= 9000
|
score1 -= 9000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(random){
|
if (random) {
|
||||||
var rand = Math.random() * -9000
|
var rand = Math.random() * -9000
|
||||||
if(score0 !== -Infinity){
|
if (score0 !== -Infinity) {
|
||||||
score0 = rand
|
score0 = rand
|
||||||
}
|
}
|
||||||
if(score1 !== -Infinity){
|
if (score1 !== -Infinity) {
|
||||||
score1 = rand
|
score1 = rand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(a[0]){
|
if (a[0]) {
|
||||||
return a[1] ? Math.max(score0, score1) : score0
|
return a[1] ? Math.max(score0, score1) : score0
|
||||||
}else{
|
} else {
|
||||||
return a[1] ? score1 : -Infinity
|
return a[1] ? score1 : -Infinity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}else{
|
} else {
|
||||||
if(random){
|
if (random) {
|
||||||
for(var i = results.length - 1; i > 0; i--){
|
for (var i = results.length - 1; i > 0; i--) {
|
||||||
var j = Math.floor(Math.random() * (i + 1))
|
var j = Math.floor(Math.random() * (i + 1))
|
||||||
var temp = results[i]
|
var temp = results[i]
|
||||||
results[i] = results[j]
|
results[i] = results[j]
|
||||||
@@ -267,22 +267,22 @@ class Search{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
results = results.slice(0, maxResults).map(result => {
|
results = results.slice(0, maxResults).map(result => {
|
||||||
return {obj: result}
|
return { obj: result }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
createResult(result, resultWidth, fontSize){
|
createResult(result, resultWidth, fontSize) {
|
||||||
var song = result.obj
|
var song = result.obj
|
||||||
var title = this.songSelect.getLocalTitle(song.title, song.title_lang)
|
var title = this.songSelect.getLocalTitle(song.title, song.title_lang)
|
||||||
var subtitle = this.songSelect.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
|
var subtitle = this.songSelect.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
|
||||||
|
|
||||||
var id = "default"
|
var id = "default"
|
||||||
if(song.category_id){
|
if (song.category_id) {
|
||||||
var cat = assets.categories.find(cat => cat.id === song.category_id)
|
var cat = assets.categories.find(cat => cat.id === song.category_id)
|
||||||
if(cat && "id" in cat){
|
if (cat && "id" in cat) {
|
||||||
id = "cat" + cat.id
|
id = "cat" + cat.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,7 @@ class Search{
|
|||||||
|
|
||||||
resultInfoDiv.appendChild(resultInfoTitle)
|
resultInfoDiv.appendChild(resultInfoTitle)
|
||||||
|
|
||||||
if(subtitle){
|
if (subtitle) {
|
||||||
resultInfoDiv.appendChild(document.createElement("br"))
|
resultInfoDiv.appendChild(document.createElement("br"))
|
||||||
var resultInfoSubtitle = document.createElement("span")
|
var resultInfoSubtitle = document.createElement("span")
|
||||||
resultInfoSubtitle.classList.add("song-search-result-subtitle")
|
resultInfoSubtitle.classList.add("song-search-result-subtitle")
|
||||||
@@ -343,14 +343,14 @@ class Search{
|
|||||||
this.songSelect.ctx.font = (1.2 * fontSize) + "px " + strings.font
|
this.songSelect.ctx.font = (1.2 * fontSize) + "px " + strings.font
|
||||||
var titleWidth = this.songSelect.ctx.measureText(title).width
|
var titleWidth = this.songSelect.ctx.measureText(title).width
|
||||||
var titleRatio = resultWidth / titleWidth
|
var titleRatio = resultWidth / titleWidth
|
||||||
if(titleRatio < 1){
|
if (titleRatio < 1) {
|
||||||
resultInfoTitle.style.transform = "scale(" + titleRatio + ", 1)"
|
resultInfoTitle.style.transform = "scale(" + titleRatio + ", 1)"
|
||||||
}
|
}
|
||||||
if(subtitle){
|
if (subtitle) {
|
||||||
this.songSelect.ctx.font = (0.8 * 1.2 * fontSize) + "px " + strings.font
|
this.songSelect.ctx.font = (0.8 * 1.2 * fontSize) + "px " + strings.font
|
||||||
var subtitleWidth = this.songSelect.ctx.measureText(subtitle).width
|
var subtitleWidth = this.songSelect.ctx.measureText(subtitle).width
|
||||||
var subtitleRatio = resultWidth / subtitleWidth
|
var subtitleRatio = resultWidth / subtitleWidth
|
||||||
if(subtitleRatio < 1){
|
if (subtitleRatio < 1) {
|
||||||
resultInfoSubtitle.style.transform = "scale(" + subtitleRatio + ", 1)"
|
resultInfoSubtitle.style.transform = "scale(" + subtitleRatio + ", 1)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,12 +358,13 @@ class Search{
|
|||||||
return resultDiv
|
return resultDiv
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightResult(text, result){
|
highlightResult(text, result) {
|
||||||
|
if (text === null || text === undefined) return document.createDocumentFragment();
|
||||||
var fragment = document.createDocumentFragment()
|
var fragment = document.createDocumentFragment()
|
||||||
var ranges = (result ? result.ranges : null) || []
|
var ranges = (result ? result.ranges : null) || []
|
||||||
var lastIdx = 0
|
var lastIdx = 0
|
||||||
ranges.forEach(range => {
|
ranges.forEach(range => {
|
||||||
if(lastIdx !== range[0]){
|
if (lastIdx !== range[0]) {
|
||||||
fragment.appendChild(document.createTextNode(text.slice(lastIdx, range[0])))
|
fragment.appendChild(document.createTextNode(text.slice(lastIdx, range[0])))
|
||||||
}
|
}
|
||||||
var span = document.createElement("span")
|
var span = document.createElement("span")
|
||||||
@@ -372,20 +373,20 @@ class Search{
|
|||||||
fragment.appendChild(span)
|
fragment.appendChild(span)
|
||||||
lastIdx = range[1] + 1
|
lastIdx = range[1] + 1
|
||||||
})
|
})
|
||||||
if(text.length !== lastIdx){
|
if (text.length !== lastIdx) {
|
||||||
fragment.appendChild(document.createTextNode(text.slice(lastIdx)))
|
fragment.appendChild(document.createTextNode(text.slice(lastIdx)))
|
||||||
}
|
}
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
setActive(idx){
|
setActive(idx) {
|
||||||
this.songSelect.playSound("se_ka")
|
this.songSelect.playSound("se_ka")
|
||||||
var active = this.div.querySelector(":scope .song-search-result-active")
|
var active = this.div.querySelector(":scope .song-search-result-active")
|
||||||
if(active){
|
if (active) {
|
||||||
active.classList.remove("song-search-result-active")
|
active.classList.remove("song-search-result-active")
|
||||||
}
|
}
|
||||||
|
|
||||||
if(idx === null){
|
if (idx === null) {
|
||||||
this.active = null
|
this.active = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -398,11 +399,11 @@ class Search{
|
|||||||
this.active = idx
|
this.active = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
display(fromButton=false){
|
display(fromButton = false) {
|
||||||
if(!this.enabled){
|
if (!this.enabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(this.opened){
|
if (this.opened) {
|
||||||
return this.remove(true)
|
return this.remove(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +413,7 @@ class Search{
|
|||||||
this.div.innerHTML = assets.pages["search"]
|
this.div.innerHTML = assets.pages["search"]
|
||||||
|
|
||||||
this.container = this.div.querySelector(":scope #song-search-container")
|
this.container = this.div.querySelector(":scope #song-search-container")
|
||||||
if(this.touchEnabled){
|
if (this.touchEnabled) {
|
||||||
this.container.classList.add("touch-enabled")
|
this.container.classList.add("touch-enabled")
|
||||||
}
|
}
|
||||||
pageEvents.add(this.container, ["mousedown", "touchstart"], this.onClick.bind(this))
|
pageEvents.add(this.container, ["mousedown", "touchstart"], this.onClick.bind(this))
|
||||||
@@ -426,9 +427,9 @@ class Search{
|
|||||||
this.setTip()
|
this.setTip()
|
||||||
cancelTouch = false
|
cancelTouch = false
|
||||||
noResizeRoot = true
|
noResizeRoot = true
|
||||||
if(this.songSelect.songs[this.songSelect.selectedSong].courses){
|
if (this.songSelect.songs[this.songSelect.selectedSong].courses) {
|
||||||
snd.previewGain.setVolumeMul(0.5)
|
snd.previewGain.setVolumeMul(0.5)
|
||||||
}else if(this.songSelect.bgmEnabled){
|
} else if (this.songSelect.bgmEnabled) {
|
||||||
snd.musicGain.setVolumeMul(0.5)
|
snd.musicGain.setVolumeMul(0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +439,7 @@ class Search{
|
|||||||
}, 10)
|
}, 10)
|
||||||
|
|
||||||
var lastQuery = localStorage.getItem("lastSearchQuery")
|
var lastQuery = localStorage.getItem("lastSearchQuery")
|
||||||
if(lastQuery){
|
if (lastQuery) {
|
||||||
this.input.value = lastQuery
|
this.input.value = lastQuery
|
||||||
this.input.dispatchEvent(new Event("input", {
|
this.input.dispatchEvent(new Event("input", {
|
||||||
value: lastQuery
|
value: lastQuery
|
||||||
@@ -446,15 +447,15 @@ class Search{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(byUser=false){
|
remove(byUser = false) {
|
||||||
if(this.opened){
|
if (this.opened) {
|
||||||
this.opened = false
|
this.opened = false
|
||||||
if(byUser){
|
if (byUser) {
|
||||||
this.songSelect.playSound("se_cancel")
|
this.songSelect.playSound("se_cancel")
|
||||||
}
|
}
|
||||||
|
|
||||||
pageEvents.remove(this.div.querySelector(":scope #song-search-container"),
|
pageEvents.remove(this.div.querySelector(":scope #song-search-container"),
|
||||||
["mousedown", "touchstart"])
|
["mousedown", "touchstart"])
|
||||||
pageEvents.remove(this.input, ["input"])
|
pageEvents.remove(this.input, ["input"])
|
||||||
|
|
||||||
this.div.remove()
|
this.div.remove()
|
||||||
@@ -465,21 +466,21 @@ class Search{
|
|||||||
delete this.active
|
delete this.active
|
||||||
cancelTouch = true
|
cancelTouch = true
|
||||||
noResizeRoot = false
|
noResizeRoot = false
|
||||||
if(this.songSelect.songs[this.songSelect.selectedSong].courses){
|
if (this.songSelect.songs[this.songSelect.selectedSong].courses) {
|
||||||
snd.previewGain.setVolumeMul(1)
|
snd.previewGain.setVolumeMul(1)
|
||||||
}else if(this.songSelect.bgmEnabled){
|
} else if (this.songSelect.bgmEnabled) {
|
||||||
snd.musicGain.setVolumeMul(1)
|
snd.musicGain.setVolumeMul(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTip(tip, error=false){
|
setTip(tip, error = false) {
|
||||||
if(this.tip){
|
if (this.tip) {
|
||||||
this.tip.remove()
|
this.tip.remove()
|
||||||
delete this.tip
|
delete this.tip
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tip){
|
if (!tip) {
|
||||||
tip = strings.search.tip + " " + strings.search.tips[Math.floor(Math.random() * strings.search.tips.length)]
|
tip = strings.search.tip + " " + strings.search.tips[Math.floor(Math.random() * strings.search.tips.length)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,12 +493,12 @@ class Search{
|
|||||||
this.tip.innerText = tip
|
this.tip.innerText = tip
|
||||||
this.div.querySelector(":scope #song-search").appendChild(this.tip)
|
this.div.querySelector(":scope #song-search").appendChild(this.tip)
|
||||||
|
|
||||||
if(error){
|
if (error) {
|
||||||
this.tip.classList.add("song-search-tip-error")
|
this.tip.classList.add("song-search-tip-error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proceed(songId){
|
proceed(songId) {
|
||||||
if (/^-?\d+$/.test(songId)) {
|
if (/^-?\d+$/.test(songId)) {
|
||||||
songId = parseInt(songId)
|
songId = parseInt(songId)
|
||||||
}
|
}
|
||||||
@@ -505,7 +506,7 @@ class Search{
|
|||||||
var song = this.songSelect.songs.find(song => song.id === songId)
|
var song = this.songSelect.songs.find(song => song.id === songId)
|
||||||
this.remove()
|
this.remove()
|
||||||
this.songSelect.playBgm(false)
|
this.songSelect.playBgm(false)
|
||||||
if(this.songSelect.previewing === "muted"){
|
if (this.songSelect.previewing === "muted") {
|
||||||
this.songSelect.previewing = null
|
this.songSelect.previewing = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,41 +515,41 @@ class Search{
|
|||||||
this.songSelect.toSelectDifficulty()
|
this.songSelect.toSelectDifficulty()
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo(element){
|
scrollTo(element) {
|
||||||
var parentNode = element.parentNode
|
var parentNode = element.parentNode
|
||||||
var selected = element.getBoundingClientRect()
|
var selected = element.getBoundingClientRect()
|
||||||
var parent = parentNode.getBoundingClientRect()
|
var parent = parentNode.getBoundingClientRect()
|
||||||
var scrollY = parentNode.scrollTop
|
var scrollY = parentNode.scrollTop
|
||||||
var selectedPosTop = selected.top - selected.height / 2
|
var selectedPosTop = selected.top - selected.height / 2
|
||||||
if(Math.floor(selectedPosTop) < Math.floor(parent.top)){
|
if (Math.floor(selectedPosTop) < Math.floor(parent.top)) {
|
||||||
parentNode.scrollTop += selectedPosTop - parent.top
|
parentNode.scrollTop += selectedPosTop - parent.top
|
||||||
}else{
|
} else {
|
||||||
var selectedPosBottom = selected.top + selected.height * 1.5 - parent.top
|
var selectedPosBottom = selected.top + selected.height * 1.5 - parent.top
|
||||||
if(Math.floor(selectedPosBottom) > Math.floor(parent.height)){
|
if (Math.floor(selectedPosBottom) > Math.floor(parent.height)) {
|
||||||
parentNode.scrollTop += selectedPosBottom - parent.height
|
parentNode.scrollTop += selectedPosBottom - parent.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseRange(string){
|
parseRange(string) {
|
||||||
var range = string.split("-")
|
var range = string.split("-")
|
||||||
if(range.length == 1){
|
if (range.length == 1) {
|
||||||
var min = parseInt(range[0]) || 0
|
var min = parseInt(range[0]) || 0
|
||||||
return min > 0 ? {min: min, max: min} : false
|
return min > 0 ? { min: min, max: min } : false
|
||||||
} else if(range.length == 2){
|
} else if (range.length == 2) {
|
||||||
var min = parseInt(range[0]) || 0
|
var min = parseInt(range[0]) || 0
|
||||||
var max = parseInt(range[1]) || 0
|
var max = parseInt(range[1]) || 0
|
||||||
return min > 0 && max > 0 ? {min: min, max: max} : false
|
return min > 0 && max > 0 ? { min: min, max: max } : false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
indexesToRanges(indexes){
|
indexesToRanges(indexes) {
|
||||||
var ranges = []
|
var ranges = []
|
||||||
var range
|
var range
|
||||||
indexes.forEach(idx => {
|
indexes.forEach(idx => {
|
||||||
if(range && range[1] === idx - 1){
|
if (range && range[1] === idx - 1) {
|
||||||
range[1] = idx
|
range[1] = idx
|
||||||
}else{
|
} else {
|
||||||
range = [idx, idx]
|
range = [idx, idx]
|
||||||
ranges.push(range)
|
ranges.push(range)
|
||||||
}
|
}
|
||||||
@@ -556,13 +557,13 @@ class Search{
|
|||||||
return ranges
|
return ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
onInput(resize){
|
onInput(resize) {
|
||||||
var text = this.input.value
|
var text = this.input.value
|
||||||
localStorage.setItem("lastSearchQuery", text)
|
localStorage.setItem("lastSearchQuery", text)
|
||||||
text = text.toLowerCase()
|
text = text.toLowerCase()
|
||||||
|
|
||||||
if(text.length === 0){
|
if (text.length === 0) {
|
||||||
if(!resize){
|
if (!resize) {
|
||||||
this.setTip()
|
this.setTip()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -570,10 +571,10 @@ class Search{
|
|||||||
|
|
||||||
var new_results = this.perform(text)
|
var new_results = this.perform(text)
|
||||||
|
|
||||||
if(new_results.length === 0){
|
if (new_results.length === 0) {
|
||||||
this.setTip(strings.search.noResults, true)
|
this.setTip(strings.search.noResults, true)
|
||||||
return
|
return
|
||||||
}else if(this.tip){
|
} else if (this.tip) {
|
||||||
this.tip.remove()
|
this.tip.remove()
|
||||||
delete this.tip
|
delete this.tip
|
||||||
}
|
}
|
||||||
@@ -601,74 +602,74 @@ class Search{
|
|||||||
this.songSelect.ctx.restore()
|
this.songSelect.ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(e){
|
onClick(e) {
|
||||||
if((e.target.id === "song-search-container" || e.target.id === "song-search-close") && e.which === 1){
|
if ((e.target.id === "song-search-container" || e.target.id === "song-search-close") && e.which === 1) {
|
||||||
this.remove(true)
|
this.remove(true)
|
||||||
}else if(e.which === 1){
|
} else if (e.which === 1) {
|
||||||
var songEl = e.target.closest(".song-search-result")
|
var songEl = e.target.closest(".song-search-result")
|
||||||
if(songEl){
|
if (songEl) {
|
||||||
var songId = songEl.dataset.songId
|
var songId = songEl.dataset.songId
|
||||||
this.proceed(songId)
|
this.proceed(songId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPress(pressed, name, event, repeat, ctrl){
|
keyPress(pressed, name, event, repeat, ctrl) {
|
||||||
if(name === "back" || (event && event.keyCode && event.keyCode === 70 && ctrl)) {
|
if (name === "back" || (event && event.keyCode && event.keyCode === 70 && ctrl)) {
|
||||||
this.remove(true)
|
this.remove(true)
|
||||||
if(event){
|
if (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
}else if(name === "down" && this.results.length){
|
} else if (name === "down" && this.results.length) {
|
||||||
if(this.input == document.activeElement && this.results){
|
if (this.input == document.activeElement && this.results) {
|
||||||
this.setActive(0)
|
this.setActive(0)
|
||||||
}else if(this.active === this.results.length - 1){
|
} else if (this.active === this.results.length - 1) {
|
||||||
this.setActive(null)
|
this.setActive(null)
|
||||||
this.input.focus()
|
this.input.focus()
|
||||||
}else if(Number.isInteger(this.active)){
|
} else if (Number.isInteger(this.active)) {
|
||||||
this.setActive(this.active + 1)
|
this.setActive(this.active + 1)
|
||||||
}else{
|
} else {
|
||||||
this.setActive(0)
|
this.setActive(0)
|
||||||
}
|
}
|
||||||
}else if(name === "up" && this.results.length){
|
} else if (name === "up" && this.results.length) {
|
||||||
if(this.input == document.activeElement && this.results){
|
if (this.input == document.activeElement && this.results) {
|
||||||
this.setActive(this.results.length - 1)
|
this.setActive(this.results.length - 1)
|
||||||
}else if(this.active === 0){
|
} else if (this.active === 0) {
|
||||||
this.setActive(null)
|
this.setActive(null)
|
||||||
this.input.focus()
|
this.input.focus()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.input.setSelectionRange(this.input.value.length, this.input.value.length)
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length)
|
||||||
}, 0)
|
}, 0)
|
||||||
}else if(Number.isInteger(this.active)){
|
} else if (Number.isInteger(this.active)) {
|
||||||
this.setActive(this.active - 1)
|
this.setActive(this.active - 1)
|
||||||
}else{
|
} else {
|
||||||
this.setActive(this.results.length - 1)
|
this.setActive(this.results.length - 1)
|
||||||
}
|
}
|
||||||
}else if(name === "confirm"){
|
} else if (name === "confirm") {
|
||||||
if(Number.isInteger(this.active)){
|
if (Number.isInteger(this.active)) {
|
||||||
this.proceed(this.results[this.active].dataset.songId)
|
this.proceed(this.results[this.active].dataset.songId)
|
||||||
}else{
|
} else {
|
||||||
this.onInput()
|
this.onInput()
|
||||||
if(event.keyCode === 13 && this.songSelect.touchEnabled){
|
if (event.keyCode === 13 && this.songSelect.touchEnabled) {
|
||||||
this.input.blur()
|
this.input.blur()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redraw(){
|
redraw() {
|
||||||
if(this.opened && this.container){
|
if (this.opened && this.container) {
|
||||||
var vmin = Math.min(innerWidth, lastHeight) / 100
|
var vmin = Math.min(innerWidth, lastHeight) / 100
|
||||||
if(this.vmin !== vmin){
|
if (this.vmin !== vmin) {
|
||||||
this.container.style.setProperty("--vmin", vmin + "px")
|
this.container.style.setProperty("--vmin", vmin + "px")
|
||||||
this.vmin = vmin
|
this.vmin = vmin
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
this.vmin = null
|
this.vmin = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clean(){
|
clean() {
|
||||||
loader.screen.removeChild(this.style)
|
loader.screen.removeChild(this.style)
|
||||||
fuzzysort.cleanup()
|
fuzzysort.cleanup()
|
||||||
delete this.container
|
delete this.container
|
||||||
|
|||||||
@@ -3335,9 +3335,14 @@ class SongSelect {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
toLeaderboard() {
|
toLeaderboard() {
|
||||||
if (!this.songs[this.selectedSong].id) {
|
var song = this.songs[this.selectedSong]
|
||||||
|
var songId = song.id || song.hash
|
||||||
|
|
||||||
|
// Allow leaderboard for any song with an ID (numeric or hash)
|
||||||
|
if (!songId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to first available difficulty if not in a valid difficulty selection
|
// Default to first available difficulty if not in a valid difficulty selection
|
||||||
var selectedDiff = this.selectedDiff - this.diffOptions.length
|
var selectedDiff = this.selectedDiff - this.diffOptions.length
|
||||||
if (selectedDiff < 0) {
|
if (selectedDiff < 0) {
|
||||||
@@ -3347,6 +3352,7 @@ class SongSelect {
|
|||||||
|
|
||||||
this.clean()
|
this.clean()
|
||||||
this.playSound("se_don")
|
this.playSound("se_don")
|
||||||
new Leaderboard().display(this.songs[this.selectedSong].id, diffId)
|
new Leaderboard().display(songId, diffId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
133
reproduce_issue.py
Normal file
133
reproduce_issue.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:34801"
|
||||||
|
|
||||||
|
def register_user(username, password):
|
||||||
|
url = f"{BASE_URL}/api/register"
|
||||||
|
data = {"username": username, "password": password}
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
print(f"Register failed: {response.text}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Register error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def login_user(username, password):
|
||||||
|
url = f"{BASE_URL}/api/login"
|
||||||
|
data = {"username": username, "password": password}
|
||||||
|
session = requests.Session()
|
||||||
|
try:
|
||||||
|
response = session.post(url, json=data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return session
|
||||||
|
print(f"Login failed: {response.text}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Login error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_csrf_token(session):
|
||||||
|
url = f"{BASE_URL}/api/csrftoken"
|
||||||
|
response = session.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()['token']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def submit_score(session, song_id, difficulty, score_obj, csrf_token):
|
||||||
|
url = f"{BASE_URL}/api/leaderboard/submit"
|
||||||
|
headers = {"X-CSRFToken": csrf_token}
|
||||||
|
data = {
|
||||||
|
"song_id": song_id,
|
||||||
|
"difficulty": difficulty,
|
||||||
|
"score": score_obj
|
||||||
|
}
|
||||||
|
response = session.post(url, json=data, headers=headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def get_leaderboard(session, song_id, difficulty):
|
||||||
|
url = f"{BASE_URL}/api/leaderboard/get"
|
||||||
|
params = {"song_id": song_id, "difficulty": difficulty}
|
||||||
|
response = session.get(url, params=params)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
username = "debug_user_1"
|
||||||
|
password = "password123"
|
||||||
|
|
||||||
|
print("1. Registering/Logging in...")
|
||||||
|
# Try login first
|
||||||
|
session = login_user(username, password)
|
||||||
|
if not session:
|
||||||
|
# Register
|
||||||
|
reg = register_user(username, password)
|
||||||
|
if reg:
|
||||||
|
session = login_user(username, password)
|
||||||
|
else:
|
||||||
|
print("Could not register or login")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("2. Getting CSRF token...")
|
||||||
|
csrf_token = get_csrf_token(session)
|
||||||
|
if not csrf_token:
|
||||||
|
print("Could not get CSRF token")
|
||||||
|
return
|
||||||
|
|
||||||
|
song_id = 1 # Assuming song 1 exists
|
||||||
|
difficulty = "oni"
|
||||||
|
|
||||||
|
# Simulate scoresheet.js submission (points only)
|
||||||
|
score_obj = {
|
||||||
|
"points": 123456,
|
||||||
|
"good": 100,
|
||||||
|
"ok": 10,
|
||||||
|
"bad": 0,
|
||||||
|
"maxCombo": 110,
|
||||||
|
"drumroll": 5,
|
||||||
|
"crown": "gold"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"3. Submitting score: {score_obj['points']}")
|
||||||
|
submit_res = submit_score(session, song_id, difficulty, score_obj, csrf_token)
|
||||||
|
print(f"Submit response: {submit_res}")
|
||||||
|
|
||||||
|
print("4. Fetching leaderboard...")
|
||||||
|
leaderboard_res = get_leaderboard(session, song_id, difficulty)
|
||||||
|
|
||||||
|
if leaderboard_res['status'] == 'ok':
|
||||||
|
leaderboard = leaderboard_res['leaderboard']
|
||||||
|
found = False
|
||||||
|
for entry in leaderboard:
|
||||||
|
if entry['username'] == username:
|
||||||
|
found = True
|
||||||
|
print(f"Found entry: {entry}")
|
||||||
|
print(f"entry.score_value: {entry.get('score_value')}")
|
||||||
|
print(f"entry.score: {entry.get('score')}")
|
||||||
|
|
||||||
|
score_value = entry.get('score_value')
|
||||||
|
score_data = entry.get('score')
|
||||||
|
|
||||||
|
# Check what leaderboard.js would see
|
||||||
|
if score_data and 'score' in score_data:
|
||||||
|
print(f"JS would assume score: {score_data['score']}")
|
||||||
|
else:
|
||||||
|
print("JS would assume score: 0 (undefined)")
|
||||||
|
|
||||||
|
if score_value == 123456:
|
||||||
|
print("SUCCESS: score_value is correct.")
|
||||||
|
else:
|
||||||
|
print(f"FAILURE: score_value is incorrect (expected 123456, got {score_value})")
|
||||||
|
|
||||||
|
if score_data and score_data.get('points') == 123456:
|
||||||
|
print("SUCCESS: points preserved in score object.")
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print("Entry not found in leaderboard (maybe not top 50?)")
|
||||||
|
else:
|
||||||
|
print(f"Failed to get leaderboard: {leaderboard_res}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -85,9 +85,10 @@ leaderboard_submit = {
|
|||||||
'$schema': 'http://json-schema.org/schema#',
|
'$schema': 'http://json-schema.org/schema#',
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'song_id': {'type': 'number'},
|
'song_id': {'type': ['number', 'string']},
|
||||||
'difficulty': {'type': 'string'},
|
'difficulty': {'type': 'string'},
|
||||||
'score': {'type': 'object'}
|
'score': {'type': 'object'}
|
||||||
},
|
},
|
||||||
'required': ['song_id', 'difficulty', 'score']
|
'required': ['song_id', 'difficulty', 'score']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
setup.sh
2
setup.sh
@@ -8,7 +8,7 @@ CODENAME=${VERSION_CODENAME:-}
|
|||||||
VERSION=${VERSION_ID:-}
|
VERSION=${VERSION_ID:-}
|
||||||
|
|
||||||
echo "更新系统软件源..."
|
echo "更新系统软件源..."
|
||||||
apt-get update -y
|
apt-get update -y || true
|
||||||
echo "安装基础依赖..."
|
echo "安装基础依赖..."
|
||||||
apt-get install -y python3 python3-venv python3-pip git ffmpeg rsync curl gnupg libcap2-bin
|
apt-get install -y python3 python3-venv python3-pip git ffmpeg rsync curl gnupg libcap2-bin
|
||||||
|
|
||||||
|
|||||||
32
test_frontend_logic.js
Normal file
32
test_frontend_logic.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
// Simulation of song objects
|
||||||
|
const songs = [
|
||||||
|
{ name: "Server Song", id: 123, hash: "hash123" },
|
||||||
|
{ name: "Custom Song (No ID)", id: undefined, hash: "hashCustom" },
|
||||||
|
{ name: "Custom Song (Null ID)", id: null, hash: "hashNull" },
|
||||||
|
{ name: "Edge Case (ID 0)", id: 0, hash: "hashZero" } // Checking if behavior is acceptable
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log("=== Frontend Logic Test: Song ID Fallback ===");
|
||||||
|
|
||||||
|
songs.forEach(song => {
|
||||||
|
// Logic from scoresheet.js / songselect.js
|
||||||
|
const songId = song.id || song.hash;
|
||||||
|
|
||||||
|
console.log(`Song: ${song.name}`);
|
||||||
|
console.log(` Raw ID: ${song.id}, Hash: ${song.hash}`);
|
||||||
|
console.log(` Resolved songId: ${songId}`);
|
||||||
|
|
||||||
|
if (song.id && songId === song.id) {
|
||||||
|
console.log(" ✅ Correctly used ID");
|
||||||
|
} else if (!song.id && songId === song.hash) {
|
||||||
|
console.log(" ✅ Correctly fell back to Hash");
|
||||||
|
} else if (song.id === 0 && songId === song.hash) {
|
||||||
|
console.log(" ⚠️ ID was 0, used Hash. (Acceptable if ID > 0 always)");
|
||||||
|
} else {
|
||||||
|
console.log(" ❌ FAILED logic mismatch");
|
||||||
|
}
|
||||||
|
console.log("---");
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Tests Completed.");
|
||||||
Reference in New Issue
Block a user