Add custom Don

- Registered users can customise the colour of their Don and it will appear for other players
- Bug fixes:
  - Add lyrics checkbox to admin page
  - 2P shows above "creative" or "with lyrics" labels
  - Prevent accidental alt and menu keyboard presses from triggering browser menus
  - Fixed mouse hitboxes on difficulty selection
  - Clean cached sounds and lyrics when another song is loading
  - Fixed debug jumping to the top-left of the screen when hidden
  - Fixed server volume not being applied to songs
This commit is contained in:
LoveEevee 2020-04-04 16:48:58 +03:00
parent ed97f9c593
commit cd288d4fa4
46 changed files with 448 additions and 57 deletions

63
app.py
View File

@ -136,6 +136,29 @@ def get_version():
return version return version
def get_db_don(user):
don_body_fill = user['don_body_fill'] if 'don_body_fill' in user else get_default_don('body_fill')
don_face_fill = user['don_face_fill'] if 'don_face_fill' in user else get_default_don('face_fill')
return {'body_fill': don_body_fill, 'face_fill': don_face_fill}
def get_default_don(part=None):
if part == None:
return {
'body_fill': get_default_don('body_fill'),
'face_fill': get_default_don('face_fill')
}
elif part == 'body_fill':
return '#5fb7c1'
elif part == 'face_fill':
return '#ff5724'
def is_hex(input):
try:
int(input, 16)
return True
except ValueError:
return False
@app.route('/') @app.route('/')
def route_index(): def route_index():
@ -213,6 +236,7 @@ def route_admin_songs_new_post():
output['preview'] = float(request.form.get('preview')) or None output['preview'] = float(request.form.get('preview')) or None
output['volume'] = float(request.form.get('volume')) or None output['volume'] = float(request.form.get('volume')) or None
output['maker_id'] = int(request.form.get('maker_id')) or None output['maker_id'] = int(request.form.get('maker_id')) or None
output['lyrics'] = True if request.form.get('lyrics') else False
output['hash'] = None output['hash'] = None
seq = db.seq.find_one({'name': 'songs'}) seq = db.seq.find_one({'name': 'songs'})
@ -262,6 +286,7 @@ def route_admin_songs_id_post(id):
output['preview'] = float(request.form.get('preview')) or None output['preview'] = float(request.form.get('preview')) or None
output['volume'] = float(request.form.get('volume')) or None output['volume'] = float(request.form.get('volume')) or None
output['maker_id'] = int(request.form.get('maker_id')) or None output['maker_id'] = int(request.form.get('maker_id')) or None
output['lyrics'] = True if request.form.get('lyrics') else False
output['hash'] = request.form.get('hash') output['hash'] = request.form.get('hash')
if request.form.get('gen_hash'): if request.form.get('gen_hash'):
@ -366,13 +391,15 @@ def route_api_register():
salt = bcrypt.gensalt() salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password, salt) hashed = bcrypt.hashpw(password, salt)
don = get_default_don()
session_id = os.urandom(24).hex() session_id = os.urandom(24).hex()
db.users.insert_one({ db.users.insert_one({
'username': username, 'username': username,
'username_lower': username.lower(), 'username_lower': username.lower(),
'password': hashed, 'password': hashed,
'display_name': username, 'display_name': username,
'don': don,
'user_level': 1, 'user_level': 1,
'session_id': session_id 'session_id': session_id
}) })
@ -380,7 +407,7 @@ def route_api_register():
session['session_id'] = session_id session['session_id'] = session_id
session['username'] = username session['username'] = username
session.permanent = True session.permanent = True
return jsonify({'status': 'ok', 'username': username, 'display_name': username}) return jsonify({'status': 'ok', 'username': username, 'display_name': username, 'don': don})
@app.route('/api/login', methods=['POST']) @app.route('/api/login', methods=['POST'])
@ -401,11 +428,13 @@ def route_api_login():
if not bcrypt.checkpw(password, result['password']): if not bcrypt.checkpw(password, result['password']):
return api_error('invalid_username_password') return api_error('invalid_username_password')
don = get_db_don(result)
session['session_id'] = result['session_id'] session['session_id'] = result['session_id']
session['username'] = result['username'] session['username'] = result['username']
session.permanent = True if data.get('remember') else False session.permanent = True if data.get('remember') else False
return jsonify({'status': 'ok', 'username': result['username'], 'display_name': result['display_name']}) return jsonify({'status': 'ok', 'username': result['username'], 'display_name': result['display_name'], 'don': don})
@app.route('/api/logout', methods=['POST']) @app.route('/api/logout', methods=['POST'])
@ -435,6 +464,31 @@ def route_api_account_display_name():
return jsonify({'status': 'ok', 'display_name': display_name}) return jsonify({'status': 'ok', 'display_name': display_name})
@app.route('/api/account/don', methods=['POST'])
@login_required
def route_api_account_don():
data = request.get_json()
if not schema.validate(data, schema.update_don):
return abort(400)
don_body_fill = data.get('body_fill', '').strip()
don_face_fill = data.get('face_fill', '').strip()
if len(don_body_fill) != 7 or\
not don_body_fill.startswith("#")\
or not is_hex(don_body_fill[1:])\
or len(don_face_fill) != 7\
or not don_face_fill.startswith("#")\
or not is_hex(don_face_fill[1:]):
return api_error('invalid_don')
db.users.update_one({'username': session.get('username')}, {'$set': {
'don_body_fill': don_body_fill,
'don_face_fill': don_face_fill,
}})
return jsonify({'status': 'ok', 'don': {'body_fill': don_body_fill, 'face_fill': don_face_fill}})
@app.route('/api/account/password', methods=['POST']) @app.route('/api/account/password', methods=['POST'])
@login_required @login_required
def route_api_account_password(): def route_api_account_password():
@ -518,7 +572,8 @@ def route_api_scores_get():
}) })
user = db.users.find_one({'username': username}) user = db.users.find_one({'username': username})
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name']}) don = get_db_don(user)
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
def make_preview(song_id, song_type, preview): def make_preview(song_id, song_type, preview):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

