Commit 67e10e74 authored by nanahira's avatar nanahira

Merge branch 'master' of github.com:bui/taiko-web

parents 0b91dc61 6c02bf90
...@@ -36,6 +36,7 @@ $RECYCLE.BIN/ ...@@ -36,6 +36,7 @@ $RECYCLE.BIN/
.Trashes .Trashes
.vscode .vscode
*.pyc
# Directories potentially created on remote AFP share # Directories potentially created on remote AFP share
.AppleDB .AppleDB
...@@ -48,7 +49,7 @@ public/api ...@@ -48,7 +49,7 @@ public/api
taiko.db taiko.db
version.json version.json
public/index.html public/index.html
config.json config.py
public/assets/song_skins public/assets/song_skins
*.log *.log
.venv* .venv*
......
...@@ -3,52 +3,35 @@ FROM debian:buster-slim as git-fetcher ...@@ -3,52 +3,35 @@ FROM debian:buster-slim as git-fetcher
RUN apt update && \ RUN apt update && \
env DEBIAN_FRONTEND=noninteractive apt install -y git && \ env DEBIAN_FRONTEND=noninteractive apt install -y git && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
mkdir /taiko-web mkdir /usr/src/app
COPY .git /taiko-web/.git COPY .git /usr/src/app/.git
WORKDIR /taiko-web WORKDIR /usr/src/app
RUN git log -1 --pretty="format:{\"commit\": \"%H\", \"commit_short\": \"%h\", \"version\": \"TAIKO_VERSION\", \"url\": \"TAIKO_GIT_URL\"}" > ./version-template.json RUN git log -1 --pretty="format:{\"commit\": \"%H\", \"commit_short\": \"%h\", \"version\": \"TAIKO_VERSION\"}" > ./version-template.json
FROM node:buster-slim FROM python:3.5.9-slim-buster
#RUN sed -i 's/deb.debian.org/mirrors.163.com/g' /etc/apt/sources.list && \ #RUN sed -i 's/deb.debian.org/mirrors.163.com/g' /etc/apt/sources.list && \
# sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list # sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list
#dependencies #dependencies
RUN apt update && \ RUN apt update && \
env DEBIAN_FRONTEND=noninteractive apt install -y python2.7 python-virtualenv python3.5 python3-virtualenv ffmpeg nginx && \ env DEBIAN_FRONTEND=noninteractive apt install -y ffmpeg && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
npm install -g pm2 mkdir /usr/src/app
COPY . /taiko-web
COPY --from=git-fetcher /taiko-web/version-template.json /taiko-web/version-template.json
WORKDIR /taiko-web
# envs # envs
RUN bash -c 'virtualenv -p /usr/bin/python2 .venv2 && \ WORKDIR /usr/src/app
source .venv2/bin/activate && \ COPY ./requirements.txt .
pip install Flask Flask-Caching ffmpy gunicorn redis && \ RUN pip install --no-cache -r ./requirements.txt
deactivate' && \
bash -c 'virtualenv -p /usr/bin/python3 .venv3 && \ COPY . .
source .venv3/bin/activate && \ COPY --from=git-fetcher ./version-template.json ./version-template.json
pip install websockets && \
deactivate' && \
ln -s /taiko-web/templates/index.html /taiko-web/public/index.html && \
ln -s /taiko-web/public/songs/taiko.db /taiko-web/taiko.db && \
cp -rf /taiko-web/docker/taiko-web-nginx.conf /etc/nginx/conf.d/ && \
rm -rf /etc/nginx/sites-enabled/*
#info #info
EXPOSE 80 EXPOSE 80
# VOLUME [ "/taiko-web/public/songs" ]
ENV TAIKO_SONGS_URL http://taiko.example.com/songs/
ENV TAIKO_ASSETS_URL http://taiko.example.com/assets/
ENV TAIKO_MULTIPLAYER_URL wss://taiko.example.com/p2/
ENV TAIKO_GIT_URL https://git.example.com/user/repo/
ENV TAIKO_VERSION Nanahira ENV TAIKO_VERSION Nanahira
ENV TAIKO_EMAIL 78877@qq.com ENTRYPOINT [ "/usr/src/app/docker/entrypoint.sh" ]
ENV TAIKO_REDIS_HOST 127.0.0.1
ENV TAIKO_REDIS_PORT 6379
#ENV TAIKO_REDIS_PASSWORD blank
ENV TAIKO_REDIS_DB 0
CMD [ "/taiko-web/docker/entrypoint.sh" ]
This diff is collapsed.
{
"songs_baseurl": "",
"assets_baseurl": "",
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_password": "",
"redis_db": 0
}
# The full URL base asset URL, with trailing slash.
ASSETS_BASEURL = ''
# The full URL base song URL, with trailing slash.
SONGS_BASEURL = ''
# The full URL base websocket URL, with trailing slash.
MULTIPLAYER_BASEURL = ''
# The email address to display in the "About Simulator" menu.
EMAIL = 'taiko@example.com'
# Whether to use the user account system.
ACCOUNTS = True
# Custom JavaScript file to load with the simulator.
CUSTOM_JS = ''
# MongoDB server settings.
MONGO = {
'host': ['127.0.0.1:27017'],
'database': 'taiko'
}
# Redis server settings, used for sessions + cache.
REDIS = {
'CACHE_TYPE': 'redis',
'CACHE_REDIS_HOST': '127.0.0.1',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_PASSWORD': None,
'CACHE_REDIS_DB': None
}
# Secret key used for sessions.
SECRET_KEY = 'change-me'
# Git repository base URL.
URL = 'https://github.com/bui/taiko-web/'
#!/bin/bash #!/bin/bash
if [ -z $TAIKO_SONGS_URL ]; then
export TAIKO_SONGS_URL=$TAIKO_URL/songs/
fi
if [ -z $TAIKO_ASSETS_URL ]; then
export TAIKO_ASSETS_URL=$TAIKO_URL/assets/
fi
echo "{\"songs_baseurl\":\"$TAIKO_SONGS_URL\",\"assets_baseurl\":\"$TAIKO_ASSETS_URL\",\"multiplayer_baseurl\":\"$TAIKO_MULTIPLAYER_URL\",\"email\":\"$TAIKO_EMAIL\",\"redis_host\":\"$TAIKO_REDIS_HOST\",\"redis_port\":$TAIKO_REDIS_PORT,\"redis_password\":\"$TAIKO_REDIS_PASSWORD\",\"redis_db\":$TAIKO_REDIS_DB,\"git_url\":\"$TAIKO_GIT_URL\"}" | tee ./config.json
sed "s#TAIKO_GIT_URL#$TAIKO_GIT_URL#g;s#TAIKO_VERSION#$TAIKO_VERSION#g" version-template.json | tee version.json sed "s#TAIKO_GIT_URL#$TAIKO_GIT_URL#g;s#TAIKO_VERSION#$TAIKO_VERSION#g" version-template.json | tee version.json
pm2-docker start ./docker/pm2-docker.json python "$@"
public/assets/img/fire_anim.png

15.7 KB | W: | H:

public/assets/img/fire_anim.png

93.7 KB | W: | H:

public/assets/img/fire_anim.png
public/assets/img/fire_anim.png
public/assets/img/fire_anim.png
public/assets/img/fire_anim.png
  • 2-up
  • Swipe
  • Onion skin
