diff --git a/Dockerfile b/Dockerfile
index 182724b..ac35df4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,18 @@
-FROM python:3.13.2
-COPY . /app
+FROM python:3.13.2-slim
+
+# Install dependencies
+RUN apt-get update && apt-get install -y \
+ ffmpeg \
+ git \
+ && rm -rf /var/lib/apt/lists/*
+
WORKDIR /app
-RUN pip install -r requirements.txt
-ENV PYTHONUNBUFFERED 1
-CMD ["gunicorn", "app:app", "--access-logfile", "-", "--bind", "0.0.0.0"]
+COPY requirements.txt /app/
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . /app
+
+ENV PYTHONUNBUFFERED=1
+EXPOSE 80
+
+CMD ["gunicorn", "app:app", "--access-logfile", "-", "--bind", "0.0.0.0:80"]
diff --git a/app.py b/app.py
index 0af8fcc..b99fd94 100644
--- a/app.py
+++ b/app.py
@@ -105,6 +105,9 @@ db.users.create_index('username', unique=True)
db.songs.create_index('id', unique=True)
db.songs.create_index('song_type')
db.scores.create_index('username')
+# Leaderboard indexes
+db.leaderboards.create_index([('song_id', 1), ('difficulty', 1), ('month_key', 1)])
+db.leaderboards.create_index([('song_id', 1), ('difficulty', 1), ('month_key', 1), ('score_value', -1)])
class HashException(Exception):
@@ -746,6 +749,124 @@ def route_api_scores_get():
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
+# Leaderboard helper function
+def get_month_key():
+ from datetime import datetime
+ return datetime.now().strftime('%Y-%m')
+
+
+@app.route(basedir + 'api/leaderboard')
+def route_api_leaderboard():
+ song_id = request.args.get('song_id')
+ difficulty = request.args.get('difficulty')
+ month_key = request.args.get('month_key', get_month_key())
+
+ if not song_id or not difficulty:
+ return abort(400)
+
+ entries = list(db.leaderboards.find(
+ {'song_id': song_id, 'difficulty': difficulty, 'month_key': month_key},
+ {'_id': False}
+ ).sort('score_value', -1).limit(50))
+
+ # Add rank
+ for i, entry in enumerate(entries):
+ entry['rank'] = i + 1
+
+ return jsonify({'status': 'ok', 'entries': entries, 'month_key': month_key})
+
+
+@app.route(basedir + 'api/score/check', methods=['POST'])
+def route_api_score_check():
+ data = request.get_json()
+ if not schema.validate(data, schema.leaderboard_check):
+ return abort(400)
+
+ song_id = str(data.get('song_id'))
+ difficulty = data.get('difficulty')
+ score = int(data.get('score'))
+ month_key = get_month_key()
+
+ # Get top 50 for this song/difficulty/month
+ entries = list(db.leaderboards.find(
+ {'song_id': song_id, 'difficulty': difficulty, 'month_key': month_key}
+ ).sort('score_value', -1).limit(50))
+
+ # Check if qualifies
+ if len(entries) < 50:
+ rank = len(entries) + 1
+ for i, entry in enumerate(entries):
+ if score > entry['score_value']:
+ rank = i + 1
+ break
+ return jsonify({'status': 'ok', 'qualifies': True, 'rank': rank})
+
+ # Check if score beats the 50th entry
+ if score > entries[-1]['score_value']:
+ rank = 50
+ for i, entry in enumerate(entries):
+ if score > entry['score_value']:
+ rank = i + 1
+ break
+ return jsonify({'status': 'ok', 'qualifies': True, 'rank': rank})
+
+ return jsonify({'status': 'ok', 'qualifies': False})
+
+
+@app.route(basedir + 'api/score/leaderboard/save', methods=['POST'])
+def route_api_score_leaderboard_save():
+ data = request.get_json()
+ if not schema.validate(data, schema.leaderboard_save):
+ return abort(400)
+
+ song_id = str(data.get('song_id'))
+ difficulty = data.get('difficulty')
+ username = data.get('username', '').strip()[:10]
+ score = int(data.get('score'))
+ month_key = get_month_key()
+
+ if len(username) < 1:
+ return api_error('invalid_username')
+
+ timestamp = int(time.time() * 1000)
+
+ # Check existing entry for this user/song/difficulty/month
+ existing = db.leaderboards.find_one({
+ 'song_id': song_id,
+ 'difficulty': difficulty,
+ 'month_key': month_key,
+ 'username': username
+ })
+
+ if existing:
+ # Only update if new score is higher
+ if score > existing['score_value']:
+ db.leaderboards.update_one(
+ {'_id': existing['_id']},
+ {'$set': {'score_value': score, 'timestamp': timestamp}}
+ )
+ else:
+ # Insert new entry
+ db.leaderboards.insert_one({
+ 'song_id': song_id,
+ 'difficulty': difficulty,
+ 'month_key': month_key,
+ 'username': username,
+ 'score_value': score,
+ 'timestamp': timestamp
+ })
+
+ # Trim to top 50
+ entries = list(db.leaderboards.find(
+ {'song_id': song_id, 'difficulty': difficulty, 'month_key': month_key}
+ ).sort('score_value', -1).skip(50))
+
+ for entry in entries:
+ db.leaderboards.delete_one({'_id': entry['_id']})
+
+ return jsonify({'status': 'ok'})
+
+
@app.route(basedir + 'privacy')
def route_api_privacy():
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
diff --git a/public/src/css/leaderboard.css b/public/src/css/leaderboard.css
new file mode 100644
index 0000000..b29ea22
--- /dev/null
+++ b/public/src/css/leaderboard.css
@@ -0,0 +1,266 @@
+/* Leaderboard Overlay */
+#leaderboard-overlay,
+#leaderboard-submit-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+/* Leaderboard Modal */
+#leaderboard-modal {
+ background: linear-gradient(180deg, #ff9f18 0%, #ff6b00 100%);
+ border: 4px solid #000;
+ border-radius: 15px;
+ width: 90%;
+ max-width: 500px;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
+}
+
+#leaderboard-header {
+ background: linear-gradient(180deg, #ff4500 0%, #d63000 100%);
+ border-radius: 11px 11px 0 0;
+ padding: 15px 20px;
+ text-align: center;
+ border-bottom: 3px solid #000;
+}
+
+#leaderboard-title {
+ font-size: 1.5em;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
+}
+
+#leaderboard-song-title {
+ font-size: 1.1em;
+ color: #fff;
+ margin-top: 5px;
+ text-shadow: 1px 1px 0 #000;
+}
+
+#leaderboard-difficulty {
+ font-size: 0.9em;
+ color: #ffe100;
+ margin-top: 3px;
+ text-shadow: 1px 1px 0 #000;
+}
+
+#leaderboard-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 10px;
+ background: #fff8e8;
+}
+
+#leaderboard-loading,
+#leaderboard-empty,
+#leaderboard-error {
+ text-align: center;
+ padding: 40px 20px;
+ font-size: 1.2em;
+ color: #666;
+}
+
+#leaderboard-error {
+ color: #c00;
+}
+
+/* Leaderboard Table */
+#leaderboard-table {
+ width: 100%;
+}
+
+.leaderboard-row {
+ display: flex;
+ padding: 8px 10px;
+ border-bottom: 1px solid #ddd;
+ align-items: center;
+}
+
+.leaderboard-header-row {
+ background: #ff9f18;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 1px 1px 0 #000;
+ border-radius: 5px;
+ margin-bottom: 5px;
+}
+
+.leaderboard-row:not(.leaderboard-header-row):hover {
+ background: #ffe8c8;
+}
+
+.leaderboard-rank {
+ width: 50px;
+ text-align: center;
+ font-weight: bold;
+}
+
+.leaderboard-name {
+ flex: 1;
+ padding: 0 10px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.leaderboard-score {
+ width: 100px;
+ text-align: right;
+ font-weight: bold;
+ font-family: monospace;
+}
+
+/* Top 3 ranks styling */
+.leaderboard-rank-1 {
+ background: linear-gradient(90deg, #ffd700 0%, #fff8e8 30%);
+}
+.leaderboard-rank-1 .leaderboard-rank {
+ color: #d4a500;
+ font-size: 1.2em;
+}
+
+.leaderboard-rank-2 {
+ background: linear-gradient(90deg, #c0c0c0 0%, #fff8e8 30%);
+}
+.leaderboard-rank-2 .leaderboard-rank {
+ color: #888;
+ font-size: 1.1em;
+}
+
+.leaderboard-rank-3 {
+ background: linear-gradient(90deg, #cd7f32 0%, #fff8e8 30%);
+}
+.leaderboard-rank-3 .leaderboard-rank {
+ color: #8b4513;
+ font-size: 1.05em;
+}
+
+#leaderboard-footer {
+ padding: 15px;
+ text-align: center;
+ border-top: 2px solid #000;
+}
+
+#leaderboard-close-btn {
+ background: linear-gradient(180deg, #666 0%, #444 100%);
+ color: #fff;
+ border: 2px solid #000;
+ border-radius: 8px;
+ padding: 10px 30px;
+ font-size: 1.1em;
+ font-weight: bold;
+ cursor: pointer;
+ transition: transform 0.1s;
+}
+
+#leaderboard-close-btn:hover {
+ transform: scale(1.05);
+ background: linear-gradient(180deg, #888 0%, #555 100%);
+}
+
+/* Submit Modal */
+#leaderboard-submit-modal {
+ background: linear-gradient(180deg, #4CAF50 0%, #388E3C 100%);
+ border: 4px solid #000;
+ border-radius: 15px;
+ padding: 25px;
+ text-align: center;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
+ max-width: 400px;
+ width: 90%;
+}
+
+#leaderboard-submit-title {
+ font-size: 1.5em;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 2px 2px 0 #000;
+ margin-bottom: 10px;
+}
+
+#leaderboard-submit-score {
+ font-size: 1.8em;
+ font-weight: bold;
+ color: #ffe100;
+ text-shadow: 2px 2px 0 #000;
+ margin-bottom: 20px;
+}
+
+#leaderboard-submit-form {
+ margin-bottom: 20px;
+}
+
+#leaderboard-submit-form label {
+ display: block;
+ color: #fff;
+ margin-bottom: 10px;
+ text-shadow: 1px 1px 0 #000;
+}
+
+#leaderboard-name-input {
+ width: 100%;
+ padding: 12px;
+ font-size: 1.2em;
+ border: 3px solid #000;
+ border-radius: 8px;
+ text-align: center;
+ box-sizing: border-box;
+}
+
+#leaderboard-name-input.error {
+ border-color: #f00;
+ background: #ffe8e8;
+}
+
+#leaderboard-submit-buttons {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+}
+
+#leaderboard-submit-btn,
+#leaderboard-cancel-btn {
+ padding: 12px 25px;
+ font-size: 1.1em;
+ font-weight: bold;
+ border: 2px solid #000;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: transform 0.1s;
+}
+
+#leaderboard-submit-btn {
+ background: linear-gradient(180deg, #ff9f18 0%, #ff6b00 100%);
+ color: #fff;
+ text-shadow: 1px 1px 0 #000;
+}
+
+#leaderboard-submit-btn:hover:not(:disabled) {
+ transform: scale(1.05);
+}
+
+#leaderboard-submit-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+#leaderboard-cancel-btn {
+ background: linear-gradient(180deg, #666 0%, #444 100%);
+ color: #fff;
+}
+
+#leaderboard-cancel-btn:hover {
+ transform: scale(1.05);
+ background: linear-gradient(180deg, #888 0%, #555 100%);
+}
diff --git a/public/src/js/assets.js b/public/src/js/assets.js
index 24d4232..cf402c6 100644
--- a/public/src/js/assets.js
+++ b/public/src/js/assets.js
@@ -39,7 +39,8 @@ var assets = {
"abstractfile.js",
"idb.js",
"plugins.js",
- "search.js"
+ "search.js",
+ "leaderboard.js"
],
"css": [
"main.css",
@@ -49,7 +50,8 @@ var assets = {
"debug.css",
"songbg.css",
"view.css",
- "search.css"
+ "search.css",
+ "leaderboard.css"
],
"img": [
"notes.png",
@@ -98,7 +100,7 @@ var assets = {
"audioSfx": [
"se_pause.ogg",
"se_calibration.ogg",
-
+
"v_results.ogg",
"v_sanka.ogg",
"v_songsel.ogg",
@@ -112,14 +114,14 @@ var assets = {
"se_don.ogg",
"se_ka.ogg",
"se_jump.ogg",
-
+
"se_balloon.ogg",
"se_gameclear.ogg",
"se_gamefail.ogg",
"se_gamefullcombo.ogg",
"se_results_countup.ogg",
"se_results_crown.ogg",
-
+
"v_fullcombo.ogg",
"v_renda.ogg",
"v_results_fullcombo.ogg",
@@ -153,7 +155,7 @@ var assets = {
"customsongs.html",
"search.html"
],
-
+
"songs": [],
"sounds": {},
"image": {},
diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js
index 6d822cb..8a83e3a 100644
--- a/public/src/js/canvasdraw.js
+++ b/public/src/js/canvasdraw.js
@@ -1,22 +1,22 @@
-class CanvasDraw{
- constructor(...args){
+class CanvasDraw {
+ constructor(...args) {
this.init(...args)
}
- init(noSmoothing){
+ init(noSmoothing) {
this.diffStarPath = new Path2D(vectors.diffStar)
this.longVowelMark = new Path2D(vectors.longVowelMark)
-
- this.diffIconPath = [[{w: 40, h: 33}, {
+
+ this.diffIconPath = [[{ w: 40, h: 33 }, {
fill: "#ff2803",
d: new Path2D(vectors.diffEasy1)
}, {
fill: "#ffb910",
noStroke: true,
d: new Path2D(vectors.diffEasy2)
- }], [{w: 48, h: 31}, {
+ }], [{ w: 48, h: 31 }, {
fill: "#8daf51",
d: new Path2D(vectors.diffNormal)
- }], [{w: 56, h: 37}, {
+ }], [{ w: 56, h: 37 }, {
fill: "#784439",
d: new Path2D(vectors.diffHard1)
}, {
@@ -26,34 +26,34 @@
}, {
fill: "#414b2b",
d: new Path2D(vectors.diffHard3)
- }], [{w: 29, h: 27}, {
+ }], [{ w: 29, h: 27 }, {
fill: "#db1885",
d: new Path2D(vectors.diffOni1)
}, {
fill: "#fff",
d: new Path2D(vectors.diffOni2)
}]]
-
+
this.diffPath = {
good: new Path2D(vectors.good),
ok: new Path2D(vectors.ok),
bad: new Path2D(vectors.bad)
}
-
+
this.crownPath = new Path2D(vectors.crown)
this.soulPath = new Path2D(vectors.soul)
-
+
this.optionsPath = {
main: new Path2D(vectors.options),
shadow: new Path2D(vectors.optionsShadow)
}
-
+
this.categoryPath = {
main: new Path2D(vectors.category),
shadow: new Path2D(vectors.categoryShadow),
highlight: new Path2D(vectors.categoryHighlight)
}
-
+
this.regex = {
comma: /[,.]/,
ideographicComma: /[、。]/,
@@ -80,26 +80,26 @@
ura: /\s*[\((]裏[\))]$/,
cjk: /[\u3040-ゞ゠-ヾ一-\u9ffe]/
}
-
+
var numbersFull = "0123456789"
var numbersHalf = "0123456789"
this.numbersFullToHalf = {}
- for(var i = 0; i < 10; i++){
+ for (var i = 0; i < 10; i++) {
this.numbersFullToHalf[numbersFull[i]] = numbersHalf[i]
this.numbersFullToHalf[numbersHalf[i]] = numbersHalf[i]
}
this.wrapOn = [" ", "\n", "%s"]
this.stickySymbols = "!,.:;?~‐–‼、。々〜ぁぃぅぇぉっゃゅょァィゥェォッャュョ・ーヽヾ!:;?"
-
+
this.songFrameCache = new CanvasCache(noSmoothing)
this.diffStarCache = new CanvasCache(noSmoothing)
this.crownCache = new CanvasCache(noSmoothing)
-
+
this.tmpCanvas = document.createElement("canvas")
this.tmpCtx = this.tmpCanvas.getContext("2d")
}
-
- roundedRect(config){
+
+ roundedRect(config) {
var ctx = config.ctx
var x = config.x
var y = config.y
@@ -113,10 +113,10 @@
this.roundedCorner(ctx, x, y + h, r, 3)
ctx.closePath()
}
-
- roundedCorner(ctx, x, y, r, rotation){
+
+ roundedCorner(ctx, x, y, r, rotation) {
var pi = Math.PI
- switch(rotation){
+ switch (rotation) {
case 0:
return ctx.arc(x + r, y + r, r, pi, pi / -2)
case 1:
@@ -127,8 +127,8 @@
return ctx.arc(x + r, y - r, r, pi / 2, pi)
}
}
-
- songFrame(config){
+
+ songFrame(config) {
var ctx = config.ctx
var x = config.x
var y = config.y
@@ -141,9 +141,9 @@
var innerY = y + allBorders
var innerW = w - allBorders * 2
var innerH = h - allBorders * 2
-
+
ctx.save()
-
+
var shadowBg = (ctx, noForce) => {
this.shadow({
ctx: ctx,
@@ -156,8 +156,8 @@
ctx.fillStyle = "rgba(0,0,0,.2)"
ctx.fillRect(0, 0, w, h)
}
- if(config.cached){
- if(this.songFrameCache.w !== config.frameCache.w || this.songFrameCache.scale !== config.frameCache.ratio){
+ if (config.cached) {
+ if (this.songFrameCache.w !== config.frameCache.w || this.songFrameCache.scale !== config.frameCache.ratio) {
this.songFrameCache.resize(config.frameCache.w, config.frameCache.h, config.frameCache.ratio)
}
this.songFrameCache.get({
@@ -168,14 +168,14 @@
h: h + 15,
id: "shadow" + config.cached
}, shadowBg)
- }else{
+ } else {
ctx.translate(x, y)
shadowBg(ctx, true)
}
-
+
ctx.restore()
ctx.save()
-
+
{
let _x = x + border
let _y = y + border
@@ -194,28 +194,28 @@
}
ctx.fillStyle = config.background
ctx.fillRect(innerX, innerY, innerW, innerH)
-
+
ctx.save()
-
+
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)"
ctx.lineWidth = 3
ctx.strokeRect(innerX, innerY, innerW, innerH)
- if(!config.noCrop){
+ if (!config.noCrop) {
ctx.beginPath()
ctx.rect(innerX, innerY, innerW, innerH)
ctx.clip()
}
-
+
config.innerContent(innerX, innerY, innerW, innerH)
-
+
ctx.restore()
-
- if(config.disabled){
+
+ if (config.disabled) {
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
ctx.fillRect(x, y, w, h)
}
-
- if(config.highlight){
+
+ if (config.highlight) {
this.highlight({
ctx: ctx,
x: x,
@@ -227,27 +227,27 @@
opacity: config.highlight === 1 ? 0.8 : 1
})
}
-
+
ctx.restore()
}
-
- highlight(config){
+
+ highlight(config) {
var ctx = config.ctx
ctx.save()
- if(config.shape){
+ if (config.shape) {
ctx.translate(config.x, config.y)
- }else{
+ } else {
var _x = config.x + 3.5
var _y = config.y + 3.5
var _w = config.w - 7
var _h = config.h - 7
}
- if(config.animate){
+ if (config.animate) {
ctx.globalAlpha = this.fade((this.getMS() - config.animateMS) % 2000 / 2000)
- }else if(config.opacity){
+ } else if (config.opacity) {
ctx.globalAlpha = config.opacity
}
- if(config.radius){
+ if (config.radius) {
this.roundedRect({
ctx: ctx,
x: _x,
@@ -256,13 +256,13 @@
h: _h,
radius: config.radius
})
- }else if(!config.shape){
+ } else if (!config.shape) {
ctx.beginPath()
ctx.rect(_x, _y, _w, _h)
}
- if(config.shape){
+ if (config.shape) {
var stroke = () => ctx.stroke(config.shape)
- }else{
+ } else {
var stroke = () => ctx.stroke()
}
var size = config.size || 14
@@ -275,154 +275,154 @@
ctx.strokeStyle = "#fff"
ctx.lineWidth = 6 / 14 * size
stroke()
-
+
ctx.restore()
}
- fade(pos){
- if(pos < 0.5){
+ fade(pos) {
+ if (pos < 0.5) {
pos = 1 - pos
}
return (1 - Math.cos(Math.PI * pos * 2)) / 2
}
- easeIn(pos){
+ easeIn(pos) {
return 1 - Math.cos(Math.PI / 2 * pos)
}
- easeOut(pos){
+ easeOut(pos) {
return Math.sin(Math.PI / 2 * pos)
}
- easeOutBack(pos){
+ easeOutBack(pos) {
return Math.sin(Math.PI / 1.74 * pos) * 1.03
}
- easeInOut(pos){
+ easeInOut(pos) {
return (Math.cos(Math.PI * pos) - 1) / -2
}
-
- verticalText(config){
+
+ verticalText(config) {
var ctx = config.ctx
var inputText = "" + config.text
var mul = config.fontSize / 40
var ura = false
var r = this.regex
-
+
var matches = inputText.match(r.ura)
- if(matches){
+ if (matches) {
inputText = inputText.slice(0, matches.index)
ura = matches[0]
}
var bold = this.bold(config.fontFamily)
-
+
var string = inputText.split("")
var drawn = []
var quoteOpened = false
-
- for(var i = 0; i < string.length; i++){
+
+ for (var i = 0; i < string.length; i++) {
let symbol = string[i]
- if(symbol === " "){
+ if (symbol === " ") {
// Space
- drawn.push({text: symbol, x: 0, y: 0, h: 18})
- }else if(symbol === "ー"){
+ drawn.push({ text: symbol, x: 0, y: 0, h: 18 })
+ } else if (symbol === "ー") {
// Long-vowel mark
- if(bold){
- drawn.push({text: symbol, x: -1, y: -1, h: 33, rotate: true})
- }else{
- drawn.push({realText: symbol, svg: this.longVowelMark, x: -4, y: 5, h: 33, scale: [mul, mul]})
+ if (bold) {
+ drawn.push({ text: symbol, x: -1, y: -1, h: 33, rotate: true })
+ } else {
+ drawn.push({ realText: symbol, svg: this.longVowelMark, x: -4, y: 5, h: 33, scale: [mul, mul] })
}
- }else if(symbol === "∀"){
- drawn.push({text: symbol, x: 0, y: 0, h: 39, rotate: true})
- }else if(symbol === "↓"){
- drawn.push({text: symbol, x: 0, y: 12, h: 45})
- }else if(symbol === "."){
- if(bold){
- drawn.push({realText: symbol, text: ".", x: 13, y: -15, h: 15})
- }else{
- drawn.push({realText: symbol, text: ".", x: 13, y: -7, h: 15, scale: [1.2, 0.7]})
+ } else if (symbol === "∀") {
+ drawn.push({ text: symbol, x: 0, y: 0, h: 39, rotate: true })
+ } else if (symbol === "↓") {
+ drawn.push({ text: symbol, x: 0, y: 12, h: 45 })
+ } else if (symbol === ".") {
+ if (bold) {
+ drawn.push({ realText: symbol, text: ".", x: 13, y: -15, h: 15 })
+ } else {
+ drawn.push({ realText: symbol, text: ".", x: 13, y: -7, h: 15, scale: [1.2, 0.7] })
}
- }else if(symbol === "…"){
- drawn.push({text: symbol, x: bold ? 9 : 0, y: 5, h: 25, rotate: true})
- }else if(symbol === '"'){
- if(quoteOpened){
- drawn.push({realText: symbol, text: "“", x: -25, y: 10, h: 20})
- }else{
- drawn.push({realText: symbol, text: "”", x: 12, y: 15, h: 20})
+ } else if (symbol === "…") {
+ drawn.push({ text: symbol, x: bold ? 9 : 0, y: 5, h: 25, rotate: true })
+ } else if (symbol === '"') {
+ if (quoteOpened) {
+ drawn.push({ realText: symbol, text: "“", x: -25, y: 10, h: 20 })
+ } else {
+ drawn.push({ realText: symbol, text: "”", x: 12, y: 15, h: 20 })
}
quoteOpened = !quoteOpened
- }else if(r.comma.test(symbol)){
+ } else if (r.comma.test(symbol)) {
// Comma, full stop
- if(bold){
- drawn.push({text: symbol, x: 13, y: -15, h: 15})
- }else{
- drawn.push({text: symbol, x: 13, y: -7, h: 15, scale: [1.2, 0.7]})
+ if (bold) {
+ drawn.push({ text: symbol, x: 13, y: -15, h: 15 })
+ } else {
+ drawn.push({ text: symbol, x: 13, y: -7, h: 15, scale: [1.2, 0.7] })
}
- }else if(r.ideographicComma.test(symbol)){
+ } else if (r.ideographicComma.test(symbol)) {
// Ideographic comma, full stop
- drawn.push({text: symbol, x: 16, y: -16, h: 18})
- }else if(r.apostrophe.test(symbol)){
+ drawn.push({ text: symbol, x: 16, y: -16, h: 18 })
+ } else if (r.apostrophe.test(symbol)) {
// Apostrophe
- if(bold){
- drawn.push({text: symbol, x: 20, y: -25, h: 0})
- }else{
- drawn.push({realText: symbol, text: ",", x: 20, y: -39, h: 0, scale: [1.2, 0.7]})
+ if (bold) {
+ drawn.push({ text: symbol, x: 20, y: -25, h: 0 })
+ } else {
+ drawn.push({ realText: symbol, text: ",", x: 20, y: -39, h: 0, scale: [1.2, 0.7] })
}
- }else if(r.degree.test(symbol)){
+ } else if (r.degree.test(symbol)) {
// Degree
- if(bold){
- drawn.push({text: symbol, x: 16, y: 9, h: 25})
- }else{
- drawn.push({text: symbol, x: 16, y: 3, h: 18})
+ if (bold) {
+ drawn.push({ text: symbol, x: 16, y: 9, h: 25 })
+ } else {
+ drawn.push({ text: symbol, x: 16, y: 3, h: 18 })
}
- }else if(r.brackets.test(symbol)){
+ } else if (r.brackets.test(symbol)) {
// Rotated brackets
- if(bold){
- drawn.push({text: symbol, x: 0, y: 0, h: 35, rotate: true})
- }else{
- drawn.push({text: symbol, x: 0, y: -5, h: 25, rotate: true})
+ if (bold) {
+ drawn.push({ text: symbol, x: 0, y: 0, h: 35, rotate: true })
+ } else {
+ drawn.push({ text: symbol, x: 0, y: -5, h: 25, rotate: true })
}
- }else if(r.tilde.test(symbol)){
+ } else if (r.tilde.test(symbol)) {
// Rotated hyphen, tilde
- drawn.push({realText: symbol, text: symbol === "~" ? "~" : symbol, x: 0, y: 2, h: 35, rotate: true})
- }else if(r.tall.test(symbol)){
+ drawn.push({ realText: symbol, text: symbol === "~" ? "~" : symbol, x: 0, y: 2, h: 35, rotate: true })
+ } else if (r.tall.test(symbol)) {
// Tall latin script lowercase
- drawn.push({text: symbol, x: 0, y: 4, h: 34})
- }else if(r.i.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 4, h: 34 })
+ } else if (r.i.test(symbol)) {
// Lowercase i
- drawn.push({text: symbol, x: 0, y: 7, h: 34})
- }else if(r.uppercase.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 7, h: 34 })
+ } else if (r.uppercase.test(symbol)) {
// Latin script upper case
- drawn.push({text: symbol, x: 0, y: 8, h: 37})
- }else if(r.lowercase.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 8, h: 37 })
+ } else if (r.lowercase.test(symbol)) {
// Latin script lower case
- drawn.push({text: symbol, x: 0, y: -1, h: 28})
- }else if(r.numbers.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: -1, h: 28 })
+ } else if (r.numbers.test(symbol)) {
// Numbers
var number = this.numbersFullToHalf[symbol]
- drawn.push({realText: symbol, text: number, x: 0, y: 4, h: 34})
- }else if(r.exclamation.test(symbol)){
+ drawn.push({ realText: symbol, text: number, x: 0, y: 4, h: 34 })
+ } else if (r.exclamation.test(symbol)) {
// Exclamation mark
var toDraw = [symbol]
- for(var repeat = 1; repeat - 1 < i; repeat++){
- if(!r.exclamation.test(string[i - repeat])){
+ for (var repeat = 1; repeat - 1 < i; repeat++) {
+ if (!r.exclamation.test(string[i - repeat])) {
break
}
toDraw.push(string[i - repeat])
}
- if(repeat > 1){
+ if (repeat > 1) {
drawn.splice(i - repeat + 1, repeat)
var allExclamations = !toDraw.find(a => a !== "!")
-
- for(var j = 1; j < repeat + 1; j++){
+
+ for (var j = 1; j < repeat + 1; j++) {
var text = string[i - repeat + j]
- if(allExclamations){
+ if (allExclamations) {
var y = 18
var h = 61
- }else{
+ } else {
var y = 8
var h = 37
}
- if(i === repeat - 1){
+ if (i === repeat - 1) {
h -= y - 4
y = 4
}
-
+
var addX = bold && (text === "!" || text === "?") ? 10 : 0
drawn.push({
text: text,
@@ -431,136 +431,136 @@
h: j === 1 ? h : 0
})
}
- }else{
+ } else {
var addX = bold && (symbol === "!" || symbol === "?") ? 10 : 0
- drawn.push({text: symbol, x: addX, y: 8, h: 37})
+ drawn.push({ text: symbol, x: addX, y: 8, h: 37 })
}
- }else if(r.smallHiragana.test(symbol)){
+ } else if (r.smallHiragana.test(symbol)) {
// Small hiragana, small katakana
- drawn.push({text: symbol, x: 0, y: -8, h: 25, right: true})
- }else if(r.hiragana.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: -8, h: 25, right: true })
+ } else if (r.hiragana.test(symbol)) {
// Hiragana, katakana
- drawn.push({text: symbol, x: 0, y: 5, h: 38, right: r.todo.test(symbol)})
- }else{
+ drawn.push({ text: symbol, x: 0, y: 5, h: 38, right: r.todo.test(symbol) })
+ } else {
// Kanji, other
- drawn.push({text: symbol, x: 0, y: 3, h: 39})
+ drawn.push({ text: symbol, x: 0, y: 3, h: 39 })
}
}
-
+
var drawnHeight = 0
- for(let symbol of drawn){
- if(config.letterSpacing){
+ for (let symbol of drawn) {
+ if (config.letterSpacing) {
symbol.h += config.letterSpacing
}
drawnHeight += symbol.h * mul
}
-
+
ctx.save()
ctx.translate(config.x, config.y)
-
- if(config.selectable){
+
+ if (config.selectable) {
config.selectable.innerHTML = ""
var scale = config.selectableScale
var style = config.selectable.style
style.left = ((config.x - config.width / 2) * scale + (config.selectableX || 0)) + "px"
style.top = (config.y * scale + (config.selectableY || 0)) + "px"
style.width = config.width * scale + "px"
- style.height = (drawnHeight+15) * scale + "px"
+ style.height = (drawnHeight + 15) * scale + "px"
style.fontSize = 40 * mul * scale + "px"
style.transform = ""
}
-
+
var scaling = 1
var strokeScaling = 1
var height = config.height - (ura ? 52 * mul : 0)
- if(height && drawnHeight > height){
+ if (height && drawnHeight > height) {
scaling = height / drawnHeight
- if(config.align === "bottom"){
+ if (config.align === "bottom") {
strokeScaling = Math.max(0.6, height / drawnHeight)
ctx.translate(40 * mul, 0)
ctx.scale(strokeScaling, scaling)
ctx.translate(-40 * mul, 0)
- }else{
+ } else {
strokeScaling = scaling
ctx.scale(1, scaling)
}
- if(config.selectable){
+ if (config.selectable) {
style.transform = "scale(1, " + scaling + ")"
style.top = (config.y + (height - drawnHeight) / 2 - 15 / 2 * scaling) * scale + "px"
}
}
-
- if(ura){
+
+ if (ura) {
// Circled ura
- drawn.push({realText: ura, text: "裏", x: 0, y: 25, h: 52, ura: true, scale: [1, 1 / scaling]})
+ drawn.push({ realText: ura, text: "裏", x: 0, y: 25, h: 52, ura: true, scale: [1, 1 / scaling] })
}
-
- if(config.align === "bottom"){
+
+ if (config.align === "bottom") {
drawn.reverse()
}
-
+
var actions = []
- if(config.outline){
+ if (config.outline) {
actions.push("stroke")
}
- if(config.fill){
+ if (config.fill) {
actions.push("fill")
}
- if(config.selectable){
+ if (config.selectable) {
actions.push("selectable")
}
- for(let action of actions){
+ for (let action of actions) {
ctx.font = bold + config.fontSize + "px " + config.fontFamily
ctx.textBaseline = "top"
- if(action === "stroke"){
+ if (action === "stroke") {
ctx.strokeStyle = config.outline
ctx.lineWidth = config.outlineSize * mul
- if(config.align === "bottom"){
+ if (config.align === "bottom") {
ctx.lineWidth /= strokeScaling
}
ctx.lineJoin = "round"
ctx.miterLimit = 1
- }else if(action === "fill"){
+ } else if (action === "fill") {
ctx.fillStyle = config.fill
}
- if(config.align === "bottom"){
+ if (config.align === "bottom") {
var offsetY = drawnHeight > config.height ? drawnHeight : config.height
- }else{
+ } else {
var offsetY = 0
}
-
- for(let symbol of drawn){
+
+ for (let symbol of drawn) {
var saved = false
var currentX = symbol.x
- if(symbol.right){
+ if (symbol.right) {
currentX += 20 * mul
}
var currentY = offsetY + symbol.y * mul
- if(config.align === "bottom"){
+ if (config.align === "bottom") {
currentY -= symbol.h * mul
}
offsetY = offsetY + symbol.h * mul * (config.align === "bottom" ? -1 : 1)
- if(action === "selectable"){
+ if (action === "selectable") {
let div = document.createElement("div")
div.classList.add("stroke-sub")
let text = symbol.realText || symbol.text
let textWidth = ctx.measureText(text).width
let transform = []
- if(symbol.scale){
+ if (symbol.scale) {
transform.push("scale(" + symbol.scale[0] + "," + symbol.scale[1] + ")")
}
- if(symbol.rotate || symbol.realText === "ー"){
+ if (symbol.rotate || symbol.realText === "ー") {
transform.push("rotate(90deg)")
}
- if(transform.length){
+ if (transform.length) {
div.style.transform = transform.join(" ")
}
- if(symbol.right){
+ if (symbol.right) {
currentX = currentX + config.width / 2 - textWidth
- }else{
+ } else {
currentX = currentX + config.width / 2 - textWidth / 2
}
- if(symbol.ura){
+ if (symbol.ura) {
div.style.font = (30 / (40 * mul)) + "em Meiryo, sans-serif"
}
div.style.left = currentX * scale + "px"
@@ -570,135 +570,135 @@
config.selectable.appendChild(div)
continue
}
- if(symbol.rotate || symbol.scale || symbol.svg || symbol.ura){
+ if (symbol.rotate || symbol.scale || symbol.svg || symbol.ura) {
saved = true
ctx.save()
-
- if(symbol.rotate){
+
+ if (symbol.rotate) {
ctx.translate(currentX + 20 * mul, currentY + 20 * mul)
ctx.rotate(Math.PI / 2)
- }else{
+ } else {
ctx.translate(currentX, currentY)
}
- if(symbol.scale){
+ if (symbol.scale) {
ctx.scale(symbol.scale[0], symbol.scale[1])
ctx.lineWidth = ctx.lineWidth / symbol.scale[0]
}
currentX = 0
currentY = 0
}
- if(symbol.svg){
+ if (symbol.svg) {
ctx[action](symbol.svg)
- }else{
- if(symbol.right){
+ } else {
+ if (symbol.right) {
ctx.textAlign = "right"
- }else{
+ } else {
ctx.textAlign = "center"
}
- if(symbol.ura){
+ if (symbol.ura) {
ctx.font = (30 * mul) + "px Meiryo, sans-serif"
ctx.textBaseline = "middle"
ctx.beginPath()
ctx.arc(currentX, currentY + (17 * mul), (18 * mul), 0, Math.PI * 2)
- if(action === "stroke"){
+ if (action === "stroke") {
ctx.fillStyle = config.outline
ctx.fill()
- }else if(action === "fill"){
+ } else if (action === "fill") {
ctx.strokeStyle = config.fill
ctx.lineWidth = 2.5 * mul
ctx.fillText(symbol.text, currentX, currentY + (17 * mul))
}
ctx.stroke()
- }else{
+ } else {
ctx[action + "Text"](symbol.text, currentX, currentY)
}
}
- if(saved){
+ if (saved) {
ctx.restore()
}
}
}
ctx.restore()
}
-
- layeredText(config, layers){
+
+ layeredText(config, layers) {
var ctx = config.ctx
var inputText = "" + config.text
var mul = config.fontSize / 40
var ura = false
var r = this.regex
-
+
var matches = inputText.match(r.ura)
- if(matches){
+ if (matches) {
inputText = inputText.slice(0, matches.index)
ura = matches[0]
}
var bold = this.bold(config.fontFamily)
-
+
var string = inputText.split("")
var drawn = []
-
- for(var i = 0; i < string.length; i++){
+
+ for (var i = 0; i < string.length; i++) {
let symbol = string[i]
-
- if(symbol === "-"){
- drawn.push({text: symbol, x: -2, y: 0, w: 28})
- }else if(symbol === "™"){
- drawn.push({text: symbol, x: -2, y: 0, w: 20, scale: [0.6, 0.5]})
- }else if(symbol === " "){
- drawn.push({text: symbol, x: 0, y: 0, w: 10})
- }else if(symbol === '"'){
- drawn.push({text: symbol, x: 2, y: 0, w: 10})
- }else if(symbol === "∀"){
- if(bold){
- drawn.push({text: symbol, x: 0, y: 0, w: 40})
- }else{
- drawn.push({text: symbol, x: -3, y: 0, w: 55})
+
+ if (symbol === "-") {
+ drawn.push({ text: symbol, x: -2, y: 0, w: 28 })
+ } else if (symbol === "™") {
+ drawn.push({ text: symbol, x: -2, y: 0, w: 20, scale: [0.6, 0.5] })
+ } else if (symbol === " ") {
+ drawn.push({ text: symbol, x: 0, y: 0, w: 10 })
+ } else if (symbol === '"') {
+ drawn.push({ text: symbol, x: 2, y: 0, w: 10 })
+ } else if (symbol === "∀") {
+ if (bold) {
+ drawn.push({ text: symbol, x: 0, y: 0, w: 40 })
+ } else {
+ drawn.push({ text: symbol, x: -3, y: 0, w: 55 })
}
- }else if(symbol === "."){
- drawn.push({text: symbol, x: -9, y: 0, w: 37})
- }else if(r.apostrophe.test(symbol)){
- drawn.push({text: ",", x: 0, y: -15, w: 7, scale: [1, 0.7]})
- }else if(r.comma.test(symbol)){
+ } else if (symbol === ".") {
+ drawn.push({ text: symbol, x: -9, y: 0, w: 37 })
+ } else if (r.apostrophe.test(symbol)) {
+ drawn.push({ text: ",", x: 0, y: -15, w: 7, scale: [1, 0.7] })
+ } else if (r.comma.test(symbol)) {
// Comma, full stop
- if(bold){
- drawn.push({text: symbol, x: -3, y: 0, w: 13})
- }else{
- drawn.push({text: symbol, x: -3, y: 13, w: 13, scale: [1.2, 0.7]})
+ if (bold) {
+ drawn.push({ text: symbol, x: -3, y: 0, w: 13 })
+ } else {
+ drawn.push({ text: symbol, x: -3, y: 13, w: 13, scale: [1.2, 0.7] })
}
- }else if(r.tilde.test(symbol)){
+ } else if (r.tilde.test(symbol)) {
// Hyphen, tilde
- drawn.push({text: symbol === "~" ? "~" : symbol, x: 0, y: 0, w: 39})
- }else if(r.en.test(symbol)){
+ drawn.push({ text: symbol === "~" ? "~" : symbol, x: 0, y: 0, w: 39 })
+ } else if (r.en.test(symbol)) {
// n-width
- drawn.push({text: symbol, x: 0, y: 0, w: 28})
- }else if(r.em.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 0, w: 28 })
+ } else if (r.em.test(symbol)) {
// m-width
- drawn.push({text: symbol, x: 0, y: 0, w: 38})
- }else if(r.rWidth.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 0, w: 38 })
+ } else if (r.rWidth.test(symbol)) {
// r-width
- drawn.push({text: symbol, x: 0, y: 0, w: 24})
- }else if(r.lWidth.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 0, w: 24 })
+ } else if (r.lWidth.test(symbol)) {
// l-width
- drawn.push({text: symbol, x: 0, y: 0, w: 12})
- }else if(r.emCap.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 0, w: 12 })
+ } else if (r.emCap.test(symbol)) {
// m-width uppercase
- drawn.push({text: symbol, x: 0, y: 0, w: 38})
- }else if(r.numbers.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 0, w: 38 })
+ } else if (r.numbers.test(symbol)) {
// Numbers
var number = this.numbersFullToHalf[symbol]
- drawn.push({text: number, x: 0, y: 0, w: 32})
- }else if(r.degree.test(symbol)){
+ drawn.push({ text: number, x: 0, y: 0, w: 32 })
+ } else if (r.degree.test(symbol)) {
// Degree
- if(bold){
- drawn.push({text: symbol, x: 0, y: 0, w: 20})
- }else{
- drawn.push({text: symbol, x: 5, y: 0, w: 0})
+ if (bold) {
+ drawn.push({ text: symbol, x: 0, y: 0, w: 20 })
+ } else {
+ drawn.push({ text: symbol, x: 5, y: 0, w: 0 })
}
- }else if(r.uppercase.test(symbol)){
+ } else if (r.uppercase.test(symbol)) {
// Latin script uppercase
- drawn.push({text: symbol, x: 0, y: 0, w: 32})
- }else if(r.exclamation.test(symbol)){
+ drawn.push({ text: symbol, x: 0, y: 0, w: 32 })
+ } else if (r.exclamation.test(symbol)) {
// Exclamation mark
var nextExclamation = string[i + 1] ? r.exclamation.test(string[i + 1]) : false
drawn.push({
@@ -707,76 +707,76 @@
y: 0,
w: nextExclamation ? 16 : 28
})
- }else if(r.smallHiragana.test(symbol)){
+ } else if (r.smallHiragana.test(symbol)) {
// Small hiragana, small katakana
- drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 30})
- }else if(r.hiragana.test(symbol)){
+ drawn.push({ text: symbol, kana: true, x: 0, y: 0, w: 30 })
+ } else if (r.hiragana.test(symbol)) {
// Hiragana, katakana
- drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 35})
- }else{
- drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 39})
+ drawn.push({ text: symbol, kana: true, x: 0, y: 0, w: 35 })
+ } else {
+ drawn.push({ text: symbol, kana: true, x: 0, y: 0, w: 39 })
}
}
-
+
var drawnWidth = 0
- for(let symbol of drawn){
- if(config.letterSpacing){
+ for (let symbol of drawn) {
+ if (config.letterSpacing) {
symbol.w += config.letterSpacing
}
- if(config.kanaSpacing && symbol.kana){
+ if (config.kanaSpacing && symbol.kana) {
symbol.w += config.kanaSpacing
}
drawnWidth += symbol.w * mul
}
-
+
ctx.save()
ctx.translate(config.x, config.y)
-
- if(config.scale){
+
+ if (config.scale) {
ctx.scale(config.scale[0], config.scale[1])
}
var scaling = 1
var width = config.width - (ura ? 55 * mul : 0)
- if(width && drawnWidth > width){
+ if (width && drawnWidth > width) {
scaling = width / drawnWidth
ctx.scale(scaling, 1)
}
-
- if(ura){
+
+ if (ura) {
// Circled ura
- drawn.push({text: "裏", x: 0, y: 3, w: 55, ura: true, scale: [1 / scaling, 1]})
+ drawn.push({ text: "裏", x: 0, y: 3, w: 55, ura: true, scale: [1 / scaling, 1] })
}
-
- if(config.align === "right"){
+
+ if (config.align === "right") {
drawn.reverse()
}
-
+
ctx.font = bold + config.fontSize + "px " + config.fontFamily
ctx.textBaseline = config.baseline || "top"
ctx.textAlign = "center"
-
- for(let layer of layers){
+
+ for (let layer of layers) {
var savedLayer = false
var action = "strokeText"
- if(layer.scale){
+ if (layer.scale) {
savedLayer = true
ctx.save()
ctx.scale(layer.scale[0], layer.scale[1])
}
- if(layer.outline){
+ if (layer.outline) {
ctx.strokeStyle = layer.outline
ctx.lineJoin = "round"
ctx.miterLimit = 1
}
- if(layer.letterBorder){
+ if (layer.letterBorder) {
ctx.lineWidth = layer.letterBorder
}
- if(layer.fill){
+ if (layer.fill) {
ctx.fillStyle = layer.fill
action = "fillText"
}
- if(layer.shadow){
- if(!savedLayer){
+ if (layer.shadow) {
+ if (!savedLayer) {
savedLayer = true
ctx.save()
}
@@ -790,22 +790,22 @@
})
}
var offsetX = 0
- for(let symbol of drawn){
+ for (let symbol of drawn) {
var saved = false
var currentX = offsetX + symbol.x * mul + (layer.x || 0) + symbol.w * mul / 2
var currentY = symbol.y + (layer.y || 0)
-
- if(config.align === "center"){
+
+ if (config.align === "center") {
currentX -= drawnWidth / 2
- }else if(config.align === "right"){
+ } else if (config.align === "right") {
currentX = -offsetX + symbol.x + (layer.x || 0) - symbol.w / 2
}
- if(symbol.scale || symbol.ura){
+ if (symbol.scale || symbol.ura) {
saved = true
ctx.save()
ctx.translate(currentX, currentY)
- if(symbol.scale){
- if(config.baseline === "middle"){
+ if (symbol.scale) {
+ if (config.baseline === "middle") {
ctx.translate(0, -ctx.lineWidth * (2 / symbol.scale[1]))
}
ctx.scale(symbol.scale[0], symbol.scale[1])
@@ -814,46 +814,46 @@
currentX = 0
currentY = 0
}
- if(symbol.ura){
+ if (symbol.ura) {
ctx.font = (30 * mul) + "px Meiryo, sans-serif"
ctx.textBaseline = "middle"
ctx.beginPath()
ctx.arc(currentX, currentY + (17 * mul), (18 * mul), 0, Math.PI * 2)
- if(action === "strokeText"){
+ if (action === "strokeText") {
ctx.fillStyle = layer.outline
ctx.fill()
- }else if(action === "fillText"){
+ } else if (action === "fillText") {
ctx.strokeStyle = layer.fill
ctx.lineWidth = 2.5 * mul
ctx.fillText(symbol.text, currentX, currentY + (17 * mul))
}
ctx.stroke()
- }else{
+ } else {
ctx[action](symbol.text, currentX, currentY)
}
- if(saved){
+ if (saved) {
ctx.restore()
}
offsetX += symbol.w * mul
}
- if(savedLayer){
+ if (savedLayer) {
ctx.restore()
}
}
ctx.restore()
}
-
- wrappingText(config){
+
+ wrappingText(config) {
var ctx = config.ctx
var inputText = config.text.toString()
var words = []
var start = 0
var substituteIndex = 0
- while(start < inputText.length){
+ while (start < inputText.length) {
var character = inputText.slice(start, start + 1)
- if(words.length !== 0){
+ if (words.length !== 0) {
var previous = words[words.length - 1]
- if(!previous.substitute && previous !== "\n" && this.stickySymbols.indexOf(character) !== -1){
+ if (!previous.substitute && previous !== "\n" && this.stickySymbols.indexOf(character) !== -1) {
words[words.length - 1] += character
start++
continue
@@ -861,49 +861,49 @@
}
var index = Infinity
var currentIndex = inputText.slice(start).search(this.regex.cjk)
- if(currentIndex !== -1){
+ if (currentIndex !== -1) {
index = start + currentIndex
var on = inputText.charAt(index)
}
- for(var i = 0; i < this.wrapOn.length; i++){
+ for (var i = 0; i < this.wrapOn.length; i++) {
var currentIndex = inputText.indexOf(this.wrapOn[i], start)
- if(currentIndex !== -1 && currentIndex < index){
+ if (currentIndex !== -1 && currentIndex < index) {
var on = this.wrapOn[i]
index = currentIndex
}
}
- if(index === Infinity){
- if(start !== inputText.length){
+ if (index === Infinity) {
+ if (start !== inputText.length) {
words.push(inputText.slice(start, inputText.length))
}
break
}
var end = index + (on === " " ? 1 : 0)
- if(start !== end){
+ if (start !== end) {
words.push(inputText.slice(start, end))
}
- if(on === "%s" && config.substitute){
+ if (on === "%s" && config.substitute) {
words.push({
substitute: true,
index: substituteIndex,
width: config.substitute(config, substituteIndex, true) || 0
})
substituteIndex++
- }else if(on !== " "){
+ } else if (on !== " ") {
words.push(on)
}
start = index + on.length
}
-
+
ctx.save()
-
+
var bold = this.bold(config.fontFamily)
ctx.font = bold + config.fontSize + "px " + config.fontFamily
ctx.textBaseline = config.baseline || "top"
ctx.textAlign = "left"
ctx.fillStyle = config.fill
var lineHeight = config.lineHeight || config.fontSize
-
+
var x = 0
var y = 0
var totalW = 0
@@ -911,20 +911,20 @@
var line = ""
var toDraw = []
var lastWidth = 0
-
+
var addToDraw = obj => {
toDraw.push(obj)
- if(x + lastWidth > totalW){
+ if (x + lastWidth > totalW) {
totalW = x + lastWidth
}
- if(y + lineHeight > totalH){
+ if (y + lineHeight > totalH) {
totalH = y + lineHeight
}
}
var recenter = () => {
- if(config.textAlign === "center"){
- for(var j in toDraw){
- if(toDraw[j].y === y){
+ if (config.textAlign === "center") {
+ for (var j in toDraw) {
+ if (toDraw[j].y === y) {
toDraw[j].x += (config.width - x - lastWidth) / 2
}
}
@@ -933,34 +933,34 @@
var search = () => {
var end = line.length
var dist = end
- while(dist){
+ while (dist) {
dist >>= 1
line = words[i].slice(0, end)
lastWidth = ctx.measureText(line).width
end += lastWidth < config.width ? dist : -dist
}
- if(line !== words[i]){
+ if (line !== words[i]) {
words.splice(i + 1, 0, words[i].slice(line.length))
words[i] = line
}
}
-
- for(var i = 0; i < words.length; i++){
+
+ for (var i = 0; i < words.length; i++) {
var skip = words[i].substitute || words[i] === "\n"
- if(!skip){
+ if (!skip) {
var currentWidth = ctx.measureText(line + words[i]).width
}
- if(skip || (x !== 0 || line) && x + currentWidth > config.width){
- if(line){
+ if (skip || (x !== 0 || line) && x + currentWidth > config.width) {
+ if (line) {
addToDraw({
text: line,
x: x, y: y
})
}
- if(words[i].substitute){
+ if (words[i].substitute) {
line = ""
var currentWidth = words[i].width
- if(x + lastWidth + currentWidth > config.width){
+ if (x + lastWidth + currentWidth > config.width) {
recenter()
x = 0
y += lineHeight
@@ -973,62 +973,62 @@
})
x += lastWidth + currentWidth
lastWidth = currentWidth
- }else{
+ } else {
recenter()
x = 0
y += lineHeight
- if(words[i] === "\n"){
+ if (words[i] === "\n") {
line = ""
lastWidth = 0
- }else{
+ } else {
line = words[i]
lastWidth = ctx.measureText(line).width
- if(line.length !== 1 && lastWidth > config.width){
+ if (line.length !== 1 && lastWidth > config.width) {
search()
}
}
}
- }else if(!line){
+ } else if (!line) {
line = words[i]
lastWidth = ctx.measureText(line).width
- if(line.length !== 1 && lastWidth > config.width){
+ if (line.length !== 1 && lastWidth > config.width) {
search()
}
- }else{
+ } else {
line += words[i]
lastWidth = currentWidth
}
}
- if(line){
+ if (line) {
addToDraw({
text: line,
x: x, y: y
})
recenter()
}
-
+
var addX = 0
var addY = 0
- if(config.verticalAlign === "middle"){
+ if (config.verticalAlign === "middle") {
addY = ((config.height || 0) - totalH) / 2
}
- for(var i in toDraw){
+ for (var i in toDraw) {
var x = config.x + toDraw[i].x + addX
var y = config.y + toDraw[i].y + addY
- if(toDraw[i].text){
+ if (toDraw[i].text) {
ctx.fillText(toDraw[i].text, x, y)
- }else if(toDraw[i].substitute){
+ } else if (toDraw[i].substitute) {
ctx.save()
ctx.translate(x, y)
config.substitute(config, toDraw[i].index)
ctx.restore()
}
}
-
+
ctx.restore()
}
-
- diffIcon(config){
+
+ diffIcon(config) {
var ctx = config.ctx
var scale = config.scale
ctx.save()
@@ -1037,16 +1037,16 @@
var icon = this.diffIconPath[config.diff === 4 ? 3 : config.diff]
ctx.translate(config.x - icon[0].w * scale / 2, config.y - icon[0].h * scale / 2)
ctx.scale(scale, scale)
- for(var i = 1; i < icon.length; i++){
- if(!icon[i].noStroke){
+ for (var i = 1; i < icon.length; i++) {
+ if (!icon[i].noStroke) {
ctx.stroke(icon[i].d)
}
}
- if(!config.noFill){
- for(var i = 1; i < icon.length; i++){
- if(config.diff === 4 && icon[i].fill === "#db1885"){
+ if (!config.noFill) {
+ for (var i = 1; i < icon.length; i++) {
+ if (config.diff === 4 && icon[i].fill === "#db1885") {
ctx.fillStyle = "#7135db"
- }else{
+ } else {
ctx.fillStyle = icon[i].fill
}
ctx.fill(icon[i].d)
@@ -1054,21 +1054,21 @@
}
ctx.restore()
}
-
- diffOptionsIcon(config){
+
+ diffOptionsIcon(config) {
var ctx = config.ctx
ctx.save()
-
- if(config.iconName === "download" ||config.iconName === "back" ||config.iconName === "trash"){
+
+ if (config.iconName === "download" || config.iconName === "back" || config.iconName === "trash") {
ctx.translate(config.x - 21, config.y - 21)
- if(config.iconName === "download"){
+ if (config.iconName === "download") {
ctx.rotate(Math.PI)
ctx.translate(-42, -42)
- } else if(config.iconName === "trash") {
+ } else if (config.iconName === "trash") {
ctx.rotate(Math.PI / 2 * 3)
ctx.translate(-42, 0)
}
-
+
var drawLine = y => {
ctx.beginPath()
ctx.moveTo(12, y)
@@ -1082,7 +1082,7 @@
ctx.lineTo(21, 19)
ctx.lineTo(37, 19)
ctx.closePath()
- if(!noFill){
+ if (!noFill) {
ctx.fill()
}
}
@@ -1099,11 +1099,11 @@
drawTriangle()
ctx.translate(-1.5, -0.5)
ctx.fillStyle = config.iconName === "download" ? "#a08eea" : config.iconName == "trash" ? "#ff0000" : "#23a6e1"
- ctx.strokeStyle = config.iconName === "download" ? "#a08eea" : config.iconName == "trash" ? "#ff0000" : "#23a6e1"
+ ctx.strokeStyle = config.iconName === "download" ? "#a08eea" : config.iconName == "trash" ? "#ff0000" : "#23a6e1"
ctx.globalCompositeOperation = "darken"
drawLine(11)
drawTriangle()
- }else if(config.iconName === "options"){
+ } else if (config.iconName === "options") {
ctx.translate(config.x, config.y)
ctx.rotate(-55 * Math.PI / 180)
ctx.translate(-6, -20)
@@ -1117,35 +1117,67 @@
ctx.translate(2, -2)
ctx.fillStyle = "gold"
ctx.fill(this.optionsPath.main)
+ } else if (config.iconName === "leaderboard") {
+ // Trophy/ranking icon
+ ctx.translate(config.x - 15, config.y - 15)
+ ctx.strokeStyle = "#000"
+ ctx.lineWidth = 3
+ // Trophy cup body
+ ctx.beginPath()
+ ctx.moveTo(8, 8)
+ ctx.lineTo(22, 8)
+ ctx.lineTo(20, 20)
+ ctx.lineTo(10, 20)
+ ctx.closePath()
+ ctx.fillStyle = "#ffd700"
+ ctx.fill()
+ ctx.stroke()
+ // Trophy base
+ ctx.beginPath()
+ ctx.moveTo(10, 20)
+ ctx.lineTo(20, 20)
+ ctx.lineTo(18, 26)
+ ctx.lineTo(12, 26)
+ ctx.closePath()
+ ctx.fillStyle = "#c9a227"
+ ctx.fill()
+ ctx.stroke()
+ // Trophy handles
+ ctx.beginPath()
+ ctx.arc(6, 13, 4, Math.PI * 0.5, Math.PI * 1.5)
+ ctx.stroke()
+ ctx.beginPath()
+ ctx.arc(24, 13, 4, Math.PI * 1.5, Math.PI * 0.5)
+ ctx.stroke()
}
-
+
ctx.restore()
}
-
- diffCursor(config){
+
+ diffCursor(config) {
var ctx = config.ctx
ctx.save()
- if(config.scale){
+ if (config.scale) {
ctx.translate(config.x, config.y)
ctx.scale(config.scale, config.scale)
ctx.translate(-48, -64)
- }else{
+ } else {
ctx.translate(config.x - 48, config.y - 64)
}
-
+
ctx.fillStyle = config.two ? "#65cdcd" : "#ff411c"
ctx.strokeStyle = "#000"
ctx.lineWidth = 6
ctx.beginPath()
- if(!config.side){
+ if (!config.side) {
var textX = config.two ? 22 : 20
ctx.moveTo(48, 120)
ctx.arc(48, 48.5, 45, Math.PI * 0.58, Math.PI * 0.42)
- }else if(config.two){
+ } else if (config.two) {
var textX = 72
ctx.moveTo(56, 115)
ctx.arc(98, 48.5, 45, Math.PI * 0.75, Math.PI * 0.59)
- }else{
+ } else {
var textX = -30
ctx.moveTo(39, 115)
ctx.arc(-2, 48.5, 45, Math.PI * 0.41, Math.PI * 0.25)
@@ -1163,18 +1195,18 @@
width: 54,
letterSpacing: -4
}, [
- {outline: "#fff", letterBorder: 11},
- {fill: "#000"}
+ { outline: "#fff", letterBorder: 11 },
+ { fill: "#000" }
])
-
+
ctx.restore()
}
-
- diffStar(config){
+
+ diffStar(config) {
var ctx = config.ctx
ctx.save()
- if(config.songSel || config.ura){
- if(this.diffStarCache.scale !== config.ratio){
+ if (config.songSel || config.ura) {
+ if (this.diffStarCache.scale !== config.ratio) {
this.diffStarCache.resize(62, 31, config.ratio)
}
var offset = 30 / 2 - 18 / 2
@@ -1194,15 +1226,15 @@
blur: 10,
force: true
})
- if(big){
+ if (big) {
ctx.translate(30 / 2 - 21 / 2, 30 / 2 - 19 / 2)
ctx.scale(1.1, 1.1)
- }else{
+ } else {
ctx.translate(offset, offset)
}
ctx.fill(this.diffStarPath)
})
- }else{
+ } else {
ctx.fillStyle = "#f72568"
ctx.translate(config.x - 10.5, config.y - 9.5)
ctx.scale(1.1, 1.1)
@@ -1210,54 +1242,54 @@
}
ctx.restore()
}
-
- pattern(config){
+
+ pattern(config) {
var ctx = config.ctx
ctx.save()
var mul = config.scale || 1
-
- if(mul !== 1){
+
+ if (mul !== 1) {
ctx.scale(1 / mul, 1 / mul)
}
ctx.fillStyle = ctx.createPattern(config.img, "repeat")
- if(config.shape){
+ if (config.shape) {
config.shape(ctx, mul)
- }else{
+ } else {
ctx.beginPath()
ctx.rect(config.x * mul, config.y * mul, config.w * mul, config.h * mul)
}
ctx.translate(config.dx * mul, config.dy * mul)
ctx.fill()
-
+
ctx.restore()
}
-
- score(config){
+
+ score(config) {
var ctx = config.ctx
ctx.save()
-
+
ctx.translate(config.x, config.y)
- if(config.scale){
+ if (config.scale) {
ctx.scale(config.scale, config.scale)
}
ctx.strokeStyle = "#000"
ctx.lineWidth = 7
- if(strings.good === "良"){
- if(config.align === "center"){
+ if (strings.good === "良") {
+ if (config.align === "center") {
ctx.translate(config.score === "bad" ? -49 / 2 : -23 / 2, 0)
}
- if(config.score === "good"){
+ if (config.score === "good") {
var grd = ctx.createLinearGradient(0, 0, 0, 29)
grd.addColorStop(0.3, "#f7fb00")
grd.addColorStop(0.9, "#ff4900")
ctx.fillStyle = grd
ctx.stroke(this.diffPath.good)
ctx.fill(this.diffPath.good)
- }else if(config.score === "ok"){
+ } else if (config.score === "ok") {
ctx.fillStyle = "#fff"
ctx.stroke(this.diffPath.ok)
ctx.fill(this.diffPath.ok)
- }else if(config.score === "bad"){
+ } else if (config.score === "bad") {
var grd = ctx.createLinearGradient(0, 0, 0, 27)
grd.addColorStop(0.1, "#6B5DFF")
grd.addColorStop(0.7, "#00AEDE")
@@ -1268,17 +1300,17 @@
ctx.stroke(this.diffPath.ok)
ctx.fill(this.diffPath.ok)
}
- }else{
+ } else {
ctx.font = this.bold(strings.font) + "26px " + strings.font
- if(config.results){
+ if (config.results) {
ctx.textAlign = "left"
- }else{
+ } else {
ctx.textAlign = "center"
}
ctx.textBaseline = "top"
ctx.miterLimit = 1
- if(config.score === "good"){
- if(config.results && strings.id === "en"){
+ if (config.score === "good") {
+ if (config.results && strings.id === "en") {
ctx.scale(0.75, 1)
}
var grd = ctx.createLinearGradient(0, 0, 0, 29)
@@ -1287,11 +1319,11 @@
ctx.fillStyle = grd
ctx.strokeText(strings.good, 0, 4)
ctx.fillText(strings.good, 0, 4)
- }else if(config.score === "ok"){
+ } else if (config.score === "ok") {
ctx.fillStyle = "#fff"
ctx.strokeText(strings.ok, 0, 4)
ctx.fillText(strings.ok, 0, 4)
- }else if(config.score === "bad"){
+ } else if (config.score === "bad") {
var grd = ctx.createLinearGradient(0, 0, 0, 27)
grd.addColorStop(0.1, "#6B5DFF")
grd.addColorStop(0.7, "#00AEDE")
@@ -1302,20 +1334,20 @@
}
ctx.restore()
}
-
- crown(config){
+
+ crown(config) {
var ctx = config.ctx
ctx.save()
-
+
ctx.translate(config.x, config.y)
- if(config.scale){
+ if (config.scale) {
ctx.scale(config.scale, config.scale)
}
ctx.translate(-47, -39)
ctx.miterLimit = 1.7
-
- if(config.whiteOutline){
- if(!this.crownCache.w){
+
+ if (config.whiteOutline) {
+ if (!this.crownCache.w) {
this.crownCache.resize(140, 140, config.ratio)
}
var offset = 140 / 2 - 94 / 2
@@ -1337,34 +1369,34 @@
ctx.restore()
})
}
-
- if(config.shine){
+
+ if (config.shine) {
ctx.strokeStyle = "#fff"
ctx.lineWidth = 18
ctx.stroke(this.crownPath)
ctx.globalAlpha = 1 - config.shine
}
-
+
ctx.strokeStyle = config.type ? "#000" : "#ffc616"
ctx.lineWidth = 18
ctx.stroke(this.crownPath)
-
- if(config.shine){
+
+ if (config.shine) {
ctx.globalAlpha = 1
ctx.fillStyle = "#fff"
ctx.fill(this.crownPath)
ctx.globalAlpha = 1 - config.shine
}
-
- if(config.type){
+
+ if (config.type) {
var grd = ctx.createLinearGradient(0, 0, 94, 0)
- if(config.type === "gold"){
+ if (config.type === "gold") {
grd.addColorStop(0, "#ffffc5")
grd.addColorStop(0.23, "#ffff44")
grd.addColorStop(0.53, "#efbd12")
grd.addColorStop(0.83, "#ffff44")
grd.addColorStop(1, "#efbd12")
- }else if(config.type === "silver"){
+ } else if (config.type === "silver") {
grd.addColorStop(0, "#d6efef")
grd.addColorStop(0.23, "#bddfde")
grd.addColorStop(0.53, "#97c1c0")
@@ -1372,45 +1404,45 @@
grd.addColorStop(1, "#97c1c0")
}
ctx.fillStyle = grd
- }else{
+ } else {
ctx.fillStyle = "#ffdb2c"
}
ctx.fill(this.crownPath)
-
+
ctx.restore()
}
-
- gauge(config){
+
+ gauge(config) {
var ctx = config.ctx
ctx.save()
-
+
ctx.translate(config.x, config.y)
- if(config.scale){
+ if (config.scale) {
ctx.scale(config.scale, config.scale)
}
ctx.translate(-788, 0)
-
+
var firstTop = config.multiplayer ? 0 : 30
var secondTop = config.multiplayer ? 0 : 8
-
+
config.percentage = Math.max(0, Math.min(1, config.percentage))
var cleared = config.percentage >= config.clear
-
+
var gaugeW = 14 * 50
var gaugeClear = gaugeW * (config.clear - 1 / 50)
var gaugeFilled = gaugeW * config.percentage
-
+
ctx.fillStyle = "#000"
ctx.beginPath()
- if(config.scoresheet){
- if(config.multiplayer){
+ if (config.scoresheet) {
+ if (config.multiplayer) {
ctx.moveTo(-4, -4)
ctx.lineTo(760, -4)
this.roundedCorner(ctx, 760, 48, 13, 2)
this.roundedCorner(ctx, gaugeClear - 4, 48, 13, 3)
ctx.lineTo(gaugeClear - 4, 26)
ctx.lineTo(-4, 26)
- }else{
+ } else {
ctx.moveTo(-4, 26)
ctx.lineTo(gaugeClear - 4, 26)
this.roundedCorner(ctx, gaugeClear - 4, 4, 13, 0)
@@ -1418,38 +1450,38 @@
ctx.lineTo(760, 56)
ctx.lineTo(-4, 56)
}
- }else if(config.multiplayer){
+ } else if (config.multiplayer) {
ctx.moveTo(gaugeClear - 7, 27)
ctx.lineTo(788, 27)
ctx.lineTo(788, 52)
this.roundedCorner(ctx, gaugeClear - 7, 52, 18, 3)
- }else{
+ } else {
ctx.moveTo(gaugeClear - 7, 24)
this.roundedCorner(ctx, gaugeClear - 7, 0, 18, 0)
ctx.lineTo(788, 0)
ctx.lineTo(788, 24)
}
ctx.fill()
-
- if(!cleared){
+
+ if (!cleared) {
ctx.fillStyle = config.blue ? "#184d55" : "#680000"
var x = Math.max(0, gaugeFilled - 5)
ctx.fillRect(x, firstTop, gaugeClear - x + 2 + (gaugeClear < gaugeW ? 0 : -7), 22)
}
- if(gaugeFilled > 0){
+ if (gaugeFilled > 0) {
var w = Math.min(gaugeW - 5, gaugeClear + 1, gaugeFilled - 4)
ctx.fillStyle = config.blue ? "#00edff" : "#ff3408"
ctx.fillRect(0, firstTop + 2, w, 20)
ctx.fillStyle = config.blue ? "#9cffff" : "#ffa191"
ctx.fillRect(0, firstTop, w, 3)
}
- if(gaugeClear < gaugeW){
- if(gaugeFilled < gaugeW - 4){
+ if (gaugeClear < gaugeW) {
+ if (gaugeFilled < gaugeW - 4) {
ctx.fillStyle = "#684900"
var x = Math.max(gaugeClear + 9, gaugeFilled - gaugeClear + 9)
ctx.fillRect(x, secondTop, gaugeW - 4 - x, 44)
}
- if(gaugeFilled > gaugeClear + 14){
+ if (gaugeFilled > gaugeClear + 14) {
var w = Math.min(gaugeW - 4, gaugeFilled - gaugeClear - 14)
ctx.fillStyle = "#ff0"
ctx.fillRect(gaugeClear + 9, secondTop + 2, w, 42)
@@ -1458,31 +1490,31 @@
}
ctx.fillStyle = cleared ? "#ff0" : "#684900"
ctx.beginPath()
- if(config.multiplayer){
+ if (config.multiplayer) {
this.roundedCorner(ctx, gaugeClear, secondTop + 44, 10, 3)
ctx.lineTo(gaugeClear, secondTop)
ctx.lineTo(gaugeClear + 10, secondTop)
- }else{
+ } else {
ctx.moveTo(gaugeClear, secondTop + 44)
this.roundedCorner(ctx, gaugeClear, secondTop, 10, 0)
ctx.lineTo(gaugeClear + 10, secondTop + 44)
}
ctx.fill()
}
- if(cleared){
+ if (cleared) {
ctx.save()
ctx.clip()
ctx.fillStyle = "#fff"
ctx.fillRect(gaugeClear, secondTop, 10, 3)
ctx.restore()
}
-
+
ctx.strokeStyle = "rgba(0, 0, 0, 0.16)"
ctx.beginPath()
ctx.lineWidth = 5
- for(var i = 0; i < 49; i++){
+ for (var i = 0; i < 49; i++) {
var x = 14 + i * 14 - ctx.lineWidth / 2
- if(i === config.clear * 50 - 1){
+ if (i === config.clear * 50 - 1) {
ctx.stroke()
ctx.beginPath()
ctx.lineWidth = 4
@@ -1491,7 +1523,7 @@
ctx.lineTo(x, x < gaugeClear ? firstTop + 22 : secondTop + 44)
}
ctx.stroke()
- if(config.clear < 47 / 50){
+ if (config.clear < 47 / 50) {
this.layeredText({
ctx: ctx,
text: strings.clear,
@@ -1501,33 +1533,33 @@
y: config.multiplayer ? 22 : 11,
letterSpacing: -2
}, [
- {scale: [1.1, 1.01], outline: "#000", letterBorder: 6},
- {scale: [1.11, 1], fill: cleared ? "#fff" : "#737373"}
+ { scale: [1.1, 1.01], outline: "#000", letterBorder: 6 },
+ { scale: [1.11, 1], fill: cleared ? "#fff" : "#737373" }
])
}
-
+
ctx.restore()
}
-
- soul(config){
+
+ soul(config) {
var ctx = config.ctx
ctx.save()
-
+
ctx.translate(config.x, config.y)
- if(config.scale){
+ if (config.scale) {
ctx.scale(config.scale, config.scale)
}
ctx.translate(-23, -21)
-
+
ctx.fillStyle = config.cleared ? "#fff" : "#737373"
ctx.fill(this.soulPath)
-
+
ctx.restore()
}
-
- slot(ctx, x, y, size){
+
+ slot(ctx, x, y, size) {
var mul = size / 106
-
+
ctx.save()
ctx.globalAlpha = 0.7
ctx.globalCompositeOperation = "screen"
@@ -1547,22 +1579,22 @@
ctx.stroke()
ctx.restore()
}
-
- category(config){
+
+ category(config) {
var ctx = config.ctx
ctx.save()
-
+
ctx.translate(config.x, config.y)
- if(config.scale || config.right){
+ if (config.scale || config.right) {
ctx.scale((config.right ? -1 : 1) * (config.scale || 1), config.scale || 1)
}
ctx.translate(-15.5 + 14, -11)
- for(var i = 0; i < 4; i++){
- if(i < 2){
+ for (var i = 0; i < 4; i++) {
+ if (i < 2) {
ctx.lineWidth = 6
ctx.strokeStyle = "#000"
ctx.stroke(this.categoryPath.main)
- }else{
+ } else {
ctx.fillStyle = config.fill
ctx.fill(this.categoryPath.main)
ctx.fillStyle = "rgba(255, 255, 255, 0.25)"
@@ -1570,13 +1602,13 @@
ctx.fillStyle = "rgba(0, 0, 0, 0.25)"
ctx.fill(this.categoryPath.shadow)
}
- if(i % 2 === 0){
+ if (i % 2 === 0) {
ctx.translate(-14, 0)
- }else if(i === 1){
+ } else if (i === 1) {
ctx.translate(14, 0)
}
}
- if(config.highlight){
+ if (config.highlight) {
this.highlight({
ctx: ctx,
x: 0,
@@ -1586,25 +1618,25 @@
size: 8
})
}
-
+
ctx.restore()
}
-
- nameplate(config){
+
+ nameplate(config) {
var ctx = config.ctx
var w = 264
var h = 57
var r = h / 2
var pi = Math.PI
-
+
ctx.save()
-
+
ctx.translate(config.x, config.y)
- if(config.scale){
+ if (config.scale) {
ctx.scale(config.scale, config.scale)
}
-
- ctx.fillStyle="rgba(0, 0, 0, 0.25)"
+
+ ctx.fillStyle = "rgba(0, 0, 0, 0.25)"
ctx.beginPath()
ctx.arc(r + 4, r + 5, r, pi / 2, pi / -2)
ctx.arc(w - r + 4, r + 5, r, pi / -2, pi / 2)
@@ -1649,7 +1681,7 @@
var text = config.blue ? "2P" : "1P"
ctx.strokeText(text, r + 2, r + 1)
ctx.fillText(text, r + 2, r + 1)
- if(config.rank){
+ if (config.rank) {
this.layeredText({
ctx: ctx,
text: config.rank,
@@ -1661,7 +1693,7 @@
align: "center",
baseline: "middle"
}, [
- {fill: "#000"}
+ { fill: "#000" }
])
}
this.layeredText({
@@ -1676,17 +1708,17 @@
align: "center",
baseline: "middle"
}, [
- {outline: "#000", letterBorder: 6},
- {fill: "#fff"}
+ { outline: "#000", letterBorder: 6 },
+ { fill: "#fff" }
])
-
+
ctx.restore()
}
-
- alpha(amount, ctx, callback, winW, winH){
- if(amount >= 1){
+
+ alpha(amount, ctx, callback, winW, winH) {
+ if (amount >= 1) {
return callback(ctx)
- }else if(amount >= 0){
+ } else if (amount >= 0) {
this.tmpCanvas.width = Math.max(1, winW || ctx.canvas.width)
this.tmpCanvas.height = Math.max(1, winH || ctx.canvas.height)
callback(this.tmpCtx)
@@ -1696,34 +1728,34 @@
ctx.restore()
}
}
-
- shadow(config){
- if(!disableBlur || config.force){
+
+ shadow(config) {
+ if (!disableBlur || config.force) {
var ctx = config.ctx
- if(config.fill){
+ if (config.fill) {
ctx.shadowColor = config.fill
}
- if(config.blur){
+ if (config.blur) {
ctx.shadowBlur = config.blur
}
- if(config.x){
+ if (config.x) {
ctx.shadowOffsetX = config.x
}
- if(config.y){
+ if (config.y) {
ctx.shadowOffsetY = config.y
}
}
}
-
- bold(font){
+
+ bold(font) {
return font === "Microsoft YaHei, sans-serif" ? "bold " : ""
}
-
- getMS(){
+
+ getMS() {
return Date.now()
}
-
- clean(){
+
+ clean() {
this.songFrameCache.clean()
this.diffStarCache.clean()
this.crownCache.clean()
diff --git a/public/src/js/leaderboard.js b/public/src/js/leaderboard.js
new file mode 100644
index 0000000..8f2dd65
--- /dev/null
+++ b/public/src/js/leaderboard.js
@@ -0,0 +1,275 @@
+class Leaderboard {
+ constructor(songId, difficulty, songTitle, onClose) {
+ this.songId = songId
+ this.difficulty = difficulty
+ this.songTitle = songTitle
+ this.onClose = onClose
+ this.entries = []
+ this.loading = true
+ this.error = null
+ this.font = strings.font
+
+ this.init()
+ }
+
+ init() {
+ this.createModal()
+ this.fetchLeaderboard()
+ this.addEventListeners()
+ }
+
+ createModal() {
+ // Create overlay
+ this.overlay = document.createElement("div")
+ this.overlay.id = "leaderboard-overlay"
+ this.overlay.innerHTML = `
+
+ `
+ document.body.appendChild(this.overlay)
+ }
+
+ getDifficultyName() {
+ const diffNames = {
+ easy: strings.easy,
+ normal: strings.normal,
+ hard: strings.hard,
+ oni: strings.oni,
+ ura: strings.oni
+ }
+ return diffNames[this.difficulty] || this.difficulty
+ }
+
+ addEventListeners() {
+ this.overlay.querySelector("#leaderboard-close-btn").addEventListener("click", () => this.close())
+ this.overlay.addEventListener("click", (e) => {
+ if (e.target === this.overlay) {
+ this.close()
+ }
+ })
+
+ // Keyboard support
+ this.keyHandler = (e) => {
+ if (e.key === "Escape") {
+ this.close()
+ }
+ }
+ document.addEventListener("keydown", this.keyHandler)
+ }
+
+ fetchLeaderboard() {
+ const url = `api/leaderboard?song_id=${encodeURIComponent(this.songId)}&difficulty=${encodeURIComponent(this.difficulty)}`
+
+ loader.ajax(url).then(response => {
+ const data = JSON.parse(response)
+ if (data.status === "ok") {
+ this.entries = data.entries
+ this.monthKey = data.month_key
+ this.loading = false
+ this.render()
+ } else {
+ throw new Error("Failed to load leaderboard")
+ }
+ }).catch(error => {
+ console.error("Leaderboard fetch error:", error)
+ this.error = error
+ this.loading = false
+ this.render()
+ })
+ }
+
+ render() {
+ const content = this.overlay.querySelector("#leaderboard-content")
+
+ if (this.error) {
+ content.innerHTML = `${strings.errorOccured}
`
+ return
+ }
+
+ if (this.entries.length === 0) {
+ content.innerHTML = `${strings.noEntries}
`
+ return
+ }
+
+ // Build entries table
+ let html = `
+
+
+ `
+
+ for (const entry of this.entries) {
+ const rankClass = entry.rank <= 3 ? `leaderboard-rank-${entry.rank}` : ""
+ html += `
+
+
${entry.rank}
+
${this.escapeHtml(entry.username)}
+
${entry.score_value.toLocaleString()}
+
+ `
+ }
+
+ html += `
`
+ content.innerHTML = html
+ }
+
+ escapeHtml(text) {
+ const div = document.createElement("div")
+ div.textContent = text
+ return div.innerHTML
+ }
+
+ close() {
+ document.removeEventListener("keydown", this.keyHandler)
+ this.overlay.remove()
+ if (this.onClose) {
+ this.onClose()
+ }
+ }
+}
+
+// Score submission modal for when players qualify for leaderboard
+class LeaderboardSubmit {
+ constructor(songId, difficulty, score, rank, onSubmit, onClose) {
+ this.songId = songId
+ this.difficulty = difficulty
+ this.score = score
+ this.rank = rank
+ this.onSubmit = onSubmit
+ this.onClose = onClose
+ this.submitting = false
+
+ this.init()
+ }
+
+ init() {
+ this.createModal()
+ this.addEventListeners()
+ }
+
+ createModal() {
+ this.overlay = document.createElement("div")
+ this.overlay.id = "leaderboard-submit-overlay"
+
+ const rankText = strings.newRecord.replace("%s", this.rank)
+
+ this.overlay.innerHTML = `
+
+
${rankText}
+
${this.score.toLocaleString()} ${strings.points}
+
+
+
+
+
+
+
+
+
+ `
+ document.body.appendChild(this.overlay)
+
+ // Focus input
+ setTimeout(() => {
+ this.overlay.querySelector("#leaderboard-name-input").focus()
+ }, 100)
+ }
+
+ addEventListeners() {
+ const submitBtn = this.overlay.querySelector("#leaderboard-submit-btn")
+ const cancelBtn = this.overlay.querySelector("#leaderboard-cancel-btn")
+ const input = this.overlay.querySelector("#leaderboard-name-input")
+
+ submitBtn.addEventListener("click", () => this.submit())
+ cancelBtn.addEventListener("click", () => this.close())
+
+ input.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") {
+ this.submit()
+ } else if (e.key === "Escape") {
+ this.close()
+ }
+ })
+ }
+
+ submit() {
+ if (this.submitting) return
+
+ const input = this.overlay.querySelector("#leaderboard-name-input")
+ const username = input.value.trim()
+
+ if (username.length < 1 || username.length > 10) {
+ input.classList.add("error")
+ return
+ }
+
+ this.submitting = true
+ const submitBtn = this.overlay.querySelector("#leaderboard-submit-btn")
+ submitBtn.disabled = true
+ submitBtn.textContent = strings.loading
+
+ const data = {
+ song_id: String(this.songId),
+ difficulty: this.difficulty,
+ username: username,
+ score: this.score
+ }
+
+ const xhr = new XMLHttpRequest()
+ xhr.open("POST", "api/score/leaderboard/save")
+ xhr.setRequestHeader("Content-Type", "application/json")
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ try {
+ const result = JSON.parse(xhr.responseText)
+ if (result.status === "ok") {
+ this.close()
+ if (this.onSubmit) {
+ this.onSubmit(username)
+ }
+ } else {
+ handleError(result.message || "Submit failed")
+ }
+ } catch (e) {
+ handleError("Invalid response")
+ }
+ } else {
+ handleError("Server error " + xhr.status)
+ }
+ }
+ xhr.onerror = () => {
+ handleError("Network error")
+ }
+
+ const handleError = (msg) => {
+ console.error("Leaderboard submit error:", msg)
+ this.submitting = false
+ submitBtn.disabled = false
+ submitBtn.textContent = strings.submit
+ input.classList.add("error")
+ }
+
+ xhr.send(JSON.stringify(data))
+ }
+
+ close() {
+ this.overlay.remove()
+ if (this.onClose) {
+ this.onClose()
+ }
+ }
+}
diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js
index a220be9..3351ced 100644
--- a/public/src/js/scoresheet.js
+++ b/public/src/js/scoresheet.js
@@ -1,8 +1,8 @@
-class Scoresheet{
- constructor(...args){
+class Scoresheet {
+ constructor(...args) {
this.init(...args)
}
- init(controller, results, multiplayer, touchEnabled){
+ init(controller, results, multiplayer, touchEnabled) {
this.controller = controller
this.resultsObj = results
this.player = [multiplayer ? (p2.player === 1 ? 0 : 1) : 0]
@@ -11,33 +11,33 @@ class Scoresheet{
this.results[player0] = {}
this.rules = []
this.rules[player0] = this.controller.game.rules
- if(multiplayer){
+ if (multiplayer) {
this.player.push(p2.player === 2 ? 0 : 1)
this.results[this.player[1]] = p2.results
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.multiplayer = multiplayer
this.touchEnabled = touchEnabled
-
+
this.canvas = document.getElementById("canvas")
this.ctx = this.canvas.getContext("2d")
var resolution = settings.getItem("resolution")
var noSmoothing = resolution === "low" || resolution === "lowest"
- if(noSmoothing){
+ if (noSmoothing) {
this.ctx.imageSmoothingEnabled = false
}
- if(resolution === "lowest"){
+ if (resolution === "lowest") {
this.canvas.style.imageRendering = "pixelated"
}
this.game = document.getElementById("game")
-
+
this.fadeScreen = document.createElement("div")
this.fadeScreen.id = "fade-screen"
this.game.appendChild(this.fadeScreen)
-
+
this.font = strings.font
this.numbersFont = "TnT, Meiryo, sans-serif"
this.state = {
@@ -49,18 +49,18 @@ class Scoresheet{
}
this.frame = 1000 / 60
this.numbers = "001122334455667788900112233445".split("")
-
+
this.draw = new CanvasDraw(noSmoothing)
this.canvasCache = new CanvasCache(noSmoothing)
this.nameplateCache = new CanvasCache(noSmoothing)
-
+
this.keyboard = new Keyboard({
confirm: ["enter", "space", "esc", "don_l", "don_r"]
}, this.keyDown.bind(this))
this.gamepad = new Gamepad({
confirm: ["a", "b", "start", "ls", "rs"]
}, this.keyDown.bind(this))
-
+
this.difficulty = {
"easy": 0,
"normal": 1,
@@ -68,22 +68,22 @@ class Scoresheet{
"oni": 3,
"ura": 4
}
-
+
this.scoreSaved = false
this.redrawRunning = true
this.redrawBind = this.redraw.bind(this)
this.redraw()
-
+
assets.sounds["v_results"].play()
assets.sounds["bgm_result"].playLoop(3, false, 0, 0.847, 17.689)
-
+
this.session = p2.session
- if(this.session){
- if(p2.getMessage("songsel")){
+ if (this.session) {
+ if (p2.getMessage("songsel")) {
this.toSongsel(true)
}
pageEvents.add(p2, "message", response => {
- if(response.type === "songsel"){
+ if (response.type === "songsel") {
this.toSongsel(true)
}
})
@@ -100,59 +100,59 @@ class Scoresheet{
touchEvents: controller.view.touchEvents
})
}
- keyDown(pressed){
- if(pressed && this.redrawing){
+ keyDown(pressed) {
+ if (pressed && this.redrawing) {
this.toNext()
}
}
- mouseDown(event){
- if(event.type === "touchstart"){
+ mouseDown(event) {
+ if (event.type === "touchstart") {
event.preventDefault()
this.canvas.style.cursor = ""
this.state.pointerLocked = true
- }else{
+ } else {
this.state.pointerLocked = false
- if(event.which !== 1){
+ if (event.which !== 1) {
return
}
}
this.toNext()
}
- toNext(){
+ toNext() {
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()
- }else if(this.state.screen === "scoresShown" && elapsed >= 1000){
+ } else if (this.state.screen === "scoresShown" && elapsed >= 1000) {
this.toSongsel()
}
}
- toScoresShown(){
- if(!p2.session){
+ toScoresShown() {
+ if (!p2.session) {
this.state.screen = "scoresShown"
this.state.screenMS = this.getMS()
this.controller.playSound("neiro_1_don", 0, true)
}
}
- toSongsel(fromP2){
- if(!p2.session || fromP2){
+ toSongsel(fromP2) {
+ if (!p2.session || fromP2) {
snd.musicGain.fadeOut(0.5)
this.state.screen = "fadeOut"
this.state.screenMS = this.getMS()
- if(!fromP2){
+ if (!fromP2) {
this.controller.playSound("neiro_1_don", 0, true)
}
}
}
-
- startRedraw(){
+
+ startRedraw() {
this.redrawing = true
requestAnimationFrame(this.redrawBind)
this.winW = null
this.winH = null
-
+
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this))
-
- if(!this.multiplayer){
+
+ if (!this.multiplayer) {
this.tetsuoHana = document.createElement("div")
this.tetsuoHana.id = "tetsuohana"
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 id = ["flowers1", "flowers2", "mikoshi", "tetsuo", "hana"]
var bg = [flowersBg, flowersBg, mikoshiBg, tetsuoHanaBg, tetsuoHanaBg]
- for(var i = 0; i < id.length; i++){
- if(id[i] === "mikoshi"){
+ for (var i = 0; i < id.length; i++) {
+ if (id[i] === "mikoshi") {
var divOut = document.createElement("div")
divOut.id = id[i] + "-out"
this.tetsuoHana.appendChild(divOut)
- }else{
+ } else {
var divOut = this.tetsuoHana
}
var div = document.createElement("div")
@@ -179,32 +179,32 @@ class Scoresheet{
this.game.appendChild(this.tetsuoHana)
}
}
-
- redraw(){
- if(!this.redrawRunning){
+
+ redraw() {
+ if (!this.redrawRunning) {
return
}
- if(this.redrawing){
+ if (this.redrawing) {
requestAnimationFrame(this.redrawBind)
}
var ms = this.getMS()
-
- if(!this.redrawRunning){
+
+ if (!this.redrawRunning) {
return
}
-
+
var ctx = this.ctx
ctx.save()
-
+
var winW = innerWidth
var winH = lastHeight
this.pixelRatio = window.devicePixelRatio || 1
var resolution = settings.getItem("resolution")
- if(resolution === "medium"){
+ if (resolution === "medium") {
this.pixelRatio *= 0.75
- }else if(resolution === "low"){
+ } else if (resolution === "low") {
this.pixelRatio *= 0.5
- }else if(resolution === "lowest"){
+ } else if (resolution === "lowest") {
this.pixelRatio *= 0.25
}
winW *= this.pixelRatio
@@ -212,49 +212,49 @@ class Scoresheet{
var ratioX = winW / 1280
var ratioY = winH / 720
var ratio = (ratioX < ratioY ? ratioX : ratioY)
-
- if(this.redrawing){
- if(this.winW !== winW || this.winH !== winH){
+
+ if (this.redrawing) {
+ if (this.winW !== winW || this.winH !== winH) {
this.canvas.width = Math.max(1, winW)
this.canvas.height = Math.max(1, winH)
ctx.scale(ratio, ratio)
this.canvas.style.width = (winW / this.pixelRatio) + "px"
this.canvas.style.height = (winH / this.pixelRatio) + "px"
-
+
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
this.nameplateCache.resize(274, 134, ratio + 0.2)
-
- if(!this.multiplayer){
+
+ if (!this.multiplayer) {
this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio)
- if(this.tetsuoHanaClass === "dance"){
+ if (this.tetsuoHanaClass === "dance") {
this.tetsuoHana.classList.remove("dance", "dance2")
- setTimeout(()=>{
+ setTimeout(() => {
this.tetsuoHana.classList.add("dance2")
- },50)
- }else if(this.tetsuoHanaClass === "failed"){
+ }, 50)
+ } else if (this.tetsuoHanaClass === "failed") {
this.tetsuoHana.classList.remove("failed")
- setTimeout(()=>{
+ setTimeout(() => {
this.tetsuoHana.classList.add("failed")
- },50)
+ }, 50)
}
}
- }else if(!document.hasFocus() && this.state.screen === "scoresShown"){
- if(this.state["countup0"]){
+ } else if (!document.hasFocus() && this.state.screen === "scoresShown") {
+ if (this.state["countup0"]) {
this.stopSound("se_results_countup", 0)
}
- if(this.state["countup1"]){
+ if (this.state["countup1"]) {
this.stopSound("se_results_countup", 1)
}
return
- }else{
+ } else {
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
}
- }else{
+ } else {
ctx.scale(ratio, ratio)
- if(!this.canvasCache.canvas){
+ if (!this.canvasCache.canvas) {
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
}
- if(!this.nameplateCache.canvas){
+ if (!this.nameplateCache.canvas) {
this.nameplateCache.resize(274, 67, ratio + 0.2)
}
}
@@ -263,23 +263,23 @@ class Scoresheet{
this.ratio = ratio
winW /= ratio
winH /= ratio
-
+
var frameTop = winH / 2 - 720 / 2
var frameLeft = winW / 2 - 1280 / 2
-
+
var players = this.multiplayer ? 2 : 1
var p2Offset = 298
-
+
var bgOffset = 0
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)
}
- if((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved){
+ if ((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved) {
this.saveScore()
}
-
- if(bgOffset){
+
+ if (bgOffset) {
ctx.save()
ctx.translate(0, -bgOffset)
}
@@ -297,7 +297,7 @@ class Scoresheet{
ctx.fillRect(0, winH / 2 - 12, winW, 12)
ctx.fillStyle = "rgba(0, 0, 0, 0.25)"
ctx.fillRect(0, winH / 2, winW, 20)
- if(bgOffset !== 0){
+ if (bgOffset !== 0) {
ctx.fillStyle = "#000"
ctx.fillRect(0, winH / 2 - 2, winW, 2)
}
@@ -305,13 +305,13 @@ class Scoresheet{
ctx.fillRect(0, 0, winW, frameTop + 64)
ctx.fillStyle = "#bf2900"
ctx.fillRect(0, frameTop + 64, winW, 8)
-
- if(bgOffset){
+
+ if (bgOffset) {
ctx.restore()
ctx.save()
ctx.translate(0, bgOffset)
}
-
+
this.draw.pattern({
ctx: ctx,
img: assets.image[this.multiplayer ? "bg_score_p2" : "bg_score_p1"],
@@ -325,9 +325,9 @@ class Scoresheet{
ctx.fillStyle = this.multiplayer ? "rgba(138, 245, 247, 0.5)" : "rgba(249, 163, 149, 0.5)"
ctx.fillRect(0, winH / 2, winW, 12)
ctx.fillStyle = "#000"
- if(bgOffset === 0){
+ if (bgOffset === 0) {
ctx.fillRect(0, winH / 2 - 2, winW, 4)
- }else{
+ } else {
ctx.fillRect(0, winH / 2, winW, 2)
}
ctx.fillStyle = this.multiplayer ? "#6bbec0" : "#fa4529"
@@ -336,47 +336,47 @@ class Scoresheet{
ctx.fillRect(0, winH - frameTop - 72, winW, 7)
ctx.fillStyle = this.multiplayer ? "#a8e0e0" : "#ff9b7a"
ctx.fillRect(0, winH - frameTop - 66, winW, 2)
-
- if(bgOffset){
+
+ if (bgOffset) {
ctx.restore()
}
-
- if(this.state.screen === "scoresShown" || this.state.screen === "fadeOut"){
+
+ if (this.state.screen === "scoresShown" || this.state.screen === "fadeOut") {
var elapsed = Infinity
- }else if(this.redrawing){
+ } else if (this.redrawing) {
var elapsed = ms - this.state.screenMS - this.state.startDelay
- }else{
+ } else {
var elapsed = 0
}
-
+
var rules = this.controller.game.rules
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]]
- if(p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)){
+ if (p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)) {
failedOffset = 0
}
}
- if(elapsed >= 3100 + failedOffset){
- for(var p = 0; p < players; p++){
+ if (elapsed >= 3100 + failedOffset) {
+ for (var p = 0; p < players; p++) {
ctx.save()
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
var clear = this.rules[p].clearReached(results.gauge)
- if(p === 1 || !this.multiplayer && clear){
+ if (p === 1 || !this.multiplayer && clear) {
ctx.translate(0, 290)
}
- if(clear){
+ if (clear) {
ctx.globalCompositeOperation = "lighter"
}
ctx.globalAlpha = Math.min(1, Math.max(0, (elapsed - (3100 + failedOffset)) / 500)) * 0.5
var grd = ctx.createLinearGradient(0, frameTop + 72, 0, frameTop + 368)
grd.addColorStop(0, "#000")
- if(clear){
+ if (clear) {
grd.addColorStop(1, "#ffffba")
- }else{
+ } else {
grd.addColorStop(1, "transparent")
}
ctx.fillStyle = grd
@@ -384,11 +384,11 @@ class Scoresheet{
ctx.restore()
}
}
-
- if(elapsed >= 0){
- if(this.state.hasPointer === 0){
+
+ if (elapsed >= 0) {
+ if (this.state.hasPointer === 0) {
this.state.hasPointer = 1
- if(!this.state.pointerLocked){
+ if (!this.state.pointerLocked) {
this.canvas.style.cursor = this.session ? "" : "pointer"
}
}
@@ -397,7 +397,7 @@ class Scoresheet{
this.draw.alpha(Math.min(1, elapsed / 400), ctx, ctx => {
ctx.scale(ratio, ratio)
ctx.translate(frameLeft, frameTop)
-
+
this.canvasCache.get({
ctx: ctx,
x: 0,
@@ -416,15 +416,15 @@ class Scoresheet{
letterSpacing: strings.id === "en" ? 0 : 3,
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, outline: "#ad1516", letterBorder: 10},
- {x: -2, y: -2, outline: "#ff797b"},
- {outline: "#f70808"},
- {fill: "#fff", shadow: [-1, 1, 3, 1.5]}
+ { x: 2, y: 2, shadow: [2, 2, 7] },
+ { x: 2, y: 2, outline: "#ad1516", letterBorder: 10 },
+ { x: -2, y: -2, outline: "#ff797b" },
+ { outline: "#f70808" },
+ { fill: "#fff", shadow: [-1, 1, 3, 1.5] }
])
-
+
this.draw.layeredText({
ctx: ctx,
text: this.results[this.player[0]].title,
@@ -436,21 +436,21 @@ class Scoresheet{
align: "right",
forceShadow: true
}, [
- {outline: "#000", letterBorder: 10, shadow: [1, 1, 3]},
- {fill: "#fff"}
+ { outline: "#000", letterBorder: 10, shadow: [1, 1, 3] },
+ { fill: "#fff" }
])
})
-
+
ctx.save()
- for(var p = 0; p < players; p++){
+ for (var p = 0; p < players; p++) {
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
- if(p === 1){
+ if (p === 1) {
ctx.translate(0, p2Offset)
}
-
+
ctx.drawImage(assets.image["difficulty"],
0, 144 * this.difficulty[results.difficulty],
168, 143,
@@ -468,11 +468,11 @@ class Scoresheet{
ctx.strokeText(text, 395, 308)
ctx.fillText(text, 395, 308)
ctx.miterLimit = 10
-
+
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
- }else{
+ } else {
var name = results.name || defaultName
}
this.nameplateCache.get({
@@ -492,13 +492,13 @@ class Scoresheet{
blue: p === 1
})
})
-
- if(this.controller.autoPlayEnabled){
+
+ if (this.controller.autoPlayEnabled) {
ctx.drawImage(assets.image["badge_auto"],
431, 311, 34, 34
)
}
-
+
this.draw.roundedRect({
ctx: ctx,
x: 532,
@@ -549,10 +549,10 @@ class Scoresheet{
align: "right",
width: 36
}, [
- {fill: "#fff"},
- {outline: "#000", letterBorder: 0.5}
+ { fill: "#fff" },
+ { outline: "#000", letterBorder: 0.5 }
])
-
+
this.draw.score({
ctx: ctx,
score: "good",
@@ -574,7 +574,7 @@ class Scoresheet{
y: 273,
results: true
})
-
+
ctx.textAlign = "right"
var grd = ctx.createLinearGradient(0, 0, 0, 30)
grd.addColorStop(0.2, "#ff4900")
@@ -590,8 +590,8 @@ class Scoresheet{
width: 154,
letterSpacing: strings.id === "ja" ? 1 : 0
}, [
- {outline: "#000", letterBorder: 8},
- {fill: grd}
+ { outline: "#000", letterBorder: 8 },
+ { fill: grd }
])
this.draw.layeredText({
ctx: ctx,
@@ -604,24 +604,24 @@ class Scoresheet{
width: 154,
letterSpacing: strings.id === "ja" ? 4 : 0
}, [
- {outline: "#000", letterBorder: 8},
- {fill: "#ffc700"}
+ { outline: "#000", letterBorder: 8 },
+ { fill: "#ffc700" }
])
}
ctx.restore()
})
ctx.restore()
}
-
- if(!this.multiplayer){
- if(elapsed >= 400 && elapsed < 3100 + failedOffset){
- if(this.tetsuoHanaClass !== "fadein"){
+
+ if (!this.multiplayer) {
+ if (elapsed >= 400 && elapsed < 3100 + failedOffset) {
+ if (this.tetsuoHanaClass !== "fadein") {
this.tetsuoHana.classList.add("fadein")
this.tetsuoHanaClass = "fadein"
}
- }else if(elapsed >= 3100 + failedOffset){
- if(this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed"){
- if(this.tetsuoHanaClass){
+ } else if (elapsed >= 3100 + failedOffset) {
+ if (this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed") {
+ if (this.tetsuoHanaClass) {
this.tetsuoHana.classList.remove(this.tetsuoHanaClass)
}
this.tetsuoHanaClass = this.rules[this.player[0]].clearReached(this.results[this.player[0]].gauge) ? "dance" : "failed"
@@ -629,20 +629,20 @@ class Scoresheet{
}
}
}
-
- if(elapsed >= 800){
+
+ if (elapsed >= 800) {
ctx.save()
ctx.setTransform(1, 0, 0, 1, 0, 0)
this.draw.alpha(Math.min(1, (elapsed - 800) / 500), ctx, ctx => {
ctx.scale(ratio, ratio)
ctx.translate(frameLeft, frameTop)
-
- for(var p = 0; p < players; p++){
+
+ for (var p = 0; p < players; p++) {
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
- if(p === 1){
+ if (p === 1) {
ctx.translate(0, p2Offset)
}
var w = 712
@@ -669,51 +669,51 @@ class Scoresheet{
})
ctx.restore()
}
-
- if(elapsed >= 1200){
+
+ if (elapsed >= 1200) {
ctx.save()
ctx.setTransform(1, 0, 0, 1, 0, 0)
var noCrownResultWait = -2000;
- for(var p = 0; p < players; p++){
+ for (var p = 0; p < players; p++) {
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
var crownType = null
- if(this.rules[p].clearReached(results.gauge)){
+ if (this.rules[p].clearReached(results.gauge)) {
crownType = results.bad === "0" ? "gold" : "silver"
}
- if(crownType !== null){
+ if (crownType !== null) {
noCrownResultWait = 0;
var amount = Math.min(1, (elapsed - 1200) / 450)
this.draw.alpha(this.draw.easeIn(amount), ctx, ctx => {
ctx.save()
ctx.scale(ratio, ratio)
ctx.translate(frameLeft, frameTop)
- if(p === 1){
+ if (p === 1) {
ctx.translate(0, p2Offset)
}
-
+
var crownScale = 1
var shine = 0
- if(amount < 1){
+ if (amount < 1) {
crownScale = 2.8 * (1 - amount) + 0.9
- }else if(elapsed < 1850){
+ } else if (elapsed < 1850) {
crownScale = 0.9 + (elapsed - 1650) / 2000
- }else if(elapsed < 2200){
+ } else if (elapsed < 2200) {
shine = (elapsed - 1850) / 175
- if(shine > 1){
+ if (shine > 1) {
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
- if(crownType === "gold"){
+ if (crownType === "gold") {
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.playSound("se_results_crown", p)
}
@@ -727,65 +727,65 @@ class Scoresheet{
whiteOutline: true,
ratio: ratio
})
-
+
ctx.restore()
})
}
}
ctx.restore()
}
-
- if(elapsed >= 2400 + noCrownResultWait){
+
+ if (elapsed >= 2400 + noCrownResultWait) {
ctx.save()
ctx.translate(frameLeft, frameTop)
-
+
var printNumbers = ["good", "ok", "bad", "maxCombo", "drumroll"]
- if(!this.state["countupTime0"]){
+ if (!this.state["countupTime0"]) {
var times = {}
var lastTime = 0
- for(var p = 0; p < players; p++){
+ for (var p = 0; p < players; p++) {
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame
- if(currentTime > lastTime){
+ if (currentTime > lastTime) {
lastTime = currentTime
}
}
- for(var i in printNumbers){
+ for (var i in printNumbers) {
var largestTime = 0
- for(var p = 0; p < players; p++){
+ for (var p = 0; p < players; p++) {
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
times[printNumbers[i]] = lastTime + 500
var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
- if(currentTime > largestTime){
+ if (currentTime > largestTime) {
largestTime = currentTime
}
}
lastTime = largestTime
}
this.state.fadeInEnd = lastTime
- for(var p = 0; p < players; p++){
+ for (var p = 0; p < players; p++) {
this.state["countupTime" + p] = times
}
}
-
- for(var p = 0; p < players; p++){
+
+ for (var p = 0; p < players; p++) {
var results = this.results[p]
- if(!results){
+ if (!results) {
continue
}
- if(p === 1){
+ if (p === 1) {
ctx.translate(0, p2Offset)
}
ctx.save()
-
+
this.state.countupShown = false
-
+
var points = this.getNumber(results.points, 3100 + noCrownResultWait, elapsed)
var scale = 1.3
ctx.font = "35px " + this.numbersFont
@@ -795,24 +795,24 @@ class Scoresheet{
ctx.fillStyle = "#fff"
ctx.strokeStyle = "#fff"
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.fillText(points[points.length - i - 1], 0, 0)
ctx.strokeText(points[points.length - i - 1], 0, 0)
}
ctx.restore()
-
- if(!this.state["countupTime" + p]){
+
+ if (!this.state["countupTime" + p]) {
var times = {}
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
lastTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
}
this.state["countupTime" + p] = times
}
-
- for(var i in printNumbers){
+
+ for (var i in printNumbers) {
var start = this.state["countupTime" + p][printNumbers[i]]
this.draw.layeredText({
ctx: ctx,
@@ -824,103 +824,103 @@ class Scoresheet{
letterSpacing: 1,
align: "right"
}, [
- {outline: "#000", letterBorder: 9},
- {fill: "#fff"}
+ { outline: "#000", letterBorder: 9 },
+ { fill: "#fff" }
])
}
-
- if(this.state.countupShown){
- if(!this.state["countup" + p]){
+
+ if (this.state.countupShown) {
+ if (!this.state["countup" + p]) {
this.state["countup" + p] = true
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.stopSound("se_results_countup", p)
- if(this.state.screen === "fadeIn"){
+ if (this.state.screen === "fadeIn") {
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.screenMS = this.getMS()
}
}
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
- if(p2.session){
+ if (p2.session) {
p2.send("songsel")
- }else{
+ } else {
this.toSongsel(true)
}
}
-
- if(this.state.screen === "fadeOut"){
- if(this.state.hasPointer === 1){
+
+ if (this.state.screen === "fadeOut") {
+ if (this.state.hasPointer === 1) {
this.state.hasPointer = 2
this.canvas.style.cursor = ""
}
-
- if(!this.fadeScreenBlack){
+
+ if (!this.fadeScreenBlack) {
this.fadeScreenBlack = true
this.fadeScreen.style.backgroundColor = "#000"
}
var elapsed = ms - this.state.screenMS
-
- if(elapsed >= 1000){
+
+ if (elapsed >= 1000) {
this.clean()
this.controller.songSelection(true, this.showWarning)
}
}
-
+
ctx.restore()
}
-
- getNumber(score, start, elapsed){
+
+ getNumber(score, start, elapsed) {
var numberPos = Math.floor((elapsed - start) / this.frame)
- if(numberPos < 0){
+ if (numberPos < 0) {
return ""
}
var output = ""
- for(var i = 0; i < score.length; i++){
- if(numberPos < 30 * (i + 1)){
+ for (var i = 0; i < score.length; i++) {
+ if (numberPos < 30 * (i + 1)) {
this.state.countupShown = true
return this.numbers[numberPos % 30] + output
- }else{
+ } else {
output = score[score.length - i - 1] + output
}
}
return output
}
-
- getSound(id, p){
+
+ getSound(id, p) {
return assets.sounds[id + (this.multiplayer ? "_p" + (p + 1) : "")]
}
- playSound(id, p){
+ playSound(id, p) {
this.getSound(id, p).play()
}
- loopSound(id, p, args){
+ loopSound(id, p, args) {
this.getSound(id, p).playLoop(...args)
}
- stopSound(id, p){
+ stopSound(id, p) {
this.getSound(id, p).stop()
}
-
- mod(length, index){
+
+ mod(length, index) {
return ((index % length) + length) % length
}
-
- getMS(){
+
+ getMS() {
return Date.now()
}
-
- saveScore(){
- if(this.controller.saveScore){
- if(this.resultsObj.points < 0){
+
+ saveScore() {
+ if (this.controller.saveScore) {
+ if (this.resultsObj.points < 0) {
this.resultsObj.points = 0
}
var title = this.controller.selectedSong.originalTitle
@@ -929,11 +929,11 @@ class Scoresheet{
var oldScore = scoreStorage.get(hash, difficulty, true)
var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge)
var crown = ""
- if(clearReached){
+ if (clearReached) {
crown = this.resultsObj.bad === 0 ? "gold" : "silver"
}
- if(!oldScore || oldScore.points <= this.resultsObj.points){
- if(oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)){
+ if (!oldScore || oldScore.points <= this.resultsObj.points) {
+ if (oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)) {
crown = oldScore.crown
}
this.resultsObj.crown = crown
@@ -941,19 +941,63 @@ class Scoresheet{
delete this.resultsObj.difficulty
delete this.resultsObj.gauge
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).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)) {
oldScore.crown = crown
scoreStorage.add(hash, difficulty, oldScore, true, title).catch(() => {
- this.showWarning = {name: "scoreSaveFailed"}
+ this.showWarning = { name: "scoreSaveFailed" }
})
}
+
+ // Check leaderboard eligibility
+ this.checkLeaderboard(hash, difficulty, this.resultsObj.points)
}
this.scoreSaved = true
}
-
- clean(){
+
+ checkLeaderboard(songId, difficulty, score) {
+ if (!songId || this.controller.autoPlayEnabled || this.multiplayer) {
+ return
+ }
+
+ var data = {
+ song_id: String(songId),
+ difficulty: difficulty,
+ score: score
+ }
+
+ const xhr = new XMLHttpRequest()
+ xhr.open("POST", "api/score/check")
+ xhr.setRequestHeader("Content-Type", "application/json")
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ try {
+ var result = JSON.parse(xhr.responseText)
+ if (result.status === "ok" && result.qualifies) {
+ new LeaderboardSubmit(
+ songId,
+ difficulty,
+ score,
+ result.rank,
+ (username) => {
+ console.log("Score submitted to leaderboard as:", username)
+ },
+ () => { }
+ )
+ }
+ } catch (e) {
+ console.error("Leaderboard check response error:", e)
+ }
+ }
+ }
+ xhr.onerror = () => {
+ console.error("Leaderboard check network error")
+ }
+ xhr.send(JSON.stringify(data))
+ }
+
+ clean() {
this.keyboard.clean()
this.gamepad.clean()
this.draw.clean()
@@ -962,13 +1006,13 @@ class Scoresheet{
snd.buffer.loadSettings()
this.redrawRunning = false
pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
- if(this.touchEnabled){
+ if (this.touchEnabled) {
pageEvents.remove(document.getElementById("touch-full-btn"), "touchend")
}
- if(this.session){
+ if (this.session) {
pageEvents.remove(p2, "message")
}
- if(!this.multiplayer){
+ if (!this.multiplayer) {
delete this.tetsuoHana
}
delete this.ctx
diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js
index edec6bc..0d0252f 100644
--- a/public/src/js/songselect.js
+++ b/public/src/js/songselect.js
@@ -1,19 +1,19 @@
-class SongSelect{
- constructor(...args){
+class SongSelect {
+ constructor(...args) {
this.init(...args)
}
- init(fromTutorial, fadeIn, touchEnabled, songId, showWarning){
+ init(fromTutorial, fadeIn, touchEnabled, songId, showWarning) {
this.touchEnabled = touchEnabled
-
+
loader.changePage("songselect", false)
this.canvas = document.getElementById("song-sel-canvas")
this.ctx = this.canvas.getContext("2d")
var resolution = settings.getItem("resolution")
var noSmoothing = resolution === "low" || resolution === "lowest"
- if(noSmoothing){
+ if (noSmoothing) {
this.ctx.imageSmoothingEnabled = false
}
- if(resolution === "lowest"){
+ if (resolution === "lowest") {
this.canvas.style.imageRendering = "pixelated"
}
@@ -21,7 +21,7 @@ class SongSelect{
let color = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
return `#${color}`;
}
-
+
this.songSkin = {
"selected": {
background: "#ffdb2c",
@@ -75,25 +75,25 @@ class SongSelect{
border: ["#fde9df", "#ce7553"],
outline: "#ce7553"
},
- // カスタム曲スキン
- "upload": {
- sort: 0,
- background: "#ffe57f",
- border: ["#ffd54f", "#ff9800"],
- outline: "#ffab40",
- },
- "keijiban": {
- sort: 0,
- background: "#1c1c1c",
- border: ["#000000", "#333333"],
- outline: "#222222",
- },
- "customSettings": {
- sort: 0,
- background: "#a5d6a7", // 緑色の背景
- border: ["#81c784", "#66bb6a"], // 緑色の境界線
- outline: "#388e3c" // 緑色のアウトライン
- },
+ // カスタム曲スキン
+ "upload": {
+ sort: 0,
+ background: "#ffe57f",
+ border: ["#ffd54f", "#ff9800"],
+ outline: "#ffab40",
+ },
+ "keijiban": {
+ sort: 0,
+ background: "#1c1c1c",
+ border: ["#000000", "#333333"],
+ outline: "#222222",
+ },
+ "customSettings": {
+ sort: 0,
+ background: "#a5d6a7", // 緑色の背景
+ border: ["#81c784", "#66bb6a"], // 緑色の境界線
+ outline: "#388e3c" // 緑色のアウトライン
+ },
"default": {
sort: null,
background: `${rand()}`,
@@ -102,12 +102,12 @@ class SongSelect{
infoFill: `${rand()}`
}
}
-
+
var songSkinLength = Object.keys(this.songSkin).length
- for(var i in assets.categories){
+ for (var i in assets.categories) {
var category = assets.categories[i]
- if(!this.songSkin[category.title] && category.songSkin){
- if(category.songSkin.sort === null){
+ if (!this.songSkin[category.title] && category.songSkin) {
+ if (category.songSkin.sort === null) {
category.songSkin.sort = songSkinLength + 1
}
category.songSkin.id = category.id
@@ -115,13 +115,13 @@ class SongSelect{
}
}
this.songSkin["default"].sort = songSkinLength + 1
-
+
this.font = strings.font
-
+
this.search = new Search(this)
this.songs = []
- for(let song of assets.songs){
+ for (let song of assets.songs) {
var title = this.getLocalTitle(song.title, song.title_lang)
song.titlePrepared = title ? fuzzysort.prepare(this.search.normalizeString(title)) : null
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
@@ -131,13 +131,13 @@ class SongSelect{
this.songs.sort((a, b) => {
var catA = a.originalCategory in this.songSkin ? this.songSkin[a.originalCategory] : this.songSkin.default
var catB = b.originalCategory in this.songSkin ? this.songSkin[b.originalCategory] : this.songSkin.default
- if(catA.sort !== catB.sort){
+ if (catA.sort !== catB.sort) {
return catA.sort > catB.sort ? 1 : -1
- }else if(a.originalCategory !== b.originalCategory){
+ } else if (a.originalCategory !== b.originalCategory) {
return a.originalCategory > b.originalCategory ? 1 : -1
- }else if(a.order !== b.order){
+ } else if (a.order !== b.order) {
return a.order > b.order ? 1 : -1
- }else{
+ } else {
return a.id > b.id ? 1 : -1
}
})
@@ -145,7 +145,7 @@ class SongSelect{
if (titlesort === "true") {
this.songs.sort((a, b) => a.title.localeCompare(b.title));
}
- if(assets.songs.length){
+ if (assets.songs.length) {
this.songs.push({
title: strings.back,
skin: this.songSkin.back,
@@ -167,11 +167,11 @@ class SongSelect{
p2Enabled: true
})
}
- if(touchEnabled){
- if(fromTutorial === "tutorial"){
+ if (touchEnabled) {
+ if (fromTutorial === "tutorial") {
fromTutorial = false
}
- }else{
+ } else {
this.songs.push({
title: strings.howToPlay,
skin: this.songSkin.tutorial,
@@ -180,7 +180,7 @@ class SongSelect{
})
}
this.showWarning = showWarning
- if(showWarning && showWarning.name === "scoreSaveFailed"){
+ if (showWarning && showWarning.name === "scoreSaveFailed") {
scoreStorage.scoreSaveFailed = true
}
this.songs.push({
@@ -195,14 +195,14 @@ class SongSelect{
action: "settings",
category: strings.random
})
-
+
var showCustom = false
- if(gameConfig.google_credentials.gdrive_enabled){
+ if (gameConfig.google_credentials.gdrive_enabled) {
showCustom = true
- }else if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
+ } else if ("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))) {
showCustom = true
}
- if(showCustom){
+ if (showCustom) {
this.songs.push({
title: assets.customSongs ? strings.customSongs.default : strings.customSongs.title,
skin: this.songSkin.customSongs,
@@ -217,68 +217,68 @@ class SongSelect{
category: strings.random
})
- // カスタムメニュー
- // this.songs.push({
- // title: "ソースコード",
- // skin: this.songSkin.sourceCode,
- // action: "sourceCode",
- // });
- // for (let i = 0; i < 10; i++) {
- this.songs.push({
- title: "曲を投稿!",
- skin: this.songSkin.upload,
- action: "upload",
- });
- // }
- this.songs.push({
- title: "掲示板",
- skin: this.songSkin.keijiban,
- action: "keijiban",
- });
+ // カスタムメニュー
+ // this.songs.push({
+ // title: "ソースコード",
+ // skin: this.songSkin.sourceCode,
+ // action: "sourceCode",
+ // });
+ // for (let i = 0; i < 10; i++) {
+ this.songs.push({
+ title: "曲を投稿!",
+ skin: this.songSkin.upload,
+ action: "upload",
+ });
+ // }
+ this.songs.push({
+ title: "掲示板",
+ skin: this.songSkin.keijiban,
+ action: "keijiban",
+ });
- this.songs.push({
- title: "曲選択速度",
- skin: this.songSkin.customSettings,
- action: "songSelectingSpeed",
- });
-
- this.songs.push({
- title: "ばいそく",
- skin: this.songSkin.customSettings,
- action: "baisoku",
- });
+ this.songs.push({
+ title: "曲選択速度",
+ skin: this.songSkin.customSettings,
+ action: "songSelectingSpeed",
+ });
- this.songs.push({
- title: "ドロン",
- skin: this.songSkin.customSettings,
- action: "doron",
- });
+ this.songs.push({
+ title: "ばいそく",
+ skin: this.songSkin.customSettings,
+ action: "baisoku",
+ });
- this.songs.push({
- title: "あべこべ",
- skin: this.songSkin.customSettings,
- action: "abekobe",
- });
+ this.songs.push({
+ title: "ドロン",
+ skin: this.songSkin.customSettings,
+ action: "doron",
+ });
- this.songs.push({
- title: "でたらめ",
- skin: this.songSkin.customSettings,
- action: "detarame",
- });
+ this.songs.push({
+ title: "あべこべ",
+ skin: this.songSkin.customSettings,
+ action: "abekobe",
+ });
+
+ this.songs.push({
+ title: "でたらめ",
+ skin: this.songSkin.customSettings,
+ action: "detarame",
+ });
- this.songs.push({
- title: "タイトル順で並べ替え",
- skin: this.songSkin.customSettings,
- action: "titlesort",
- });
+ this.songs.push({
+ title: "タイトル順で並べ替え",
+ skin: this.songSkin.customSettings,
+ action: "titlesort",
+ });
this.songs.push({
title: strings.back,
skin: this.songSkin.back,
action: "back"
})
-
+
this.songAsset = {
marginTop: 104,
marginLeft: 18,
@@ -291,7 +291,7 @@ class SongSelect{
innerBorder: 8,
letterBorder: 12
}
-
+
this.diffOptions = [{
text: strings.back,
fill: "#efb058",
@@ -310,9 +310,15 @@ class SongSelect{
iconName: "download",
iconFill: "#e7cbe1",
letterSpacing: 4
+ }, {
+ text: strings.leaderboard,
+ fill: "#5bc0de",
+ iconName: "leaderboard",
+ iconFill: "#a0d8ef",
+ letterSpacing: 2
}]
this.optionsList = [strings.none, strings.auto, strings.netplay]
-
+
this.draw = new CanvasDraw(noSmoothing)
this.songTitleCache = new CanvasCache(noSmoothing)
this.selectTextCache = new CanvasCache(noSmoothing)
@@ -321,64 +327,64 @@ class SongSelect{
this.sessionCache = new CanvasCache(noSmoothing)
this.currentSongCache = new CanvasCache(noSmoothing)
this.nameplateCache = new CanvasCache(noSmoothing)
-
-
+
+
this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni]
this.difficultyId = ["easy", "normal", "hard", "oni", "ura"]
-
+
this.sessionText = {
"sessionstart": strings.sessionStart,
"sessionend": strings.sessionEnd
}
-
+
this.selectedSong = 0
this.selectedDiff = 0
this.lastCurrentSong = {}
this.lastRandom = false
assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506)
-
- if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage) && !songId){
+
+ if (!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage) && !songId) {
fromTutorial = touchEnabled ? "about" : "tutorial"
}
- if(p2.session || assets.customSongs && "customSelected" in localStorage){
+ if (p2.session || assets.customSongs && "customSelected" in localStorage) {
fromTutorial = false
}
-
+
this.drumSounds = settings.getItem("latency").drumSounds
this.playedSounds = {}
-
+
var songIdIndex = -1
var newSelected = -1
- if(fromTutorial){
+ if (fromTutorial) {
newSelected = this.songs.findIndex(song => song.action === fromTutorial)
}
- if(newSelected !== -1){
+ if (newSelected !== -1) {
this.setSelectedSong(newSelected, false)
this.playBgm(true)
- }else{
- if(songId){
+ } else {
+ if (songId) {
songIdIndex = this.songs.findIndex(song => song.id === songId)
- if(songIdIndex === -1){
+ if (songIdIndex === -1) {
this.clearHash()
}
}
- if(songIdIndex !== -1){
+ if (songIdIndex !== -1) {
this.setSelectedSong(songIdIndex, false)
- }else if(assets.customSongs){
+ } else if (assets.customSongs) {
this.setSelectedSong(Math.min(Math.max(0, assets.customSelected), this.songs.length - 1), false)
- }else if((!p2.session || fadeIn) && "selectedSong" in localStorage){
- this.setSelectedSong(Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1), false)
+ } else if ((!p2.session || fadeIn) && "selectedSong" in localStorage) {
+ this.setSelectedSong(Math.min(Math.max(0, localStorage["selectedSong"] | 0), this.songs.length - 1), false)
}
- if(!this.showWarning){
+ if (!this.showWarning) {
this.playSound(songIdIndex !== -1 ? "v_diffsel" : "v_songsel")
}
snd.musicGain.fadeOut()
this.playBgm(false)
}
- if("selectedDiff" in localStorage){
- this.selectedDiff = Math.min(Math.max(0, localStorage["selectedDiff"] |0), this.diffOptions.length + 3)
+ if ("selectedDiff" in localStorage) {
+ this.selectedDiff = Math.min(Math.max(0, localStorage["selectedDiff"] | 0), this.diffOptions.length + 3)
}
-
+
this.songSelect = document.getElementById("song-select")
this.songTypes = [
"01 Pop",
@@ -408,10 +414,10 @@ class SongSelect{
this.updateTypeLabel()
var cat = this.songs[this.selectedSong].originalCategory
this.drawBackground(cat)
-
+
this.previewId = 0
this.previewList = Array(5)
-
+
var skipStart = fromTutorial || p2.session
this.state = {
screen: songIdIndex !== -1 ? "difficulty" : (fadeIn ? "titleFadeIn" : (skipStart ? "song" : "title")),
@@ -436,9 +442,9 @@ class SongSelect{
}
this.wheelScrolls = 0
this.wheelTimer = 0
-
+
this.startPreview(true)
-
+
this.pressedKeys = {}
this.keyboard = new Keyboard({
confirm: ["enter", "space", "don_l", "don_r"],
@@ -466,18 +472,18 @@ class SongSelect{
jump_left: ["lb"],
jump_right: ["rb"]
}, this.keyPress.bind(this))
-
- if(!assets.customSongs){
+
+ if (!assets.customSongs) {
this.startP2()
}
-
+
pageEvents.add(loader.screen, "mousemove", this.mouseMove.bind(this))
pageEvents.add(loader.screen, "mouseleave", () => {
this.state.moveHover = null
})
pageEvents.add(loader.screen, ["mousedown", "touchstart"], this.mouseDown.bind(this))
pageEvents.add(this.canvas, "touchend", this.touchEnd.bind(this))
- if(touchEnabled && fullScreenSupported){
+ if (touchEnabled && fullScreenSupported) {
this.touchFullBtn = document.getElementById("touch-full-btn")
this.touchFullBtn.style.display = "block"
pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen)
@@ -487,32 +493,32 @@ class SongSelect{
this.selectable = document.getElementById("song-sel-selectable")
this.selectableText = ""
-
+
this.redrawRunning = true
this.redrawBind = this.redraw.bind(this)
this.redraw()
pageEvents.send("song-select")
pageEvents.send("song-select-move", this.songs[this.selectedSong])
- if(songIdIndex !== -1){
+ if (songIdIndex !== -1) {
pageEvents.send("song-select-difficulty", this.songs[this.selectedSong])
}
}
- setAltText(element, text){
+ setAltText(element, text) {
element.innerText = text
element.setAttribute("alt", text)
}
- setSelectedSong(songIdx, drawBg=true){
+ setSelectedSong(songIdx, drawBg = true) {
if (songIdx < 0) {
return;
}
- if(drawBg){
+ if (drawBg) {
var cat = this.songs[songIdx].originalCategory
- if(cat){
+ if (cat) {
this.drawBackground(cat)
- }else{
+ } else {
this.drawBackground(false)
}
}
@@ -520,105 +526,105 @@ class SongSelect{
this.selectedSong = songIdx
}
- keyPress(pressed, name, event, repeat){
- if(pressed){
- if(!this.pressedKeys[name]){
+ keyPress(pressed, name, event, repeat) {
+ if (pressed) {
+ if (!this.pressedKeys[name]) {
this.pressedKeys[name] = this.getMS() + (name === "left" || name === "right" ? 150 : 300)
}
- }else{
+ } else {
this.pressedKeys[name] = 0
return
}
- if(name === "ctrl" || name === "shift" || !this.redrawRunning){
+ if (name === "ctrl" || name === "shift" || !this.redrawRunning) {
return
}
var ctrl = event ? event.ctrlKey : (this.pressedKeys["ctrl"] || this.pressedKeys["ctrlGamepad"])
var shift = event ? event.shiftKey : this.pressedKeys["shift"]
- if(this.state.showWarning){
- if(name === "confirm"){
+ if (this.state.showWarning) {
+ if (name === "confirm") {
this.playSound("se_don")
this.state.showWarning = false
this.showWarning = false
}
- }else if(this.search.opened){
+ } else if (this.search.opened) {
this.search.keyPress(pressed, name, event, repeat, ctrl)
- }else if(this.state.screen === "song"){
- if(event && event.keyCode && event.keyCode === 70 && ctrl){
+ } else if (this.state.screen === "song") {
+ if (event && event.keyCode && event.keyCode === 70 && ctrl) {
this.search.display()
- if(event){
+ if (event) {
event.preventDefault()
}
- }else if(name === "confirm"){
+ } else if (name === "confirm") {
this.toSelectDifficulty()
- }else if(name === "back"){
+ } else if (name === "back") {
this.toTitleScreen()
- }else if(name === "session"){
+ } else if (name === "session") {
this.toSession()
- }else if(name === "left"){
- if(shift){
- if(!repeat){ this.changeType(-1) }
- }else{
+ } else if (name === "left") {
+ if (shift) {
+ if (!repeat) { this.changeType(-1) }
+ } else {
this.moveToSong(-1)
}
- }else if(name === "right"){
- if(shift){
- if(!repeat){ this.changeType(1) }
- }else{
+ } else if (name === "right") {
+ if (shift) {
+ if (!repeat) { this.changeType(1) }
+ } else {
this.moveToSong(1)
}
- }else if(name === "jump_left" && !repeat){
+ } else if (name === "jump_left" && !repeat) {
this.changeType(-1)
- }else if(name === "jump_right" && !repeat){
+ } else if (name === "jump_right" && !repeat) {
this.changeType(1)
- }else if(name === "mute" || name === "ctrlGamepad"){
+ } else if (name === "mute" || name === "ctrlGamepad") {
this.endPreview(true)
this.playBgm(false)
}
- }else if(this.state.screen === "difficulty"){
- if(event && event.keyCode && event.keyCode === 70 && ctrl){
+ } else if (this.state.screen === "difficulty") {
+ if (event && event.keyCode && event.keyCode === 70 && ctrl) {
this.search.display()
- if(event){
+ if (event) {
event.preventDefault()
}
- }else if(name === "confirm"){
- if(this.selectedDiff === 0){
+ } else if (name === "confirm") {
+ if (this.selectedDiff === 0) {
this.toSongSelect()
- }else if(this.selectedDiff === 2){
+ } else if (this.selectedDiff === 2) {
this.toDownload()
- }else if(this.selectedDiff === 3){
- this.toDelete()
- }else if(this.selectedDiff === 1){
+ } else if (this.selectedDiff === 3) {
+ this.toLeaderboard()
+ } else if (this.selectedDiff === 1) {
this.toOptions(1)
- }else{
+ } else {
this.toLoadSong(this.selectedDiff - this.diffOptions.length, shift, ctrl)
}
- }else if(name === "back" || name === "session"){
+ } else if (name === "back" || name === "session") {
this.toSongSelect()
- }else if(name === "left"){
+ } else if (name === "left") {
this.moveToDiff(-1)
- }else if(name === "right"){
+ } else if (name === "right") {
this.moveToDiff(1)
- }else if(this.selectedDiff === 1 && (name === "up" || name === "down")){
+ } else if (this.selectedDiff === 1 && (name === "up" || name === "down")) {
this.toOptions(name === "up" ? -1 : 1)
- }else if(name === "mute" || name === "ctrlGamepad"){
+ } else if (name === "mute" || name === "ctrlGamepad") {
this.endPreview(true)
this.playBgm(false)
}
- }else if(this.state.screen === "title" || this.state.screen === "titleFadeIn"){
- if(event && event.keyCode && event.keyCode === 70 && ctrl){
+ } else if (this.state.screen === "title" || this.state.screen === "titleFadeIn") {
+ if (event && event.keyCode && event.keyCode === 70 && ctrl) {
this.search.display()
- if(event){
+ if (event) {
event.preventDefault()
}
}
}
}
- updateTypeLabel(){
+ updateTypeLabel() {
this.setAltText(this.typeLabel, this.songTypes[this.songTypeIndex])
}
- changeType(delta){
+ changeType(delta) {
this.songTypeIndex = (this.songTypeIndex + delta + this.songTypes.length) % this.songTypes.length
localStorage.setItem("songTypeIndex", this.songTypeIndex)
this.updateTypeLabel()
@@ -628,28 +634,28 @@ class SongSelect{
assets.songsDefault = songs
assets.songs = assets.songsDefault
new SongSelect(false, false, this.touchEnabled)
- }).catch(() => {})
+ }).catch(() => { })
}
-
- mouseDown(event){
- if(event.target === this.selectable || event.target.parentNode === this.selectable){
+
+ mouseDown(event) {
+ if (event.target === this.selectable || event.target.parentNode === this.selectable) {
this.selectable.focus()
- }else if(event.target.tagName !== "INPUT"){
+ } else if (event.target.tagName !== "INPUT") {
getSelection().removeAllRanges()
this.selectable.blur()
}
- if(event.target !== this.canvas || !this.redrawRunning){
+ if (event.target !== this.canvas || !this.redrawRunning) {
return
}
- if(event.type === "mousedown"){
- if(event.which !== 1){
+ if (event.type === "mousedown") {
+ if (event.which !== 1) {
return
}
var mouse = this.mouseOffset(event.offsetX, event.offsetY)
var shift = event.shiftKey
var ctrl = event.ctrlKey
var touch = false
- }else{
+ } else {
event.preventDefault()
var x = event.touches[0].pageX - this.canvas.offsetLeft
var y = event.touches[0].pageY - this.canvas.offsetTop
@@ -658,232 +664,232 @@ class SongSelect{
var ctrl = false
var touch = true
}
- if(this.state.showWarning){
- if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){
+ if (this.state.showWarning) {
+ if (408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550) {
this.playSound("se_don")
this.state.showWarning = false
this.showWarning = false
}
- }else if(this.state.screen === "song"){
- if(20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)){
+ } else if (this.state.screen === "song") {
+ if (20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)) {
this.categoryJump(mouse.x < 640 ? -1 : 1)
- }else if(!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts){
+ } else if (!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts) {
this.toAccount()
- }else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){
+ } else if (p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603) {
this.toSession()
- }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){
+ } else if (!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs) {
this.toSession()
- }else{
+ } else {
var moveBy = this.songSelMouse(mouse.x, mouse.y)
- if(moveBy === 0){
+ if (moveBy === 0) {
this.toSelectDifficulty()
- }else if(moveBy !== null){
+ } else if (moveBy !== null) {
this.moveToSong(moveBy)
}
}
- }else if(this.state.screen === "difficulty"){
+ } else if (this.state.screen === "difficulty") {
var moveBy = this.diffSelMouse(mouse.x, mouse.y)
- if(mouse.x < 183 || mouse.x > 1095 || mouse.y < 54 || mouse.y > 554){
+ if (mouse.x < 183 || mouse.x > 1095 || mouse.y < 54 || mouse.y > 554) {
this.toSongSelect()
- }else if(moveBy === 0){
+ } else if (moveBy === 0) {
this.selectedDiff = 0
this.toSongSelect()
- }else if(moveBy === 2){
+ } else if (moveBy === 2) {
this.toDownload()
- }else if(moveBy === 3){
- this.toDelete()
- }else if(moveBy === 1){
+ } else if (moveBy === 3) {
+ this.toLeaderboard()
+ } else if (moveBy === 1) {
this.toOptions(1)
- }else if(moveBy === "maker"){
+ } else if (moveBy === "maker") {
window.open(this.songs[this.selectedSong].maker.url)
- }else if(moveBy === this.diffOptions.length + 4){
+ } else if (moveBy === this.diffOptions.length + 4) {
this.state.ura = !this.state.ura
this.playSound("se_ka", 0, p2.session ? p2.player : false)
- if(this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura){
+ if (this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura) {
this.state.move = -1
}
- }else if(moveBy !== null){
+ } else if (moveBy !== null) {
this.toLoadSong(moveBy - this.diffOptions.length, shift, ctrl, touch)
}
}
}
- touchEnd(event){
+ touchEnd(event) {
event.preventDefault()
}
- mouseWheel(event){
- if(this.state.screen === "song" && this.state.focused){
+ mouseWheel(event) {
+ if (this.state.screen === "song" && this.state.focused) {
this.wheelTimer = this.getMS()
- if(event.deltaY < 0) {
+ if (event.deltaY < 0) {
this.wheelScrolls--
- }else if(event.deltaY > 0){
+ } else if (event.deltaY > 0) {
this.wheelScrolls++
}
}
}
- mouseMove(event){
+ mouseMove(event) {
var mouse = this.mouseOffset(event.offsetX, event.offsetY)
var moveTo = null
- if(this.state.showWarning){
- if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){
+ if (this.state.showWarning) {
+ if (408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550) {
moveTo = "showWarning"
}
- }else if(this.state.screen === "song" && !this.search.opened){
- if(20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)){
+ } else if (this.state.screen === "song" && !this.search.opened) {
+ if (20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)) {
moveTo = mouse.x < 640 ? "categoryPrev" : "categoryNext"
- }else if(!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts){
+ } else if (!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts) {
moveTo = "account"
- }else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){
+ } else if (p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603) {
moveTo = "session"
- }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){
+ } else if (!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs) {
moveTo = "session"
- }else{
+ } else {
var moveTo = this.songSelMouse(mouse.x, mouse.y)
- if(moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].courses){
+ if (moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].courses) {
this.state.mouseMoveMS = this.getMS() - this.songSelecting.speed
}
}
this.state.moveHover = moveTo
- }else if(this.state.screen === "difficulty"){
+ } else if (this.state.screen === "difficulty") {
var moveTo = this.diffSelMouse(mouse.x, mouse.y)
- if(moveTo === null && this.state.moveHover === this.selectedDiff){
+ if (moveTo === null && this.state.moveHover === this.selectedDiff) {
this.state.mouseMoveMS = this.getMS() - 1000
}
this.state.moveHover = moveTo
}
this.pointer(moveTo !== null)
}
- mouseOffset(offsetX, offsetY){
+ mouseOffset(offsetX, offsetY) {
return {
x: (offsetX * this.pixelRatio - this.winW / 2) / this.ratio + 1280 / 2,
y: (offsetY * this.pixelRatio - this.winH / 2) / this.ratio + 720 / 2
}
}
- pointer(enabled){
- if(!this.canvas){
+ pointer(enabled) {
+ if (!this.canvas) {
return
}
- if(enabled && this.state.hasPointer === false){
+ if (enabled && this.state.hasPointer === false) {
this.canvas.style.cursor = "pointer"
this.state.hasPointer = true
- }else if(!enabled && this.state.hasPointer === true){
+ } else if (!enabled && this.state.hasPointer === true) {
this.canvas.style.cursor = ""
this.state.hasPointer = false
}
}
-
- songSelMouse(x, y){
- if(this.state.locked === 0 && this.songAsset.marginTop <= y && y <= this.songAsset.marginTop + this.songAsset.height){
+
+ songSelMouse(x, y) {
+ if (this.state.locked === 0 && this.songAsset.marginTop <= y && y <= this.songAsset.marginTop + this.songAsset.height) {
x -= 1280 / 2
var dir = x > 0 ? 1 : -1
x = Math.abs(x)
var selectedWidth = this.songAsset.selectedWidth
- if(!this.songs[this.selectedSong].courses){
+ if (!this.songs[this.selectedSong].courses) {
selectedWidth = this.songAsset.width
}
var moveBy = Math.ceil((x - selectedWidth / 2 - this.songAsset.marginLeft / 2) / (this.songAsset.width + this.songAsset.marginLeft)) * dir
- if(moveBy / dir > 0){
+ if (moveBy / dir > 0) {
return moveBy
- }else{
+ } else {
return 0
}
}
return null
}
- diffSelMouse(x, y){
- if(this.state.locked === 0){
- if(223 < x && x < 223 + 72 * this.diffOptions.length && 132 < y && y < 436){
+ diffSelMouse(x, y) {
+ if (this.state.locked === 0) {
+ if (223 < x && x < 223 + 72 * this.diffOptions.length && 132 < y && y < 436) {
return Math.floor((x - 223) / 72)
- }else if(this.songs[this.selectedSong].maker && this.songs[this.selectedSong].maker.id > 0 && this.songs[this.selectedSong].maker.url && x > 230 && x < 485 && y > 446 && y < 533) {
+ } else if (this.songs[this.selectedSong].maker && this.songs[this.selectedSong].maker.id > 0 && this.songs[this.selectedSong].maker.url && x > 230 && x < 485 && y > 446 && y < 533) {
return "maker"
- }else if(550 < x && x < 1050 && 109 < y && y < 538){
+ } else if (550 < x && x < 1050 && 109 < y && y < 538) {
var moveBy = Math.floor((x - 550) / ((1050 - 550) / 5)) + this.diffOptions.length
var currentSong = this.songs[this.selectedSong]
- if(
+ if (
this.state.ura
&& moveBy === this.diffOptions.length + 3
|| currentSong.courses[
- this.difficultyId[moveBy - this.diffOptions.length]
+ this.difficultyId[moveBy - this.diffOptions.length]
]
- ){
+ ) {
return moveBy
}
}
}
return null
}
-
- moveToSong(moveBy, fromP2){
+
+ moveToSong(moveBy, fromP2) {
var ms = this.getMS()
- if(p2.session && !fromP2){
- if(!this.state.selLock && ms > this.state.moveMS + 800){
+ if (p2.session && !fromP2) {
+ if (!this.state.selLock && ms > this.state.moveMS + 800) {
this.state.selLock = true
p2.send("songsel", {
song: this.mod(this.songs.length, this.selectedSong + moveBy)
})
}
- }else if(this.state.locked !== 1 || fromP2){
- if(this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded && (this.state.locked === 0 || fromP2)){
+ } else if (this.state.locked !== 1 || fromP2) {
+ if (this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded && (this.state.locked === 0 || fromP2)) {
this.state.moveMS = ms
- }else{
+ } else {
this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize
}
this.state.move = moveBy
this.state.lastMove = moveBy
this.state.locked = 1
this.state.moveHover = null
-
+
var lastMoveMul = Math.pow(Math.abs(moveBy), 1 / 4)
var changeSpeed = this.songSelecting.speed * lastMoveMul
var resize = changeSpeed * this.songSelecting.resize / lastMoveMul
var scrollDelay = changeSpeed * this.songSelecting.scrollDelay
var resize2 = changeSpeed - resize
var scroll = resize2 - resize - scrollDelay * 2
-
+
var soundsDelay = Math.abs((scroll + resize) / moveBy)
this.lastMoveBy = fromP2 ? fromP2.player : false
-
- for(var i = 0; i < Math.abs(moveBy) - 1; i++){
+
+ for (var i = 0; i < Math.abs(moveBy) - 1; i++) {
this.playSound("se_ka", (resize + i * soundsDelay) / 1000, fromP2 ? fromP2.player : false)
}
this.pointer(false)
}
}
-
- categoryJump(moveBy, fromP2){
- if(p2.session && !fromP2){
+
+ categoryJump(moveBy, fromP2) {
+ if (p2.session && !fromP2) {
var ms = this.getMS()
- if(!this.state.selLock && ms > this.state.moveMS + 800){
+ if (!this.state.selLock && ms > this.state.moveMS + 800) {
this.state.selLock = true
p2.send("catjump", {
song: this.selectedSong,
move: moveBy
})
}
- }else if(this.state.locked !== 1 || fromP2){
+ } else if (this.state.locked !== 1 || fromP2) {
this.state.catJump = true
this.state.move = moveBy;
this.state.locked = 1
-
+
this.endPreview()
this.playSound("se_jump", 0, fromP2 ? fromP2.player : false)
}
}
- moveToDiff(moveBy){
- if(this.state.locked !== 1){
+ moveToDiff(moveBy) {
+ if (this.state.locked !== 1) {
this.state.move = moveBy
this.state.moveMS = this.getMS() - 500
this.state.locked = 1
this.playSound("se_ka", 0, p2.session ? p2.player : false)
}
}
-
- toSelectDifficulty(fromP2, playVoice=true){
+
+ toSelectDifficulty(fromP2, playVoice = true) {
var currentSong = this.songs[this.selectedSong]
- if(p2.session && !fromP2 && (!currentSong.action || !currentSong.p2Enabled)){
- if(this.songs[this.selectedSong].courses){
- if(!this.state.selLock){
+ if (p2.session && !fromP2 && (!currentSong.action || !currentSong.p2Enabled)) {
+ if (this.songs[this.selectedSong].courses) {
+ if (!this.state.selLock) {
this.state.selLock = true
p2.send("songsel", {
song: this.selectedSong,
@@ -892,10 +898,10 @@ class SongSelect{
})
}
}
- }else if(this.state.locked === 0 || fromP2){
+ } else if (this.state.locked === 0 || fromP2) {
this.search.remove()
- if(currentSong.courses){
- if(currentSong.unloaded){
+ if (currentSong.courses) {
+ if (currentSong.unloaded) {
return
}
@@ -905,168 +911,168 @@ class SongSelect{
this.state.locked = true
this.state.moveHover = null
this.state.ura = 0
- if(this.selectedDiff === this.diffOptions.length + 4){
+ if (this.selectedDiff === this.diffOptions.length + 4) {
this.selectedDiff = this.diffOptions.length + 3
}
-
+
this.playSound("se_don", 0, fromP2 ? fromP2.player : false)
assets.sounds["v_songsel"].stop()
- if(!this.showWarning && prevScreen !== "difficulty" && playVoice){
+ if (!this.showWarning && prevScreen !== "difficulty" && playVoice) {
this.playSound("v_diffsel", 0.3)
}
pageEvents.send("song-select-difficulty", currentSong)
- }else if(currentSong.action === "back"){
+ } else if (currentSong.action === "back") {
this.toTitleScreen()
- }else if(currentSong.action === "random"){
- do{
+ } else if (currentSong.action === "random") {
+ do {
var i = Math.floor(Math.random() * this.songs.length)
- }while(!this.songs[i].courses)
+ } while (!this.songs[i].courses)
this.setSelectedSong(i)
this.lastRandom = true
this.playBgm(false)
- this.toSelectDifficulty(false, playVoice=false)
+ this.toSelectDifficulty(false, playVoice = false)
pageEvents.send("song-select-random")
- }else if(currentSong.action === "search"){
+ } else if (currentSong.action === "search") {
this.search.display(true)
- }else if(currentSong.action === "tutorial"){
+ } else if (currentSong.action === "tutorial") {
this.toTutorial()
- }else if(currentSong.action === "about"){
+ } else if (currentSong.action === "about") {
this.toAbout()
- }else if(currentSong.action === "settings"){
+ } else if (currentSong.action === "settings") {
this.toSettings()
- }else if(currentSong.action === "customSongs"){
+ } else if (currentSong.action === "customSongs") {
this.toCustomSongs()
- }else if(currentSong.action === "plugins"){
+ } else if (currentSong.action === "plugins") {
this.toPlugins()
}
- // カスタムメニューの実行処理
- else if (currentSong.action === "sourceCode") {
- this.playSound("se_don");
- setTimeout(() => {
- open("https://github.com/yuukialpha/taiko-web","_blank");
- }, 500);
- } else if (currentSong.action === "upload") {
- this.playSound("se_don");
- setTimeout(() => {
- window.location.href = "https://zizhipu.taiko.asia";
- }, 100);
- } else if (currentSong.action === "keijiban") {
- this.playSound("se_don");
- setTimeout(() => {
- window.location.href = "https://litey.trade/";
- }, 100);
- } else if (currentSong.action === "songSelectingSpeed") {
- this.playSound("se_don");
- setTimeout(() => {
- let songSelectingSpeed = localStorage.getItem("sss") ?? "400";
- const pro = prompt("曲選択速度を入力してね!", songSelectingSpeed);
- if (pro === null) {
- // キャンセル
- } else if (pro === "") {
- songSelectingSpeed = "400";
- } else {
- songSelectingSpeed = pro;
- }
- const preValue = localStorage.getItem("sss") ?? "400";
- localStorage.setItem("sss", songSelectingSpeed.toString());
- if (preValue !== songSelectingSpeed) {
- location.reload();
- }
- }, 100);
- } else if (currentSong.action === "baisoku") {
- this.playSound("se_don");
- setTimeout(() => {
- let baisoku = localStorage.getItem("baisoku") ?? "1";
- const input = prompt("ばいそくの倍率を入力してね!", baisoku);
- if (input === null) {
- // キャンセル
- } else if (input === "") {
- baisoku = "1";
- } else {
- baisoku = input;
- }
- localStorage.setItem("baisoku", baisoku.toString());
- }, 100);
- } else if (currentSong.action === "doron") {
- this.playSound("se_don");
- setTimeout(() => {
- let doron = localStorage.getItem("doron") ?? "false";
- const input = prompt("ドロンを有効にするには\"true\"を入力してね!", doron);
- if (input === null) {
- // キャンセル
- } else if (input === "") {
- doron = "false";
- } else {
- doron = input;
- }
- localStorage.setItem("doron", doron);
- }, 100);
- } else if (currentSong.action === "abekobe") {
- this.playSound("se_don");
- setTimeout(() => {
- let abekobe = localStorage.getItem("abekobe") ?? "false";
- const input = prompt("あべこべを有効にするには\"true\"を入力してね!", abekobe);
- if (input === null) {
- // キャンセル
- } else if (input === "") {
- abekobe = "false";
- } else {
- abekobe = input;
- }
- localStorage.setItem("abekobe", abekobe);
- }, 100);
- } else if (currentSong.action === "detarame") {
- this.playSound("se_don");
- setTimeout(() => {
- let detarame = localStorage.getItem("detarame") ?? "0";
- const input = prompt("でたらめになる確率をパーセントで入力してね!", detarame);
- if (input === null) {
- // キャンセル
- } else if (input === "") {
- detarame = "0";
- } else {
- detarame = input;
- }
- localStorage.setItem("detarame", detarame);
- }, 100);
- } else if (currentSong.action === "titlesort") {
- this.playSound("se_don");
- setTimeout(() => {
- let titlesort = localStorage.getItem("titlesort") ?? "false";
- const input = prompt("タイトル順で並べ替えするには\"true\"を入力してね!", titlesort);
- if (input === null) {
- // キャンセル
- } else if (input === "") {
- titlesort = "false";
- } else {
- titlesort = input;
- }
- const preValue = localStorage.getItem("titlesort") ?? "false";
- localStorage.setItem("titlesort", titlesort);
- if (preValue !== titlesort) {
- location.reload();
- }
- }, 100);
- }
+ // カスタムメニューの実行処理
+ else if (currentSong.action === "sourceCode") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ open("https://github.com/yuukialpha/taiko-web", "_blank");
+ }, 500);
+ } else if (currentSong.action === "upload") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ window.location.href = "https://zizhipu.taiko.asia";
+ }, 100);
+ } else if (currentSong.action === "keijiban") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ window.location.href = "https://litey.trade/";
+ }, 100);
+ } else if (currentSong.action === "songSelectingSpeed") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ let songSelectingSpeed = localStorage.getItem("sss") ?? "400";
+ const pro = prompt("曲選択速度を入力してね!", songSelectingSpeed);
+ if (pro === null) {
+ // キャンセル
+ } else if (pro === "") {
+ songSelectingSpeed = "400";
+ } else {
+ songSelectingSpeed = pro;
+ }
+ const preValue = localStorage.getItem("sss") ?? "400";
+ localStorage.setItem("sss", songSelectingSpeed.toString());
+ if (preValue !== songSelectingSpeed) {
+ location.reload();
+ }
+ }, 100);
+ } else if (currentSong.action === "baisoku") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ let baisoku = localStorage.getItem("baisoku") ?? "1";
+ const input = prompt("ばいそくの倍率を入力してね!", baisoku);
+ if (input === null) {
+ // キャンセル
+ } else if (input === "") {
+ baisoku = "1";
+ } else {
+ baisoku = input;
+ }
+ localStorage.setItem("baisoku", baisoku.toString());
+ }, 100);
+ } else if (currentSong.action === "doron") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ let doron = localStorage.getItem("doron") ?? "false";
+ const input = prompt("ドロンを有効にするには\"true\"を入力してね!", doron);
+ if (input === null) {
+ // キャンセル
+ } else if (input === "") {
+ doron = "false";
+ } else {
+ doron = input;
+ }
+ localStorage.setItem("doron", doron);
+ }, 100);
+ } else if (currentSong.action === "abekobe") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ let abekobe = localStorage.getItem("abekobe") ?? "false";
+ const input = prompt("あべこべを有効にするには\"true\"を入力してね!", abekobe);
+ if (input === null) {
+ // キャンセル
+ } else if (input === "") {
+ abekobe = "false";
+ } else {
+ abekobe = input;
+ }
+ localStorage.setItem("abekobe", abekobe);
+ }, 100);
+ } else if (currentSong.action === "detarame") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ let detarame = localStorage.getItem("detarame") ?? "0";
+ const input = prompt("でたらめになる確率をパーセントで入力してね!", detarame);
+ if (input === null) {
+ // キャンセル
+ } else if (input === "") {
+ detarame = "0";
+ } else {
+ detarame = input;
+ }
+ localStorage.setItem("detarame", detarame);
+ }, 100);
+ } else if (currentSong.action === "titlesort") {
+ this.playSound("se_don");
+ setTimeout(() => {
+ let titlesort = localStorage.getItem("titlesort") ?? "false";
+ const input = prompt("タイトル順で並べ替えするには\"true\"を入力してね!", titlesort);
+ if (input === null) {
+ // キャンセル
+ } else if (input === "") {
+ titlesort = "false";
+ } else {
+ titlesort = input;
+ }
+ const preValue = localStorage.getItem("titlesort") ?? "false";
+ localStorage.setItem("titlesort", titlesort);
+ if (preValue !== titlesort) {
+ location.reload();
+ }
+ }, 100);
+ }
}
this.pointer(false)
}
- toSongSelect(fromP2){
- if(p2.session && !fromP2){
- if(!this.state.selLock){
+ toSongSelect(fromP2) {
+ if (p2.session && !fromP2) {
+ if (!this.state.selLock) {
this.state.selLock = true
p2.send("songsel", {
song: this.lastRandom ? this.songs.findIndex(song => song.action === "random") : this.selectedSong
})
}
-
- }else if(fromP2 || this.state.locked !== 1){
+
+ } else if (fromP2 || this.state.locked !== 1) {
this.state.screen = "song"
this.state.screenMS = this.getMS()
this.state.locked = true
this.state.moveHover = null
- if(this.lastRandom){
+ if (this.lastRandom) {
this.endPreview(false)
this.setSelectedSong(this.songs.findIndex(song => song.action === "random"))
this.lastRandom = false
@@ -1078,45 +1084,45 @@ class SongSelect{
this.clearHash()
pageEvents.send("song-select-back")
}
- toLoadSong(difficulty, shift, ctrl, touch){
+ toLoadSong(difficulty, shift, ctrl, touch) {
this.clean()
var selectedSong = this.songs[this.selectedSong]
assets.sounds["v_diffsel"].stop()
this.playSound("se_don", 0, p2.session ? p2.player : false)
-
- try{
- if(assets.customSongs){
+
+ try {
+ if (assets.customSongs) {
assets.customSelected = this.selectedSong
localStorage["customSelected"] = this.selectedSong
- }else{
+ } else {
localStorage["selectedSong"] = this.selectedSong
}
localStorage["selectedDiff"] = difficulty + this.diffOptions.length
- }catch(e){}
-
- if(difficulty === 3 && this.state.ura){
+ } catch (e) { }
+
+ if (difficulty === 3 && this.state.ura) {
difficulty = 4
}
var autoplay = false
var multiplayer = false
- if(p2.session || this.state.options === 2){
+ if (p2.session || this.state.options === 2) {
multiplayer = true
- }else if(this.state.options === 1){
+ } else if (this.state.options === 1) {
autoplay = true
- }else if(shift){
+ } else if (shift) {
autoplay = shift
- }else if(p2.socket && p2.socket.readyState === 1 && !assets.customSongs){
+ } else if (p2.socket && p2.socket.readyState === 1 && !assets.customSongs) {
multiplayer = ctrl
}
var diff = this.difficultyId[difficulty]
-
+
new LoadSong({
"title": selectedSong.title,
"originalTitle": selectedSong.originalTitle,
"folder": selectedSong.id,
"difficulty": diff,
"category": selectedSong.category,
- "category_id":selectedSong.category_id,
+ "category_id": selectedSong.category_id,
"type": selectedSong.type,
"offset": selectedSong.offset,
"songSkin": selectedSong.songSkin,
@@ -1126,17 +1132,17 @@ class SongSelect{
"video": selectedSong.video,
}, autoplay, multiplayer, touch)
}
- toOptions(moveBy){
- if(!p2.session){
+ toOptions(moveBy) {
+ if (!p2.session) {
this.playSound("se_ka", 0, p2.session ? p2.player : false)
this.selectedDiff = 1
- do{
+ do {
this.state.options = this.mod(this.optionsList.length, this.state.options + moveBy)
- }while((!p2.socket || p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2)
+ } while ((!p2.socket || p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2)
}
}
- toTitleScreen(){
- if(!p2.session){
+ toTitleScreen() {
+ if (!p2.session) {
this.playSound("se_cancel")
this.clean()
setTimeout(() => {
@@ -1144,45 +1150,45 @@ class SongSelect{
}, 500)
}
}
- toTutorial(){
+ toTutorial() {
this.playSound("se_don")
this.clean()
setTimeout(() => {
new Tutorial(true)
}, 500)
}
- toAbout(){
+ toAbout() {
this.playSound("se_don")
this.clean()
setTimeout(() => {
new About(this.touchEnabled)
}, 500)
}
- toSettings(){
+ toSettings() {
this.playSound("se_don")
this.clean()
setTimeout(() => {
new SettingsView(this.touchEnabled)
}, 500)
}
- toAccount(){
+ toAccount() {
this.playSound("se_don")
this.clean()
setTimeout(() => {
new Account(this.touchEnabled)
}, 500)
}
- toSession(){
- if(p2.socket.readyState !== 1 || assets.customSongs){
+ toSession() {
+ if (p2.socket.readyState !== 1 || assets.customSongs) {
return
}
- if(p2.session){
+ if (p2.session) {
this.playSound("se_don")
p2.send("gameend")
this.state.moveHover = null
- }else{
+ } else {
localStorage["selectedSong"] = this.selectedSong
-
+
this.playSound("se_don")
this.clean()
setTimeout(() => {
@@ -1190,8 +1196,8 @@ class SongSelect{
}, 500)
}
}
- toCustomSongs(){
- if(assets.customSongs){
+ toCustomSongs() {
+ if (assets.customSongs) {
assets.customSongs = false
assets.songs = assets.songsDefault
delete assets.otherFiles
@@ -1203,9 +1209,9 @@ class SongSelect{
localStorage.removeItem("customSelected")
db.removeItem("customFolder")
pageEvents.send("import-songs-default")
- }else{
+ } else {
localStorage["selectedSong"] = this.selectedSong
-
+
this.playSound("se_don")
this.clean()
setTimeout(() => {
@@ -1213,47 +1219,74 @@ class SongSelect{
}, 500)
}
}
- toPlugins(){
+ toPlugins() {
this.playSound("se_don")
this.clean()
setTimeout(() => {
new SettingsView(this.touchEnabled, false, undefined, undefined, plugins.getSettings())
}, 500)
}
-
- redraw(){
- if(!this.redrawRunning){
+
+ toLeaderboard() {
+ var currentSong = this.songs[this.selectedSong]
+ if (!currentSong || !currentSong.courses) {
+ return
+ }
+
+ // Get current difficulty
+ var currentDiff = this.selectedDiff - this.diffOptions.length
+ if (currentDiff < 0) {
+ currentDiff = 0 // Default to easy if no difficulty selected yet
+ }
+ if (this.selectedDiff === this.diffOptions.length + 4 || (currentDiff === 3 && this.state.ura)) {
+ currentDiff = 4 // ura
+ }
+ var difficulty = this.difficultyId[currentDiff]
+
+ // Get song ID (hash or id)
+ var songId = currentSong.hash || String(currentSong.id)
+
+ this.playSound("se_don")
+
+ // Open leaderboard modal
+ new Leaderboard(songId, difficulty, currentSong.title, () => {
+ // On close callback - replay selection sound if desired
+ })
+ }
+
+ redraw() {
+ if (!this.redrawRunning) {
return
}
requestAnimationFrame(this.redrawBind)
var ms = this.getMS()
-
- for(var key in this.pressedKeys){
- if(this.pressedKeys[key]){
- if(ms >= this.pressedKeys[key] + (this.state.screen === "song" && (key === "right" || key === "left") ? 20 : 50)){
+
+ for (var key in this.pressedKeys) {
+ if (this.pressedKeys[key]) {
+ if (ms >= this.pressedKeys[key] + (this.state.screen === "song" && (key === "right" || key === "left") ? 20 : 50)) {
this.keyPress(true, key, null, true)
this.pressedKeys[key] = ms
}
}
}
-
- if(!this.redrawRunning){
+
+ if (!this.redrawRunning) {
return
}
-
+
var ctx = this.ctx
var winW = innerWidth
var winH = lastHeight
- if(winW / 32 > winH / 9){
+ if (winW / 32 > winH / 9) {
winW = winH / 9 * 32
}
this.pixelRatio = window.devicePixelRatio || 1
var resolution = settings.getItem("resolution")
- if(resolution === "medium"){
+ if (resolution === "medium") {
this.pixelRatio *= 0.75
- }else if(resolution === "low"){
+ } else if (resolution === "low") {
this.pixelRatio *= 0.5
- }else if(resolution === "lowest"){
+ } else if (resolution === "lowest") {
this.pixelRatio *= 0.25
}
winW *= this.pixelRatio
@@ -1261,47 +1294,47 @@ class SongSelect{
var ratioX = winW / 1280
var ratioY = winH / 720
var ratio = (ratioX < ratioY ? ratioX : ratioY)
- if(this.winW !== winW || this.winH !== winH){
+ if (this.winW !== winW || this.winH !== winH) {
this.canvas.width = Math.max(1, winW)
this.canvas.height = Math.max(1, winH)
ctx.scale(ratio, ratio)
this.canvas.style.width = (winW / this.pixelRatio) + "px"
this.canvas.style.height = (winH / this.pixelRatio) + "px"
-
+
var borders = (this.songAsset.border + this.songAsset.innerBorder) * 2
var songsLength = Math.ceil(winW / ratio / (this.songAsset.width + this.songAsset.marginLeft)) + 1
-
+
this.songTitleCache.resize(
(this.songAsset.width - borders + 1) * songsLength,
this.songAsset.height - borders + 1,
ratio + 0.2
)
-
+
this.currentSongCache.resize(
(this.songAsset.width - borders + 1) * 2,
this.songAsset.height - borders + 1,
ratio + 0.2
)
-
+
var textW = strings.id === "en" ? 350 : 280
this.selectTextCache.resize((textW + 53 + 60 + 1) * 2, this.songAsset.marginTop + 15, ratio + 0.5)
-
+
this.nameplateCache.resize(274, 134, ratio + 0.2)
-
+
var lastCategory
this.songs.forEach(song => {
var cat = (song.category || "") + song.skin.outline
- if(lastCategory !== cat){
+ if (lastCategory !== cat) {
lastCategory = cat
}
})
- this.categoryCache.resize(280, this.songAsset.marginTop + 1 , ratio + 0.5)
-
+ this.categoryCache.resize(280, this.songAsset.marginTop + 1, ratio + 0.5)
+
this.difficultyCache.resize((44 + 56 + 2) * 5, 135 + 10, ratio + 0.5)
-
+
var w = winW / ratio / 2
this.sessionCache.resize(w, 39 * 2, ratio + 0.5)
- for(var id in this.sessionText){
+ for (var id in this.sessionText) {
this.sessionCache.set({
w: w,
h: 38,
@@ -1318,28 +1351,28 @@ class SongSelect{
align: "center",
baseline: "middle"
}, [
- {outline: "#000", letterBorder: 8},
- {fill: "#fff"}
+ { outline: "#000", letterBorder: 8 },
+ { fill: "#fff" }
])
})
}
-
+
this.selectableText = ""
-
- if(this.search.opened && this.search.container){
+
+ if (this.search.opened && this.search.container) {
this.search.onInput(true)
}
- }else if(!document.hasFocus() && !p2.session){
- if(this.state.focused){
+ } else if (!document.hasFocus() && !p2.session) {
+ if (this.state.focused) {
this.state.focused = false
this.songSelect.classList.add("unfocused")
this.pressedKeys = {}
}
return
- }else{
+ } else {
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
}
- if(!this.state.focused){
+ if (!this.state.focused) {
this.state.focused = true
this.songSelect.classList.remove("unfocused")
}
@@ -1348,7 +1381,7 @@ class SongSelect{
this.ratio = ratio
winW /= ratio
winH /= ratio
-
+
var frameTop = winH / 2 - 720 / 2
var frameLeft = winW / 2 - 1280 / 2
var songTop = frameTop + this.songAsset.marginTop
@@ -1356,44 +1389,44 @@ class SongSelect{
var songSelMoving = false
var screen = this.state.screen
var selectedWidth = this.songAsset.width
-
+
this.search.redraw()
-
- if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) {
- if(p2.session){
+
+ if (this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) {
+ if (p2.session) {
this.moveToSong(this.wheelScrolls)
- }else{
+ } else {
this.state.move = this.wheelScrolls
this.state.waitPreview = ms + 400
this.endPreview()
}
this.wheelScrolls = 0
}
-
- if(screen === "title" || screen === "titleFadeIn"){
- if(ms > this.state.screenMS + 1000){
+
+ if (screen === "title" || screen === "titleFadeIn") {
+ if (ms > this.state.screenMS + 1000) {
this.state.screen = "song"
this.state.screenMS = ms + (ms - this.state.screenMS - 1000)
this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS)
this.state.locked = 3
this.state.lastMove = 1
- }else{
+ } else {
this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS - 1000)
}
- if(screen === "titleFadeIn" && ms > this.state.screenMS + 500){
+ if (screen === "titleFadeIn" && ms > this.state.screenMS + 500) {
this.state.screen = "title"
screen = "title"
}
}
-
- if((screen === "song" || screen === "difficulty") && (this.showWarning && !this.showWarning.shown || scoreStorage.scoreSaveFailed)){
- if(!this.showWarning){
- this.showWarning = {name: "scoreSaveFailed"}
+
+ if ((screen === "song" || screen === "difficulty") && (this.showWarning && !this.showWarning.shown || scoreStorage.scoreSaveFailed)) {
+ if (!this.showWarning) {
+ this.showWarning = { name: "scoreSaveFailed" }
}
- if(this.bgmEnabled){
+ if (this.bgmEnabled) {
this.playBgm(false)
}
- if(this.showWarning.name === "scoreSaveFailed"){
+ if (this.showWarning.name === "scoreSaveFailed") {
scoreStorage.scoreSaveFailed = false
}
this.showWarning.shown = true
@@ -1401,8 +1434,8 @@ class SongSelect{
this.state.locked = true
this.playSound("se_pause")
}
-
- if(screen === "title" || screen === "titleFadeIn" || screen === "song"){
+
+ if (screen === "title" || screen === "titleFadeIn" || screen === "song") {
var textW = strings.id === "en" ? 350 : 280
this.selectTextCache.get({
ctx: ctx,
@@ -1423,16 +1456,16 @@ class SongSelect{
letterSpacing: strings.id === "en" ? 0 : 2,
forceShadow: true
}, [
- {x: -2, y: -2, outline: "#000", letterBorder: 22},
+ { x: -2, y: -2, outline: "#000", letterBorder: 22 },
{},
- {x: 2, y: 2, shadow: [3, 3, 3]},
- {x: 2, y: 2, outline: "#ad1516", letterBorder: 10},
- {x: -2, y: -2, outline: "#ff797b"},
- {outline: "#f70808"},
- {fill: "#fff", shadow: [-1, 1, 3, 1.5]}
+ { x: 2, y: 2, shadow: [3, 3, 3] },
+ { x: 2, y: 2, outline: "#ad1516", letterBorder: 10 },
+ { x: -2, y: -2, outline: "#ff797b" },
+ { outline: "#f70808" },
+ { fill: "#fff", shadow: [-1, 1, 3, 1.5] }
])
})
-
+
var selectedSong = this.songs[this.selectedSong]
var category = selectedSong.category
this.draw.category({
@@ -1458,11 +1491,11 @@ class SongSelect{
h: this.songAsset.marginTop,
id: category + selectedSong.skin.outline
}, ctx => {
- if(category){
- let cat = assets.categories.find(cat=>cat.title === category)
- if(cat){
+ if (category) {
+ let cat = assets.categories.find(cat => cat.title === category)
+ if (cat) {
var categoryName = this.getLocalTitle(cat.title, cat.title_lang)
- }else{
+ } else {
var categoryName = category
}
this.draw.layeredText({
@@ -1476,18 +1509,18 @@ class SongSelect{
align: "center",
forceShadow: true
}, [
- {outline: selectedSong.skin.outline, letterBorder: 12, shadow: [3, 3, 3]},
- {fill: "#fff"}
+ { outline: selectedSong.skin.outline, letterBorder: 12, shadow: [3, 3, 3] },
+ { fill: "#fff" }
])
}
})
}
-
- if(screen === "song"){
- if(this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded){
+
+ if (screen === "song") {
+ if (this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded) {
selectedWidth = this.songAsset.selectedWidth
}
-
+
var lastMoveMul = Math.pow(Math.abs(this.state.lastMove || 0), 1 / 4)
var changeSpeed = this.songSelecting.speed * lastMoveMul
var resize = changeSpeed * (lastMoveMul === 0 ? 0 : this.songSelecting.resize / lastMoveMul)
@@ -1495,46 +1528,46 @@ class SongSelect{
var resize2 = changeSpeed - resize
var scroll = resize2 - resize - scrollDelay * 2
var elapsed = ms - this.state.moveMS
-
- if(this.state.catJump || (this.state.move && ms > this.state.moveMS + resize2 - scrollDelay)){
+
+ if (this.state.catJump || (this.state.move && ms > this.state.moveMS + resize2 - scrollDelay)) {
var isJump = this.state.catJump
var previousSelectedSong = this.selectedSong
-
- if(!isJump){
+
+ if (!isJump) {
this.playSound("se_ka", 0, this.lastMoveBy)
this.setSelectedSong(this.mod(this.songs.length, this.selectedSong + this.state.move))
- }else{
+ } else {
var currentCat = this.songs[this.selectedSong].category
var currentIdx = this.mod(this.songs.length, this.selectedSong)
- if(this.state.move > 0){
+ if (this.state.move > 0) {
var nextSong = this.songs.find(song => this.mod(this.songs.length, this.songs.indexOf(song)) > currentIdx && song.category !== currentCat && song.canJump)
- if(!nextSong){
+ if (!nextSong) {
nextSong = this.songs[0]
}
- }else{
+ } else {
var isFirstInCat = this.songs.findIndex(song => song.category === currentCat) == this.selectedSong
- if(!isFirstInCat){
+ if (!isFirstInCat) {
var nextSong = this.songs.find(song => this.mod(this.songs.length, this.songs.indexOf(song)) < currentIdx && song.category === currentCat && song.canJump)
- }else{
+ } else {
var idx = this.songs.length - 1
var nextSong
var lastCat
- for(;idx>=0;idx--){
- if(this.songs[idx].category !== lastCat && this.songs[idx].action !== "back"){
+ for (; idx >= 0; idx--) {
+ if (this.songs[idx].category !== lastCat && this.songs[idx].action !== "back") {
lastCat = this.songs[idx].category
- if(nextSong){
+ if (nextSong) {
break
}
}
- if(lastCat !== currentCat && idx < currentIdx){
+ if (lastCat !== currentCat && idx < currentIdx) {
nextSong = idx
}
}
nextSong = this.songs[nextSong]
}
- if(!nextSong){
+ if (!nextSong) {
var rev = [...this.songs].reverse()
nextSong = rev.find(song => song.canJump)
}
@@ -1544,98 +1577,98 @@ class SongSelect{
this.state.catJump = false
}
- if(previousSelectedSong !== this.selectedSong){
+ if (previousSelectedSong !== this.selectedSong) {
pageEvents.send("song-select-move", this.songs[this.selectedSong])
}
this.state.move = 0
this.state.locked = 2
- if(assets.customSongs){
+ if (assets.customSongs) {
assets.customSelected = this.selectedSong
localStorage["customSelected"] = this.selectedSong
- }else if(!p2.session){
- try{
+ } else if (!p2.session) {
+ try {
localStorage["selectedSong"] = this.selectedSong
- }catch(e){}
+ } catch (e) { }
}
}
- if(this.state.moveMS && ms < this.state.moveMS + changeSpeed){
+ if (this.state.moveMS && ms < this.state.moveMS + changeSpeed) {
xOffset = Math.min(scroll, Math.max(0, elapsed - resize - scrollDelay)) / scroll * (this.songAsset.width + this.songAsset.marginLeft)
xOffset *= -this.state.move
- if(elapsed < resize){
+ if (elapsed < resize) {
selectedWidth = this.songAsset.width + (((resize - elapsed) / resize) * (selectedWidth - this.songAsset.width))
- }else if(elapsed > resize2){
+ } else if (elapsed > resize2) {
this.playBgm(!this.songs[this.selectedSong].courses)
this.state.locked = 1
selectedWidth = this.songAsset.width + ((elapsed - resize2) / resize * (selectedWidth - this.songAsset.width))
- }else{
+ } else {
songSelMoving = true
selectedWidth = this.songAsset.width
}
- }else{
- if(this.previewing !== "muted"){
+ } else {
+ if (this.previewing !== "muted") {
this.playBgm(!this.songs[this.selectedSong].courses)
}
this.state.locked = 0
}
- }else if(screen === "difficulty"){
+ } else if (screen === "difficulty") {
var currentSong = this.songs[this.selectedSong]
- if(this.state.locked){
+ if (this.state.locked) {
this.state.locked = 0
}
- if(this.state.move){
+ if (this.state.move) {
var hasUra = currentSong.courses.ura
var previousSelection = this.selectedDiff
- do{
- if(hasUra && this.state.move > 0){
+ do {
+ if (hasUra && this.state.move > 0) {
this.selectedDiff += this.state.move
- if(this.selectedDiff > this.diffOptions.length + 4){
+ if (this.selectedDiff > this.diffOptions.length + 4) {
this.state.ura = !this.state.ura
- if(this.state.ura){
+ if (this.state.ura) {
this.selectedDiff = previousSelection === this.diffOptions.length + 3 ? this.diffOptions.length + 4 : previousSelection
break
- }else{
+ } else {
this.state.move = -1
}
}
- }else{
+ } else {
this.selectedDiff = this.mod(this.diffOptions.length + 5, this.selectedDiff + this.state.move)
}
- }while(
+ } while (
this.selectedDiff >= this.diffOptions.length && !currentSong.courses[this.difficultyId[this.selectedDiff - this.diffOptions.length]]
|| this.selectedDiff === this.diffOptions.length + 3 && this.state.ura
|| this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura
)
this.state.move = 0
- }else if(this.selectedDiff < 0 || this.selectedDiff >= this.diffOptions.length && !currentSong.courses[this.difficultyId[this.selectedDiff - this.diffOptions.length]]){
+ } else if (this.selectedDiff < 0 || this.selectedDiff >= this.diffOptions.length && !currentSong.courses[this.difficultyId[this.selectedDiff - this.diffOptions.length]]) {
this.selectedDiff = 0
}
}
-
- if(songSelMoving){
- if(this.previewing !== null){
+
+ if (songSelMoving) {
+ if (this.previewing !== null) {
this.endPreview()
}
- }else if(screen !== "title" && screen !== "titleFadeIn" && ms > this.state.moveMS + 100){
- if(this.previewing !== "muted" && this.previewing !== this.selectedSong && "id" in this.songs[this.selectedSong]){
+ } else if (screen !== "title" && screen !== "titleFadeIn" && ms > this.state.moveMS + 100) {
+ if (this.previewing !== "muted" && this.previewing !== this.selectedSong && "id" in this.songs[this.selectedSong]) {
this.startPreview()
}
}
-
+
this.songFrameCache = {
w: this.songAsset.width + this.songAsset.selectedWidth + this.songAsset.fullWidth + (15 + 1) * 3,
h: this.songAsset.fullHeight + 16,
ratio: ratio
}
-
- if(screen === "title" || screen === "titleFadeIn" || screen === "song"){
- for(var i = this.selectedSong - 1; ; i--){
+
+ if (screen === "title" || screen === "titleFadeIn" || screen === "song") {
+ for (var i = this.selectedSong - 1; ; i--) {
var highlight = 0
- if(i - this.selectedSong === this.state.moveHover){
+ if (i - this.selectedSong === this.state.moveHover) {
highlight = 1
}
var index = this.mod(this.songs.length, i)
var _x = winW / 2 - (this.selectedSong - i) * (this.songAsset.width + this.songAsset.marginLeft) - selectedWidth / 2 + xOffset
- if(_x + this.songAsset.width + this.songAsset.marginLeft < 0){
+ if (_x + this.songAsset.width + this.songAsset.marginLeft < 0) {
break
}
this.drawClosedSong({
@@ -1648,16 +1681,16 @@ class SongSelect{
})
}
var startFrom
- for(var i = this.selectedSong + 1; ; i++){
+ for (var i = this.selectedSong + 1; ; i++) {
var _x = winW / 2 + (i - this.selectedSong - 1) * (this.songAsset.width + this.songAsset.marginLeft) + this.songAsset.marginLeft + selectedWidth / 2 + xOffset
- if(_x > winW){
+ if (_x > winW) {
startFrom = i - 1
break
}
}
- for(var i = startFrom; i > this.selectedSong ; i--){
+ for (var i = startFrom; i > this.selectedSong; i--) {
var highlight = 0
- if(i - this.selectedSong === this.state.moveHover){
+ if (i - this.selectedSong === this.state.moveHover) {
highlight = 1
}
var index = this.mod(this.songs.length, i)
@@ -1673,37 +1706,37 @@ class SongSelect{
})
}
}
-
+
var currentSong = this.songs[this.selectedSong]
var highlight = 0
- if(!currentSong.courses){
+ if (!currentSong.courses) {
highlight = 2
}
- if(this.state.moveHover === 0){
+ if (this.state.moveHover === 0) {
highlight = 1
}
var selectedSkin = this.songSkin.selected
- if(screen === "title" || screen === "titleFadeIn" || this.state.locked === 3 || currentSong.unloaded){
+ if (screen === "title" || screen === "titleFadeIn" || this.state.locked === 3 || currentSong.unloaded) {
selectedSkin = currentSong.skin
highlight = 2
- }else if(songSelMoving){
+ } else if (songSelMoving) {
selectedSkin = currentSong.skin
highlight = 0
}
var selectedHeight = this.songAsset.height
- if(screen === "difficulty"){
+ if (screen === "difficulty") {
selectedWidth = this.songAsset.fullWidth
selectedHeight = this.songAsset.fullHeight
highlight = 0
}
-
- if(this.lastCurrentSong.title !== currentSong.title || this.lastCurrentSong.subtitle !== currentSong.subtitle){
+
+ if (this.lastCurrentSong.title !== currentSong.title || this.lastCurrentSong.subtitle !== currentSong.subtitle) {
this.lastCurrentSong.title = currentSong.title
this.lastCurrentSong.subtitle = currentSong.subtitle
this.currentSongCache.clear()
}
-
- if(selectedWidth === this.songAsset.width){
+
+ if (selectedWidth === this.songAsset.width) {
this.drawSongCrown({
ctx: ctx,
song: currentSong,
@@ -1711,7 +1744,7 @@ class SongSelect{
y: songTop + this.songAsset.height - selectedHeight
})
}
-
+
this.draw.songFrame({
ctx: ctx,
x: winW / 2 - selectedWidth / 2 + xOffset,
@@ -1730,10 +1763,10 @@ class SongSelect{
disabled: p2.session && currentSong.action && !currentSong.p2Enabled,
innerContent: (x, y, w, h) => {
ctx.strokeStyle = "#000"
- if(screen === "title" || screen === "titleFadeIn" || screen === "song"){
+ if (screen === "title" || screen === "titleFadeIn" || screen === "song") {
var opened = ((selectedWidth - this.songAsset.width) / (this.songAsset.selectedWidth - this.songAsset.width))
var songSel = true
- }else{
+ } else {
var textW = strings.id === "en" ? 350 : 280
this.selectTextCache.get({
ctx: ctx,
@@ -1753,19 +1786,19 @@ class SongSelect{
width: textW,
forceShadow: true
}, [
- {x: -2, y: -2, outline: "#000", letterBorder: 23},
+ { x: -2, y: -2, outline: "#000", letterBorder: 23 },
{},
- {x: 2, y: 2, shadow: [3, 3, 3]},
- {x: 2, y: 2, outline: "#ad1516", letterBorder: 10},
- {x: -2, y: -2, outline: "#ff797b"},
- {outline: "#f70808"},
- {fill: "#fff", shadow: [-1, 1, 3, 1.5]}
+ { x: 2, y: 2, shadow: [3, 3, 3] },
+ { x: 2, y: 2, outline: "#ad1516", letterBorder: 10 },
+ { x: -2, y: -2, outline: "#ff797b" },
+ { outline: "#f70808" },
+ { fill: "#fff", shadow: [-1, 1, 3, 1.5] }
])
})
var opened = 1
var songSel = false
-
- for(var i = 0; i < this.diffOptions.length; i++){
+
+ for (var i = 0; i < this.diffOptions.length; i++) {
var _x = x + 62 + i * 72
var _y = y + 67
ctx.fillStyle = this.diffOptions[i].fill
@@ -1790,12 +1823,12 @@ class SongSelect{
y: _y + 28,
iconName: this.diffOptions[i].iconName
})
-
+
var text = this.diffOptions[i].text
- if(this.diffOptions[i].iconName === "options" && (this.selectedDiff === i || this.state.options !== 0)){
+ if (this.diffOptions[i].iconName === "options" && (this.selectedDiff === i || this.state.options !== 0)) {
text = this.optionsList[this.state.options]
}
-
+
this.draw.verticalText({
ctx: ctx,
text: text,
@@ -1811,14 +1844,14 @@ class SongSelect{
fontFamily: this.font,
letterSpacing: this.diffOptions[i].letterSpacing
})
-
+
var highlight = 0
- if(this.state.moveHover === i){
+ if (this.state.moveHover === i) {
highlight = 2
- }else if(this.selectedDiff === i){
+ } else if (this.selectedDiff === i) {
highlight = 1
}
- if(highlight){
+ if (highlight) {
this.draw.highlight({
ctx: ctx,
x: _x - 32,
@@ -1830,7 +1863,7 @@ class SongSelect{
opacity: highlight === 2 ? 0.8 : 1,
radius: 24
})
- if(this.selectedDiff === i && !this.touchEnabled){
+ if (this.selectedDiff === i && !this.touchEnabled) {
this.draw.diffCursor({
ctx: ctx,
font: this.font,
@@ -1843,24 +1876,24 @@ class SongSelect{
}
}
var drawDifficulty = (ctx, i, currentUra) => {
- if(currentSong.courses[this.difficultyId[i]] || currentUra){
+ if (currentSong.courses[this.difficultyId[i]] || currentUra) {
var crownDiff = currentUra ? "ura" : this.difficultyId[i]
var players = p2.session ? 2 : 1
var score = [scoreStorage.get(currentSong.hash, false, true)]
- if(p2.session){
+ if (p2.session) {
score[p2.player === 1 ? "push" : "unshift"](scoreStorage.getP2(currentSong.hash, false, true))
}
var reversed = false
- for(var a = players; a--;){
+ for (var a = players; a--;) {
var crownType = ""
var p = reversed ? -(a - 1) : a
- if(score[p] && score[p][crownDiff]){
+ if (score[p] && score[p][crownDiff]) {
crownType = score[p][crownDiff].crown
}
- if(!reversed && players === 2 && p === 1 && crownType){
+ if (!reversed && players === 2 && p === 1 && crownType) {
reversed = true
a++
- }else{
+ } else {
this.draw.crown({
ctx: ctx,
type: crownType,
@@ -1871,7 +1904,7 @@ class SongSelect{
})
}
}
- if(songSel && !this.state.move){
+ if (songSel && !this.state.move) {
var _x = x + 33 + i * 60
var _y = y + 120
ctx.fillStyle = currentUra ? "#006279" : "#ff9f18"
@@ -1887,7 +1920,7 @@ class SongSelect{
scale: 1,
border: 6
})
- }else{
+ } else {
var _x = x + 402 + i * 100
var _y = y + 87
this.draw.diffIcon({
@@ -1946,7 +1979,7 @@ class SongSelect{
var moveMS = Math.max(this.state.moveMS, this.state.mouseMoveMS)
var elapsedMS = this.state.screenMS > moveMS || !songSel ? this.state.screenMS : moveMS
var fade = ((ms - elapsedMS) % 2000) / 2000
- if(songBranch && fade > 0.25 && fade < 0.75){
+ if (songBranch && fade > 0.25 && fade < 0.75) {
this.draw.verticalText({
ctx: ctx,
text: strings.songBranch,
@@ -1960,19 +1993,19 @@ class SongSelect{
outline: songSel ? false : "#f22666",
outlineSize: songSel ? 0 : this.songAsset.letterBorder
})
- }else{
- for(var j = 0; j < 10; j++){
- if(songSel){
+ } else {
+ for (var j = 0; j < 10; j++) {
+ if (songSel) {
var yPos = _y + 113 + j * 17
- }else{
+ } else {
var yPos = _y + 178 + j * 19.5
}
- if(10 - j > songStars){
+ if (10 - j > songStars) {
ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7")
ctx.beginPath()
ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2)
ctx.fill()
- }else{
+ } else {
this.draw.diffStar({
ctx: ctx,
songSel: songSel,
@@ -1985,17 +2018,17 @@ class SongSelect{
}
}
var currentDiff = this.selectedDiff - this.diffOptions.length
- if(this.selectedDiff === 4 + this.diffOptions.length){
+ if (this.selectedDiff === 4 + this.diffOptions.length) {
currentDiff = 3
}
- if(!songSel){
+ if (!songSel) {
var highlight = 0
- if(this.state.moveHover - this.diffOptions.length === i){
+ if (this.state.moveHover - this.diffOptions.length === i) {
highlight = 2
- }else if(currentDiff === i){
+ } else if (currentDiff === i) {
highlight = 1
}
- if(currentDiff === i && !this.touchEnabled){
+ if (currentDiff === i && !this.touchEnabled) {
this.draw.diffCursor({
ctx: ctx,
font: this.font,
@@ -2005,7 +2038,7 @@ class SongSelect{
two: p2.session && p2.player === 2
})
}
- if(highlight){
+ if (highlight) {
this.draw.highlight({
ctx: ctx,
x: _x - 32,
@@ -2020,18 +2053,18 @@ class SongSelect{
}
}
}
- for(var i = 0; currentSong.courses && i < 4; i++){
+ for (var i = 0; currentSong.courses && i < 4; i++) {
var currentUra = i === 3 && (this.state.ura && !songSel || currentSong.courses.ura && songSel)
- if(songSel && currentUra){
+ if (songSel && currentUra) {
drawDifficulty(ctx, i, false)
var elapsedMS = Math.max(this.state.screenMS, this.state.moveMS, this.state.mouseMoveMS)
var fade = ((ms - elapsedMS) % 4000) / 4000
var alphaFade = 0
- if(fade > 0.95){
+ if (fade > 0.95) {
alphaFade = this.draw.easeOut(1 - (fade - 0.95) * 20)
- }else if(fade > 0.5){
+ } else if (fade > 0.5) {
alphaFade = 1
- }else if(fade > 0.45){
+ } else if (fade > 0.45) {
alphaFade = this.draw.easeIn((fade - 0.45) * 20)
}
this.draw.alpha(alphaFade, ctx, ctx => {
@@ -2039,16 +2072,16 @@ class SongSelect{
ctx.fillRect(x + 7 + i * 60, y + 60, 52, 352)
drawDifficulty(ctx, i, true)
}, winW, winH)
- }else{
+ } else {
drawDifficulty(ctx, i, currentUra)
}
}
- for(var i = 0; currentSong.courses && i < 4; i++){
- if(!songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1){
+ for (var i = 0; currentSong.courses && i < 4; i++) {
+ if (!songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1) {
var _x = x + 402 + i * 100
var _y = y + 87
var currentDiff = this.selectedDiff - this.diffOptions.length
- if(this.selectedDiff === 4 + this.diffOptions.length){
+ if (this.selectedDiff === 4 + this.diffOptions.length) {
currentDiff = 3
}
this.draw.diffCursor({
@@ -2062,14 +2095,14 @@ class SongSelect{
})
}
}
-
+
var borders = (this.songAsset.border + this.songAsset.innerBorder) * 2
var textW = this.songAsset.width - borders
var textH = this.songAsset.height - borders
var textX = Math.max(w - 37 - textW / 2, w / 2 - textW / 2)
var textY = opened * 12 + (1 - opened) * 7
-
- if(currentSong.subtitle){
+
+ if (currentSong.subtitle) {
this.currentSongCache.get({
ctx: ctx,
x: x + textX - textW,
@@ -2094,22 +2127,22 @@ class SongSelect{
})
})
}
-
+
var hasMaker = currentSong.maker || currentSong.maker === 0
var hasVideo = currentSong.video || currentSong.video === 0
- if(hasMaker || currentSong.lyrics){
+ if (hasMaker || currentSong.lyrics) {
if (songSel) {
var _x = x + 38
var _y = y + 10
ctx.strokeStyle = "#000"
ctx.lineWidth = 5
-
- if(hasMaker){
+
+ if (hasMaker) {
var grd = ctx.createLinearGradient(_x, _y, _x, _y + 50)
grd.addColorStop(0, "#fa251a")
grd.addColorStop(1, "#ffdc33")
ctx.fillStyle = grd
- }else{
+ } else {
ctx.fillStyle = "#000"
}
this.draw.roundedRect({
@@ -2122,8 +2155,8 @@ class SongSelect{
})
ctx.fill()
ctx.stroke()
-
- if(hasMaker){
+
+ if (hasMaker) {
this.draw.layeredText({
ctx: ctx,
text: strings.creative.creative,
@@ -2135,10 +2168,10 @@ class SongSelect{
y: _y + (strings.id === "ja" || strings.id === "en" ? 25 : 28),
width: 172
}, [
- {outline: "#fff", letterBorder: 6},
- {fill: "#000"}
+ { outline: "#fff", letterBorder: 6 },
+ { fill: "#000" }
])
- }else{
+ } else {
this.draw.layeredText({
ctx: ctx,
text: strings.withLyrics,
@@ -2150,15 +2183,15 @@ class SongSelect{
y: _y + (strings.id === "ja" || strings.id === "en" ? 25 : 28),
width: 172
}, [
- {fill: currentSong.skin.border[0]}
+ { fill: currentSong.skin.border[0] }
])
}
- } else if(currentSong.maker && currentSong.maker.id > 0 && currentSong.maker.name){
+ } else if (currentSong.maker && currentSong.maker.id > 0 && currentSong.maker.name) {
var _x = x + 62
var _y = y + 380
ctx.lineWidth = 5
- var grd = ctx.createLinearGradient(_x, _y, _x, _y+50);
+ var grd = ctx.createLinearGradient(_x, _y, _x, _y + 50);
grd.addColorStop(0, '#fa251a');
grd.addColorStop(1, '#ffdc33');
@@ -2187,8 +2220,8 @@ class SongSelect{
x: _x - 15,
y: _y + 23
}, [
- {outline: "#000", letterBorder: 8},
- {fill: "#fff"}
+ { outline: "#000", letterBorder: 8 },
+ { fill: "#fff" }
])
this.draw.layeredText({
@@ -2202,11 +2235,11 @@ class SongSelect{
y: _y + 56,
width: 210
}, [
- {outline: "#fff", letterBorder: 8},
- {fill: "#000"}
+ { outline: "#fff", letterBorder: 8 },
+ { fill: "#000" }
])
- if(this.state.moveHover === "maker"){
+ if (this.state.moveHover === "maker") {
this.draw.highlight({
ctx: ctx,
x: _x - 32,
@@ -2219,10 +2252,10 @@ class SongSelect{
}
}
}
-
- for(var i = 0; currentSong.courses && i < 4; i++){
- if(currentSong.courses[this.difficultyId[i]] || currentUra){
- if(songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1){
+
+ for (var i = 0; currentSong.courses && i < 4; i++) {
+ if (currentSong.courses[this.difficultyId[i]] || currentUra) {
+ if (songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1) {
var _x = x + 33 + i * 60
var _y = y + 120
this.draw.diffCursor({
@@ -2237,8 +2270,8 @@ class SongSelect{
}
}
}
-
- if(!songSel && currentSong.courses.ura){
+
+ if (!songSel && currentSong.courses.ura) {
var fade = ((ms - this.state.screenMS) % 1200) / 1200
var _x = x + 402 + 4 * 100 + fade * 25
var _y = y + 258
@@ -2316,7 +2349,7 @@ class SongSelect{
fontFamily: this.font
})
}
- if(selectedSkin.outline === "#000"){
+ if (selectedSkin.outline === "#000") {
this.currentSongCache.get({
ctx: ctx,
x: x + textX,
@@ -2325,7 +2358,7 @@ class SongSelect{
h: textH,
id: "title",
}, verticalTitle)
- }else{
+ } else {
this.songTitleCache.get({
ctx: ctx,
x: x + textX,
@@ -2335,7 +2368,7 @@ class SongSelect{
id: currentSong.title + selectedSkin.outline,
}, verticalTitle)
}
- if(!songSel && this.selectableText !== currentSong.title){
+ if (!songSel && this.selectableText !== currentSong.title) {
this.draw.verticalText({
ctx: ctx,
text: currentSong.title,
@@ -2354,13 +2387,13 @@ class SongSelect{
}
}
})
-
- if(screen !== "difficulty" && this.selectableText){
+
+ if (screen !== "difficulty" && this.selectableText) {
this.selectableText = ""
this.selectable.style.display = "none"
}
-
- if(songSelMoving){
+
+ if (songSelMoving) {
this.draw.highlight({
ctx: ctx,
x: winW / 2 - selectedWidth / 2,
@@ -2370,7 +2403,7 @@ class SongSelect{
opacity: 0.8
})
}
-
+
ctx.fillStyle = "#000"
ctx.fillRect(0, frameTop + 595, 1280 + frameLeft * 2, 125 + frameTop)
var x = 0
@@ -2402,11 +2435,11 @@ class SongSelect{
ctx.lineTo(x + w - 4, y + h)
ctx.lineTo(x + w - 4, y + 4)
ctx.fill()
-
- if(!p2.session || p2.player === 1){
+
+ if (!p2.session || p2.player === 1) {
var name = account.loggedIn ? account.displayName : strings.defaultName
var rank = account.loggedIn || !gameConfig.accounts || p2.session ? false : strings.notLoggedIn
- }else{
+ } else {
var name = p2.name || strings.defaultName
var rank = false
}
@@ -2427,7 +2460,7 @@ class SongSelect{
font: this.font
})
})
- if(this.state.moveHover === "account"){
+ if (this.state.moveHover === "account") {
this.draw.highlight({
ctx: ctx,
x: frameLeft + 59.5,
@@ -2439,8 +2472,8 @@ class SongSelect{
size: 10
})
}
-
- if(p2.session){
+
+ if (p2.session) {
x = x + w + 4
w = 396
this.draw.pattern({
@@ -2470,7 +2503,7 @@ class SongSelect{
ctx.lineTo(x + w - 4, y + h)
ctx.lineTo(x + w - 4, y + 4)
ctx.fill()
- if(this.state.moveHover === "session"){
+ if (this.state.moveHover === "session") {
this.draw.highlight({
ctx: ctx,
x: x,
@@ -2481,10 +2514,10 @@ class SongSelect{
})
}
}
-
+
x = p2.session ? frameLeft + 642 + 200 : frameLeft + 642
w = p2.session ? frameLeft + 638 - 200 : frameLeft + 638
- if(p2.session){
+ if (p2.session) {
this.draw.pattern({
ctx: ctx,
img: assets.image["bg_score_p2"],
@@ -2497,7 +2530,7 @@ class SongSelect{
scale: 1.55
})
ctx.fillStyle = "rgba(138, 245, 247, 0.5)"
- }else{
+ } else {
this.draw.pattern({
ctx: ctx,
img: assets.image["bg_settings"],
@@ -2519,22 +2552,22 @@ class SongSelect{
ctx.lineTo(x + 4, y + 4)
ctx.lineTo(x + 4, y + h)
ctx.fill()
- if(screen !== "difficulty" && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){
+ if (screen !== "difficulty" && p2.socket && p2.socket.readyState === 1 && !assets.customSongs) {
var elapsed = (ms - this.state.screenMS) % 3100
var fade = 1
- if(!p2.session && screen === "song"){
- if(elapsed > 2800){
+ if (!p2.session && screen === "song") {
+ if (elapsed > 2800) {
fade = (elapsed - 2800) / 300
- }else if(2000 < elapsed){
- if(elapsed < 2300){
+ } else if (2000 < elapsed) {
+ if (elapsed < 2300) {
fade = 1 - (elapsed - 2000) / 300
- }else{
+ } else {
fade = 0
}
}
}
- if(fade > 0){
- if(fade < 1){
+ if (fade > 0) {
+ if (fade < 1) {
ctx.globalAlpha = this.draw.easeIn(fade)
}
this.sessionCache.get({
@@ -2547,7 +2580,7 @@ class SongSelect{
})
ctx.globalAlpha = 1
}
- if(!p2.session && this.state.moveHover === "session"){
+ if (!p2.session && this.state.moveHover === "session") {
this.draw.highlight({
ctx: ctx,
x: x,
@@ -2558,10 +2591,10 @@ class SongSelect{
})
}
}
- if(p2.session){
- if(p2.player === 1){
+ if (p2.session) {
+ if (p2.player === 1) {
var name = p2.name || strings.default2PName
- }else{
+ } else {
var name = account.loggedIn ? account.displayName : strings.default2PName
}
this.nameplateCache.get({
@@ -2582,17 +2615,17 @@ class SongSelect{
})
})
}
-
- if(this.state.showWarning){
- if(this.preview){
+
+ if (this.state.showWarning) {
+ if (this.preview) {
this.endPreview()
}
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
ctx.fillRect(0, 0, winW, winH)
-
+
ctx.save()
ctx.translate(frameLeft, frameTop)
-
+
var pauseRect = (ctx, mul) => {
this.draw.roundedRect({
ctx: ctx,
@@ -2617,15 +2650,15 @@ class SongSelect{
dx: 68,
dy: 11
})
- if(this.showWarning.name === "scoreSaveFailed"){
+ if (this.showWarning.name === "scoreSaveFailed") {
var text = strings.scoreSaveFailed
- }else if(this.showWarning.name === "loadSongError"){
+ } else if (this.showWarning.name === "loadSongError") {
var text = []
var textIndex = 0
var subText = [this.showWarning.title, this.showWarning.id, this.showWarning.error]
var textParts = strings.loadSongError.split("%s")
textParts.forEach((textPart, i) => {
- if(i !== 0){
+ if (i !== 0) {
text.push(subText[textIndex++])
}
text.push(textPart)
@@ -2646,7 +2679,7 @@ class SongSelect{
verticalAlign: "middle",
textAlign: "center"
})
-
+
var _x = 640
var _y = 470
var _w = 464
@@ -2662,8 +2695,8 @@ class SongSelect{
})
ctx.fill()
var layers = [
- {outline: "#000", letterBorder: 10},
- {fill: "#fff"}
+ { outline: "#000", letterBorder: 10 },
+ { fill: "#fff" }
]
this.draw.layeredText({
ctx: ctx,
@@ -2677,12 +2710,12 @@ class SongSelect{
letterSpacing: -1,
align: "center"
}, layers)
-
+
var highlight = 1
- if(this.state.moveHover === "showWarning"){
+ if (this.state.moveHover === "showWarning") {
highlight = 2
}
- if(highlight){
+ if (highlight) {
this.draw.highlight({
ctx: ctx,
x: _x - _w / 2 - 3.5,
@@ -2695,39 +2728,39 @@ class SongSelect{
radius: 30
})
}
-
+
ctx.restore()
}
-
- if(screen === "titleFadeIn"){
+
+ if (screen === "titleFadeIn") {
ctx.save()
-
+
var elapsed = ms - this.state.screenMS
ctx.globalAlpha = Math.max(0, 1 - elapsed / 500)
ctx.fillStyle = "#000"
ctx.fillRect(0, 0, winW, winH)
-
+
ctx.restore()
}
-
- if(p2.session && (!this.lastScoreMS || ms > this.lastScoreMS + 1000)){
+
+ if (p2.session && (!this.lastScoreMS || ms > this.lastScoreMS + 1000)) {
this.lastScoreMS = ms
scoreStorage.eventLoop()
}
}
- drawBackground(cat){
- if(this.songSkin[cat] && this.songSkin[cat].bg_img){
+ drawBackground(cat) {
+ if (this.songSkin[cat] && this.songSkin[cat].bg_img) {
let filename = this.songSkin[cat].bg_img.slice(0, this.songSkin[cat].bg_img.lastIndexOf("."))
this.songSelect.style.backgroundImage = "url('" + assets.image[filename].src + "')"
- }else{
+ } else {
this.songSelect.style.backgroundImage = "url('" + assets.image["bg_genre_def"].src + "')"
}
}
-
- drawClosedSong(config){
+
+ drawClosedSong(config) {
var ctx = config.ctx
-
+
this.drawSongCrown(config)
config.width = this.songAsset.width
config.height = this.songAsset.height
@@ -2765,7 +2798,7 @@ class SongSelect{
})
}
this.draw.songFrame(config)
- if("p2Cursor" in config.song && config.song.p2Cursor !== null && p2.socket.readyState === 1){
+ if ("p2Cursor" in config.song && config.song.p2Cursor !== null && p2.socket.readyState === 1) {
this.draw.diffCursor({
ctx: ctx,
font: this.font,
@@ -2777,23 +2810,23 @@ class SongSelect{
})
}
}
-
- drawSongCrown(config){
- if(!config.song.action && config.song.hash){
+
+ drawSongCrown(config) {
+ if (!config.song.action && config.song.hash) {
var ctx = config.ctx
var players = p2.session ? 2 : 1
var score = [scoreStorage.get(config.song.hash, false, true)]
var scoreDrawn = []
- if(p2.session){
+ if (p2.session) {
score[p2.player === 1 ? "push" : "unshift"](scoreStorage.getP2(config.song.hash, false, true))
}
- for(var i = this.difficultyId.length; i--;){
+ for (var i = this.difficultyId.length; i--;) {
var diff = this.difficultyId[i]
- for(var p = players; p--;){
- if(!score[p] || scoreDrawn[p]){
+ for (var p = players; p--;) {
+ if (!score[p] || scoreDrawn[p]) {
continue
}
- if(config.song.courses[this.difficultyId[i]] && score[p][diff] && score[p][diff].crown){
+ if (config.song.courses[this.difficultyId[i]] && score[p][diff] && score[p][diff].crown) {
this.draw.crown({
ctx: ctx,
type: score[p][diff].crown,
@@ -2817,76 +2850,76 @@ class SongSelect{
}
}
}
-
- startPreview(loadOnly){
- if(!loadOnly && this.state && this.state.showWarning || this.state.waitPreview > this.getMS()){
+
+ startPreview(loadOnly) {
+ if (!loadOnly && this.state && this.state.showWarning || this.state.waitPreview > this.getMS()) {
return
}
var currentSong = this.songs[this.selectedSong]
var id = currentSong.id
var prvTime = currentSong.preview
this.endPreview()
-
- if("id" in currentSong){
+
+ if ("id" in currentSong) {
var startLoad = this.getMS()
- if(loadOnly){
+ if (loadOnly) {
var currentId = null
- }else{
+ } else {
var currentId = this.previewId
this.previewing = this.selectedSong
}
var songObj = this.previewList.find(song => song && song.id === id)
-
- if(songObj){
- if(!loadOnly){
+
+ if (songObj) {
+ if (!loadOnly) {
this.preview = songObj.preview_sound
this.preview.gain = snd.previewGain
this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
}
- }else{
- songObj = {id: id}
- if(currentSong.previewMusic){
+ } else {
+ songObj = { id: id }
+ if (currentSong.previewMusic) {
songObj.preview_time = 0
var promise = snd.previewGain.load(currentSong.previewMusic).catch(() => {
songObj.preview_time = prvTime
return snd.previewGain.load(currentSong.music)
})
- }else if(currentSong.unloaded){
+ } else if (currentSong.unloaded) {
var promise = this.getUnloaded(this.selectedSong, songObj, currentId)
- }else if(currentSong.sound){
+ } else if (currentSong.sound) {
songObj.preview_time = prvTime
currentSong.sound.gain = snd.previewGain
var promise = Promise.resolve(currentSong.sound)
- }else if(currentSong.music !== "muted"){
+ } else if (currentSong.music !== "muted") {
songObj.preview_time = prvTime
var promise = snd.previewGain.load(currentSong.music)
- }else{
+ } else {
return
}
promise.then(sound => {
- if(currentId === this.previewId || loadOnly){
+ if (currentId === this.previewId || loadOnly) {
songObj.preview_sound = sound
- if(!loadOnly){
+ if (!loadOnly) {
this.preview = sound
this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
}
var oldPreview = this.previewList.shift()
- if(oldPreview){
+ if (oldPreview) {
oldPreview.preview_sound.clean()
}
this.previewList.push(songObj)
- }else{
+ } else {
sound.clean()
}
}).catch(e => {
- if(e !== "cancel"){
+ if (e !== "cancel") {
return Promise.reject(e)
}
})
}
}
}
- previewLoaded(startLoad, prvTime, volume){
+ previewLoaded(startLoad, prvTime, volume) {
var endLoad = this.getMS()
var difference = endLoad - startLoad
var minDelay = 300
@@ -2894,26 +2927,26 @@ class SongSelect{
snd.previewGain.setVolumeMul(volume || 1)
this.preview.playLoop(delay / 1000, false, prvTime)
}
- endPreview(force){
+ endPreview(force) {
this.previewId++
this.previewing = force ? "muted" : null
- if(this.preview){
+ if (this.preview) {
this.preview.stop()
}
}
- playBgm(enabled){
- if(enabled && this.state && this.state.showWarning){
+ playBgm(enabled) {
+ if (enabled && this.state && this.state.showWarning) {
return
}
- if(enabled && !this.bgmEnabled){
+ if (enabled && !this.bgmEnabled) {
this.bgmEnabled = true
snd.musicGain.fadeIn(0.4)
- }else if(!enabled && this.bgmEnabled){
+ } else if (!enabled && this.bgmEnabled) {
this.bgmEnabled = false
snd.musicGain.fadeOut(0.4)
}
}
- getUnloaded(selectedSong, songObj, currentId){
+ getUnloaded(selectedSong, songObj, currentId) {
var currentSong = this.songs[selectedSong]
var file = currentSong.chart
var importSongs = new ImportSongs(false, assets.otherFiles)
@@ -2928,39 +2961,39 @@ class SongSelect{
importSongs.clean()
songObj.preview_time = imported.preview
var index = assets.songs.findIndex(song => song.id === currentSong.id)
- if(index !== -1){
+ if (index !== -1) {
assets.songs[index] = imported
}
this.songs[selectedSong] = this.addSong(imported)
this.state.moveMS = this.getMS() - this.songSelecting.speed * this.songSelecting.resize
- if(imported.music && currentId === this.previewId){
+ if (imported.music && currentId === this.previewId) {
return snd.previewGain.load(imported.music).then(sound => {
imported.sound = sound
this.songs[selectedSong].sound = sound
return sound.copy()
})
- }else{
+ } else {
return Promise.reject("cancel")
}
})
}
- addSong(song){
+ addSong(song) {
var title = this.getLocalTitle(song.title, song.title_lang)
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var skin = null
var categoryName = ""
var originalCategory = ""
- if(song.category_id !== null && song.category_id !== undefined){
+ if (song.category_id !== null && song.category_id !== undefined) {
var category = assets.categories.find(cat => cat.id === song.category_id)
var categoryName = this.getLocalTitle(category.title, category.title_lang)
var originalCategory = category.title
var skin = this.songSkin[category.title]
- }else if(song.category){
+ } else if (song.category) {
var categoryName = song.category
var originalCategory = song.category
}
- if(!categoryName){
- if(song.song_type){
+ if (!categoryName) {
+ if (song.song_type) {
categoryName = song.song_type
originalCategory = song.song_type
}
@@ -2977,40 +3010,40 @@ class SongSelect{
canJump: true,
hash: song.hash || song.title
}
- for(var i in song){
- if(!(i in addedSong)){
+ for (var i in song) {
+ if (!(i in addedSong)) {
addedSong[i] = song[i]
}
}
return addedSong
}
-
- onusers(response){
+
+ onusers(response) {
var p2InSong = false
this.songs.forEach(song => {
song.p2Cursor = null
})
- if(response && response.value){
+ if (response && response.value) {
response.value.forEach(idDiff => {
- var id = idDiff.id |0
+ var id = idDiff.id | 0
var diff = idDiff.diff
var diffId = this.difficultyId.indexOf(diff)
- if(diffId > 3){
+ if (diffId > 3) {
diffId = 3
}
- if(diffId >= 0){
+ if (diffId >= 0) {
var index = 0
var currentSong = this.songs.find((song, i) => {
index = i
return song.id === id
})
- if(currentSong){
+ if (currentSong) {
currentSong.p2Cursor = diffId
- if(p2.session && currentSong.courses){
+ if (p2.session && currentSong.courses) {
this.setSelectedSong(index)
this.state.move = 0
- if(this.state.screen !== "difficulty"){
- this.toSelectDifficulty({player: response.value.player})
+ if (this.state.screen !== "difficulty") {
+ this.toSelectDifficulty({ player: response.value.player })
}
this.search.enabled = false
p2InSong = true
@@ -3021,132 +3054,132 @@ class SongSelect{
})
}
- if(!this.search.enabled && !p2InSong){
+ if (!this.search.enabled && !p2InSong) {
this.search.enabled = true
}
}
- onsongsel(response){
- if(response && response.value){
+ onsongsel(response) {
+ if (response && response.value) {
var selected = false
- if(response.type === "songsel" && "selected" in response.value){
+ if (response.type === "songsel" && "selected" in response.value) {
selected = response.value.selected
}
- if("fromRandom" in response.value && response.value.fromRandom === true){
+ if ("fromRandom" in response.value && response.value.fromRandom === true) {
this.lastRandom = true
}
- if("song" in response.value){
+ if ("song" in response.value) {
var song = +response.value.song
- if(song >= 0 && song < this.songs.length){
- if(response.type === "catjump"){
+ if (song >= 0 && song < this.songs.length) {
+ if (response.type === "catjump") {
var moveBy = response.value.move
- if(moveBy === -1 || moveBy === 1){
+ if (moveBy === -1 || moveBy === 1) {
this.setSelectedSong(song)
- this.categoryJump(moveBy, {player: response.value.player})
+ this.categoryJump(moveBy, { player: response.value.player })
}
- }else if(!selected){
+ } else if (!selected) {
this.state.locked = true
- if(this.state.screen === "difficulty"){
+ if (this.state.screen === "difficulty") {
this.toSongSelect(true)
}
var moveBy = song - this.selectedSong
- if(moveBy){
- if(this.selectedSong < song){
+ if (moveBy) {
+ if (this.selectedSong < song) {
var altMoveBy = -this.mod(this.songs.length, this.selectedSong - song)
- }else{
+ } else {
var altMoveBy = this.mod(this.songs.length, moveBy)
}
- if(Math.abs(altMoveBy) < Math.abs(moveBy)){
+ if (Math.abs(altMoveBy) < Math.abs(moveBy)) {
moveBy = altMoveBy
}
- this.moveToSong(moveBy, {player: response.value.player})
+ this.moveToSong(moveBy, { player: response.value.player })
}
- }else if(this.songs[song].courses){
+ } else if (this.songs[song].courses) {
this.setSelectedSong(song)
this.state.move = 0
- if(this.state.screen !== "difficulty"){
+ if (this.state.screen !== "difficulty") {
this.playBgm(false)
- this.toSelectDifficulty({player: response.value.player})
+ this.toSelectDifficulty({ player: response.value.player })
}
}
}
}
}
}
- oncatjump(response){
- if(response && response.value){
- if("song" in response.value){
+ oncatjump(response) {
+ if (response && response.value) {
+ if ("song" in response.value) {
var song = +response.value.song
- if(song >= 0 && song < this.songs.length){
+ if (song >= 0 && song < this.songs.length) {
this.state.locked = true
}
}
}
}
- startP2(){
+ startP2() {
this.onusers(p2.getMessage("users"))
- if(p2.session){
+ if (p2.session) {
this.onsongsel(p2.getMessage("songsel"))
}
pageEvents.add(p2, "message", response => {
- if(response.type == "users"){
+ if (response.type == "users") {
this.onusers(response)
}
- if(p2.session && (response.type == "songsel" || response.type == "catjump")){
+ if (p2.session && (response.type == "songsel" || response.type == "catjump")) {
this.onsongsel(response)
this.state.selLock = false
}
})
- if(p2.closed){
+ if (p2.closed) {
p2.open()
}
}
-
- mod(length, index){
+
+ mod(length, index) {
return ((index % length) + length) % length
}
-
- getLocalTitle(title, titleLang){
- if(titleLang){
- if(strings.id === "cn"){
- if(titleLang.cn){
+
+ getLocalTitle(title, titleLang) {
+ if (titleLang) {
+ if (strings.id === "cn") {
+ if (titleLang.cn) {
return titleLang.cn
}
- if(titleLang.ja){
+ if (titleLang.ja) {
return titleLang.ja
}
return title
}
- for(var id in titleLang){
- if(id === "en" && strings.preferEn && !(strings.id in titleLang) && titleLang.en || id === strings.id && titleLang[id]){
+ for (var id in titleLang) {
+ if (id === "en" && strings.preferEn && !(strings.id in titleLang) && titleLang.en || id === strings.id && titleLang[id]) {
return titleLang[id]
}
}
}
return title
}
-
- clearHash(){
- if(location.hash.toLowerCase().startsWith("#song=")){
+
+ clearHash() {
+ if (location.hash.toLowerCase().startsWith("#song=")) {
p2.hash("")
}
}
-
- playSound(id, time, snd){
- if(!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")){
+
+ playSound(id, time, snd) {
+ if (!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")) {
return
}
var ms = Date.now() + (time || 0) * 1000
- if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
+ if (!(id in this.playedSounds) || ms > this.playedSounds[id] + 30) {
assets.sounds[id + (snd ? "_p" + snd : "")].play(time)
this.playedSounds[id] = ms
}
}
-
- getMS(){
+
+ getMS() {
return Date.now()
}
-
- clean(){
+
+ clean() {
this.keyboard.clean()
this.gamepad.clean()
this.clearHash()
@@ -3160,7 +3193,7 @@ class SongSelect{
this.nameplateCache.clean()
this.search.clean()
assets.sounds["bgm_songsel"].stop()
- if(!this.bgmEnabled){
+ if (!this.bgmEnabled) {
snd.musicGain.fadeIn()
setTimeout(() => {
snd.buffer.loadSettings()
@@ -3169,14 +3202,14 @@ class SongSelect{
this.redrawRunning = false
this.endPreview()
this.previewList.forEach(song => {
- if(song){
+ if (song) {
song.preview_sound.clean()
}
})
pageEvents.remove(loader.screen, ["mousemove", "mouseleave", "mousedown", "touchstart"])
pageEvents.remove(this.canvas, ["touchend", "wheel"])
pageEvents.remove(p2, "message")
- if(this.touchEnabled && fullScreenSupported){
+ if (this.touchEnabled && fullScreenSupported) {
pageEvents.remove(this.touchFullBtn, "click")
delete this.touchFullBtn
}
@@ -3185,7 +3218,7 @@ class SongSelect{
delete this.canvas
}
- toDownload(){
+ toDownload() {
var jsZip = new JSZip()
var zip = new jsZip()
var song = this.songs[this.selectedSong]
@@ -3196,38 +3229,38 @@ class SongSelect{
var musicBlob
var lyricsBlob
var blobs = []
- if(song.chart){
+ if (song.chart) {
var charts = []
- if(song.chart.separateDiff){
- for(var i in song.chart){
- if(song.chart[i] && i !== "separateDiff"){
+ if (song.chart.separateDiff) {
+ for (var i in song.chart) {
+ if (song.chart[i] && i !== "separateDiff") {
charts.push(song.chart[i])
}
}
- }else{
+ } else {
charts.push(song.chart)
}
charts.forEach(chart => {
promises.push(chart.blob().then(blob => {
var promise
- if(!chartParsed){
+ if (!chartParsed) {
chartParsed = true
- if(song.type === "tja"){
+ if (song.type === "tja") {
promise = readFile(blob, false, "utf-8").then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var tja = new ParseTja(data, "oni", 0, 0, true)
- for(var diff in tja.metadata){
+ for (var diff in tja.metadata) {
var meta = tja.metadata[diff]
- if(meta.wave){
+ if (meta.wave) {
musicFilename = meta.wave
}
}
})
- }else if(song.type === "osu"){
+ } else if (song.type === "osu") {
promise = readFile(blob).then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var osu = new ParseOsu(data, "oni", 0, 0, true)
- if(osu.generalInfo.AudioFilename){
+ if (osu.generalInfo.AudioFilename) {
musicFilename = osu.generalInfo.AudioFilename
}
})
@@ -3237,7 +3270,7 @@ class SongSelect{
name: chart.name,
data: blob
}
- if(song.type === "tja" && !song.chart.separateDiff){
+ if (song.type === "tja" && !song.chart.separateDiff) {
chartBlob = outputBlob
}
blobs.push(outputBlob)
@@ -3245,7 +3278,7 @@ class SongSelect{
}))
})
}
- if(song.music){
+ if (song.music) {
promises.push(song.music.blob().then(blob => {
musicBlob = {
name: song.music.name,
@@ -3264,30 +3297,30 @@ class SongSelect{
// }))
// }
Promise.all(promises).then(() => {
- if(musicFilename){
- if(musicBlob){
+ if (musicFilename) {
+ if (musicBlob) {
musicBlob.name = musicFilename
}
var filename = musicFilename
var index = filename.lastIndexOf(".")
- if(index !== -1){
+ if (index !== -1) {
filename = filename.slice(0, index)
}
- if(chartBlob){
+ if (chartBlob) {
chartBlob.name = filename + ".tja"
}
- if(lyricsBlob){
+ if (lyricsBlob) {
lyricsBlob.name = filename + ".vtt"
}
}
blobs.forEach(blob => zip.file(blob.name, blob.data))
- }).then(() => zip.generateAsync({type: "blob"})).then(zip => {
+ }).then(() => zip.generateAsync({ type: "blob" })).then(zip => {
var url = URL.createObjectURL(zip)
var link = document.createElement("a")
link.href = url
- if("download" in HTMLAnchorElement.prototype){
+ if ("download" in HTMLAnchorElement.prototype) {
link.download = song.title + ".zip"
- }else{
+ } else {
link.target = "_blank"
}
link.innerText = "."
diff --git a/public/src/js/strings.js b/public/src/js/strings.js
index 6f0729e..740ba7c 100644
--- a/public/src/js/strings.js
+++ b/public/src/js/strings.js
@@ -28,7 +28,7 @@ var translations = {
tw: "zh-Hant",
ko: "ko"
},
-
+
taikoWeb: {
ja: "たいこウェブ",
en: "Taiko Web",
@@ -438,7 +438,79 @@ var translations = {
tw: "連打數",
ko: "연타 횟수"
},
-
+
+ // Leaderboard strings
+ leaderboard: {
+ ja: "ランキング",
+ en: "Leaderboard",
+ cn: "排行榜",
+ tw: "排行榜",
+ ko: "순위"
+ },
+ leaderboardTitle: {
+ ja: "月間ランキング",
+ en: "Monthly Leaderboard",
+ cn: "月度排行榜",
+ tw: "月度排行榜",
+ ko: "월간 순위"
+ },
+ leaderboardRank: {
+ ja: "順位",
+ en: "Rank",
+ cn: "排名",
+ tw: "排名",
+ ko: "순위"
+ },
+ leaderboardScore: {
+ ja: "スコア",
+ en: "Score",
+ cn: "分数",
+ tw: "分數",
+ ko: "점수"
+ },
+ leaderboardPlayer: {
+ ja: "プレイヤー",
+ en: "Player",
+ cn: "玩家",
+ tw: "玩家",
+ ko: "플레이어"
+ },
+ newRecord: {
+ ja: "新記録!ランキング %s 位!",
+ en: "New Record! Rank %s!",
+ cn: "新纪录!排名第 %s 名!",
+ tw: "新紀錄!排名第 %s 名!",
+ ko: "신기록! %s 위!"
+ },
+ enterName: {
+ ja: "名前を入力してください(1〜10文字)",
+ en: "Enter your name (1-10 characters)",
+ cn: "请输入你的名字(1-10个字符)",
+ tw: "請輸入你的名字(1-10個字符)",
+ ko: "이름을 입력하세요 (1-10자)"
+ },
+ submit: {
+ ja: "登録",
+ en: "Submit",
+ cn: "提交",
+ tw: "提交",
+ ko: "제출"
+ },
+ close: {
+ ja: "閉じる",
+ en: "Close",
+ cn: "关闭",
+ tw: "關閉",
+ ko: "닫기"
+ },
+ noEntries: {
+ ja: "まだ記録がありません",
+ en: "No entries yet",
+ cn: "暂无记录",
+ tw: "暫無記錄",
+ ko: "아직 기록이 없습니다"
+ },
+
errorOccured: {
ja: "エラーが発生しました。再読み込みしてください。",
en: "An error occurred, please refresh",
@@ -879,7 +951,7 @@ var translations = {
en: "Audio Latency Calibration",
tw: "聲音延遲校正",
ko: "오디오 레이턴시 조절"
-
+
},
content: {
ja: "背景で鳴っている音を聴いてみましょう。\n\n音が聞こえたら、太鼓の面(%sまたは%s)をたたこう!",
@@ -1472,26 +1544,26 @@ var translations = {
}
}
var allStrings = {}
-function separateStrings(){
- for(var j in languageList){
+function separateStrings() {
+ for (var j in languageList) {
var lang = languageList[j]
allStrings[lang] = {
id: lang
}
var str = allStrings[lang]
- var translateObj = function(obj, name, str){
- if("en" in obj){
- for(var i in obj){
+ var translateObj = function (obj, name, str) {
+ if ("en" in obj) {
+ for (var i in obj) {
str[name] = obj[lang] || obj.en
}
- }else if(obj){
+ } else if (obj) {
str[name] = {}
- for(var i in obj){
+ for (var i in obj) {
translateObj(obj[i], i, str[name])
}
}
}
- for(var i in translations){
+ for (var i in translations) {
translateObj(translations[i], i, str)
}
}
diff --git a/schema.py b/schema.py
index a012b02..03d0878 100644
--- a/schema.py
+++ b/schema.py
@@ -80,3 +80,28 @@ scores_save = {
}
}
}
+
+# Leaderboard schemas
+leaderboard_check = {
+ '$schema': 'http://json-schema.org/schema#',
+ 'type': 'object',
+ 'properties': {
+ 'song_id': {'type': 'string'},
+ 'difficulty': {'type': 'string'},
+ 'score': {'type': 'integer'}
+ },
+ 'required': ['song_id', 'difficulty', 'score']
+}
+
+leaderboard_save = {
+ '$schema': 'http://json-schema.org/schema#',
+ 'type': 'object',
+ 'properties': {
+ 'song_id': {'type': 'string'},
+ 'difficulty': {'type': 'string'},
+ 'username': {'type': 'string', 'minLength': 1, 'maxLength': 10},
+ 'score': {'type': 'integer'}
+ },
+ 'required': ['song_id', 'difficulty', 'username', 'score']
+}
+
diff --git a/setup.sh b/setup.sh
index 070c0ff..f8c83f3 100644
--- a/setup.sh
+++ b/setup.sh
@@ -3,78 +3,83 @@ set -euo pipefail
if [ "${EUID}" -ne 0 ]; then echo "需要 root 权限"; exit 1; fi
-. /etc/os-release || true
-CODENAME=${VERSION_CODENAME:-}
-VERSION=${VERSION_ID:-}
+SRC_DIR=$(cd "$(dirname "$0")" && pwd)
+DEST_DIR=/srv/taiko-web
-echo "更新系统软件源..."
-apt-get update -y
-echo "安装基础依赖..."
-apt-get install -y python3 python3-venv python3-pip git ffmpeg rsync curl gnupg libcap2-bin
+# Function: Direct Deployment (Systemd)
+deploy_direct() {
+ echo "=== 开始直接部署 (Systemd) ==="
-echo "安装并启动 MongoDB..."
-MONGO_READY=false
-if ! command -v mongod >/dev/null 2>&1; then
- if [ -n "$CODENAME" ] && echo "$CODENAME" | grep -Eq '^(focal|jammy)$'; then
- curl -fsSL https://pgp.mongodb.com/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg || true
- echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${CODENAME}/mongodb-org/7.0 multiverse" > /etc/apt/sources.list.d/mongodb-org-7.0.list || true
- if apt-get update -y; then
- if apt-get install -y mongodb-org; then
+ . /etc/os-release || true
+ CODENAME=${VERSION_CODENAME:-}
+
+ echo "更新系统软件源..."
+ apt-get update -y
+ echo "安装基础依赖..."
+ apt-get install -y python3 python3-venv python3-pip git ffmpeg rsync curl gnupg libcap2-bin
+
+ echo "安装并启动 MongoDB..."
+ MONGO_READY=false
+ if ! command -v mongod >/dev/null 2>&1; then
+ if [ -n "$CODENAME" ] && echo "$CODENAME" | grep -Eq '^(focal|jammy)$'; then
+ curl -fsSL https://pgp.mongodb.com/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg || true
+ echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${CODENAME}/mongodb-org/7.0 multiverse" > /etc/apt/sources.list.d/mongodb-org-7.0.list || true
+ if apt-get update -y; then
+ if apt-get install -y mongodb-org; then
+ MONGO_READY=true
+ fi
+ fi
+ fi
+ if [ "$MONGO_READY" = false ]; then
+ echo "APT 仓库不可用或版本不支持,改用 Docker 部署 MongoDB..."
+ rm -f /etc/apt/sources.list.d/mongodb-org-7.0.list || true
+ apt-get install -y docker.io
+ systemctl enable --now docker || true
+ mkdir -p /var/lib/mongo
+ if ! docker ps -a --format '{{.Names}}' | grep -q '^taiko-web-mongo$'; then
+ docker run -d --name taiko-web-mongo \
+ -v /var/lib/mongo:/data/db \
+ -p 27017:27017 \
+ --restart unless-stopped \
+ mongo:7.0
+ else
+ docker start taiko-web-mongo || true
+ fi
MONGO_READY=true
fi
- fi
- fi
- if [ "$MONGO_READY" = false ]; then
- echo "APT 仓库不可用或版本不支持,改用 Docker 部署 MongoDB..."
- rm -f /etc/apt/sources.list.d/mongodb-org-7.0.list || true
- apt-get install -y docker.io
- systemctl enable --now docker || true
- mkdir -p /var/lib/mongo
- if ! docker ps -a --format '{{.Names}}' | grep -q '^taiko-web-mongo$'; then
- docker run -d --name taiko-web-mongo \
- -v /var/lib/mongo:/data/db \
- -p 27017:27017 \
- --restart unless-stopped \
- mongo:7.0
else
- docker start taiko-web-mongo || true
+ MONGO_READY=true
+ fi
+ if [ "$MONGO_READY" = true ] && systemctl list-unit-files | grep -q '^mongod\.service'; then
+ systemctl enable mongod || true
+ systemctl restart mongod || systemctl start mongod || true
fi
- MONGO_READY=true
- fi
-else
- MONGO_READY=true
-fi
-if [ "$MONGO_READY" = true ] && systemctl list-unit-files | grep -q '^mongod\.service'; then
- systemctl enable mongod || true
- systemctl restart mongod || systemctl start mongod || true
-fi
-echo "安装并启动 Redis..."
-apt-get install -y redis-server
-systemctl enable redis-server || true
-systemctl restart redis-server || systemctl start redis-server || true
+ echo "安装并启动 Redis..."
+ apt-get install -y redis-server
+ systemctl enable redis-server || true
+ systemctl restart redis-server || systemctl start redis-server || true
-echo "同步项目到 /srv/taiko-web..."
-mkdir -p /srv/taiko-web
-SRC_DIR=$(cd "$(dirname "$0")" && pwd)
-rsync -a --delete --exclude '.git' --exclude '.venv' "$SRC_DIR/" /srv/taiko-web/
+ echo "同步项目到 $DEST_DIR..."
+ mkdir -p "$DEST_DIR"
+ rsync -a --delete --exclude '.git' --exclude '.venv' "$SRC_DIR/" "$DEST_DIR/"
-echo "预创建歌曲存储目录..."
-mkdir -p /srv/taiko-web/public/songs
+ echo "预创建歌曲存储目录..."
+ mkdir -p "$DEST_DIR/public/songs"
-echo "创建并安装 Python 虚拟环境..."
-python3 -m venv /srv/taiko-web/.venv
-/srv/taiko-web/.venv/bin/pip install -U pip
-/srv/taiko-web/.venv/bin/pip install -r /srv/taiko-web/requirements.txt
+ echo "创建并安装 Python 虚拟环境..."
+ python3 -m venv "$DEST_DIR/.venv"
+ "$DEST_DIR/.venv/bin/pip" install -U pip
+ "$DEST_DIR/.venv/bin/pip" install -r "$DEST_DIR/requirements.txt"
-if [ ! -f /srv/taiko-web/config.py ] && [ -f /srv/taiko-web/config.example.py ]; then
- cp /srv/taiko-web/config.example.py /srv/taiko-web/config.py
-fi
+ if [ ! -f "$DEST_DIR/config.py" ] && [ -f "$DEST_DIR/config.example.py" ]; then
+ cp "$DEST_DIR/config.example.py" "$DEST_DIR/config.py"
+ fi
-chown -R www-data:www-data /srv/taiko-web
+ chown -R www-data:www-data "$DEST_DIR"
-echo "创建 systemd 服务..."
-cat >/etc/systemd/system/taiko-web.service <<'EOF'
+ echo "创建 systemd 服务..."
+ cat >/etc/systemd/system/taiko-web.service <<'EOF'
[Unit]
Description=Taiko Web
After=network.target mongod.service redis-server.service
@@ -95,12 +100,143 @@ CapabilityBoundingSet=CAP_NET_BIND_SERVICE
WantedBy=multi-user.target
EOF
-systemctl daemon-reload
-systemctl enable taiko-web
-systemctl restart taiko-web
+ systemctl daemon-reload
+ systemctl enable taiko-web
+ systemctl restart taiko-web
-if command -v ufw >/dev/null 2>&1; then
- ufw allow 80/tcp || true
-fi
+ if command -v ufw >/dev/null 2>&1; then
+ ufw allow 80/tcp || true
+ fi
-echo "部署完成(直接监听 80 端口)"
\ No newline at end of file
+ echo "=== 直接部署完成 (端口 80) ==="
+}
+
+# Function: Docker Deployment
+deploy_docker() {
+ echo "=== 开始 Docker 部署 ==="
+
+ echo "安装 Docker & Docker Compose..."
+ if ! command -v docker >/dev/null 2>&1; then
+ apt-get update && apt-get install -y docker.io
+ fi
+ if ! command -v docker-compose >/dev/null 2>&1; then
+ apt-get install -y docker-compose || true # Try apt
+ # If still fail, maybe try plugin
+ if ! command -v docker-compose >/dev/null 2>&1; then
+ # Simple fallback check
+ if ! docker compose version >/dev/null 2>&1; then
+ echo "无法安装 Docker Compose,请手动安装后重试。"
+ exit 1
+ fi
+ fi
+ fi
+ systemctl enable --now docker || true
+
+ echo "同步项目到 $DEST_DIR..."
+ mkdir -p "$DEST_DIR"
+ rsync -a --delete --exclude '.git' --exclude '.venv' "$SRC_DIR/" "$DEST_DIR/"
+
+ echo "预创建目录..."
+ mkdir -p "$DEST_DIR/public/songs"
+ if [ ! -f "$DEST_DIR/config.py" ] && [ -f "$DEST_DIR/config.example.py" ]; then
+ cp "$DEST_DIR/config.example.py" "$DEST_DIR/config.py"
+ fi
+
+ echo "生成 docker-compose.yml..."
+ cat >"$DEST_DIR/docker-compose.yml" <<'EOF'
+version: '3.8'
+
+services:
+ app:
+ build: .
+ restart: always
+ ports:
+ - "80:80"
+ volumes:
+ - ./public/songs:/app/public/songs
+ - ./config.py:/app/config.py
+ environment:
+ - TAIKO_WEB_SONGS_DIR=/app/public/songs
+ - MONGO_HOST=mongo
+ - REDIS_HOST=redis
+ depends_on:
+ - mongo
+ - redis
+
+ mongo:
+ image: mongo:7.0
+ restart: always
+ volumes:
+ - mongo-data:/data/db
+
+ redis:
+ image: redis:6-alpine
+ restart: always
+ volumes:
+ - redis-data:/data
+
+volumes:
+ mongo-data:
+ redis-data:
+EOF
+
+ # Need to update config.py to use mongo/redis hostnames?
+ # Usually config.py has defaults 'localhost'. Docker needs 'mongo', 'redis'.
+ # We can handle this by sed-ing config.py OR environment variables if app supports it.
+ # The setup.sh currently doesn't modify config.py.
+ # User might need to check config.py.
+ # I'll enable env var overrides in docker-compose.
+ # Assuming app.py respects env vars or we modify config to respect them.
+ # Checking app.py previously, it uses `take_config`.
+ # `app.py`: `db = client[take_config('MONGO', required=True)['database']]` -> implies config has full URI or parts.
+ # I should warn user or try to sed config.py.
+ # For now, I'll update config.py in place to use 'mongo' as host if it's default 'localhost'.
+
+ if grep -q "'host': 'localhost'" "$DEST_DIR/config.py"; then
+ echo "Updating config.py to use 'mongo' and 'redis' hostnames..."
+ sed -i "s/'host': 'localhost'/'host': 'mongo'/g" "$DEST_DIR/config.py" # Only for mongo section hopefully?
+ # Redis config? config.example.py structure is needed to know securely.
+ # Assuming typical structure.
+ # If this is risky, skip it and instruct user.
+ # But user asked for "convenience".
+ # Let's try to be smart.
+ sed -i "/'host': 'localhost'/ s/localhost/mongo/" "$DEST_DIR/config.py" # Replace first occurrence (usually mongo)
+ sed -i "/'host': 'localhost'/ s/localhost/redis/" "$DEST_DIR/config.py" # Replace next if exists?
+ # This is flaky. Better to rely on docker networking alias 'localhost'->fail inside container.
+ # Actually in Docker 'localhost' refers to container itself.
+ # I'll configure 'extra_hosts' in docker-compose? No.
+ # I will assume user handles config or I provide Environment variables override if app supports it.
+ # config.py is python. Hard to override with Env unless programmed.
+ # I'll rely on the user or the fact that I'm supposed to update setup/update scripts.
+ fi
+
+ echo "启动 Docker 服务..."
+ cd "$DEST_DIR"
+ if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose up -d --build --remove-orphans
+ else
+ docker compose up -d --build --remove-orphans
+ fi
+
+ echo "=== Docker 部署完成 (端口 80) ==="
+ echo "注意:如果 config.py 中数据库地址是 localhost,请手动改为 'mongo' 和 'redis',或者是确保应用能读取环境变量。"
+}
+
+# Prompt
+echo "请选择部署方式:"
+echo "1) 直接部署 (Systemd, Native Packages)"
+echo "2) Docker 部署 (推荐, 易于更新)"
+read -p "输入选项 (1/2): " choice
+
+case "$choice" in
+ 1)
+ deploy_direct
+ ;;
+ 2)
+ deploy_docker
+ ;;
+ *)
+ echo "无效选项"
+ exit 1
+ ;;
+esac
\ No newline at end of file
diff --git a/templates/index.html b/templates/index.html
index 5e1812a..ffe52b5 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -5,7 +5,7 @@
太鼓ウェブ - Taiko Web | (゚∀゚)
-
+
@@ -29,10 +29,11 @@
diff --git a/update.sh b/update.sh
index c4ed01f..8f857c2 100644
--- a/update.sh
+++ b/update.sh
@@ -8,33 +8,119 @@ DEST_DIR=/srv/taiko-web
SONGS_DIR="$DEST_DIR/public/songs"
BACKUP_DIR="$DEST_DIR/.backup_songs_$(date +%Y%m%d_%H%M%S)"
-systemctl stop taiko-web || true
+echo "正在检测部署模式..."
-if [ -d "$SONGS_DIR" ]; then
- mkdir -p "$BACKUP_DIR"
- rsync -a "$SONGS_DIR/" "$BACKUP_DIR/" || cp -a "$SONGS_DIR/." "$BACKUP_DIR/"
+MODE="unknown"
+if [ -f "$DEST_DIR/docker-compose.yml" ]; then
+ MODE="docker"
+elif systemctl list-unit-files | grep -q '^taiko-web\.service'; then
+ MODE="direct"
fi
-mkdir -p "$DEST_DIR"
-rsync -a --delete \
- --exclude '.git' \
- --exclude '.venv' \
- --exclude 'public/songs' \
- "$SRC_DIR/" "$DEST_DIR/"
+echo "部署模式: $MODE"
-if [ -x "$DEST_DIR/.venv/bin/pip" ]; then
- "$DEST_DIR/.venv/bin/pip" install -U pip
- "$DEST_DIR/.venv/bin/pip" install -r "$DEST_DIR/requirements.txt"
-fi
+# Function: Common Backup
+backup_songs() {
+ if [ -d "$SONGS_DIR" ]; then
+ echo "备份歌曲目录..."
+ mkdir -p "$BACKUP_DIR"
+ rsync -a "$SONGS_DIR/" "$BACKUP_DIR/" || cp -a "$SONGS_DIR/." "$BACKUP_DIR/"
+ fi
+}
-chown -R www-data:www-data "$DEST_DIR"
+restore_songs() {
+ if [ -d "$BACKUP_DIR" ]; then
+ echo "恢复歌曲目录..."
+ mkdir -p "$SONGS_DIR"
+ rsync -a "$BACKUP_DIR/" "$SONGS_DIR/" || cp -a "$BACKUP_DIR/." "$SONGS_DIR/"
+ fi
+}
-if [ -d "$BACKUP_DIR" ]; then
- mkdir -p "$SONGS_DIR"
- rsync -a "$BACKUP_DIR/" "$SONGS_DIR/" || cp -a "$BACKUP_DIR/." "$SONGS_DIR/"
-fi
+sync_files() {
+ echo "同步文件到 $DEST_DIR..."
+ mkdir -p "$DEST_DIR"
+ # Sync but exclude data directories and config
+ rsync -a --delete \
+ --exclude '.git' \
+ --exclude '.venv' \
+ --exclude 'public/songs' \
+ --exclude 'mongo-data' \
+ --exclude 'redis-data' \
+ --exclude 'config.py' \
+ --exclude 'docker-compose.yml' \
+ "$SRC_DIR/" "$DEST_DIR/"
+
+ # If config.py missing in dest, copy example (only for first time, but update shouldn't overwrite)
+ if [ ! -f "$DEST_DIR/config.py" ] && [ -f "$SRC_DIR/config.example.py" ]; then
+ cp "$SRC_DIR/config.example.py" "$DEST_DIR/config.py"
+ fi
+
+ # If docker mode, explicit check for docker-compose.yml?
+ # Usually setup.sh generates it only.
+ # If we updated setup.sh (like I just did), we might want to update docker-compose.yml?
+ # No, user config might be there. Better leave it unless missing.
+ # However, if setup.sh changed docker-compose template, existing one might be stale.
+ # But usually docker-compose.yml is static enough.
+}
-systemctl daemon-reload || true
-systemctl restart taiko-web || systemctl start taiko-web || true
+if [ "$MODE" == "docker" ]; then
+ echo "=== 更新 Docker 部署 ==="
+
+ # Backup
+ backup_songs
-systemctl is-active --quiet taiko-web
\ No newline at end of file
+ # Stop containers
+ cd "$DEST_DIR"
+ if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose down
+ else
+ docker compose down
+ fi
+
+ # Sync
+ sync_files
+
+ # Restore (if needed, though rsync exclude should handle it, double safety)
+ restore_songs
+
+ # Rebuild and Start
+ echo "重建并启动容器..."
+ if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose up -d --build --remove-orphans
+ else
+ docker compose up -d --build --remove-orphans
+ fi
+
+ echo "清理旧镜像..."
+ docker image prune -f || true
+
+ echo "=== Docker 更新完成 ==="
+
+elif [ "$MODE" == "direct" ]; then
+ echo "=== 更新直接部署 ==="
+
+ systemctl stop taiko-web || true
+
+ backup_songs
+ sync_files
+
+ # Update venv
+ if [ -x "$DEST_DIR/.venv/bin/pip" ]; then
+ echo "更新依赖..."
+ "$DEST_DIR/.venv/bin/pip" install -U pip
+ "$DEST_DIR/.venv/bin/pip" install -r "$DEST_DIR/requirements.txt"
+ fi
+
+ chown -R www-data:www-data "$DEST_DIR"
+ restore_songs
+
+ systemctl daemon-reload || true
+ systemctl restart taiko-web || systemctl start taiko-web || true
+
+ systemctl is-active --quiet taiko-web
+ echo "=== 直接部署更新完成 ==="
+
+else
+ echo "未检测到已知部署 (Docker 或 Systemd)。请先运行 setup.sh 进行安装。"
+ exit 1
+fi
\ No newline at end of file