View File

@ -107,10 +107,14 @@ main {
width: 50px; width: 50px;
} }
h1 small { h1 .song-id {
color: #4a4a4a; color: #4a4a4a;
} }
.song .song-id {
color: #a01300;
}
.form-field-indent { .form-field-indent {
margin-left: 20px; margin-left: 20px;
} }

View File

@ -100,6 +100,7 @@
font-size: calc(45px * var(--scale)); font-size: calc(45px * var(--scale));
line-height: 1.2; line-height: 1.2;
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: break-word;
} }
#game.portrait #song-lyrics{ #game.portrait #song-lyrics{
right: calc(20px * var(--scale)); right: calc(20px * var(--scale));

View File

@ -377,3 +377,29 @@ kbd{
font-size: 1.1em; font-size: 1.1em;
color: #d00; color: #d00;
} }
.customdon-div{
display: flex;
justify-content: center;
align-items: center;
text-align: right;
}
.customdon-canvas{
max-width: 40vw;
}
.customdon-div label{
display: block;
padding: 0.3em;
}
.customdon-div input[type="color"]{
font-size: inherit;
width: 2.6em;
height: 1.6em;
padding: 0 0.1em;
vertical-align: middle;
}
.customdon-reset{
width: 100%;
font-family: inherit;
font-size: 1em;
padding: 0.2em;
}

View File

@ -42,6 +42,35 @@ class Account{
this.displayname.value = account.displayName this.displayname.value = account.displayName
this.inputForms.push(this.displayname) this.inputForms.push(this.displayname)
this.redrawRunning = true
this.customdonRedrawBind = this.customdonRedraw.bind(this)
this.start = new Date().getTime()
this.frames = [
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,7 ,8 ,9 ,10,
11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17
]
this.customdonCache = new CanvasCache()
this.customdonCache.resize(723 * 2, 1858, 1)
this.customdonCanvas = this.getElement("customdon-canvas")
this.customdonCtx = this.customdonCanvas.getContext("2d")
this.customdonBodyFill = this.getElement("customdon-bodyfill")
this.customdonBodyFill.value = account.don.body_fill
var parent = this.customdonBodyFill.parentNode
parent.insertBefore(document.createTextNode(strings.account.customdon.bodyFill), parent.firstChild)
pageEvents.add(this.customdonBodyFill, "change", this.customdonChange.bind(this))
this.customdonFaceFill = this.getElement("customdon-facefill")
this.customdonFaceFill.value = account.don.face_fill
var parent = this.customdonFaceFill.parentNode
parent.insertBefore(document.createTextNode(strings.account.customdon.faceFill), parent.firstChild)
pageEvents.add(this.customdonFaceFill, "change", this.customdonChange.bind(this))
this.customdonResetBtn = this.getElement("customdon-reset")
this.customdonResetBtn.value = strings.account.customdon.reset
pageEvents.add(this.customdonResetBtn, ["click", "touchstart"], this.customdonReset.bind(this))
this.customdonChange()
this.customdonRedraw()
this.accountPassButton = this.getElement("accountpass-btn") this.accountPassButton = this.getElement("accountpass-btn")
this.setAltText(this.accountPassButton, strings.account.changePassword) this.setAltText(this.accountPassButton, strings.account.changePassword)
pageEvents.add(this.accountPassButton, ["click", "touchstart"], event => { pageEvents.add(this.accountPassButton, ["click", "touchstart"], event => {
@ -83,6 +112,70 @@ class Account{
pageEvents.add(this.inputForms[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this)) pageEvents.add(this.inputForms[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this))
} }
} }
customdonChange(){
var ctx = this.customdonCtx
this.customdonCache.clear()
var w = 722
var h = 1858
this.customdonCache.set({
w: w, h: h, id: "bodyFill"
}, ctx => {
ctx.drawImage(assets.image["don_anim_normal_b1"], 0, 0)
ctx.globalCompositeOperation = "source-atop"
ctx.fillStyle = this.customdonBodyFill.value
ctx.fillRect(0, 0, w, h)
})
this.customdonCache.set({
w: w, h: h, id: "faceFill"
}, ctx => {
ctx.drawImage(assets.image["don_anim_normal_b2"], 0, 0)
ctx.globalCompositeOperation = "source-atop"
ctx.fillStyle = this.customdonFaceFill.value
ctx.fillRect(0, 0, w, h)
ctx.globalCompositeOperation = "source-over"
this.customdonCache.get({
ctx: ctx,
x: 0, y: 0, w: w, h: h,
id: "bodyFill"
})
})
}
customdonReset(event){
if(event.type === "touchstart"){
event.preventDefault()
}
this.customdonBodyFill.value = defaultDon.body_fill
this.customdonFaceFill.value = defaultDon.face_fill
this.customdonChange()
}
customdonRedraw(){
if(!this.redrawRunning){
return
}
requestAnimationFrame(this.customdonRedrawBind)
if(!document.hasFocus()){
return
}
var ms = new Date().getTime()
var ctx = this.customdonCtx
var frame = this.frames[Math.floor((ms - this.start) / 30) % this.frames.length]
var w = 360
var h = 184
var sx = Math.floor(frame / 10) * (w + 2)
var sy = (frame % 10) * (h + 2)
ctx.clearRect(0, 0, w, h)
this.customdonCache.get({
ctx: ctx,
sx: sx, sy: sy, sw: w, sh: h,
x: -26, y: 0, w: w, h: h,
id: "faceFill"
})
ctx.drawImage(assets.image["don_anim_normal_a"],
sx, sy, w, h,
-26, 0, w, h
)
}
showDiv(event, div){ showDiv(event, div){
if(event){ if(event){
if(event.type === "touchstart"){ if(event.type === "touchstart"){
@ -257,6 +350,7 @@ class Account{
account.loggedIn = true account.loggedIn = true
account.username = response.username account.username = response.username
account.displayName = response.display_name account.displayName = response.display_name
account.don = response.don
var loadScores = scores => { var loadScores = scores => {
scoreStorage.load(scores) scoreStorage.load(scores)
this.onEnd(false, true, true) this.onEnd(false, true, true)
@ -300,6 +394,7 @@ class Account{
account.loggedIn = false account.loggedIn = false
delete account.username delete account.username
delete account.displayName delete account.displayName
delete account.don
var loadScores = () => { var loadScores = () => {
scoreStorage.load() scoreStorage.load()
this.onEnd(false, true) this.onEnd(false, true)
@ -344,6 +439,7 @@ class Account{
account.loggedIn = false account.loggedIn = false
delete account.username delete account.username
delete account.displayName delete account.displayName
delete account.don
scoreStorage.load() scoreStorage.load()
pageEvents.send("logout") pageEvents.send("logout")
return Promise.resolve return Promise.resolve
@ -357,6 +453,16 @@ class Account{
account.displayName = response.display_name account.displayName = response.display_name
})) }))
} }
var bodyFill = this.customdonBodyFill.value
var faceFill = this.customdonFaceFill.value
if(!noNameChange && (bodyFill !== account.body_fill || this.customdonFaceFill.value !== account.face_fill)){
promises.push(this.request("account/don", {
body_fill: bodyFill,
face_fill: faceFill
}).then(response => {
account.don = response.don
}))
}
var error = false var error = false
var errorFunc = response => { var errorFunc = response => {
if(error){ if(error){
@ -470,6 +576,11 @@ class Account{
this.accountPass.reset() this.accountPass.reset()
this.accountDel.reset() this.accountDel.reset()
} }
this.redrawRunning = false
this.customdonCache.clean()
pageEvents.remove(this.customdonBodyFill, "change")
pageEvents.remove(this.customdonFaceFill, "change")
pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"])
pageEvents.remove(this.accounPassButton, ["click", "touchstart"]) pageEvents.remove(this.accounPassButton, ["click", "touchstart"])
pageEvents.remove(this.accountDelButton, ["click", "touchstart"]) pageEvents.remove(this.accountDelButton, ["click", "touchstart"])
pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"]) pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"])
@ -479,6 +590,12 @@ class Account{
} }
delete this.errorDiv delete this.errorDiv
delete this.displayname delete this.displayname
delete this.frames
delete this.customdonCanvas
delete this.customdonCtx
delete this.customdonBodyFill
delete this.customdonFaceFill
delete this.customdonResetBtn
delete this.accountPassButton delete this.accountPassButton
delete this.accountPass delete this.accountPass
delete this.accountPassDiv delete this.accountPassDiv

View File

@ -58,11 +58,21 @@ var assets = {
"dancing-don.gif", "dancing-don.gif",
"bg-pattern-1.png", "bg-pattern-1.png",
"difficulty.png", "difficulty.png",
"don_anim_normal.png", "don_anim_normal_a.png",
"don_anim_10combo.png", "don_anim_normal_b1.png",
"don_anim_gogo.png", "don_anim_normal_b2.png",
"don_anim_gogostart.png", "don_anim_10combo_a.png",
"don_anim_clear.png", "don_anim_10combo_b1.png",
"don_anim_10combo_b2.png",
"don_anim_gogo_a.png",
"don_anim_gogo_b1.png",
"don_anim_gogo_b2.png",
"don_anim_gogostart_a.png",
"don_anim_gogostart_b1.png",
"don_anim_gogostart_b2.png",
"don_anim_clear_a.png",
"don_anim_clear_b1.png",
"don_anim_clear_b2.png",
"fire_anim.png", "fire_anim.png",
"fireworks_anim.png", "fireworks_anim.png",
"bg_genre_0.png", "bg_genre_0.png",

View File

@ -44,11 +44,47 @@ class CanvasAsset{
mod(length, index){ mod(length, index){
return ((index % length) + length) % length return ((index % length) + length) % length
} }
addFrames(name, frames, image){ addFrames(name, frames, image, don){
var framesObj = { var framesObj = {
frames: frames frames: frames,
don: don
} }
if(image){ if(don){
var img = assets.image[image + "_a"]
var cache1 = new CanvasCache()
var cache2 = new CanvasCache()
var w = img.width
var h = img.height
cache1.resize(w, h, 1)
cache2.resize(w, h, 1)
cache1.set({
w: w, h: h, id: "1"
}, ctx => {
ctx.drawImage(assets.image[image + "_b1"], 0, 0)
ctx.globalCompositeOperation = "source-atop"
ctx.fillStyle = don.body_fill
ctx.fillRect(0, 0, w, h)
})
cache2.set({
w: w, h: h, id: "2"
}, ctx => {
ctx.drawImage(assets.image[image + "_b2"], 0, 0)
ctx.globalCompositeOperation = "source-atop"
ctx.fillStyle = don.face_fill
ctx.fillRect(0, 0, w, h)
ctx.globalCompositeOperation = "source-over"
cache1.get({
ctx: ctx,
x: 0, y: 0, w: w, h: h,
id: "1"
})
ctx.drawImage(img, 0, 0)
})
cache1.clean()
framesObj.cache = cache2
framesObj.image = cache2.canvas
}else if(image){
framesObj.image = assets.image[image] framesObj.image = assets.image[image]
} }
this.animationFrames[name] = framesObj this.animationFrames[name] = framesObj
@ -61,6 +97,7 @@ class CanvasAsset{
if(framesObj.image){ if(framesObj.image){
this.image = framesObj.image this.image = framesObj.image
} }
this.don = framesObj.don
}else{ }else{
this.animation = false this.animation = false
} }
@ -100,4 +137,12 @@ class CanvasAsset{
} }
this.beatInterval = beatMS this.beatInterval = beatMS
} }
clean(){
for(var i in this.animationFrames){
var frame = this.animationFrames[i]
if(frame.cache){
frame.cache.clean()
}
}
}
} }

View File

@ -91,8 +91,12 @@ class CanvasCache{
return return
} }
var z = this.scale var z = this.scale
var sx = (img.x + (config.sx || 0)) * z |0
var sy = (img.y + (config.sy || 0)) * z |0
var sw = (config.sw || img.w) * z |0
var sh = (config.sh || img.h) * z |0
config.ctx.drawImage(this.canvas, config.ctx.drawImage(this.canvas,
img.x * z |0, img.y * z |0, img.w * z |0, img.h * z |0, sx, sy, sw, sh,
config.x |0, config.y |0, config.w |0, config.h |0 config.x |0, config.y |0, config.w |0, config.h |0
) )
if(saved){ if(saved){

View File

@ -8,8 +8,16 @@ class Controller{
this.touchEnabled = touchEnabled this.touchEnabled = touchEnabled
if(multiplayer === 2){ if(multiplayer === 2){
this.snd = p2.player === 2 ? "_p1" : "_p2" this.snd = p2.player === 2 ? "_p1" : "_p2"
this.don = p2.don || defaultDon
}else{ }else{
this.snd = multiplayer ? "_p" + p2.player : "" this.snd = multiplayer ? "_p" + p2.player : ""
this.don = account.loggedIn ? account.don : defaultDon
}
if(this.snd === "_p2" && this.objEqual(defaultDon, this.don)){
this.don = {
body_fill: defaultDon.face_fill,
face_fill: defaultDon.body_fill
}
} }
this.calibrationMode = selectedSong.folder === "calibration" this.calibrationMode = selectedSong.folder === "calibration"
@ -317,6 +325,14 @@ class Controller{
return this.mekadon.play(circle) return this.mekadon.play(circle)
} }
} }
objEqual(a, b){
for(var i in a){
if(a[i] !== b[i]){
return false
}
}
return true
}
clean(){ clean(){
if(this.multiplayer === 1){ if(this.multiplayer === 1){
this.syncWith.clean() this.syncWith.clean()

View File

@ -82,6 +82,9 @@ class Debug{
} }
} }
stopMove(event){ stopMove(event){
if(this.debugDiv.style.display === "none"){
return
}
if(!event || event.type === "resize"){ if(!event || event.type === "resize"){
var divPos = this.debugDiv.getBoundingClientRect() var divPos = this.debugDiv.getBoundingClientRect()
var x = divPos.left var x = divPos.left

View File

@ -65,7 +65,13 @@ class Keyboard{
} }
keyEvent(event){ keyEvent(event){
var key = event.key.toLowerCase() var key = event.key.toLowerCase()
if(key === "escape" || key === "backspace" || key === "tab"){ if(
key === "escape" ||
key === "backspace" ||
key === "tab" ||
key === "contextmenu" ||
key === "alt"
){
event.preventDefault() event.preventDefault()
} }
if(!event.repeat){ if(!event.repeat){

View File

@ -178,6 +178,7 @@ class Loader{
account.loggedIn = true account.loggedIn = true
account.username = response.username account.username = response.username
account.displayName = response.display_name account.displayName = response.display_name
account.don = response.don
scoreStorage.load(response.scores) scoreStorage.load(response.scores)
pageEvents.send("login", account.username) pageEvents.send("login", account.username)
} }
@ -236,7 +237,8 @@ class Loader{
}) })
p2.send("invite", { p2.send("invite", {
id: location.hash.slice(1).toLowerCase(), id: location.hash.slice(1).toLowerCase(),
name: account.loggedIn ? account.displayName : null name: account.loggedIn ? account.displayName : null,
don: account.loggedIn ? account.don : null
}) })
setTimeout(() => { setTimeout(() => {
if(p2.socket.readyState !== 1){ if(p2.socket.readyState !== 1){

View File

@ -37,7 +37,18 @@ class LoadSong{
this.promises = [] this.promises = []
if(song.folder !== "calibration"){ if(song.folder !== "calibration"){
assets.sounds["v_start"].play() assets.sounds["v_start"].play()
var songObj = assets.songs.find(song => song.id === id) var songObj
assets.songs.forEach(song => {
if(song.id === id){
songObj = song
}else{
if(song.sound){
song.sound.clean()
delete song.sound
}
delete song.lyricsData
}
})
}else{ }else{
var songObj = { var songObj = {
"music": "muted", "music": "muted",
@ -167,6 +178,9 @@ class LoadSong{
}), url) }), url)
img.src = url img.src = url
} }
if(songObj.volume && songObj.volume !== 1){
this.promises.push(new Promise(resolve => setTimeout(resolve, 500)))
}
Promise.all(this.promises).then(() => { Promise.all(this.promises).then(() => {
if(!this.error){ if(!this.error){
this.setupMultiplayer() this.setupMultiplayer()
@ -338,7 +352,8 @@ class LoadSong{
p2.send("join", { p2.send("join", {
id: song.folder, id: song.folder,
diff: song.difficulty, diff: song.difficulty,
name: account.loggedIn ? account.displayName : null name: account.loggedIn ? account.displayName : null,
don: account.loggedIn ? account.don : null
}) })
}else{ }else{
this.clean() this.clean()

View File

@ -80,6 +80,10 @@ var perf = {
allImg: 0, allImg: 0,
load: 0 load: 0
} }
var defaultDon = {
body_fill: "#5fb7c1",
face_fill: "#ff5724"
}
var strings var strings
var vectors var vectors
var settings var settings

View File

@ -131,6 +131,7 @@ class P2Connection{
this.hashLock = false this.hashLock = false
} }
this.name = null this.name = null
this.don = null
scoreStorage.clearP2() scoreStorage.clearP2()
break break
case "gameresults": case "gameresults":
@ -165,7 +166,8 @@ class P2Connection{
} }
break break
case "name": case "name":
this.name = response.value ? response.value.toString() : response.value this.name = response.value ? (response.value.name || "").toString() : ""
this.don = response.value ? (response.value.don) : null
break break
case "getcrowns": case "getcrowns":
if(response.value){ if(response.value){

View File

@ -285,6 +285,7 @@ class ScoreStorage{
account.loggedIn = false account.loggedIn = false
delete account.username delete account.username
delete account.displayName delete account.displayName
delete account.don
this.load() this.load()
pageEvents.send("logout") pageEvents.send("logout")
return Promise.reject() return Promise.reject()

View File

@ -492,7 +492,7 @@ class SongSelect{
} }
}else if(this.state.screen === "difficulty"){ }else if(this.state.screen === "difficulty"){
var moveBy = this.diffSelMouse(mouse.x, mouse.y) var moveBy = this.diffSelMouse(mouse.x, mouse.y)
if(mouse.x < 183 || mouse.x > 1095 || mouse.y < 40 || mouse.y > 540){ if(mouse.x < 183 || mouse.x > 1095 || mouse.y < 54 || mouse.y > 554){
this.toSongSelect() this.toSongSelect()
}else if(moveBy === 0){ }else if(moveBy === 0){
this.selectedDiff = 0 this.selectedDiff = 0
@ -596,11 +596,11 @@ class SongSelect{
} }
diffSelMouse(x, y){ diffSelMouse(x, y){
if(this.state.locked === 0){ if(this.state.locked === 0){
if(223 < x && x < 367 && 118 < y && y < 422){ if(223 < x && x < 367 && 132 < y && y < 436){
return Math.floor((x - 223) / ((367 - 223) / 2)) return Math.floor((x - 223) / ((367 - 223) / 2))
}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 > 432 && y < 519) { }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" return "maker"
}else if(550 < x && x < 1050 && 95 < y && y < 524){ }else if(550 < x && x < 1050 && 109 < y && y < 538){
var moveBy = Math.floor((x - 550) / ((1050 - 550) / 5)) + this.diffOptions.length var moveBy = Math.floor((x - 550) / ((1050 - 550) / 5)) + this.diffOptions.length
var currentSong = this.songs[this.selectedSong] var currentSong = this.songs[this.selectedSong]
if( if(
@ -1631,17 +1631,6 @@ class SongSelect{
if(this.selectedDiff === 4 + this.diffOptions.length){ if(this.selectedDiff === 4 + this.diffOptions.length){
currentDiff = 3 currentDiff = 3
} }
if(songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1){
this.draw.diffCursor({
ctx: ctx,
font: this.font,
x: _x,
y: _y - 45,
two: !p2.session || p2.player === 1,
side: false,
scale: 0.7
})
}
if(!songSel){ if(!songSel){
var highlight = 0 var highlight = 0
if(this.state.moveHover - this.diffOptions.length === i){ if(this.state.moveHover - this.diffOptions.length === i){
@ -1870,6 +1859,24 @@ 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){
var _x = x + 33 + i * 60
var _y = y + 120
this.draw.diffCursor({
ctx: ctx,
font: this.font,
x: _x,
y: _y - 45,
two: !p2.session || p2.player === 1,
side: false,
scale: 0.7
})
}
}
}
if(!songSel && currentSong.courses.ura){ if(!songSel && currentSong.courses.ura){
var fade = ((ms - this.state.screenMS) % 1200) / 1200 var fade = ((ms - this.state.screenMS) % 1200) / 1200
var _x = x + 402 + 4 * 100 + fade * 25 var _x = x + 402 + 4 * 100 + fade * 25

View File

@ -995,8 +995,23 @@ var translations = {
en: "Save", en: "Save",
}, },
displayName: { displayName: {
ja: null,
en: "Displayed Name", en: "Displayed Name",
}, },
customdon: {
bodyFill: {
ja: null,
en: "Body",
},
faceFill: {
ja: null,
en: "Face",
},
reset: {
ja: null,
en: "Reset",
}
},
changePassword: { changePassword: {
ja: null, ja: null,
en: "Change Password", en: "Change Password",
@ -1043,6 +1058,10 @@ var translations = {
ja: null, ja: null,
en: "Cannot use this name, please check that your new name is at most 25 characters long", en: "Cannot use this name, please check that your new name is at most 25 characters long",
}, },
invalid_don: {
ja: null,
en: "Could not save your custom Don"
},
current_password_invalid: { current_password_invalid: {
ja: null, ja: null,
en: "Current password does not match", en: "Current password does not match",

View File

@ -9,18 +9,17 @@ class ViewAssets{
this.don = this.createAsset("background", frame => { this.don = this.createAsset("background", frame => {
var imgw = 360 var imgw = 360
var imgh = 184 var imgh = 184
var scale = 165
var w = imgw var w = imgw
var h = imgh var h = imgh
return { return {
sx: Math.floor(frame / 10) * imgw, sx: Math.floor(frame / 10) * (imgw + 2),
sy: (frame % 10) * imgh + 1, sy: (frame % 10) * (imgh + 2),
sw: imgw, sw: imgw,
sh: imgh - 1, sh: imgh,
x: view.portrait ? -60 : 0, x: view.portrait ? -60 : 0,
y: view.portrait ? (view.player === 2 ? 560 : 35) : (view.player === 2 ? 360 : 2), y: view.portrait ? (view.player === 2 ? 560 : 35) : (view.player === 2 ? 360 : 0),
w: w, w: w / h * (h + 0.5),
h: h - 1 h: h + 0.5
} }
}) })
this.don.addFrames("normal", [ this.don.addFrames("normal", [
@ -28,15 +27,15 @@ class ViewAssets{
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 , 0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,7 ,8 ,9 ,10, 0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,7 ,8 ,9 ,10,
11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17 11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17
], "don_anim_normal") ], "don_anim_normal", this.controller.don)
this.don.addFrames("10combo", 22, "don_anim_10combo") this.don.addFrames("10combo", 22, "don_anim_10combo", this.controller.don)
this.don.addFrames("gogo", [ this.don.addFrames("gogo", [
42,43,43,44,45,46,47,48,49,50,51,52,53,54, 42,43,43,44,45,46,47,48,49,50,51,52,53,54,
55,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,11,12,13, 55,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,11,12,13,
14,14,15,16,17,18,19,20,21,22,23,24,25,26, 14,14,15,16,17,18,19,20,21,22,23,24,25,26,
27,28,29,30,31,32,33,34,35,36,37,38,39,40,41 27,28,29,30,31,32,33,34,35,36,37,38,39,40,41
], "don_anim_gogo") ], "don_anim_gogo", this.controller.don)
this.don.addFrames("gogostart", 27, "don_anim_gogostart") this.don.addFrames("gogostart", 27, "don_anim_gogostart", this.controller.don)
this.don.normalAnimation = () => { this.don.normalAnimation = () => {
if(this.view.gogoTime){ if(this.view.gogoTime){
var length = this.don.getAnimationLength("gogo") var length = this.don.getAnimationLength("gogo")
@ -58,7 +57,7 @@ class ViewAssets{
} }
} }
} }
this.don.addFrames("clear", 30, "don_anim_clear") this.don.addFrames("clear", 30, "don_anim_clear", this.controller.don)
this.don.normalAnimation() this.don.normalAnimation()
// Bar // Bar
@ -176,6 +175,7 @@ class ViewAssets{
}) })
} }
clean(){ clean(){
this.don.clean()
delete this.ctx delete this.ctx
delete this.don delete this.don
delete this.fire delete this.fire

View File

@ -7,6 +7,20 @@
<div class="displayname-hint"></div> <div class="displayname-hint"></div>
<input type="text" class="displayname" maxlength="25"> <input type="text" class="displayname" maxlength="25">
</div> </div>
<div class="customdon-div">
<canvas class="customdon-canvas" width="254" height="184"></canvas>
<div>
<label>
<input type="color" class="customdon-bodyfill">
</label>
<label>
<input type="color" class="customdon-facefill">
</label>
<label>
<input type="button" class="customdon-reset" value="Reset">
</label>
</div>
</div>
<form class="accountpass-form"> <form class="accountpass-form">
<div> <div>
<div class="accountpass-btn taibtn stroke-sub link-btn"></div> <div class="accountpass-btn taibtn stroke-sub link-btn"></div>

View File

@ -34,6 +34,15 @@ update_display_name = {
} }
} }
update_don = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {
'body_fill': {'type': 'string'},
'face_fill': {'type': 'string'}
}
}
update_password = { update_password = {
'$schema': 'http://json-schema.org/schema#', '$schema': 'http://json-schema.org/schema#',
'type': 'object', 'type': 'object',

View File

@ -43,7 +43,8 @@ async def connection(ws, path):
"ws": ws, "ws": ws,
"action": "ready", "action": "ready",
"session": False, "session": False,
"name": None "name": None,
"don": None
} }
server_status["users"].append(user) server_status["users"].append(user)
try: try:
@ -81,6 +82,7 @@ async def connection(ws, path):
id = value["id"] if "id" in value else None id = value["id"] if "id" in value else None
diff = value["diff"] if "diff" in value else None diff = value["diff"] if "diff" in value else None
user["name"] = value["name"] if "name" in value else None user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
if not id or not diff: if not id or not diff:
continue continue
if id not in waiting: if id not in waiting:
@ -95,6 +97,7 @@ async def connection(ws, path):
else: else:
# Join the other user and start game # Join the other user and start game
user["name"] = value["name"] if "name" in value else None user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
user["other_user"] = waiting[id]["user"] user["other_user"] = waiting[id]["user"]
waiting_diff = waiting[id]["diff"] waiting_diff = waiting[id]["diff"]
del waiting[id] del waiting[id]
@ -107,8 +110,14 @@ async def connection(ws, path):
await asyncio.wait([ await asyncio.wait([
ws.send(msgobj("gameload", {"diff": waiting_diff, "player": 2})), ws.send(msgobj("gameload", {"diff": waiting_diff, "player": 2})),
user["other_user"]["ws"].send(msgobj("gameload", {"diff": diff, "player": 1})), user["other_user"]["ws"].send(msgobj("gameload", {"diff": diff, "player": 1})),
ws.send(msgobj("name", user["other_user"]["name"])), ws.send(msgobj("name", {
user["other_user"]["ws"].send(msgobj("name", user["name"])) "name": user["other_user"]["name"],
"don": user["other_user"]["don"]
})),
user["other_user"]["ws"].send(msgobj("name", {
"name": user["name"],
"don": user["don"]
}))
]) ])
else: else:
# Wait for another user # Wait for another user
@ -130,10 +139,12 @@ async def connection(ws, path):
user["action"] = "invite" user["action"] = "invite"
user["session"] = invite user["session"] = invite
user["name"] = value["name"] if "name" in value else None user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
await ws.send(msgobj("invite", invite)) await ws.send(msgobj("invite", invite))
elif value and "id" in value and value["id"] in server_status["invites"]: elif value and "id" in value and value["id"] in server_status["invites"]:
# Join a session with the other user # Join a session with the other user
user["name"] = value["name"] if "name" in value else None user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
user["other_user"] = server_status["invites"][value["id"]] user["other_user"] = server_status["invites"][value["id"]]
del server_status["invites"][value["id"]] del server_status["invites"][value["id"]]
if "ws" in user["other_user"]: if "ws" in user["other_user"]:
@ -146,8 +157,14 @@ async def connection(ws, path):
ws.send(msgobj("session", {"player": 2})), ws.send(msgobj("session", {"player": 2})),
user["other_user"]["ws"].send(msgobj("session", {"player": 1})), user["other_user"]["ws"].send(msgobj("session", {"player": 1})),
ws.send(msgobj("invite")), ws.send(msgobj("invite")),
ws.send(msgobj("name", user["other_user"]["name"])), ws.send(msgobj("name", {
user["other_user"]["ws"].send(msgobj("name", user["name"])) "name": user["other_user"]["name"],
"don": user["other_user"]["don"]
})),
user["other_user"]["ws"].send(msgobj("name", {
"name": user["name"],
"don": user["don"]
}))
]) ])
else: else:
del user["other_user"] del user["other_user"]

View File

@ -1,6 +1,10 @@
{% extends 'admin.html' %} {% extends 'admin.html' %}
{% block content %} {% block content %}
<h1>{{ song.title }} <small>(ID: {{ song.id }})</small></h1> {% if song.title_lang.en %}
<h1>{{ song.title_lang.en }} <small>({{ song.title }})</small> <small class="song-id">(ID: {{ song.id }})</small></h1>
{% else %}
<h1>{{ song.title }} <small class="song-id">(ID: {{ song.id }})</small></h1>
{% endif %}
{% for cat, message in get_flashed_messages(with_categories=true) %} {% for cat, message in get_flashed_messages(with_categories=true) %}
<div class="message{% if cat %} message-{{cat}}{% endif %}">{{ message }}</div> <div class="message{% if cat %} message-{{cat}}{% endif %}">{{ message }}</div>
{% endfor %} {% endfor %}
@ -116,9 +120,14 @@
</select> </select>
</div> </div>
<div class="form-field">
<p><label for="lyrics">Lyrics</label></p>
<span class="checkbox"><input type="checkbox" name="lyrics" id="lyrics"{% if song.lyrics %} checked{% endif %}><label for="lyrics"> Enabled</label></span>
</div>
<div class="form-field"> <div class="form-field">
<p><label for="hash">Hash</label></p> <p><label for="hash">Hash</label></p>
<input type="text" id="hash" value="{{song.hash}}" name="hash"> <span class="checkbox"><input type="checkbox" name="gen_hash" id="gen_hash"{><label for="gen_hash"> Generate</label></span> <input type="text" id="hash" value="{{song.hash}}" name="hash"> <span class="checkbox"><input type="checkbox" name="gen_hash" id="gen_hash"><label for="gen_hash"> Generate</label></span>
</div> </div>
<button type="submit" class="save-song">Save</button> <button type="submit" class="save-song">Save</button>

View File

@ -116,6 +116,11 @@
</select> </select>
</div> </div>
<div class="form-field">
<p><label for="lyrics">Lyrics</label></p>
<span class="checkbox"><input type="checkbox" name="lyrics" id="lyrics"><label for="lyrics"> Enabled</label></span>
</div>
<button type="submit" class="save-song">Save</button> <button type="submit" class="save-song">Save</button>
</form> </form>
</div> </div>

View File

@ -11,9 +11,9 @@
<a href="/admin/songs/{{ song.id }}" class="song-link"> <a href="/admin/songs/{{ song.id }}" class="song-link">
<div class="song"> <div class="song">
{% if song.title_lang.en %} {% if song.title_lang.en %}
<p>{{ song.title_lang.en }} <small>({{ song.title }})</small></p> <p><span class="song-id">{{ song.id }}.</span> {{ song.title_lang.en }} <small>({{ song.title }})</small></p>
{% else %} {% else %}
<p>{{ song.title }}</p> <p><span class="song-id">{{ song.id }}.</span> {{ song.title }}</p>
{% endif %} {% endif %}
</div> </div>
</a> </a>