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
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('/')
def route_index():
@ -213,6 +236,7 @@ def route_admin_songs_new_post():
output['preview'] = float(request.form.get('preview')) or None
output['volume'] = float(request.form.get('volume')) 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
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['volume'] = float(request.form.get('volume')) 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')
if request.form.get('gen_hash'):
@ -366,13 +391,15 @@ def route_api_register():
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password, salt)
don = get_default_don()
session_id = os.urandom(24).hex()
db.users.insert_one({
'username': username,
'username_lower': username.lower(),
'password': hashed,
'display_name': username,
'display_name': username,
'don': don,
'user_level': 1,
'session_id': session_id
})
@ -380,7 +407,7 @@ def route_api_register():
session['session_id'] = session_id
session['username'] = username
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'])
@ -401,11 +428,13 @@ def route_api_login():
if not bcrypt.checkpw(password, result['password']):
return api_error('invalid_username_password')
don = get_db_don(result)
session['session_id'] = result['session_id']
session['username'] = result['username']
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'])
@ -435,6 +464,31 @@ def route_api_account_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'])
@login_required
def route_api_account_password():
@ -518,7 +572,8 @@ def route_api_scores_get():
})
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):

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;
}
h1 small {
h1 .song-id {
color: #4a4a4a;
}
.song .song-id {
color: #a01300;
}
.form-field-indent {
margin-left: 20px;
}

View File

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

View File

@ -377,3 +377,29 @@ kbd{
font-size: 1.1em;
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.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.setAltText(this.accountPassButton, strings.account.changePassword)
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))
}
}
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){
if(event){
if(event.type === "touchstart"){
@ -257,6 +350,7 @@ class Account{
account.loggedIn = true
account.username = response.username
account.displayName = response.display_name
account.don = response.don
var loadScores = scores => {
scoreStorage.load(scores)
this.onEnd(false, true, true)
@ -300,6 +394,7 @@ class Account{
account.loggedIn = false
delete account.username
delete account.displayName
delete account.don
var loadScores = () => {
scoreStorage.load()
this.onEnd(false, true)
@ -344,6 +439,7 @@ class Account{
account.loggedIn = false
delete account.username
delete account.displayName
delete account.don
scoreStorage.load()
pageEvents.send("logout")
return Promise.resolve
@ -357,6 +453,16 @@ class Account{
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 errorFunc = response => {
if(error){
@ -470,6 +576,11 @@ class Account{
this.accountPass.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.accountDelButton, ["click", "touchstart"])
pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"])
@ -479,6 +590,12 @@ class Account{
}
delete this.errorDiv
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.accountPass
delete this.accountPassDiv

View File

@ -58,11 +58,21 @@ var assets = {
"dancing-don.gif",
"bg-pattern-1.png",
"difficulty.png",
"don_anim_normal.png",
"don_anim_10combo.png",
"don_anim_gogo.png",
"don_anim_gogostart.png",
"don_anim_clear.png",
"don_anim_normal_a.png",
"don_anim_normal_b1.png",
"don_anim_normal_b2.png",
"don_anim_10combo_a.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",
"fireworks_anim.png",
"bg_genre_0.png",

View File

@ -44,11 +44,47 @@ class CanvasAsset{
mod(length, index){
return ((index % length) + length) % length
}
addFrames(name, frames, image){
addFrames(name, frames, image, don){
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]
}
this.animationFrames[name] = framesObj
@ -61,6 +97,7 @@ class CanvasAsset{
if(framesObj.image){
this.image = framesObj.image
}
this.don = framesObj.don
}else{
this.animation = false
}
@ -100,4 +137,12 @@ class CanvasAsset{
}
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
}
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,
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
)
if(saved){

View File

@ -8,8 +8,16 @@ class Controller{
this.touchEnabled = touchEnabled
if(multiplayer === 2){
this.snd = p2.player === 2 ? "_p1" : "_p2"
this.don = p2.don || defaultDon
}else{
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"
@ -317,6 +325,14 @@ class Controller{
return this.mekadon.play(circle)
}
}
objEqual(a, b){
for(var i in a){
if(a[i] !== b[i]){
return false
}
}
return true
}
clean(){
if(this.multiplayer === 1){
this.syncWith.clean()

View File

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

View File

@ -65,7 +65,13 @@ class Keyboard{
}
keyEvent(event){
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()
}
if(!event.repeat){

View File

@ -178,6 +178,7 @@ class Loader{
account.loggedIn = true
account.username = response.username
account.displayName = response.display_name
account.don = response.don
scoreStorage.load(response.scores)
pageEvents.send("login", account.username)
}
@ -236,7 +237,8 @@ class Loader{
})
p2.send("invite", {
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(() => {
if(p2.socket.readyState !== 1){

View File

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

View File

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

View File

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

View File

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

View File

@ -492,7 +492,7 @@ class SongSelect{
}
}else if(this.state.screen === "difficulty"){
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()
}else if(moveBy === 0){
this.selectedDiff = 0
@ -596,11 +596,11 @@ class SongSelect{
}
diffSelMouse(x, y){
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))
}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"
}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 currentSong = this.songs[this.selectedSong]
if(
@ -1631,17 +1631,6 @@ class SongSelect{
if(this.selectedDiff === 4 + this.diffOptions.length){
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){
var highlight = 0
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){
var fade = ((ms - this.state.screenMS) % 1200) / 1200
var _x = x + 402 + 4 * 100 + fade * 25

View File

@ -995,8 +995,23 @@ var translations = {
en: "Save",
},
displayName: {
ja: null,
en: "Displayed Name",
},
customdon: {
bodyFill: {
ja: null,
en: "Body",
},
faceFill: {
ja: null,
en: "Face",
},
reset: {
ja: null,
en: "Reset",
}
},
changePassword: {
ja: null,
en: "Change Password",
@ -1043,6 +1058,10 @@ var translations = {
ja: null,
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: {
ja: null,
en: "Current password does not match",

View File

@ -9,18 +9,17 @@ class ViewAssets{
this.don = this.createAsset("background", frame => {
var imgw = 360
var imgh = 184
var scale = 165
var w = imgw
var h = imgh
return {
sx: Math.floor(frame / 10) * imgw,
sy: (frame % 10) * imgh + 1,
sx: Math.floor(frame / 10) * (imgw + 2),
sy: (frame % 10) * (imgh + 2),
sw: imgw,
sh: imgh - 1,
sh: imgh,
x: view.portrait ? -60 : 0,
y: view.portrait ? (view.player === 2 ? 560 : 35) : (view.player === 2 ? 360 : 2),
w: w,
h: h - 1
y: view.portrait ? (view.player === 2 ? 560 : 35) : (view.player === 2 ? 360 : 0),
w: w / h * (h + 0.5),
h: h + 0.5
}
})
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 ,7 ,8 ,9 ,10,
11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17
], "don_anim_normal")
this.don.addFrames("10combo", 22, "don_anim_10combo")
], "don_anim_normal", this.controller.don)
this.don.addFrames("10combo", 22, "don_anim_10combo", this.controller.don)
this.don.addFrames("gogo", [
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,
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
], "don_anim_gogo")
this.don.addFrames("gogostart", 27, "don_anim_gogostart")
], "don_anim_gogo", this.controller.don)
this.don.addFrames("gogostart", 27, "don_anim_gogostart", this.controller.don)
this.don.normalAnimation = () => {
if(this.view.gogoTime){
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()
// Bar
@ -176,6 +175,7 @@ class ViewAssets{
})
}
clean(){
this.don.clean()
delete this.ctx
delete this.don
delete this.fire

View File

@ -7,6 +7,20 @@
<div class="displayname-hint"></div>
<input type="text" class="displayname" maxlength="25">
</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">
<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 = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',

View File

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

View File

@ -1,6 +1,10 @@
{% extends 'admin.html' %}
{% 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) %}
<div class="message{% if cat %} message-{{cat}}{% endif %}">{{ message }}</div>
{% endfor %}
@ -116,9 +120,14 @@
</select>
</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">
<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>
<button type="submit" class="save-song">Save</button>

View File

@ -116,6 +116,11 @@
</select>
</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>
</form>
</div>

View File

@ -11,9 +11,9 @@
<a href="/admin/songs/{{ song.id }}" class="song-link">
<div class="song">
{% 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 %}
<p>{{ song.title }}</p>
<p><span class="song-id">{{ song.id }}.</span> {{ song.title }}</p>
{% endif %}
</div>
</a>