body {
margin: 0;
font-family: 'Noto Sans JP', sans-serif;
background: #FF7F00;
}
.nav {
margin: 0;
padding: 0;
width: 200px;
background-color: #A01300;
position: fixed;
height: 100%;
overflow: auto;
}
.nav a {
display: block;
color: #FFF;
padding: 16px;
text-decoration: none;
}
.nav a.active {
background-color: #4CAF50;
color: white;
}
.nav a:hover:not(.active) {
background-color: #555;
color: white;
}
main {
margin-left: 200px;
padding: 1px 16px;
height: 1000px;
}
@media screen and (max-width: 700px) {
.nav {
width: 100%;
height: auto;
position: relative;
}
.nav a {float: left;}
main {margin-left: 0;}
}
@media screen and (max-width: 400px) {
.sidebar a {
text-align: center;
float: none;
}
}
.container {
margin-bottom: 40px;
}
.song {
background: #F84828;
color: white;
padding: 10px;
font-size: 14pt;
margin: 10px 0;
}
.song p {
margin: 0;
}
.song-link {
text-decoration: none;
}
.song-form {
background: #ff5333;
color: #FFF;
padding: 20px;
}
.form-field {
background: #555555;
padding: 15px 20px 20px 20px;
margin-bottom: 20px;
}
.form-field p {
margin: 0;
font-size: 18pt;
}
.form-field > label {
display: block;
}
.form-field input {
margin: 5px 0;
}
.form-field input[type="text"] {
width: 300px;
}
.form-field input[type="number"] {
width: 50px;
}
h1 .song-id {
color: #4a4a4a;
}
.song .song-id {
color: #a01300;
}
.form-field-indent {
margin-left: 20px;
}
.checkbox {
display: inline-block;
}
.checkbox input {
margin-right: 3px;
margin-left: 5px;
}
.message {
background: #2c862f;
padding: 15px;
margin-bottom: 10px;
color: white;
}
.message-error {
background: #b92222;
}
.save-song {
font-size: 22pt;
width: 120px;
}
.delete-song button {
float: right;
margin-top: -25px;
font-size: 12pt;
}
.side-button {
float: right;
background: green;
padding: 5px 20px;
color: white;
text-decoration: none;
margin-top: 25px;
}
...@@ -123,6 +123,7 @@ ...@@ -123,6 +123,7 @@
} }
#debug .autoplay-label, #debug .autoplay-label,
#debug .branch-hide{ #debug .branch-hide,
#debug .lyrics-hide{
display: none; display: none;
} }
...@@ -89,3 +89,40 @@ ...@@ -89,3 +89,40 @@
.fix-animations *{ .fix-animations *{
animation: none !important; animation: none !important;
} }
#song-lyrics{
position: absolute;
right: calc((100vw - 1280 / 720 * 100vh) / 2 + 100px * var(--scale));
bottom: calc(44 / 720 * 100vh - 30px * var(--scale));
left: calc((100vw - 1280 / 720 * 100vh) / 2 + 100px * var(--scale));
text-align: center;
font-family: Meiryo, sans-serif;
font-weight: bold;
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));
left: calc(20px * var(--scale));
}
#song-lyrics .stroke,
#song-lyrics .fill{
position: absolute;
right: 0;
bottom: 0;
left: 0;
}
#song-lyrics .stroke{
-webkit-text-stroke: calc(7px * var(--scale)) #00a;
}
#song-lyrics .fill{
color: #fff;
}
#song-lyrics ruby{
display: inline-flex;
flex-direction: column-reverse;
}
#song-lyrics rt{
line-height: 1;
}
...@@ -117,3 +117,20 @@ body{ ...@@ -117,3 +117,20 @@ body{
color: #777; color: #777;
text-shadow: 0.05em 0.05em #fff; text-shadow: 0.05em 0.05em #fff;
} }
.view-outer.loader-error-div,
.loader-error-div .diag-txt{
display: none
}
.loader-error-div{
font-family: sans-serif;
}
.loader-error-div .debug-link{
color: #00f;
text-decoration: underline;
cursor: pointer;
float: right;
}
.loader-error-div .diag-txt textarea,
.loader-error-div .diag-txt iframe{
height: 10em;
}
...@@ -108,8 +108,8 @@ kbd{ ...@@ -108,8 +108,8 @@ kbd{
.left-buttons .taibtn{ .left-buttons .taibtn{
margin-right: 0.4em; margin-right: 0.4em;
} }
#diag-txt textarea, .diag-txt textarea,
#diag-txt iframe{ .diag-txt iframe{
width: 100%; width: 100%;
height: 5em; height: 5em;
font-size: inherit; font-size: inherit;
...@@ -119,6 +119,7 @@ kbd{ ...@@ -119,6 +119,7 @@ kbd{
background: #fff; background: #fff;
border: 1px solid #a9a9a9; border: 1px solid #a9a9a9;
user-select: all; user-select: all;
box-sizing: border-box;
} }
.text-warn{ .text-warn{
color: #d00; color: #d00;
...@@ -291,3 +292,114 @@ kbd{ ...@@ -291,3 +292,114 @@ kbd{
.left-buttons .taibtn{ .left-buttons .taibtn{
z-index: 1; z-index: 1;
} }
.accountpass-form,
.accountdel-form,
.login-form{
text-align: center;
width: 80%;
margin: auto;
}
.accountpass-form .accountpass-div,
.accountdel-form .accountdel-div,
.login-form .password2-div{
display: none;
}
.account-view .displayname,
.accountpass-form input[type=password],
.accountdel-form input[type=password],
.login-form input[type=text],
.login-form input[type=password]{
width: 100%;
font-size: 1.4em;
margin: 0.1em 0;
padding: 0.3em;
box-sizing: border-box;
}
.accountpass-form input[type=password]{
width: calc(100% / 3);
}
.accountpass-form input[type=password]::placeholder{
font-size: 0.8em;
}
.login-form input[type=checkbox]{
transform: scale(1.4);
}
.account-view .displayname-hint,
.login-form .username-hint,
.login-form .password-hint,
.login-form .remember-label{
display: block;
font-size: 1.1em;
padding: 0.5em;
}
.login-form .remember-label{
padding: 0.85em;
}
.account-view .save-btn{
float: right;
padding: 0.4em 1.5em;
font-weight: bold;
border-color: #000;
color: #000;
z-index: 1;
}
.account-view .view-end-button{
margin-right: 0.4em;
font-weight: normal;
border-color: #dacdb2;
color: #555;
}
.account-view .save-btn:hover,
.account-view .save-btn.selected,
.account-view .view-end-button:hover,
.account-view .view-end-button.selected{
color: #fff;
border-color: #fff;
}
.account-view .displayname-div{
width: 80%;
margin: 0 auto;
}
.accountpass-form .accountpass-btn,
.accountdel-form .accountdel-btn,
.login-form .login-btn{
z-index: 1;
}
.accountpass-form,
.accountdel-form{
margin: 0.3em auto;
}
.view-content .error-div{
display: none;
width: 80%;
margin: 0 auto;
padding: 0.5em;
font-size: 1.1em;
color: #d00;
}
.customdon-div{
display: flex;
justify-content: center;
align-items: center;
text-align: right;
}
.customdon-canvas{
width: 13em;
}
.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;
}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
cancelTouch = false cancelTouch = false
this.endButton = this.getElement("view-end-button") this.endButton = this.getElement("view-end-button")
this.diagTxt = document.getElementById("diag-txt") this.diagTxt = this.getElement("diag-txt")
this.version = document.getElementById("version-link").href this.version = document.getElementById("version-link").href
this.tutorialOuter = this.getElement("view-outer") this.tutorialOuter = this.getElement("view-outer")
if(touchEnabled){ if(touchEnabled){
......
This diff is collapsed.
...@@ -31,7 +31,9 @@ var assets = { ...@@ -31,7 +31,9 @@ var assets = {
"importsongs.js", "importsongs.js",
"logo.js", "logo.js",
"settings.js", "settings.js",
"scorestorage.js" "scorestorage.js",
"account.js",
"lyrics.js"
], ],
"css": [ "css": [
"main.css", "main.css",
...@@ -56,11 +58,21 @@ var assets = { ...@@ -56,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",
...@@ -93,11 +105,7 @@ var assets = { ...@@ -93,11 +105,7 @@ var assets = {
"settings_gamepad.png" "settings_gamepad.png"
], ],
"audioSfx": [ "audioSfx": [
"se_cancel.wav",
"se_don.wav",
"se_ka.wav",
"se_pause.wav", "se_pause.wav",
"se_jump.wav",
"se_calibration.wav", "se_calibration.wav",
"v_results.wav", "v_results.wav",
...@@ -109,6 +117,10 @@ var assets = { ...@@ -109,6 +117,10 @@ var assets = {
"audioSfxLR": [ "audioSfxLR": [
"neiro_1_don.wav", "neiro_1_don.wav",
"neiro_1_ka.wav", "neiro_1_ka.wav",
"se_cancel.wav",
"se_don.wav",
"se_ka.wav",
"se_jump.wav",
"se_balloon.wav", "se_balloon.wav",
"se_gameclear.wav", "se_gameclear.wav",
...@@ -144,7 +156,9 @@ var assets = { ...@@ -144,7 +156,9 @@ var assets = {
"about.html", "about.html",
"debug.html", "debug.html",
"session.html", "session.html",
"settings.html" "settings.html",
"account.html",
"login.html"
], ],
"songs": [], "songs": [],
......
...@@ -44,11 +44,47 @@ class CanvasAsset{ ...@@ -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{ ...@@ -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{ ...@@ -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()
}
}
}
} }
...@@ -91,8 +91,12 @@ class CanvasCache{ ...@@ -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){
......
...@@ -706,12 +706,12 @@ ...@@ -706,12 +706,12 @@
}) })
}else if(r.smallHiragana.test(symbol)){ }else if(r.smallHiragana.test(symbol)){
// Small hiragana, small katakana // Small hiragana, small katakana
drawn.push({text: symbol, x: 0, y: 0, w: 30}) drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 30})
}else if(r.hiragana.test(symbol)){ }else if(r.hiragana.test(symbol)){
// Hiragana, katakana // Hiragana, katakana
drawn.push({text: symbol, x: 0, y: 0, w: 35}) drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 35})
}else{ }else{
drawn.push({text: symbol, x: 0, y: 0, w: 39}) drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 39})
} }
} }
...@@ -720,6 +720,9 @@ ...@@ -720,6 +720,9 @@
if(config.letterSpacing){ if(config.letterSpacing){
symbol.w += config.letterSpacing symbol.w += config.letterSpacing
} }
if(config.kanaSpacing && symbol.kana){
symbol.w += config.kanaSpacing
}
drawnWidth += symbol.w * mul drawnWidth += symbol.w * mul
} }
...@@ -924,8 +927,22 @@ ...@@ -924,8 +927,22 @@
} }
} }
} }
var search = () => {
var end = line.length
var dist = end
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]){
words.splice(i + 1, 0, words[i].slice(line.length))
words[i] = line
}
}
for(var i in words){ for(var i = 0; i < words.length; i++){
var skip = words[i].substitute || words[i] === "\n" var skip = words[i].substitute || words[i] === "\n"
if(!skip){ if(!skip){
var currentWidth = ctx.measureText(line + words[i]).width var currentWidth = ctx.measureText(line + words[i]).width
...@@ -957,8 +974,22 @@ ...@@ -957,8 +974,22 @@
recenter() recenter()
x = 0 x = 0
y += lineHeight y += lineHeight
line = words[i] === "\n" ? "" : words[i] if(words[i] === "\n"){
lastWidth = ctx.measureText(line).width line = ""
lastWidth = 0
}else{
line = words[i]
lastWidth = ctx.measureText(line).width
if(line.length !== 1 && lastWidth > config.width){
search()
}
}
}
}else if(!line){
line = words[i]
lastWidth = ctx.measureText(line).width
if(line.length !== 1 && lastWidth > config.width){
search()
} }
}else{ }else{
line += words[i] line += words[i]
...@@ -1549,6 +1580,99 @@ ...@@ -1549,6 +1580,99 @@
ctx.restore() ctx.restore()
} }
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){
ctx.scale(config.scale, config.scale)
}
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)
ctx.fill()
ctx.beginPath()
ctx.moveTo(r, 0)
this.roundedCorner(ctx, w, 0, r, 1)
ctx.lineTo(r, r)
ctx.fillStyle = config.blue ? "#67cecb" : "#ff421d"
ctx.fill()
ctx.beginPath()
ctx.moveTo(r, r)
this.roundedCorner(ctx, w, h, r, 2)
ctx.lineTo(r, h)
ctx.fillStyle = "rgba(255, 255, 255, 0.8)"
ctx.fill()
ctx.strokeStyle = "#000"
ctx.lineWidth = 4
ctx.beginPath()
ctx.moveTo(r, 0)
ctx.arc(w - r, r, r, pi / -2, pi / 2)
ctx.lineTo(r, h)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(r, r - 1)
ctx.lineTo(w, r - 1)
ctx.lineWidth = 2
ctx.stroke()
ctx.beginPath()
ctx.arc(r, r, r, 0, pi * 2)
ctx.fillStyle = config.blue ? "#67cecb" : "#ff421d"
ctx.fill()
ctx.lineWidth = 4
ctx.stroke()
ctx.font = this.bold(config.font) + "28px " + config.font
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.lineWidth = 5
ctx.miterLimit = 1
ctx.strokeStyle = "#fff"
ctx.fillStyle = "#000"
var text = config.blue ? "2P" : "1P"
ctx.strokeText(text, r + 2, r + 1)
ctx.fillText(text, r + 2, r + 1)
if(config.rank){
this.layeredText({
ctx: ctx,
text: config.rank,
fontSize: 20,
fontFamily: config.font,
x: w / 2 + r * 0.7,
y: r * 0.5,
width: 180,
align: "center",
baseline: "middle"
}, [
{fill: "#000"}
])
}
this.layeredText({
ctx: ctx,
text: config.name || "",
fontSize: 21,
fontFamily: config.font,
x: w / 2 + r * 0.7,
y: r * 1.5 - 0.5,
width: 180,
kanaSpacing: 10,
align: "center",
baseline: "middle"
}, [
{outline: "#000", letterBorder: 6},
{fill: "#fff"}
])
ctx.restore()
}
alpha(amount, ctx, callback, winW, winH){ alpha(amount, ctx, callback, winW, winH){
if(amount >= 1){ if(amount >= 1){
return callback(ctx) return callback(ctx)
......
...@@ -7,7 +7,19 @@ class Controller{ ...@@ -7,7 +7,19 @@ class Controller{
this.saveScore = !autoPlayEnabled this.saveScore = !autoPlayEnabled
this.multiplayer = multiplayer this.multiplayer = multiplayer
this.touchEnabled = touchEnabled this.touchEnabled = touchEnabled
this.snd = this.multiplayer ? "_p" + this.multiplayer : "" 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" this.calibrationMode = selectedSong.folder === "calibration"
this.audioLatency = 0 this.audioLatency = 0
...@@ -54,6 +66,15 @@ class Controller{ ...@@ -54,6 +66,15 @@ class Controller{
if(song.id == this.selectedSong.folder){ if(song.id == this.selectedSong.folder){
this.mainAsset = song.sound this.mainAsset = song.sound
this.volume = song.volume || 1 this.volume = song.volume || 1
if(!multiplayer && (!this.touchEnabled || this.autoPlayEnabled) && settings.getItem("showLyrics")){
if(song.lyricsData){
var lyricsDiv = document.getElementById("song-lyrics")
this.lyrics = new Lyrics(song.lyricsData, selectedSong.offset, lyricsDiv)
}else if(this.parsedSongData.lyrics){
var lyricsDiv = document.getElementById("song-lyrics")
this.lyrics = new Lyrics(this.parsedSongData.lyrics, selectedSong.offset, lyricsDiv, true)
}
}
} }
}) })
} }
...@@ -156,10 +177,16 @@ class Controller{ ...@@ -156,10 +177,16 @@ class Controller{
if(this.mainLoopRunning){ if(this.mainLoopRunning){
if(this.multiplayer !== 2){ if(this.multiplayer !== 2){
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.viewLoop() var player = this.multiplayer ? p2.player : 1
if(player === 1){
this.viewLoop()
}
if(this.multiplayer === 1){ if(this.multiplayer === 1){
this.syncWith.viewLoop() this.syncWith.viewLoop()
} }
if(player === 2){
this.viewLoop()
}
if(this.scoresheet){ if(this.scoresheet){
if(this.view.ctx){ if(this.view.ctx){
this.view.ctx.save() this.view.ctx.save()
...@@ -198,14 +225,14 @@ class Controller{ ...@@ -198,14 +225,14 @@ class Controller{
displayScore(score, notPlayed, bigNote){ displayScore(score, notPlayed, bigNote){
this.view.displayScore(score, notPlayed, bigNote) this.view.displayScore(score, notPlayed, bigNote)
} }
songSelection(fadeIn){ songSelection(fadeIn, showWarning){
if(!fadeIn){ if(!fadeIn){
this.clean() this.clean()
} }
if(this.calibrationMode){ if(this.calibrationMode){
new SettingsView(this.touchEnabled, false, null, "latency") new SettingsView(this.touchEnabled, false, null, "latency")
}else{ }else{
new SongSelect(false, fadeIn, this.touchEnabled) new SongSelect(false, fadeIn, this.touchEnabled, null, showWarning)
} }
} }
restartSong(){ restartSong(){
...@@ -218,20 +245,27 @@ class Controller{ ...@@ -218,20 +245,27 @@ class Controller{
resolve() resolve()
}else{ }else{
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder) var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
var promises = []
if(songObj.chart && songObj.chart !== "blank"){ if(songObj.chart && songObj.chart !== "blank"){
var reader = new FileReader() var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => { promises.push(pageEvents.load(reader).then(event => {
this.songData = event.target.result.replace(/\0/g, "").split("\n") this.songData = event.target.result.replace(/\0/g, "").split("\n")
resolve() return Promise.resolve()
}) }))
if(this.selectedSong.type === "tja"){ if(this.selectedSong.type === "tja"){
reader.readAsText(songObj.chart, "sjis") reader.readAsText(songObj.chart, "sjis")
}else{ }else{
reader.readAsText(songObj.chart) reader.readAsText(songObj.chart)
} }
}else{
resolve()
} }
if(songObj.lyricsFile){
var reader = new FileReader()
promises.push(pageEvents.load(reader).then(event => {
songObj.lyricsData = event.target.result
}, () => Promise.resolve()), songObj.lyricsFile.webkitRelativePath)
reader.readAsText(songObj.lyricsFile)
}
Promise.all(promises).then(resolve)
} }
}).then(() => { }).then(() => {
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled) var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
...@@ -292,6 +326,14 @@ class Controller{ ...@@ -292,6 +326,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()
...@@ -307,6 +349,9 @@ class Controller{ ...@@ -307,6 +349,9 @@ class Controller{
debugObj.debug.updateStatus() debugObj.debug.updateStatus()
} }
} }
if(this.lyrics){
this.lyrics.clean()
}
} }
getModBadge() { getModBadge() {
if (!this.mods) { if (!this.mods) {
......
...@@ -17,6 +17,8 @@ class Debug{ ...@@ -17,6 +17,8 @@ class Debug{
this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0] this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0]
this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0] this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0]
this.volumeDiv = this.byClass("music-volume") this.volumeDiv = this.byClass("music-volume")
this.lyricsHideDiv = this.byClass("lyrics-hide")
this.lyricsOffsetDiv = this.byClass("lyrics-offset")
this.restartLabel = this.byClass("change-restart-label") this.restartLabel = this.byClass("change-restart-label")
this.restartCheckbox = this.byClass("change-restart") this.restartCheckbox = this.byClass("change-restart")
this.autoplayLabel = this.byClass("autoplay-label") this.autoplayLabel = this.byClass("autoplay-label")
...@@ -50,6 +52,9 @@ class Debug{ ...@@ -50,6 +52,9 @@ class Debug{
this.volumeSlider.onchange(this.volumeChange.bind(this)) this.volumeSlider.onchange(this.volumeChange.bind(this))
this.volumeSlider.set(1) this.volumeSlider.set(1)
this.lyricsSlider = new InputSlider(this.lyricsOffsetDiv, -60, 60, 3)
this.lyricsSlider.onchange(this.lyricsChange.bind(this))
this.moveTo(100, 100) this.moveTo(100, 100)
this.restore() this.restore()
this.updateStatus() this.updateStatus()
...@@ -77,6 +82,9 @@ class Debug{ ...@@ -77,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
...@@ -129,6 +137,9 @@ class Debug{ ...@@ -129,6 +137,9 @@ class Debug{
if(this.controller.parsedSongData.branches){ if(this.controller.parsedSongData.branches){
this.branchHideDiv.style.display = "block" this.branchHideDiv.style.display = "block"
} }
if(this.controller.lyrics){
this.lyricsHideDiv.style.display = "block"
}
var selectedSong = this.controller.selectedSong var selectedSong = this.controller.selectedSong
this.defaultOffset = selectedSong.offset || 0 this.defaultOffset = selectedSong.offset || 0
...@@ -136,19 +147,21 @@ class Debug{ ...@@ -136,19 +147,21 @@ class Debug{
this.offsetChange(this.offsetSlider.get(), true) this.offsetChange(this.offsetSlider.get(), true)
this.branchChange(null, true) this.branchChange(null, true)
this.volumeChange(this.volumeSlider.get(), true) this.volumeChange(this.volumeSlider.get(), true)
this.lyricsChange(this.lyricsSlider.get(), true)
}else{ }else{
this.songHash = selectedSong.hash this.songHash = selectedSong.hash
this.offsetSlider.set(this.defaultOffset) this.offsetSlider.set(this.defaultOffset)
this.branchReset(null, true) this.branchReset(null, true)
this.volumeSlider.set(this.controller.volume) this.volumeSlider.set(this.controller.volume)
this.lyricsSlider.set(this.controller.lyrics ? this.controller.lyrics.vttOffset / 1000 : 0)
} }
var measures = this.controller.parsedSongData.measures.filter((measure, i, array) => { var measures = this.controller.parsedSongData.measures.filter((measure, i, array) => {
return i === 0 || Math.abs(measure.ms - array[i - 1].ms) > 0.01 return i === 0 || Math.abs(measure.ms - array[i - 1].ms) > 0.01
}) })
this.measureNumSlider.setMinMax(0, measures.length - 1) this.measureNumSlider.setMinMax(0, measures.length - 1)
if(this.measureNum && measures.length > this.measureNum){ if(this.measureNum > 0 && measures.length >= this.measureNum){
var measureMS = measures[this.measureNum].ms var measureMS = measures[this.measureNum - 1].ms
var game = this.controller.game var game = this.controller.game
game.started = true game.started = true
var timestamp = Date.now() var timestamp = Date.now()
...@@ -174,6 +187,7 @@ class Debug{ ...@@ -174,6 +187,7 @@ class Debug{
this.restartBtn.style.display = "" this.restartBtn.style.display = ""
this.autoplayLabel.style.display = "" this.autoplayLabel.style.display = ""
this.branchHideDiv.style.display = "" this.branchHideDiv.style.display = ""
this.lyricsHideDiv.style.display = ""
this.controller = null this.controller = null
} }
this.stopMove() this.stopMove()
...@@ -194,6 +208,9 @@ class Debug{ ...@@ -194,6 +208,9 @@ class Debug{
branch.ms = branch.originalMS + offset branch.ms = branch.originalMS + offset
}) })
} }
if(this.controller.lyrics){
this.controller.lyrics.offsetChange(value * 1000)
}
if(this.restartCheckbox.checked && !noRestart){ if(this.restartCheckbox.checked && !noRestart){
this.restartSong() this.restartSong()
} }
...@@ -213,6 +230,14 @@ class Debug{ ...@@ -213,6 +230,14 @@ class Debug{
this.restartSong() this.restartSong()
} }
} }
lyricsChange(value, noRestart){
if(this.controller && this.controller.lyrics){
this.controller.lyrics.offsetChange(undefined, value * 1000)
}
if(this.restartCheckbox.checked && !noRestart){
this.restartSong()
}
}
restartSong(){ restartSong(){
if(this.controller){ if(this.controller){
this.controller.restartSong() this.controller.restartSong()
...@@ -259,6 +284,7 @@ class Debug{ ...@@ -259,6 +284,7 @@ class Debug{
this.offsetSlider.clean() this.offsetSlider.clean()
this.measureNumSlider.clean() this.measureNumSlider.clean()
this.volumeSlider.clean() this.volumeSlider.clean()
this.lyricsSlider.clean()
pageEvents.remove(window, ["mousedown", "mouseup", "touchstart", "touchend", "blur", "resize"], this.windowSymbol) pageEvents.remove(window, ["mousedown", "mouseup", "touchstart", "touchend", "blur", "resize"], this.windowSymbol)
pageEvents.mouseRemove(this) pageEvents.mouseRemove(this)
...@@ -285,6 +311,8 @@ class Debug{ ...@@ -285,6 +311,8 @@ class Debug{
delete this.branchSelect delete this.branchSelect
delete this.branchResetBtn delete this.branchResetBtn
delete this.volumeDiv delete this.volumeDiv
delete this.lyricsHideDiv
delete this.lyricsOffsetDiv
delete this.restartCheckbox delete this.restartCheckbox
delete this.autoplayLabel delete this.autoplayLabel
delete this.autoplayCheckbox delete this.autoplayCheckbox
......
...@@ -5,6 +5,7 @@ class Game{ ...@@ -5,6 +5,7 @@ class Game{
this.songData = songData this.songData = songData
this.elapsedTime = 0 this.elapsedTime = 0
this.currentCircle = -1 this.currentCircle = -1
this.currentEvent = 0
this.updateCurrentCircle() this.updateCurrentCircle()
this.combo = 0 this.combo = 0
this.rules = new GameRules(this) this.rules = new GameRules(this)
...@@ -47,13 +48,7 @@ class Game{ ...@@ -47,13 +48,7 @@ class Game{
} }
initTiming(){ initTiming(){
// Date when the chrono is started (before the game begins) // Date when the chrono is started (before the game begins)
var firstCircle var firstCircle = this.songData.circles[0]
for(var i = 0; i < this.songData.circles.length; i++){
firstCircle = this.songData.circles[i]
if(firstCircle.type !== "event"){
break
}
}
if(this.controller.calibrationMode){ if(this.controller.calibrationMode){
var offsetTime = 0 var offsetTime = 0
}else{ }else{
...@@ -238,9 +233,6 @@ class Game{ ...@@ -238,9 +233,6 @@ class Game{
} }
} }
skipNote(circle){ skipNote(circle){
if(circle.type === "event"){
return
}
if(circle.section){ if(circle.section){
this.resetSection() this.resetSection()
} }
...@@ -258,9 +250,6 @@ class Game{ ...@@ -258,9 +250,6 @@ class Game{
checkPlays(){ checkPlays(){
var circles = this.songData.circles var circles = this.songData.circles
var circle = circles[this.currentCircle] var circle = circles[this.currentCircle]
if(circle && circle.type === "event"){
this.updateCurrentCircle()
}
if(this.controller.autoPlayEnabled){ if(this.controller.autoPlayEnabled){
while(circle && this.controller.autoPlay(circle)){ while(circle && this.controller.autoPlay(circle)){
...@@ -469,9 +458,7 @@ class Game{ ...@@ -469,9 +458,7 @@ class Game{
} }
getLastCircle(circles){ getLastCircle(circles){
for(var i = circles.length; i--;){ for(var i = circles.length; i--;){
if(circles[i].type !== "event"){ return circles[i]
return circles[i]
}
} }
} }
whenLastCirclePlayed(){ whenLastCirclePlayed(){
...@@ -505,7 +492,9 @@ class Game{ ...@@ -505,7 +492,9 @@ class Game{
var musicDuration = duration * 1000 - this.controller.offset var musicDuration = duration * 1000 - this.controller.offset
if(this.musicFadeOut === 0){ if(this.musicFadeOut === 0){
if(this.controller.multiplayer === 1){ if(this.controller.multiplayer === 1){
p2.send("gameresults", this.getGlobalScore()) var obj = this.getGlobalScore()
obj.name = account.loggedIn ? account.displayName : null
p2.send("gameresults", obj)
} }
this.musicFadeOut++ this.musicFadeOut++
}else if(this.musicFadeOut === 1 && ms >= started + 1600){ }else if(this.musicFadeOut === 1 && ms >= started + 1600){
...@@ -621,7 +610,7 @@ class Game{ ...@@ -621,7 +610,7 @@ class Game{
var circles = this.songData.circles var circles = this.songData.circles
do{ do{
var circle = circles[++this.currentCircle] var circle = circles[++this.currentCircle]
}while(circle && (circle.branch && !circle.branch.active || circle.type === "event")) }while(circle && (circle.branch && !circle.branch.active))
} }
getCurrentCircle(){ getCurrentCircle(){
return this.currentCircle return this.currentCircle
......
...@@ -202,12 +202,16 @@ ...@@ -202,12 +202,16 @@
var tja = new ParseTja(data, "oni", 0, 0, true, null) var tja = new ParseTja(data, "oni", 0, 0, true, null)
var songObj = { var songObj = {
id: index + 1, id: index + 1,
order: index + 1,
type: "tja", type: "tja",
chart: file, chart: file,
stars: [], courses: {},
music: "muted" music: "muted"
} }
var coursesAdded = false
var titleLang = {} var titleLang = {}
var titleLangAdded = false
var subtitleLangAdded = false
var subtitleLang = {} var subtitleLang = {}
var dir = file.webkitRelativePath.toLowerCase() var dir = file.webkitRelativePath.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1) dir = dir.slice(0, dir.lastIndexOf("/") + 1)
...@@ -221,7 +225,11 @@ ...@@ -221,7 +225,11 @@
} }
songObj.subtitle = subtitle songObj.subtitle = subtitle
songObj.preview = meta.demostart || 0 songObj.preview = meta.demostart || 0
songObj.stars[this.courseTypes[diff]] = (meta.level || "0") + (meta.branch ? " B" : "") songObj.courses[diff] = {
stars: meta.level || 0,
branch: !!meta.branch
}
coursesAdded = true
if(meta.wave){ if(meta.wave){
songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music
} }
...@@ -252,6 +260,15 @@ ...@@ -252,6 +260,15 @@
id: 1 id: 1
} }
} }
if(meta.lyrics){
var lyricsFile = this.normPath(this.joinPath(dir, meta.lyrics))
if(lyricsFile in this.otherFiles){
songObj.lyrics = true
songObj.lyricsFile = this.otherFiles[lyricsFile]
}
}else if(meta.inlineLyrics){
songObj.lyrics = true
}
for(var id in allStrings){ for(var id in allStrings){
var songTitle = songObj.title var songTitle = songObj.title
var ura = "" var ura = ""
...@@ -264,32 +281,27 @@ ...@@ -264,32 +281,27 @@
} }
if(meta["title" + id]){ if(meta["title" + id]){
titleLang[id] = meta["title" + id] titleLang[id] = meta["title" + id]
titleLangAdded = true
}else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){ }else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){
titleLang[id] = this.songTitle[songTitle][id] + ura titleLang[id] = this.songTitle[songTitle][id] + ura
titleLangAdded = true
} }
if(meta["subtitle" + id]){ if(meta["subtitle" + id]){
subtitleLang[id] = meta["subtitle" + id] subtitleLang[id] = meta["subtitle" + id]
subtitleLangAdded = true
} }
} }
} }
var titleLangArray = [] if(titleLangAdded){
for(var id in titleLang){ songObj.title_lang = titleLang
titleLangArray.push(id + " " + titleLang[id])
}
if(titleLangArray.length !== 0){
songObj.title_lang = titleLangArray.join("\n")
} }
var subtitleLangArray = [] if(subtitleLangAdded){
for(var id in subtitleLang){ songObj.subtitle_lang = subtitleLang
subtitleLangArray.push(id + " " + subtitleLang[id])
}
if(subtitleLangArray.length !== 0){
songObj.subtitle_lang = subtitleLangArray.join("\n")
} }
if(!songObj.category){ if(!songObj.category){
songObj.category = category || this.getCategory(file, [songTitle || songObj.title, file.name.slice(0, file.name.lastIndexOf("."))]) songObj.category = category || this.getCategory(file, [songTitle || songObj.title, file.name.slice(0, file.name.lastIndexOf("."))])
} }
if(songObj.stars.length !== 0){ if(coursesAdded){
this.songs[index] = songObj this.songs[index] = songObj
} }
var hash = md5.base64(event.target.result).slice(0, -2) var hash = md5.base64(event.target.result).slice(0, -2)
...@@ -316,12 +328,20 @@ ...@@ -316,12 +328,20 @@
dir = dir.slice(0, dir.lastIndexOf("/") + 1) dir = dir.slice(0, dir.lastIndexOf("/") + 1)
var songObj = { var songObj = {
id: index + 1, id: index + 1,
order: index + 1,
type: "osu", type: "osu",
chart: file, chart: file,
subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist, subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist,
subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode, subtitle_lang: {
en: osu.metadata.Artist || osu.metadata.ArtistUnicode
},
preview: osu.generalInfo.PreviewTime / 1000, preview: osu.generalInfo.PreviewTime / 1000,
stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1], courses: {
oni:{
stars: parseInt(osu.difficulty.overallDifficulty) || 0,
branch: false
}
},
music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted" music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted"
} }
var filename = file.name.slice(0, file.name.lastIndexOf(".")) var filename = file.name.slice(0, file.name.lastIndexOf("."))
...@@ -333,7 +353,9 @@ ...@@ -333,7 +353,9 @@
suffix = " " + matches[0] suffix = " " + matches[0]
} }
songObj.title = title + suffix songObj.title = title + suffix
songObj.title_lang = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix songObj.title_lang = {
en: (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix
}
}else{ }else{
songObj.title = filename songObj.title = filename
} }
...@@ -417,7 +439,7 @@ ...@@ -417,7 +439,7 @@
for(var i = path.length - 2; i >= 0; i--){ for(var i = path.length - 2; i >= 0; i--){
var hasTitle = false var hasTitle = false
for(var j in exclude){ for(var j in exclude){
if(path[i].indexOf(exclude[j].toLowerCase()) !== -1){ if(exclude[j] && path[i].indexOf(exclude[j].toLowerCase()) !== -1){
hasTitle = true hasTitle = true
break break
} }
......
...@@ -65,7 +65,13 @@ class Keyboard{ ...@@ -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){
......
This diff is collapsed.
...@@ -34,10 +34,21 @@ class LoadSong{ ...@@ -34,10 +34,21 @@ class LoadSong{
run(){ run(){
var song = this.selectedSong var song = this.selectedSong
var id = song.folder var id = song.folder
var 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",
...@@ -92,9 +103,9 @@ class LoadSong{ ...@@ -92,9 +103,9 @@ class LoadSong{
img.crossOrigin = "Anonymous" img.crossOrigin = "Anonymous"
} }
let promise = pageEvents.load(img) let promise = pageEvents.load(img)
promises.push(promise.then(() => { this.addPromise(promise.then(() => {
return this.scaleImg(img, filename, prefix, force) return this.scaleImg(img, filename, prefix, force)
})) }), songObj.music ? filename + ".png" : skinBase + filename + ".png")
if(songObj.music){ if(songObj.music){
img.src = URL.createObjectURL(song.songSkin[filename + ".png"]) img.src = URL.createObjectURL(song.songSkin[filename + ".png"])
}else{ }else{
...@@ -102,14 +113,15 @@ class LoadSong{ ...@@ -102,14 +113,15 @@ class LoadSong{
} }
} }
} }
promises.push(this.loadSongBg(id)) this.loadSongBg(id)
promises.push(new Promise((resolve, reject) => { var url = gameConfig.songs_baseurl + id + "/main.ogg"
this.addPromise(new Promise((resolve, reject) => {
if(songObj.sound){ if(songObj.sound){
songObj.sound.gain = snd.musicGain songObj.sound.gain = snd.musicGain
resolve() resolve()
}else if(!songObj.music){ }else if(!songObj.music){
snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.ogg").then(sound => { snd.musicGain.load(url).then(sound => {
songObj.sound = sound songObj.sound = sound
resolve() resolve()
}, reject) }, reject)
...@@ -121,84 +133,123 @@ class LoadSong{ ...@@ -121,84 +133,123 @@ class LoadSong{
}else{ }else{
resolve() resolve()
} }
})) }), songObj.music ? songObj.music.webkitRelativePath : url)
if(songObj.chart){ if(songObj.chart){
if(songObj.chart === "blank"){ if(songObj.chart === "blank"){
this.songData = "" this.songData = ""
}else{ }else{
var reader = new FileReader() var reader = new FileReader()
promises.push(pageEvents.load(reader).then(event => { this.addPromise(pageEvents.load(reader).then(event => {
this.songData = event.target.result.replace(/\0/g, "").split("\n") this.songData = event.target.result.replace(/\0/g, "").split("\n")
})) }), songObj.chart.webkitRelativePath)
if(song.type === "tja"){ if(song.type === "tja"){
reader.readAsText(songObj.chart, "sjis") reader.readAsText(songObj.chart, "sjis")
}else{ }else{
reader.readAsText(songObj.chart) reader.readAsText(songObj.chart)
} }
} }
if(songObj.lyricsFile && settings.getItem("showLyrics")){
var reader = new FileReader()
this.addPromise(pageEvents.load(reader).then(event => {
songObj.lyricsData = event.target.result
}, () => Promise.resolve()), songObj.lyricsFile.webkitRelativePath)
reader.readAsText(songObj.lyricsFile)
}
}else{ }else{
promises.push(loader.ajax(this.getSongPath(song)).then(data => { var url = this.getSongPath(song)
this.addPromise(loader.ajax(url).then(data => {
this.songData = data.replace(/\0/g, "").split("\n") this.songData = data.replace(/\0/g, "").split("\n")
})) }), url)
if(song.lyrics && !songObj.lyricsData && !this.multiplayer && (!this.touchEnabled || this.autoPlayEnabled) && settings.getItem("showLyrics")){
var url = this.getSongDir(song) + "main.vtt"
this.addPromise(loader.ajax(url).then(data => {
songObj.lyricsData = data
}), url)
}
} }
if(this.touchEnabled && !assets.image["touch_drum"]){ if(this.touchEnabled && !assets.image["touch_drum"]){
let img = document.createElement("img") let img = document.createElement("img")
if(this.imgScale !== 1){ if(this.imgScale !== 1){
img.crossOrigin = "Anonymous" img.crossOrigin = "Anonymous"
} }
promises.push(pageEvents.load(img).then(() => { var url = gameConfig.assets_baseurl + "img/touch_drum.png"
this.addPromise(pageEvents.load(img).then(() => {
return this.scaleImg(img, "touch_drum", "") return this.scaleImg(img, "touch_drum", "")
})) }), url)
img.src = gameConfig.assets_baseurl + "img/touch_drum.png" img.src = url
} }
Promise.all(promises).then(() => { if(songObj.volume && songObj.volume !== 1){
this.setupMultiplayer() this.promises.push(new Promise(resolve => setTimeout(resolve, 500)))
}, error => { }
if(Array.isArray(error) && error[1] instanceof HTMLElement){ Promise.all(this.promises).then(() => {
error = error[0] + ": " + error[1].outerHTML if(!this.error){
this.setupMultiplayer()
} }
console.error(error)
pageEvents.send("load-song-error", error)
errorMessage(new Error(error).stack)
alert(strings.errorOccured)
}) })
} }
loadSongBg(){ addPromise(promise, url){
return new Promise((resolve, reject) => { this.promises.push(promise.catch(response => {
var promises = [] this.errorMsg(response, url)
var filenames = [] return Promise.resolve()
if(this.selectedSong.songBg !== null){ }))
filenames.push("bg_song_" + this.selectedSong.songBg) }
errorMsg(error, url){
if(!this.error){
if(url){
error = (Array.isArray(error) ? error[0] + ": " : (error ? error + ": " : "")) + url
} }
if(this.selectedSong.donBg !== null){ pageEvents.send("load-song-error", error)
filenames.push("bg_don_" + this.selectedSong.donBg) errorMessage(new Error(error).stack)
if(this.multiplayer){ var title = this.selectedSong.title
filenames.push("bg_don2_" + this.selectedSong.donBg) if(title !== this.selectedSong.originalTitle){
} title += " (" + this.selectedSong.originalTitle + ")"
} }
if(this.selectedSong.songStage !== null){ assets.sounds["v_start"].stop()
filenames.push("bg_stage_" + this.selectedSong.songStage) setTimeout(() => {
this.clean()
new SongSelect(false, false, this.touchEnabled, null, {
name: "loadSongError",
title: title,
id: this.selectedSong.folder,
error: error
})
}, 500)
}
this.error = true
}
loadSongBg(){
var filenames = []
if(this.selectedSong.songBg !== null){
filenames.push("bg_song_" + this.selectedSong.songBg)
}
if(this.selectedSong.donBg !== null){
filenames.push("bg_don_" + this.selectedSong.donBg)
if(this.multiplayer){
filenames.push("bg_don2_" + this.selectedSong.donBg)
} }
for(var i = 0; i < filenames.length; i++){ }
var filename = filenames[i] if(this.selectedSong.songStage !== null){
var stage = filename.startsWith("bg_stage_") filenames.push("bg_stage_" + this.selectedSong.songStage)
for(var letter = 0; letter < (stage ? 1 : 2); letter++){ }
let filenameAb = filenames[i] + (stage ? "" : (letter === 0 ? "a" : "b")) for(var i = 0; i < filenames.length; i++){
if(!(filenameAb in assets.image)){ var filename = filenames[i]
let img = document.createElement("img") var stage = filename.startsWith("bg_stage_")
let force = filenameAb.startsWith("bg_song_") && this.touchEnabled for(var letter = 0; letter < (stage ? 1 : 2); letter++){
if(this.imgScale !== 1 || force){ let filenameAb = filenames[i] + (stage ? "" : (letter === 0 ? "a" : "b"))
img.crossOrigin = "Anonymous" if(!(filenameAb in assets.image)){
} let img = document.createElement("img")
promises.push(pageEvents.load(img).then(() => { let force = filenameAb.startsWith("bg_song_") && this.touchEnabled
return this.scaleImg(img, filenameAb, "", force) if(this.imgScale !== 1 || force){
})) img.crossOrigin = "Anonymous"
img.src = gameConfig.assets_baseurl + "img/" + filenameAb + ".png"
} }
var url = gameConfig.assets_baseurl + "img/" + filenameAb + ".png"
this.addPromise(pageEvents.load(img).then(() => {
return this.scaleImg(img, filenameAb, "", force)
}), url)
img.src = url
} }
} }
Promise.all(promises).then(resolve, reject) }
})
} }
scaleImg(img, filename, prefix, force){ scaleImg(img, filename, prefix, force){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -238,8 +289,11 @@ class LoadSong{ ...@@ -238,8 +289,11 @@ class LoadSong{
randInt(min, max){ randInt(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min return Math.floor(Math.random() * (max - min + 1)) + min
} }
getSongDir(selectedSong){
return gameConfig.songs_baseurl + selectedSong.folder + "/"
}
getSongPath(selectedSong){ getSongPath(selectedSong){
var directory = gameConfig.songs_baseurl + selectedSong.folder + "/" var directory = this.getSongDir(selectedSong)
if(selectedSong.type === "tja"){ if(selectedSong.type === "tja"){
return directory + "main.tja" return directory + "main.tja"
}else{ }else{
...@@ -264,14 +318,14 @@ class LoadSong{ ...@@ -264,14 +318,14 @@ class LoadSong{
if(event.type === "gameload"){ if(event.type === "gameload"){
this.cancelButton.style.display = "" this.cancelButton.style.display = ""
if(event.value === song.difficulty){ if(event.value.diff === song.difficulty){
this.startMultiplayer() this.startMultiplayer()
}else{ }else{
this.selectedSong2 = {} this.selectedSong2 = {}
for(var i in this.selectedSong){ for(var i in this.selectedSong){
this.selectedSong2[i] = this.selectedSong[i] this.selectedSong2[i] = this.selectedSong[i]
} }
this.selectedSong2.difficulty = event.value this.selectedSong2.difficulty = event.value.diff
if(song.type === "tja"){ if(song.type === "tja"){
this.startMultiplayer() this.startMultiplayer()
}else{ }else{
...@@ -297,7 +351,9 @@ class LoadSong{ ...@@ -297,7 +351,9 @@ 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,
don: account.loggedIn ? account.don : null
}) })
}else{ }else{
this.clean() this.clean()
...@@ -332,6 +388,7 @@ class LoadSong{ ...@@ -332,6 +388,7 @@ class LoadSong{
pageEvents.send("load-song-cancel") pageEvents.send("load-song-cancel")
} }
clean(){ clean(){
delete this.promises
pageEvents.remove(p2, "message") pageEvents.remove(p2, "message")
if(this.cancelButton){ if(this.cancelButton){
pageEvents.remove(this.cancelButton, ["mousedown", "touchstart"]) pageEvents.remove(this.cancelButton, ["mousedown", "touchstart"])
......
class Lyrics{
constructor(file, songOffset, div, parsed){
this.div = div
this.stroke = document.createElement("div")
this.stroke.classList.add("stroke")
div.appendChild(this.stroke)
this.fill = document.createElement("div")
this.fill.classList.add("fill")
div.appendChild(this.fill)
this.current = 0
this.shown = -1
this.songOffset = songOffset || 0
this.vttOffset = 0
this.rLinebreak = /\n|\r\n/
this.lines = parsed ? file : this.parseFile(file)
this.length = this.lines.length
}
parseFile(file){
var lines = []
var commands = file.split(/\n\n|\r\n\r\n/)
var arrow = " --> "
for(var i in commands){
var matches = commands[i].match(this.rLinebreak)
if(matches){
var cmd = commands[i].slice(0, matches.index)
var value = commands[i].slice(matches.index + 1)
}else{
var cmd = commands[i]
var value = ""
}
if(cmd.startsWith("WEBVTT")){
var nameValue = cmd.slice(7).split(";")
for(var j in nameValue){
var [name, value] = nameValue[j].split(":")
if(name.trim().toLowerCase() === "offset"){
this.vttOffset = (parseFloat(value.trim()) || 0) * 1000
}
}
}else{
var time = null
var index = cmd.indexOf(arrow)
if(index !== -1){
time = cmd
}else{
var matches = value.match(this.rLinebreak)
if(matches){
var value1 = value.slice(0, matches.index)
index = value1.indexOf(arrow)
if(index !== -1){
time = value1
value = value.slice(index)
}
}
}
if(time !== null){
var start = time.slice(0, index)
var end = time.slice(index + arrow.length)
var index = end.indexOf(" ")
if(index !== -1){
end = end.slice(0, index)
}
var text = value.trim()
var textLang = ""
var firstLang = -1
var index2 = -1
while(true){
var index1 = text.indexOf("<lang ", index2 + 1)
if(firstLang === -1){
firstLang = index1
}
if(index1 !== -1){
index2 = text.indexOf(">", index1 + 6)
if(index2 === -1){
break
}
var lang = text.slice(index1 + 6, index2).toLowerCase()
if(strings.id === lang){
var index3 = text.indexOf("<lang ", index2 + 1)
if(index3 !== -1){
textLang = text.slice(index2 + 1, index3)
}else{
textLang = text.slice(index2 + 1)
}
}
}else{
break
}
}
if(!textLang){
textLang = firstLang === -1 ? text : text.slice(0, firstLang)
}
lines.push({
start: this.convertTime(start),
end: this.convertTime(end),
text: textLang
})
}
}
}
return lines
}
convertTime(time){
if(time.startsWith("-")){
var mul = -1
time = time.slice(1)
}else{
var mul = 1
}
var array = time.split(":")
if(array.length === 2){
var h = 0
var m = array[0]
var s = array[1]
}else{
var h = parseInt(array[0])
var m = array[1]
var s = array[2]
}
var index = s.indexOf(",")
if(index !== -1){
s = s.slice(0, index) + "." + s.slice(index + 1)
}
return ((h * 60 + parseInt(m)) * 60 + parseFloat(s)) * 1000 * mul
}
update(ms){
if(this.current >= this.length){
return
}
ms += this.songOffset + this.vttOffset
var currentLine = this.lines[this.current]
while(currentLine && ms > currentLine.end){
currentLine = this.lines[++this.current]
}
if(this.shown !== this.current){
if(currentLine && ms >= currentLine.start){
this.setText(this.lines[this.current].text)
this.shown = this.current
}else if(this.shown !== -1){
this.setText("")
this.shown = -1
}
}
}
setText(text){
this.stroke.innerHTML = this.fill.innerHTML = ""
var hasRuby = false
while(text){
var matches = text.match(this.rLinebreak)
var index1 = matches ? matches.index : -1
var index2 = text.indexOf("<ruby>")
if(index1 !== -1 && (index2 === -1 || index2 > index1)){
this.textNode(text.slice(0, index1))
this.linebreakNode()
text = text.slice(index1 + matches[0].length)
}else if(index2 !== -1){
hasRuby = true
this.textNode(text.slice(0, index2))
text = text.slice(index2 + 6)
var index = text.indexOf("</ruby>")
if(index !== -1){
var ruby = text.slice(0, index)
text = text.slice(index + 7)
}else{
var ruby = text
text = ""
}
var index = ruby.indexOf("<rt>")
if(index !== -1){
var node1 = ruby.slice(0, index)
ruby = ruby.slice(index + 4)
var index = ruby.indexOf("</rt>")
if(index !== -1){
var node2 = ruby.slice(0, index)
}else{
var node2 = ruby
}
}else{
var node1 = ruby
var node2 = ""
}
this.rubyNode(node1, node2)
}else{
this.textNode(text)
break
}
}
}
insertNode(func){
this.stroke.appendChild(func())
this.fill.appendChild(func())
}
textNode(text){
this.insertNode(() => document.createTextNode(text))
}
linebreakNode(){
this.insertNode(() => document.createElement("br"))
}
rubyNode(node1, node2){
this.insertNode(() => {
var ruby = document.createElement("ruby")
var rt = document.createElement("rt")
ruby.appendChild(document.createTextNode(node1))
rt.appendChild(document.createTextNode(node2))
ruby.appendChild(rt)
return ruby
})
}
setScale(ratio){
this.div.style.setProperty("--scale", ratio)
}
offsetChange(songOffset, vttOffset){
if(typeof songOffset !== "undefined"){
this.songOffset = songOffset
}
if(typeof vttOffset !== "undefined"){
this.vttOffset = vttOffset
}
this.setText("")
this.current = 0
this.shown = -1
}
clean(){
if(this.shown !== -1){
this.setText("")
}
delete this.div
delete this.stroke
delete this.fill
delete this.lines
}
}
...@@ -80,10 +80,15 @@ var perf = { ...@@ -80,10 +80,15 @@ 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
var scoreStorage var scoreStorage
var account = {}
pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){ if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){
......
...@@ -3,6 +3,8 @@ class P2Connection{ ...@@ -3,6 +3,8 @@ class P2Connection{
this.closed = true this.closed = true
this.lastMessages = {} this.lastMessages = {}
this.otherConnected = false this.otherConnected = false
this.name = null
this.player = 1
this.allEvents = new Map() this.allEvents = new Map()
this.addEventListener("message", this.message.bind(this)) this.addEventListener("message", this.message.bind(this))
this.currentHash = "" this.currentHash = ""
...@@ -107,6 +109,10 @@ class P2Connection{ ...@@ -107,6 +109,10 @@ class P2Connection{
} }
message(response){ message(response){
switch(response.type){ switch(response.type){
case "gameload":
if("player" in response.value){
this.player = response.value.player === 2 ? 2 : 1
}
case "gamestart": case "gamestart":
this.otherConnected = true this.otherConnected = true
this.notes = [] this.notes = []
...@@ -115,6 +121,7 @@ class P2Connection{ ...@@ -115,6 +121,7 @@ class P2Connection{
this.kaAmount = 0 this.kaAmount = 0
this.results = false this.results = false
this.branch = "normal" this.branch = "normal"
scoreStorage.clearP2()
break break
case "gameend": case "gameend":
this.otherConnected = false this.otherConnected = false
...@@ -128,11 +135,14 @@ class P2Connection{ ...@@ -128,11 +135,14 @@ class P2Connection{
this.hash("") this.hash("")
this.hashLock = false this.hashLock = false
} }
this.name = null
this.don = null
scoreStorage.clearP2()
break break
case "gameresults": case "gameresults":
this.results = {} this.results = {}
for(var i in response.value){ for(var i in response.value){
this.results[i] = response.value[i].toString() this.results[i] = response.value[i] === null ? null : response.value[i].toString()
} }
break break
case "note": case "note":
...@@ -155,6 +165,45 @@ class P2Connection{ ...@@ -155,6 +165,45 @@ class P2Connection{
this.clearMessage("users") this.clearMessage("users")
this.otherConnected = true this.otherConnected = true
this.session = true this.session = true
scoreStorage.clearP2()
if("player" in response.value){
this.player = response.value.player === 2 ? 2 : 1
}
break
case "name":
this.name = response.value ? (response.value.name || "").toString() : ""
this.don = response.value ? (response.value.don) : null
break
case "getcrowns":
if(response.value){
var output = {}
for(var i in response.value){
if(response.value[i]){
var score = scoreStorage.get(response.value[i], false, true)
if(score){
var crowns = {}
for(var diff in score){
if(diff !== "title"){
crowns[diff] = {
crown: score[diff].crown
}
}
}
}else{
var crowns = null
}
output[response.value[i]] = crowns
}
}
p2.send("crowns", output)
}
break
case "crowns":
if(response.value){
for(var i in response.value){
scoreStorage.addP2(i, false, response.value[i], true)
}
}
break break
} }
} }
......
...@@ -86,6 +86,9 @@ class PageEvents{ ...@@ -86,6 +86,9 @@ class PageEvents{
}) })
} }
keyEvent(event){ keyEvent(event){
if(!("key" in event) || event.ctrlKey && (event.key === "c" || event.key === "x" || event.key === "v")){
return
}
if(this.kbd.indexOf(event.key.toLowerCase()) !== -1){ if(this.kbd.indexOf(event.key.toLowerCase()) !== -1){
this.lastKeyEvent = Date.now() this.lastKeyEvent = Date.now()
event.preventDefault() event.preventDefault()
......
...@@ -48,6 +48,7 @@ class ParseOsu{ ...@@ -48,6 +48,7 @@ class ParseOsu{
lastBeatInterval: 0, lastBeatInterval: 0,
bpm: 0 bpm: 0
} }
this.events = []
this.generalInfo = this.parseGeneralInfo() this.generalInfo = this.parseGeneralInfo()
this.metadata = this.parseMetadata() this.metadata = this.parseMetadata()
this.editor = this.parseEditor() this.editor = this.parseEditor()
...@@ -249,6 +250,18 @@ class ParseOsu{ ...@@ -249,6 +250,18 @@ class ParseOsu{
var circles = [] var circles = []
var circleID = 0 var circleID = 0
var indexes = this.getStartEndIndexes("HitObjects") var indexes = this.getStartEndIndexes("HitObjects")
var lastBeatMS = this.beatInfo.beatInterval
var lastGogo = false
var pushCircle = circle => {
circles.push(circle)
if(lastBeatMS !== circle.beatMS || lastGogo !== circle.gogoTime){
lastBeatMS = circle.beatMS
lastGogo = circle.gogoTime
this.events.push(circle)
}
}
for(var i = indexes.start; i <= indexes.end; i++){ for(var i = indexes.start; i <= indexes.end; i++){
circleID++ circleID++
var values = this.data[i].split(",") var values = this.data[i].split(",")
...@@ -282,7 +295,7 @@ class ParseOsu{ ...@@ -282,7 +295,7 @@ class ParseOsu{
var endTime = parseInt(values[this.osu.ENDTIME]) var endTime = parseInt(values[this.osu.ENDTIME])
var hitMultiplier = this.difficultyRange(this.difficulty.overallDifficulty, 3, 5, 7.5) * 1.65 var hitMultiplier = this.difficultyRange(this.difficulty.overallDifficulty, 3, 5, 7.5) * 1.65
var requiredHits = Math.floor(Math.max(1, (endTime - start) / 1000 * hitMultiplier)) var requiredHits = Math.floor(Math.max(1, (endTime - start) / 1000 * hitMultiplier))
circles.push(new Circle({ pushCircle(new Circle({
id: circleID, id: circleID,
start: start + this.offset, start: start + this.offset,
type: "balloon", type: "balloon",
...@@ -309,7 +322,7 @@ class ParseOsu{ ...@@ -309,7 +322,7 @@ class ParseOsu{
type = "drumroll" type = "drumroll"
txt = strings.note.drumroll txt = strings.note.drumroll
} }
circles.push(new Circle({ pushCircle(new Circle({
id: circleID, id: circleID,
start: start + this.offset, start: start + this.offset,
type: type, type: type,
...@@ -366,7 +379,7 @@ class ParseOsu{ ...@@ -366,7 +379,7 @@ class ParseOsu{
break; break;
} }
} }
circles.push(new Circle({ pushCircle(new Circle({
id: circleID, id: circleID,
start: start + this.offset, start: start + this.offset,
type: type, type: type,
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
this.metadata = this.parseMetadata() this.metadata = this.parseMetadata()
this.measures = [] this.measures = []
this.beatInfo = {} this.beatInfo = {}
this.events = []
if(!metaOnly){ if(!metaOnly){
this.circles = this.parseCircles() this.circles = this.parseCircles()
} }
...@@ -88,6 +89,8 @@ ...@@ -88,6 +89,8 @@
} }
}else if(name.startsWith("branchstart") && inSong){ }else if(name.startsWith("branchstart") && inSong){
courses[courseName].branch = true courses[courseName].branch = true
}else if(name.startsWith("lyric") && inSong){
courses[courseName].inlineLyrics = true
} }
}else if(!inSong){ }else if(!inSong){
...@@ -162,6 +165,7 @@ ...@@ -162,6 +165,7 @@
var circleID = 0 var circleID = 0
var regexAZ = /[A-Z]/ var regexAZ = /[A-Z]/
var regexSpace = /\s/ var regexSpace = /\s/
var regexLinebreak = /\\n/g
var isAllDon = (note_chain, start_pos) => { var isAllDon = (note_chain, start_pos) => {
for (var i = start_pos; i < note_chain.length; ++i) { for (var i = start_pos; i < note_chain.length; ++i) {
var note = note_chain[i]; var note = note_chain[i];
...@@ -275,7 +279,12 @@ ...@@ -275,7 +279,12 @@
lastDrumroll = circleObj lastDrumroll = circleObj
} }
circles.push(circleObj) if(note.event){
this.events.push(circleObj)
}
if(note.type !== "event"){
circles.push(circleObj)
}
} else if (!(currentMeasure.length >= 24 && (!currentMeasure[i + 1] || currentMeasure[i + 1].type)) } else if (!(currentMeasure.length >= 24 && (!currentMeasure[i + 1] || currentMeasure[i + 1].type))
&& !(currentMeasure.length >= 48 && (!currentMeasure[i + 2] || currentMeasure[i + 2].type || !currentMeasure[i + 3] || currentMeasure[i + 3].type))) { && !(currentMeasure.length >= 48 && (!currentMeasure[i + 2] || currentMeasure[i + 2].type || !currentMeasure[i + 3] || currentMeasure[i + 3].type))) {
if (note_chain.length > 1 && currentMeasure.length >= 8) { if (note_chain.length > 1 && currentMeasure.length >= 8) {
...@@ -293,9 +302,12 @@ ...@@ -293,9 +302,12 @@
} }
} }
var insertNote = circleObj => { var insertNote = circleObj => {
lastBpm = bpm
lastGogo = gogo
if(circleObj){ if(circleObj){
if(bpm !== lastBpm || gogo !== lastGogo){
circleObj.event = true
lastBpm = bpm
lastGogo = gogo
}
currentMeasure.push(circleObj) currentMeasure.push(circleObj)
} }
} }
...@@ -429,6 +441,18 @@ ...@@ -429,6 +441,18 @@
} }
branchObj[branchName] = currentBranch branchObj[branchName] = currentBranch
break break
case "lyric":
if(!this.lyrics){
this.lyrics = []
}
if(this.lyrics.length !== 0){
this.lyrics[this.lyrics.length - 1].end = ms
}
this.lyrics.push({
start: ms,
text: value.trim().replace(regexLinebreak, "\n")
})
break
} }
}else{ }else{
...@@ -563,6 +587,10 @@ ...@@ -563,6 +587,10 @@
this.scoreinit = autoscore.ScoreInit; this.scoreinit = autoscore.ScoreInit;
this.scorediff = autoscore.ScoreDiff; this.scorediff = autoscore.ScoreDiff;
} }
if(this.lyrics){
var line = this.lyrics[this.lyrics.length - 1]
line.end = Math.max(ms, line.start) + 5000
}
return circles return circles
} }
} }
...@@ -2,9 +2,19 @@ class Scoresheet{ ...@@ -2,9 +2,19 @@ class Scoresheet{
constructor(controller, results, multiplayer, touchEnabled){ constructor(controller, results, multiplayer, touchEnabled){
this.controller = controller this.controller = controller
this.resultsObj = results this.resultsObj = results
this.results = {} this.player = [multiplayer ? (p2.player === 1 ? 0 : 1) : 0]
var player0 = this.player[0]
this.results = []
this.results[player0] = {}
this.rules = []
this.rules[player0] = this.controller.game.rules
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[i] = results[i].toString() this.results[player0][i] = results[i] === null ? null : results[i].toString()
} }
this.multiplayer = multiplayer this.multiplayer = multiplayer
this.touchEnabled = touchEnabled this.touchEnabled = touchEnabled
...@@ -39,6 +49,7 @@ class Scoresheet{ ...@@ -39,6 +49,7 @@ class Scoresheet{
this.draw = new CanvasDraw(noSmoothing) this.draw = new CanvasDraw(noSmoothing)
this.canvasCache = new CanvasCache(noSmoothing) this.canvasCache = new CanvasCache(noSmoothing)
this.nameplateCache = new CanvasCache(noSmoothing)
this.keyboard = new Keyboard({ this.keyboard = new Keyboard({
confirm: ["enter", "space", "esc", "don_l", "don_r"] confirm: ["enter", "space", "esc", "don_l", "don_r"]
...@@ -208,6 +219,7 @@ class Scoresheet{ ...@@ -208,6 +219,7 @@ class Scoresheet{
this.canvas.style.height = (winH / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px"
this.canvasCache.resize(winW / ratio, 80 + 1, ratio) 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) this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio)
...@@ -233,6 +245,9 @@ class Scoresheet{ ...@@ -233,6 +245,9 @@ class Scoresheet{
if(!this.canvasCache.canvas){ if(!this.canvasCache.canvas){
this.canvasCache.resize(winW / ratio, 80 + 1, ratio) this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
} }
if(!this.nameplateCache.canvas){
this.nameplateCache.resize(274, 67, ratio + 0.2)
}
} }
this.winW = winW this.winW = winW
this.winH = winH this.winH = winH
...@@ -243,7 +258,7 @@ class Scoresheet{ ...@@ -243,7 +258,7 @@ class Scoresheet{
var frameTop = winH / 2 - 720 / 2 var frameTop = winH / 2 - 720 / 2
var frameLeft = winW / 2 - 1280 / 2 var frameLeft = winW / 2 - 1280 / 2
var players = this.multiplayer && p2.results ? 2 : 1 var players = this.multiplayer ? 2 : 1
var p2Offset = 298 var p2Offset = 298
var bgOffset = 0 var bgOffset = 0
...@@ -326,28 +341,21 @@ class Scoresheet{ ...@@ -326,28 +341,21 @@ class Scoresheet{
} }
var rules = this.controller.game.rules var rules = this.controller.game.rules
var gaugePercent = rules.gaugePercent(this.results.gauge) var failedOffset = rules.clearReached(this.results[this.player[0]].gauge) ? 0 : -2000
var gaugeClear = [rules.gaugeClear] if(players === 2 && failedOffset !== 0){
if(players === 2){ var p2results = this.results[this.player[1]]
gaugeClear.push(this.controller.syncWith.game.rules.gaugeClear) if(p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)){
}
var failedOffset = gaugePercent >= gaugeClear[0] ? 0 : -2000
if(players === 2){
var gauge2 = this.controller.syncWith.game.rules.gaugePercent(p2.results.gauge)
if(gauge2 > gaugePercent && failedOffset !== 0 && gauge2 >= gaugeClear[1]){
failedOffset = 0 failedOffset = 0
} }
} }
if(elapsed >= 3100 + failedOffset){ if(elapsed >= 3100 + failedOffset){
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
ctx.save() ctx.save()
var results = this.results var results = this.results[p]
if(p === 1){ if(!results){
results = p2.results continue
} }
var playerRules = p === 0 ? rules : this.controller.syncWith.game.rules var clear = this.rules[p].clearReached(results.gauge)
var resultGauge = playerRules.gaugePercent(results.gauge)
var clear = resultGauge >= gaugeClear[p]
if(p === 1 || !this.multiplayer && clear){ if(p === 1 || !this.multiplayer && clear){
ctx.translate(0, 290) ctx.translate(0, 290)
} }
...@@ -410,7 +418,7 @@ class Scoresheet{ ...@@ -410,7 +418,7 @@ class Scoresheet{
this.draw.layeredText({ this.draw.layeredText({
ctx: ctx, ctx: ctx,
text: this.results.title, text: this.results[this.player[0]].title,
fontSize: 40, fontSize: 40,
fontFamily: this.font, fontFamily: this.font,
x: 1257, x: 1257,
...@@ -426,9 +434,11 @@ class Scoresheet{ ...@@ -426,9 +434,11 @@ class Scoresheet{
ctx.save() ctx.save()
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
var results = this.results var results = this.results[p]
if(!results){
continue
}
if(p === 1){ if(p === 1){
results = p2.results
ctx.translate(0, p2Offset) ctx.translate(0, p2Offset)
} }
...@@ -450,6 +460,30 @@ class Scoresheet{ ...@@ -450,6 +460,30 @@ class Scoresheet{
ctx.fillText(text, 395, 308) ctx.fillText(text, 395, 308)
ctx.miterLimit = 10 ctx.miterLimit = 10
var defaultName = p === 0 ? strings.defaultName : strings.default2PName
if(p === this.player[0]){
var name = account.loggedIn ? account.displayName : defaultName
}else{
var name = results.name || defaultName
}
this.nameplateCache.get({
ctx: ctx,
x: 259,
y: 92,
w: 273,
h: 66,
id: p.toString() + "p" + name,
}, ctx => {
this.draw.nameplate({
ctx: ctx,
x: 3,
y: 3,
name: name,
font: this.font,
blue: p === 1
})
})
let badge_name = this.controller.getModBadge(); let badge_name = this.controller.getModBadge();
if(this.controller.autoPlayEnabled) { if(this.controller.autoPlayEnabled) {
badge_name = "badge_auto"; badge_name = "badge_auto";
...@@ -585,7 +619,7 @@ class Scoresheet{ ...@@ -585,7 +619,7 @@ class Scoresheet{
if(this.tetsuoHanaClass){ if(this.tetsuoHanaClass){
this.tetsuoHana.classList.remove(this.tetsuoHanaClass) this.tetsuoHana.classList.remove(this.tetsuoHanaClass)
} }
this.tetsuoHanaClass = rules.clearReached(this.results.gauge) ? "dance" : "failed" this.tetsuoHanaClass = this.rules[this.player[0]].clearReached(this.results[this.player[0]].gauge) ? "dance" : "failed"
this.tetsuoHana.classList.add(this.tetsuoHanaClass) this.tetsuoHana.classList.add(this.tetsuoHanaClass)
} }
} }
...@@ -599,32 +633,32 @@ class Scoresheet{ ...@@ -599,32 +633,32 @@ class Scoresheet{
ctx.translate(frameLeft, frameTop) ctx.translate(frameLeft, frameTop)
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
var results = this.results var results = this.results[p]
if(!results){
continue
}
if(p === 1){ if(p === 1){
results = p2.results
ctx.translate(0, p2Offset) ctx.translate(0, p2Offset)
} }
var gaugePercent = rules.gaugePercent(results.gauge)
var w = 712 var w = 712
this.draw.gauge({ this.draw.gauge({
ctx: ctx, ctx: ctx,
x: 558 + w, x: 558 + w,
y: p === 1 ? 124 : 116, y: p === 1 ? 124 : 116,
clear: gaugeClear[p], clear: this.rules[p].gaugeClear,
percentage: gaugePercent, percentage: this.rules[p].gaugePercent(results.gauge),
font: this.font, font: this.font,
scale: w / 788, scale: w / 788,
scoresheet: true, scoresheet: true,
blue: p === 1, blue: p === 1,
multiplayer: p === 1 multiplayer: p === 1
}) })
var playerRules = p === 0 ? rules : this.controller.syncWith.game.rules
this.draw.soul({ this.draw.soul({
ctx: ctx, ctx: ctx,
x: 1215, x: 1215,
y: 144, y: 144,
scale: 36 / 42, scale: 36 / 42,
cleared: playerRules.clearReached(results.gauge) cleared: this.rules[p].clearReached(results.gauge)
}) })
} }
}) })
...@@ -637,13 +671,12 @@ class Scoresheet{ ...@@ -637,13 +671,12 @@ class Scoresheet{
var noCrownResultWait = -2000; var noCrownResultWait = -2000;
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
var results = this.results var results = this.results[p]
if(p === 1){ if(!results){
results = p2.results continue
} }
var crownType = null var crownType = null
var playerRules = p === 0 ? rules : this.controller.syncWith.game.rules if(this.rules[p].clearReached(results.gauge)){
if(playerRules.clearReached(results.gauge)){
crownType = results.bad === "0" ? "gold" : "silver" crownType = results.bad === "0" ? "gold" : "silver"
} }
if(crownType !== null){ if(crownType !== null){
...@@ -706,7 +739,10 @@ class Scoresheet{ ...@@ -706,7 +739,10 @@ class Scoresheet{
var times = {} var times = {}
var lastTime = 0 var lastTime = 0
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
var results = p === 0 ? this.results : p2.results var results = this.results[p]
if(!results){
continue
}
var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame
if(currentTime > lastTime){ if(currentTime > lastTime){
lastTime = currentTime lastTime = currentTime
...@@ -715,7 +751,10 @@ class Scoresheet{ ...@@ -715,7 +751,10 @@ class Scoresheet{
for(var i in printNumbers){ for(var i in printNumbers){
var largestTime = 0 var largestTime = 0
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
var results = p === 0 ? this.results : p2.results var results = this.results[p]
if(!results){
continue
}
times[printNumbers[i]] = lastTime + 500 times[printNumbers[i]] = lastTime + 500
var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
if(currentTime > largestTime){ if(currentTime > largestTime){
...@@ -731,9 +770,11 @@ class Scoresheet{ ...@@ -731,9 +770,11 @@ class Scoresheet{
} }
for(var p = 0; p < players; p++){ for(var p = 0; p < players; p++){
var results = this.results var results = this.results[p]
if(!results){
continue
}
if(p === 1){ if(p === 1){
results = p2.results
ctx.translate(0, p2Offset) ctx.translate(0, p2Offset)
} }
ctx.save() ctx.save()
...@@ -827,7 +868,7 @@ class Scoresheet{ ...@@ -827,7 +868,7 @@ class Scoresheet{
if(elapsed >= 1000){ if(elapsed >= 1000){
this.clean() this.clean()
this.controller.songSelection(true) this.controller.songSelection(true, this.showWarning)
} }
} }
...@@ -894,10 +935,14 @@ class Scoresheet{ ...@@ -894,10 +935,14 @@ class Scoresheet{
delete this.resultsObj.title delete this.resultsObj.title
delete this.resultsObj.difficulty delete this.resultsObj.difficulty
delete this.resultsObj.gauge delete this.resultsObj.gauge
scoreStorage.add(hash, difficulty, this.resultsObj, true, title) scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => {
this.showWarning = {name: "scoreSaveFailed"}
})
}else if(oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)){ }else if(oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)){
oldScore.crown = crown oldScore.crown = crown
scoreStorage.add(hash, difficulty, oldScore, true, title) scoreStorage.add(hash, difficulty, oldScore, true, title).catch(() => {
this.showWarning = {name: "scoreSaveFailed"}
})
} }
} }
this.scoreSaved = true this.scoreSaved = true
...@@ -912,7 +957,7 @@ class Scoresheet{ ...@@ -912,7 +957,7 @@ class Scoresheet{
snd.buffer.loadSettings() snd.buffer.loadSettings()
this.redrawRunning = false this.redrawRunning = false
pageEvents.remove(this.canvas, ["mousedown", "touchstart"]) pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
if(this.multiplayer !== 2 && this.touchEnabled){ if(this.touchEnabled){
pageEvents.remove(document.getElementById("touch-full-btn"), "touchend") pageEvents.remove(document.getElementById("touch-full-btn"), "touchend")
} }
if(this.session){ if(this.session){
...@@ -924,5 +969,7 @@ class Scoresheet{ ...@@ -924,5 +969,7 @@ class Scoresheet{
delete this.ctx delete this.ctx
delete this.canvas delete this.canvas
delete this.fadeScreen delete this.fadeScreen
delete this.results
delete this.rules
} }
} }
class ScoreStorage{ class ScoreStorage{
constructor(){ constructor(){
this.scores = {} this.scores = {}
this.scoresP2 = {}
this.requestP2 = new Set()
this.requestedP2 = new Set()
this.songTitles = {} this.songTitles = {}
this.difficulty = ["oni", "ura", "hard", "normal", "easy"] this.difficulty = ["oni", "ura", "hard", "normal", "easy"]
this.scoreKeys = ["points", "good", "ok", "bad", "maxCombo", "drumroll"] this.scoreKeys = ["points", "good", "ok", "bad", "maxCombo", "drumroll"]
this.crownValue = ["", "silver", "gold"] this.crownValue = ["", "silver", "gold"]
this.load()
} }
load(){ load(strings, loadFailed){
this.scores = {} var scores = {}
this.scoreStrings = {} var scoreStrings = {}
try{ if(loadFailed){
var localScores = localStorage.getItem("scoreStorage") try{
if(localScores){ var localScores = localStorage.getItem("saveFailed")
this.scoreStrings = JSON.parse(localScores) if(localScores){
} scoreStrings = JSON.parse(localScores)
}catch(e){} }
for(var hash in this.scoreStrings){ }catch(e){}
var scoreString = this.scoreStrings[hash] }else if(strings){
scoreStrings = this.prepareStrings(strings)
}else if(account.loggedIn){
return
}else{
try{
var localScores = localStorage.getItem("scoreStorage")
if(localScores){
scoreStrings = JSON.parse(localScores)
}
}catch(e){}
}
for(var hash in scoreStrings){
var scoreString = scoreStrings[hash]
var songAdded = false var songAdded = false
if(typeof scoreString === "string" && scoreString){ if(typeof scoreString === "string" && scoreString){
var diffArray = scoreString.split(";") var diffArray = scoreString.split(";")
...@@ -37,25 +52,63 @@ class ScoreStorage{ ...@@ -37,25 +52,63 @@ class ScoreStorage{
score[name] = value score[name] = value
} }
if(!songAdded){ if(!songAdded){
this.scores[hash] = {title: null} scores[hash] = {title: null}
songAdded = true songAdded = true
} }
this.scores[hash][this.difficulty[i]] = score scores[hash][this.difficulty[i]] = score
}
}
}
}
if(loadFailed){
for(var hash in scores){
for(var i in this.difficulty){
var diff = this.difficulty[i]
if(scores[hash][diff]){
this.add(hash, diff, scores[hash][diff], true, this.songTitles[hash] || null).then(() => {
localStorage.removeItem("saveFailed")
}, () => {})
} }
} }
} }
}else{
this.scores = scores
this.scoreStrings = scoreStrings
}
if(strings){
this.load(false, true)
} }
} }
prepareScores(scores){
var output = []
for (var k in scores) {
output.push({'hash': k, 'score': scores[k]})
}
return output
}
prepareStrings(scores){
var output = {}
for(var k in scores){
output[scores[k].hash] = scores[k].score
}
return output
}
save(){ save(){
for(var hash in this.scores){ for(var hash in this.scores){
this.writeString(hash) this.writeString(hash)
} }
this.write() this.write()
return this.sendToServer({
scores: this.prepareScores(this.scoreStrings),
is_import: true
})
} }
write(){ write(){
try{ if(!account.loggedIn){
localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings)) try{
}catch(e){} localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings))
}catch(e){}
}
} }
writeString(hash){ writeString(hash){
var score = this.scores[hash] var score = this.scores[hash]
...@@ -101,17 +154,82 @@ class ScoreStorage{ ...@@ -101,17 +154,82 @@ class ScoreStorage{
} }
} }
} }
add(song, difficulty, scoreObject, isHash, setTitle){ getP2(song, difficulty, isHash){
if(!song){
return this.scoresP2
}else{
var hash = isHash ? song : this.titleHash(song)
if(!(hash in this.scoresP2) && !this.requestP2.has(hash) && !this.requestedP2.has(hash)){
this.requestP2.add(hash)
this.requestedP2.add(hash)
}
if(difficulty){
if(hash in this.scoresP2){
return this.scoresP2[hash][difficulty]
}
}else{
return this.scoresP2[hash]
}
}
}
add(song, difficulty, scoreObject, isHash, setTitle, saveFailed){
var hash = isHash ? song : this.titleHash(song) var hash = isHash ? song : this.titleHash(song)
if(!(hash in this.scores)){ if(!(hash in this.scores)){
this.scores[hash] = {} this.scores[hash] = {}
} }
if(setTitle){ if(difficulty){
this.scores[hash].title = setTitle if(setTitle){
this.scores[hash].title = setTitle
}
this.scores[hash][difficulty] = scoreObject
}else{
this.scores[hash] = scoreObject
if(setTitle){
this.scores[hash].title = setTitle
}
} }
this.scores[hash][difficulty] = scoreObject
this.writeString(hash) this.writeString(hash)
this.write() this.write()
if(saveFailed){
var failedScores = {}
try{
var localScores = localStorage.getItem("saveFailed")
if(localScores){
failedScores = JSON.parse(localScores)
}
}catch(e){}
if(!(hash in failedScores)){
failedScores[hash] = {}
}
failedScores[hash] = this.scoreStrings[hash]
try{
localStorage.setItem("saveFailed", JSON.stringify(failedScores))
}catch(e){}
return Promise.reject()
}else{
var obj = {}
obj[hash] = this.scoreStrings[hash]
return this.sendToServer({
scores: this.prepareScores(obj)
}).catch(() => this.add(song, difficulty, scoreObject, isHash, setTitle, true))
}
}
addP2(song, difficulty, scoreObject, isHash, setTitle){
var hash = isHash ? song : this.titleHash(song)
if(!(hash in this.scores)){
this.scoresP2[hash] = {}
}
if(difficulty){
if(setTitle){
this.scoresP2[hash].title = setTitle
}
this.scoresP2[hash][difficulty] = scoreObject
}else{
this.scoresP2[hash] = scoreObject
if(setTitle){
this.scoresP2[hash].title = setTitle
}
}
} }
template(){ template(){
var template = {crown: ""} var template = {crown: ""}
...@@ -146,6 +264,63 @@ class ScoreStorage{ ...@@ -146,6 +264,63 @@ class ScoreStorage{
delete this.scoreStrings[hash] delete this.scoreStrings[hash]
} }
this.write() this.write()
this.sendToServer({
scores: this.prepareScores(this.scoreStrings),
is_import: true
})
} }
} }
sendToServer(obj, retry){
if(account.loggedIn){
return loader.getCsrfToken().then(token => {
var request = new XMLHttpRequest()
request.open("POST", "api/scores/save")
var promise = pageEvents.load(request).then(response => {
if(request.status !== 200){
return Promise.reject()
}
}).catch(() => {
if(retry){
this.scoreSaveFailed = true
account.loggedIn = false
delete account.username
delete account.displayName
delete account.don
this.load()
pageEvents.send("logout")
return Promise.reject()
}else{
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 3000)
}).then(() => this.sendToServer(obj, true))
}
})
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
request.setRequestHeader("X-CSRFToken", token)
request.send(JSON.stringify(obj))
return promise
})
}else{
return Promise.resolve()
}
}
eventLoop(){
if(p2.session && this.requestP2.size){
var req = []
this.requestP2.forEach(hash => {
req.push(hash)
})
this.requestP2.clear()
if(req.length){
p2.send("getcrowns", req)
}
}
}
clearP2(){
this.scoresP2 = {}
this.requestP2.clear()
this.requestedP2.clear()
}
} }
...@@ -34,7 +34,11 @@ class Session{ ...@@ -34,7 +34,11 @@ class Session{
pageEvents.send("session-start", "host") pageEvents.send("session-start", "host")
} }
}) })
p2.send("invite") p2.send("invite", {
id: null,
name: account.loggedIn ? account.displayName : null,
don: account.loggedIn ? account.don : null
})
pageEvents.send("session") pageEvents.send("session")
} }
getElement(name){ getElement(name){
......
...@@ -50,6 +50,10 @@ class Settings{ ...@@ -50,6 +50,10 @@ class Settings{
easierBigNotes: { easierBigNotes: {
type: "toggle", type: "toggle",
default: false default: false
},
showLyrics: {
type: "toggle",
default: true
} }
} }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -9,18 +9,17 @@ class ViewAssets{ ...@@ -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.multiplayer === 2 ? 560 : 35) : (view.multiplayer === 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{ ...@@ -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{ ...@@ -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{ ...@@ -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
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="view"> <div class="view">
<div class="view-title stroke-sub"></div> <div class="view-title stroke-sub"></div>
<div class="view-content"></div> <div class="view-content"></div>
<div id="diag-txt"></div> <div class="diag-txt"></div>
<div class="left-buttons"> <div class="left-buttons">
<div id="link-issues" class="taibtn stroke-sub link-btn"> <div id="link-issues" class="taibtn stroke-sub link-btn">
<a target="_blank"></a> <a target="_blank"></a>
......
<div class="view-outer">
<div class="view account-view">
<div class="view-title stroke-sub"></div>
<div class="view-content">
<div class="error-div"></div>
<div class="displayname-div">
<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>
</div>
<div class="accountpass-div">
<input type="password" name="password"><input type="password" name="newpassword" autocomplete="new-password"><input type="password" name="newpassword2" autocomplete="new-password">
</div>
</form>
<form class="accountdel-form">
<div>
<div class="accountdel-btn taibtn stroke-sub link-btn"></div>
</div>
<div class="accountdel-div">
<input type="password" name="password">
</div>
</form>
</div>
<div id="diag-txt"></div>
<div class="left-buttons">
<div class="logout-btn taibtn stroke-sub link-btn"></div>
</div>
<div class="save-btn taibtn stroke-sub selected"></div>
<div class="view-end-button taibtn stroke-sub"></div>
</div>
</div>
...@@ -24,6 +24,12 @@ ...@@ -24,6 +24,12 @@
<div class="music-volume input-slider"> <div class="music-volume input-slider">
<span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span> <span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span>
</div> </div>
<div class="lyrics-hide">
<div>Lyrics offset:</div>
<div class="lyrics-offset input-slider">
<span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span>
</div>
</div>
<label class="change-restart-label"><input class="change-restart" type="checkbox">Restart on change</label> <label class="change-restart-label"><input class="change-restart" type="checkbox">Restart on change</label>
<label class="autoplay-label"><input class="autoplay" type="checkbox">Auto play</label> <label class="autoplay-label"><input class="autoplay" type="checkbox">Auto play</label>
<div class="bottom-btns"> <div class="bottom-btns">
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
<div id="touch-drum-img"></div> <div id="touch-drum-img"></div>
</div> </div>
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<div id="song-lyrics"></div>
<div id="touch-buttons"> <div id="touch-buttons">
<div id="touch-full-btn"></div><div id="touch-pause-btn"></div> <div id="touch-full-btn"></div><div id="touch-pause-btn"></div>
</div> </div>
......
...@@ -2,3 +2,10 @@ ...@@ -2,3 +2,10 @@
<div class="progress"></div> <div class="progress"></div>
<span class="percentage">0%</span> <span class="percentage">0%</span>
</div> </div>
<div class="view-outer loader-error-div">
<div class="view">
<div class="view-content">An error occurred, please refresh</div>
<div class="diag-txt"></div>
<span class="debug-link">Debug</span>
</div>
</div>
<div class="view-outer">
<div class="view">
<div class="view-title stroke-sub"></div>
<div class="view-content">
<div class="error-div"></div>
<form class="login-form">
<div class="username-hint"></div>
<input type="text" name="username" maxlength="20" required>
<div class="password-hint"></div>
<input type="password" name="password" required>
<div class="password2-div"></div>
<div class="remember-div">
<label class="remember-label">
<input type="checkbox" checked="checked" name="remember">
</label>
</div>
<div class="login-btn taibtn stroke-sub link-btn"></div>
</form>
</div>
<div class="left-buttons">
<div class="register-btn taibtn stroke-sub link-btn"></div>
</div>
<div class="view-end-button taibtn stroke-sub selected"></div>
</div>
</div>
bcrypt==3.1.7
ffmpy==0.2.2
Flask==1.1.1
Flask-Caching==1.8.0
Flask-Session==0.3.1
Flask-WTF==0.14.3
gunicorn==20.0.4
jsonschema==3.2.0
pymongo==3.10.1
redis==3.4.1
requests==2.23.0
websockets==7.0
import jsonschema
def validate(data, schema):
try:
jsonschema.validate(data, schema)
return True
except jsonschema.exceptions.ValidationError:
return False
register = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {
'username': {'type': 'string'},
'password': {'type': 'string'}
}
}
login = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {
'username': {'type': 'string'},
'password': {'type': 'string'},
'remember': {'type': 'boolean'}
}
}
update_display_name = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {
'display_name': {'type': 'string'}
}
}
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',
'properties': {
'current_password': {'type': 'string'},
'new_password': {'type': 'string'}
}
}
delete_account = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {
'password': {'type': 'string'}
}
}
scores_save = {
'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {
'scores': {
'type': 'array',
'items': {'$ref': '#/definitions/score'}
},
'is_import': {'type': 'boolean'}
},
'definitions': {
'score': {
'type': 'object',
'properties': {
'hash': {'type': 'string'},
'score': {'type': 'string'}
}
}
}
}
This diff is collapsed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Taiko Web Admin</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
<link href="/src/css/admin.css" rel="stylesheet">
</head>
<body>
<header>
<div class="nav">
<a href="/admin/songs">Songs</a>
</div>
</header>
<main>
<div class="container">
{% block content %}{% endblock %}
</div>
</main>
</body>
</html>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
@echo off @echo off
( (
git log -1 --pretty="format:{\"commit\": \"%%H\", \"commit_short\": \"%%h\", \"version\": \"%%ad\", \"url\": \"https://github.com/bui/taiko-web/\"}" --date="format:%%y.%%m.%%d" git log -1 --pretty="format:{\"commit\": \"%%H\", \"commit_short\": \"%%h\", \"version\": \"%%ad\"}" --date="format:%%y.%%m.%%d"
) > ../version.json ) > ../version.json
git log -1 --pretty="format:{\"commit\": \"%H\", \"commit_short\": \"%h\", \"version\": \"%ad\", \"url\": \"https://github.com/bui/taiko-web/\"}" --date="format:%y.%m.%d" > ../version.json #!/bin/bash
toplevel=$( git rev-parse --show-toplevel )
git log -1 --pretty="format:{\"commit\": \"%H\", \"commit_short\": \"%h\", \"version\": \"%ad\"}" --date="format:%y.%m.%d" > "$toplevel/version.json"
#!/bin/bash
./tools/get_version.sh
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment