refactor: remove tjaf dependency; add local TJA parser
This commit is contained in:
160
public/src/css/admin.css
Normal file
160
public/src/css/admin.css
Normal file
@@ -0,0 +1,160 @@
|
||||
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;
|
||||
}
|
||||
129
public/src/css/debug.css
Normal file
129
public/src/css/debug.css
Normal file
@@ -0,0 +1,129 @@
|
||||
#debug{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 260px;
|
||||
background: #fff;
|
||||
border: 1px solid #333;
|
||||
color: #000;
|
||||
z-index: 50;
|
||||
font-size: 14px;
|
||||
font-family: TnT, Meiryo, sans-serif;
|
||||
}
|
||||
|
||||
#debug .title{
|
||||
position: relative;
|
||||
height: 25px;
|
||||
padding: 5px 0 0 5px;
|
||||
box-sizing: border-box;
|
||||
background: #bbb;
|
||||
color: #fff;
|
||||
cursor: default;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
#debug .title::before{
|
||||
left: auto;
|
||||
-webkit-text-stroke: 0.25em #555;
|
||||
}
|
||||
|
||||
#debug .minimise{
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
background: #d77;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#debug .content{
|
||||
height: calc(100% - 25px);
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#debug .input-slider,
|
||||
#debug .select{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
margin: 5px 0 15px 0;
|
||||
}
|
||||
#debug .input-slider>input{
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 18px;
|
||||
font-family: monospace;
|
||||
padding: 2px 4px;
|
||||
text-align: center;
|
||||
}
|
||||
#debug .input-slider>span,
|
||||
#debug .select>span{
|
||||
display: block;
|
||||
width: 10%;
|
||||
height: 100%;
|
||||
opacity: 0.8;
|
||||
background: #666;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
#debug .input-slider>span:hover,
|
||||
#debug .select>span:hover{
|
||||
opacity: 1;
|
||||
background: #333;
|
||||
}
|
||||
#debug .select select{
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
#debug label{
|
||||
display: block;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
#debug input[type="checkbox"]{
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#debug .bottom-btns{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
#debug .bottom-btns div{
|
||||
width: calc(50% - 3px);
|
||||
height: 30px;
|
||||
opacity: 0.8;
|
||||
background: #666;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
#debug .bottom-btns div:hover{
|
||||
opacity: 1;
|
||||
background: #333;
|
||||
}
|
||||
#debug .restart-btn{
|
||||
display: none;
|
||||
margin-right: 3px;
|
||||
}
|
||||
#debug .exit-btn{
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
#debug .autoplay-label,
|
||||
#debug .branch-hide,
|
||||
#debug .lyrics-hide{
|
||||
display: none;
|
||||
}
|
||||
143
public/src/css/game.css
Normal file
143
public/src/css/game.css
Normal file
@@ -0,0 +1,143 @@
|
||||
#game{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
background-size: calc(100vh / 720 * 512);
|
||||
background-position: center;
|
||||
}
|
||||
#screen.view{
|
||||
background-image: none;
|
||||
background-color: #000;
|
||||
}
|
||||
#canvas{
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
#touch-drum{
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
#touch-drum-img{
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: top;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#touch-buttons{
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 8vh;
|
||||
right: 2vh;
|
||||
opacity: 0.5;
|
||||
z-index: 5;
|
||||
}
|
||||
#touch-buttons div{
|
||||
display: inline-block;
|
||||
width: 12.5vmin;
|
||||
height: 12.5vmin;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.portrait #touch-buttons{
|
||||
top: 11vh;
|
||||
}
|
||||
.touchp2 #touch-buttons{
|
||||
top: -1.9vh;
|
||||
}
|
||||
.touch-visible #touch-drum,
|
||||
.touch-visible #touch-buttons{
|
||||
display: block;
|
||||
}
|
||||
.touch-visible .window{
|
||||
width: 80vmin;
|
||||
height: 53vmin;
|
||||
}
|
||||
.touch-visible #pause-menu .window button{
|
||||
font-size: 5vmin;
|
||||
}
|
||||
.touch-visible #pause-menu .window button.selected{
|
||||
color: #000;
|
||||
background: #fff;
|
||||
border-color: #ae7a26;
|
||||
}
|
||||
.touch-results #touch-pause-btn{
|
||||
display: none;
|
||||
}
|
||||
#fade-screen{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
transition: 1s background-color linear;
|
||||
}
|
||||
.fix-animations *{
|
||||
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;
|
||||
}
|
||||
.pixelated #canvas,
|
||||
.pixelated .donbg>div,
|
||||
.pixelated #songbg>div,
|
||||
.pixelated #song-stage,
|
||||
.pixelated #touch-drum-img,
|
||||
.pixelated #flowers1-in,
|
||||
.pixelated #flowers2-in,
|
||||
.pixelated #mikoshi-in,
|
||||
.pixelated #tetsuo-in,
|
||||
.pixelated #hana-in{
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
136
public/src/css/loader.css
Normal file
136
public/src/css/loader.css
Normal file
@@ -0,0 +1,136 @@
|
||||
html,
|
||||
body{
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fe7839;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
#screen{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #000;
|
||||
background-position: center;
|
||||
background-size: 30vh;
|
||||
}
|
||||
#screen.pattern-bg{
|
||||
background-color: #fe7839;
|
||||
}
|
||||
#assets,
|
||||
#browse{
|
||||
display: none;
|
||||
}
|
||||
#loader{
|
||||
width:90%;
|
||||
height:10%;
|
||||
border:1px solid black;
|
||||
position: absolute;
|
||||
top:45%;
|
||||
left:5%;
|
||||
background: rgba(0,0,0,0.65);
|
||||
}
|
||||
|
||||
#loader .progress{
|
||||
width:0%;
|
||||
height: 100%;
|
||||
background: #b52a2a;
|
||||
opacity: 0.90;
|
||||
}
|
||||
|
||||
#loader .percentage{
|
||||
position:absolute;
|
||||
top:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
left:0;
|
||||
display:flex;
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
text-align:center;
|
||||
font-family: sans-serif;
|
||||
font-size: 5vmin;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#unsupportedBrowser{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
max-height: 100%;
|
||||
overflow: hidden auto;
|
||||
padding: 0.5em;
|
||||
background: #aef;
|
||||
font-family: sans-serif;
|
||||
font-size: 20px;
|
||||
cursor: default;
|
||||
z-index: 10;
|
||||
}
|
||||
#unsupportedWarn{
|
||||
display: inline-block;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
margin-right: 0.5em;
|
||||
background: #39a;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
#unsupportedBrowser.hidden{
|
||||
width: 1.5em;
|
||||
}
|
||||
#unsupportedBrowser.hidden *:not(#unsupportedWarn){
|
||||
display: none !important;
|
||||
}
|
||||
#unsupportedBrowser a{
|
||||
color: #02e;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
#unsupportedBrowser a:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
#unsupportedBrowser ul{
|
||||
margin: 0.25em;
|
||||
}
|
||||
#unsupportedDetails{
|
||||
display: none;
|
||||
margin: 0.5em 2.5em 0 2.5em;
|
||||
border: 0.15em solid #39a;
|
||||
padding: 0.25em;
|
||||
cursor: auto;
|
||||
user-select: text;
|
||||
}
|
||||
#unsupportedHide{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
text-align: center;
|
||||
line-height: 2.25em;
|
||||
color: #777;
|
||||
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;
|
||||
}
|
||||
40
public/src/css/loadsong.css
Normal file
40
public/src/css/loadsong.css
Normal file
@@ -0,0 +1,40 @@
|
||||
#load-song{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#loading-song{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20vmax;
|
||||
height: 15vmax;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
border-radius: 5px;
|
||||
border: 3px solid white;
|
||||
color: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
#loading-don{
|
||||
width: 10vmax;
|
||||
height: calc(10vmax / 120 * 115);
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.loading-text{
|
||||
position: relative;
|
||||
font-size: 1.5vmax;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
#p2-cancel-button{
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: -55px;
|
||||
}
|
||||
128
public/src/css/main.css
Normal file
128
public/src/css/main.css
Normal file
@@ -0,0 +1,128 @@
|
||||
.window{
|
||||
width: 60vmin;
|
||||
height: 23vmin;
|
||||
padding: 3vmin;
|
||||
color: black;
|
||||
background: rgba(255, 220, 47, 0.95);
|
||||
border: .5vmin outset #f4ae00;
|
||||
box-shadow: 2px 2px 10px black;
|
||||
margin: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.stroke-sub::before{
|
||||
content: attr(alt);
|
||||
position: absolute;
|
||||
-webkit-text-stroke: 0.25em #000;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
#session-invite{
|
||||
width: 100%;
|
||||
height: 1.9em;
|
||||
font-family: sans-serif;
|
||||
font-size: 2em;
|
||||
background: #fff;
|
||||
border: 1px solid #a9a9a9;
|
||||
padding: 0.3em;
|
||||
margin: 0.3em 0;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
user-select: all;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
}
|
||||
@keyframes bgscroll{
|
||||
from{
|
||||
background-position: 50% top;
|
||||
}
|
||||
to{
|
||||
background-position: calc(50% - 100vh / 720 * 512) top;
|
||||
}
|
||||
}
|
||||
#song-select{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: calc(100vh / 720 * 512);
|
||||
background-repeat: repeat no-repeat;
|
||||
animation: bgscroll 16s infinite linear;
|
||||
white-space: nowrap;
|
||||
transition: background-image 0.5s;
|
||||
}
|
||||
#song-select.unfocused{
|
||||
animation-play-state: paused;
|
||||
}
|
||||
#song-sel-canvas{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
#song-select #touch-full-btn{
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 12.5vmin;
|
||||
height: 12.5vmin;
|
||||
opacity: 0.5;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#song-sel-selectable{
|
||||
position: absolute;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
user-select: all;
|
||||
cursor: text;
|
||||
color: transparent;
|
||||
}
|
||||
#song-sel-selectable:focus{
|
||||
background: #ffdb2c;
|
||||
color: #000;
|
||||
}
|
||||
#song-sel-selectable .stroke-sub{
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
#song-sel-selectable .stroke-sub::before{
|
||||
-webkit-text-stroke: 0;
|
||||
}
|
||||
#song-sel-selectable:focus .stroke-sub::before{
|
||||
-webkit-text-stroke: 0.25em #fff;
|
||||
}
|
||||
|
||||
#version {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
font-size: 2vh;
|
||||
position: absolute;
|
||||
bottom: 1vh;
|
||||
right: 1vh;
|
||||
opacity: 0.7;
|
||||
font-family: TnT, Meiryo, sans-serif;
|
||||
pointer-events: none;
|
||||
}
|
||||
#version:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#version-link{
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
pointer-events: none;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.version-hide{
|
||||
pointer-events: none;
|
||||
}
|
||||
287
public/src/css/search.css
Normal file
287
public/src/css/search.css
Normal file
@@ -0,0 +1,287 @@
|
||||
#song-search-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
#song-search {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: min(100%, 60em);
|
||||
height: 80%;
|
||||
border-radius: 0.8em;
|
||||
border: 0.35em solid #8C0C42;
|
||||
color: #fff;
|
||||
padding: 1em 1em 0 1em;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
background-size: auto, 3.12em;
|
||||
background-position: 0%, -2%;
|
||||
}
|
||||
|
||||
#song-search-container.touch-enabled{
|
||||
font-size: calc(3 * var(--vmin, 1vmin));
|
||||
}
|
||||
@media (max-width: 950px){
|
||||
#song-search-container:not(.touch-enabled){
|
||||
font-size: calc(3 * var(--vmin, 1vmin));
|
||||
}
|
||||
}
|
||||
@media (max-height: 650px){
|
||||
#song-search-container:not(.touch-enabled){
|
||||
font-size: calc(2 * var(--vmin, 1vmin));
|
||||
}
|
||||
}
|
||||
|
||||
#song-search-input {
|
||||
width: 100%;
|
||||
font-size: 1.8em;
|
||||
padding: 0.5em 0.7em;
|
||||
border-radius: 0.2em;
|
||||
border: 0.13em black solid;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing:border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#song-search-input:focus {
|
||||
border-color: #fff923;
|
||||
}
|
||||
|
||||
#song-search-results {
|
||||
margin-top: 0.5em;
|
||||
overflow-y: scroll;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
#song-search-results::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.song-search-result {
|
||||
display: flex;
|
||||
height: 3.2em;
|
||||
margin: 0.2em;
|
||||
padding: 0.7em;
|
||||
flex-direction: row;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0.3em black solid;
|
||||
position: relative;
|
||||
--course-width: min(3em, calc(7 * var(--vmin, 1vmin)));
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 1px 3.2em;
|
||||
}
|
||||
|
||||
.song-search-result::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
border: 0.4em solid;
|
||||
}
|
||||
|
||||
.song-search-result:last-of-type {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.song-search-result-info {
|
||||
font-size: 1.2em;
|
||||
padding: 0.3em 0.3em 0.3em 0.5em;
|
||||
text-align: left;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
width: calc(100% - (var(--course-width) + 0.4em) * 5 - 0.6em);
|
||||
}
|
||||
|
||||
.song-search-result-info .highlighted-text {
|
||||
color: #faff00;
|
||||
}
|
||||
|
||||
.song-search-result-title,
|
||||
.song-search-result-subtitle {
|
||||
display: inline-block;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.song-search-result-subtitle {
|
||||
font-size: 0.8em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.song-search-result-title::before,
|
||||
.song-search-result-subtitle::before {
|
||||
content: attr(alt);
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
-webkit-text-stroke-width: 0.4em;
|
||||
}
|
||||
|
||||
.song-search-result-course {
|
||||
width: var(--course-width);
|
||||
height: 100%;
|
||||
margin: 0.2em;
|
||||
font-size: 1.2em;
|
||||
border-radius: 0.3em;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.song-search-result-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.song-search-result:hover {
|
||||
border-color: #fff923;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.song-search-result-active {
|
||||
border-color: #fff923;
|
||||
}
|
||||
|
||||
.song-search-result-course::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.5;
|
||||
z-index: -1;
|
||||
background-size: 4.8em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
.song-search-result-stars {
|
||||
bottom: 0;
|
||||
background: rgb(0 0 0 / 47%);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding: 0.1em 0;
|
||||
border-radius: 0 0 0.3em 0.3em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.song-search-result-easy {
|
||||
background-color: #D13215;
|
||||
}
|
||||
|
||||
.song-search-result-easy::before {
|
||||
background-position-x: center;
|
||||
background-position-y: -0.6em;
|
||||
}
|
||||
|
||||
.song-search-result-normal {
|
||||
background-color: #799C22;
|
||||
}
|
||||
|
||||
.song-search-result-normal::before {
|
||||
background-position-x: center;
|
||||
background-position-y: -5.1em;
|
||||
}
|
||||
|
||||
.song-search-result-hard {
|
||||
background-color: #31799B;
|
||||
}
|
||||
|
||||
.song-search-result-hard::before {
|
||||
background-position-x: center;
|
||||
background-position-y: -9.1em;
|
||||
}
|
||||
|
||||
.song-search-result-oni {
|
||||
background-color: #AF2C7F;
|
||||
}
|
||||
|
||||
.song-search-result-oni::before {
|
||||
background-position-x: center;
|
||||
background-position-y: -13.1em;
|
||||
}
|
||||
|
||||
.song-search-result-ura {
|
||||
background-color: #604AD5;
|
||||
}
|
||||
|
||||
.song-search-result-ura::before {
|
||||
background-position-x: center;
|
||||
background-position-y: -17.2em;
|
||||
}
|
||||
|
||||
.song-search-result-crown {
|
||||
background-size: 1.4em;
|
||||
background-position-x: center;
|
||||
background-repeat: repeat-y;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
width: 1.4em;
|
||||
height: 1.3em;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
.song-search-result-gold {
|
||||
background-position-y: 59%;
|
||||
}
|
||||
|
||||
.song-search-result-silver {
|
||||
background-position-y: 29%;
|
||||
}
|
||||
|
||||
.song-search-result-noclear {
|
||||
background-position-y: -1%;
|
||||
}
|
||||
|
||||
#song-search-tip {
|
||||
font-size: 1em;
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top;
|
||||
background-size: 10em;
|
||||
background-color: #00000087;
|
||||
border-radius: 0.5em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#song-search-close {
|
||||
position: absolute;
|
||||
right: -0.5em;
|
||||
top: -0.8em;
|
||||
font-size: 2em;
|
||||
font-family: TnT;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#song-search-close:hover::before{
|
||||
-webkit-text-stroke: 0.25em #fff923;
|
||||
}
|
||||
|
||||
.song-search-tip-error {
|
||||
height: 8em;
|
||||
}
|
||||
364
public/src/css/songbg.css
Normal file
364
public/src/css/songbg.css
Normal file
@@ -0,0 +1,364 @@
|
||||
#songbg,
|
||||
#songbg>*,
|
||||
.donbg,
|
||||
.donbg *,
|
||||
#song-stage{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-width: calc(100vh / 9 * 32);
|
||||
}
|
||||
#songbg{
|
||||
height: 50.1%;
|
||||
}
|
||||
#songbg>*{
|
||||
top: 0;
|
||||
}
|
||||
#songbg,
|
||||
#songbg>*{
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
bottom: 0;
|
||||
}
|
||||
#song-stage{
|
||||
height: calc(44 / 720 * 100vh);
|
||||
background-position: center bottom;
|
||||
background-repeat-y: no-repeat;
|
||||
background-size: auto 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
.portrait #songbg{
|
||||
height: 63.4%;
|
||||
max-height: calc(50% + 24vw);
|
||||
}
|
||||
.touchp2 #songbg{
|
||||
height: calc(50% - 5.9vw);
|
||||
min-height: 39.5%;
|
||||
}
|
||||
.multiplayer.portrait #songbg{
|
||||
height: calc(50% - 37vw);
|
||||
min-height: calc(29% + 1px);
|
||||
}
|
||||
.multiplayer:not(.touchp2):not(.portrait) #songbg,
|
||||
.multiplayer:not(.touchp2):not(.portrait) #song-stage{
|
||||
display: none;
|
||||
}
|
||||
.game-paused *{
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
@keyframes songbg-strobe{
|
||||
0%{opacity: 1}
|
||||
25%{opacity: 0}
|
||||
50%{opacity: 0.66}
|
||||
75%{opacity: 0}
|
||||
}
|
||||
@keyframes songbg-pulse{
|
||||
0%{opacity: 1}
|
||||
50%{opacity: 0}
|
||||
}
|
||||
.songbg-1 #layer2,
|
||||
.songbg-2 #layer2,
|
||||
.songbg-3 #layer2{
|
||||
animation: 0.4s linear songbg-strobe infinite;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
.songbg-4 #layer2{
|
||||
animation: 0.4s linear songbg-pulse infinite;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
.songbg-5 #layer2{
|
||||
animation: 1s linear songbg-pulse infinite;
|
||||
mix-blend-mode: color-dodge;
|
||||
}
|
||||
.songbg-strobe #layer2{
|
||||
animation: 0.4s linear songbg-strobe infinite;
|
||||
}
|
||||
.songbg-pulse #layer2{
|
||||
animation: 0.4s linear songbg-pulse infinite;
|
||||
}
|
||||
.songbg-slowfade #layer2{
|
||||
animation: 2s cubic-bezier(0.68, -0.55, 0.27, 1.55) songbg-pulse infinite;
|
||||
}
|
||||
.touch-visible #layer2{
|
||||
display: none;
|
||||
background-image: none;
|
||||
animation: none;
|
||||
}
|
||||
.donbg{
|
||||
top: 0;
|
||||
height: calc(50% - 13.7vw);
|
||||
min-height: 25.6%;
|
||||
}
|
||||
.multiplayer .donbg{
|
||||
min-height: 27.2%;
|
||||
}
|
||||
.portrait .donbg{
|
||||
height: calc(50% - 48.9vw);
|
||||
min-height: 22.5%;
|
||||
}
|
||||
.donbg *{
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-repeat-y: no-repeat;
|
||||
}
|
||||
.donbg.donbg-bottom{
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
.portrait .donbg.donbg-bottom {
|
||||
top: calc(50% + -1vw);
|
||||
bottom: auto;
|
||||
}
|
||||
@keyframes donbg-scroll{
|
||||
from{background-position-x: 0}
|
||||
to{background-position-x: calc(var(--h) / var(--sh1) * var(--sw) * -1)}
|
||||
}
|
||||
@keyframes donbg-scroll1{
|
||||
from{background-position-x: 0}
|
||||
to{background-position-x: calc(var(--h) / var(--sh1) * var(--sw1) * -1)}
|
||||
}
|
||||
@keyframes donbg-scroll2{
|
||||
from{background-position-x: 0}
|
||||
to{background-position-x: calc(var(--h) / var(--sh1) * var(--sw2) * -1)}
|
||||
}
|
||||
@keyframes donbg-raise{
|
||||
from{background-position-y: 0}
|
||||
to{background-position-y: var(--raised)}
|
||||
}
|
||||
@keyframes donbg-anim3{
|
||||
0%{background-position-y: 0}
|
||||
13%{background-position-y: var(--raised)}
|
||||
15%{background-position-y: var(--raised)}
|
||||
45%{background-position-y: 0}
|
||||
50%{background-position-y: 0}
|
||||
65%{background-position-y: calc(var(--raised) / 2)}
|
||||
80%{background-position-y: 0}
|
||||
}
|
||||
@keyframes donbg-anim5{
|
||||
0%{background-position-y: 0}
|
||||
13%{background-position-y: var(--raised)}
|
||||
17%{background-position-y: var(--raised)}
|
||||
30%{background-position-y: 0}
|
||||
}
|
||||
.donlayer1{
|
||||
animation: 5s linear donbg-scroll infinite;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
.donlayer2{
|
||||
background-size: auto calc(var(--sh2) / var(--sh1) * 100%);
|
||||
--raised: calc((var(--sh2) - var(--sh1)) / var(--sh2) * var(--h) * -1);
|
||||
}
|
||||
.donlayer3{
|
||||
background-color: #000;
|
||||
opacity: 0;
|
||||
transition: 0.15s opacity linear;
|
||||
}
|
||||
.donbg-dark .donlayer3{
|
||||
opacity: 0.5;
|
||||
}
|
||||
.donbg-1 .donlayer2,
|
||||
.donbg-2 .donlayer2,
|
||||
.donbg-4 .donlayer2,
|
||||
.donbg-6 .donlayer2,
|
||||
.donbg-raise .donlayer2{
|
||||
animation: 5s linear donbg-scroll infinite, 1s linear donbg-raise infinite alternate;
|
||||
}
|
||||
.donbg-3 .donlayer2,
|
||||
.donbg-anim3 .donlayer2{
|
||||
animation: 3.4s linear donbg-scroll infinite, 1.8s linear donbg-anim3 infinite;
|
||||
}
|
||||
.donbg-5 .donlayer2,
|
||||
.donbg-anim5 .donlayer2{
|
||||
animation: 2.7s linear donbg-scroll infinite, 2.2s linear donbg-anim5 infinite;
|
||||
}
|
||||
.donbg-equalscroll .donlayer1{
|
||||
animation: 5.3s linear donbg-scroll1 infinite;
|
||||
}
|
||||
.donbg-equalscroll .donlayer2{
|
||||
animation: 5.3s linear donbg-scroll2 infinite;
|
||||
}
|
||||
.donbg-fastscroll .donlayer1{
|
||||
animation: 2s linear donbg-scroll1 infinite;
|
||||
}
|
||||
.donbg-fastscroll .donlayer2{
|
||||
animation: 1s linear donbg-scroll2 infinite;
|
||||
}
|
||||
|
||||
#tetsuohana{
|
||||
position: absolute;
|
||||
right: calc(-12px * var(--scale));
|
||||
left: calc(-12px * var(--scale));
|
||||
margin: auto;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
top: calc(50% - 15px * var(--scale));
|
||||
width: calc(1304px * var(--scale));
|
||||
height: calc(375px * var(--scale));
|
||||
--frame: 0;
|
||||
--low: calc(36px * var(--scale));
|
||||
}
|
||||
#tetsuo,
|
||||
#hana{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: calc(292px * var(--scale));
|
||||
height: calc(425px * var(--scale));
|
||||
transform: translateY(calc(360px * var(--scale)));
|
||||
}
|
||||
#tetsuo-in,
|
||||
#hana-in{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(292px * var(--scale) * 2);
|
||||
background-position-y: calc(-425px * var(--frame) * var(--scale));
|
||||
}
|
||||
#tetsuo{
|
||||
left: calc(173px * var(--scale));
|
||||
}
|
||||
#hana{
|
||||
right: calc(178px * var(--scale));
|
||||
}
|
||||
#hana-in{
|
||||
background-position-x: calc(-292px * var(--scale));
|
||||
}
|
||||
#mikoshi{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(390px * var(--scale));
|
||||
width: calc(553px * var(--scale));
|
||||
height: calc(416px * var(--scale));
|
||||
transform: translateY(calc(461px * var(--scale)));
|
||||
}
|
||||
#mikoshi-in{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
#flowers1,
|
||||
#flowers2{
|
||||
position: absolute;
|
||||
top: calc(218px * var(--scale));
|
||||
width: calc(483px * var(--scale));
|
||||
height: calc(159px * var(--scale));
|
||||
transform: translateY(calc(243px * var(--scale))) scaleX(var(--flip));
|
||||
}
|
||||
#flowers1-in,
|
||||
#flowers2-in{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(483px * var(--scale));
|
||||
background-position-y: calc(-159px * var(--frame) * var(--scale));
|
||||
}
|
||||
#flowers1{
|
||||
left: 0;
|
||||
--flip: 1;
|
||||
}
|
||||
#flowers2{
|
||||
right: calc(4px * var(--scale));
|
||||
--flip: -1;
|
||||
}
|
||||
#tetsuohana.fadein,
|
||||
#tetsuohana.dance,
|
||||
#tetsuohana.dance2,
|
||||
#tetsuohana.failed{
|
||||
height: calc(461px * var(--scale));
|
||||
}
|
||||
#tetsuohana.fadein #tetsuo,
|
||||
#tetsuohana.fadein #hana{
|
||||
transition: 0.5s transform cubic-bezier(0.2, 0.6, 0.4, 1.2);
|
||||
transform: translateY(var(--low));
|
||||
}
|
||||
@keyframes tetsuohana-dance{
|
||||
0%{transform: translateY(var(--low))}
|
||||
50%{transform: translateY(0)}
|
||||
100%{transform: translateY(0)}
|
||||
}
|
||||
@keyframes tetsuohana-failed1{
|
||||
0%{transform: translateY(calc(10px * var(--scale)))}
|
||||
50%{transform: translateY(0)}
|
||||
100%{transform: translateY(0)}
|
||||
}
|
||||
@keyframes tetsuohana-failed2{
|
||||
0%{transform: translateY(0)}
|
||||
49%{transform: translateY(0)}
|
||||
50%{transform: translateY(calc(5px * var(--scale)))}
|
||||
100%{transform: translateY(calc(15px * var(--scale)))}
|
||||
}
|
||||
@keyframes tetsuohana-flowers{
|
||||
0%{background-position-y: 0}
|
||||
50%{background-position-y: calc(-159px * var(--scale))}
|
||||
100%{background-position-y: calc(-318px * var(--scale))}
|
||||
}
|
||||
@keyframes tetsuohana-mikoshi{
|
||||
0%{transform: translateY(calc(425px * var(--scale)))}
|
||||
100%{transform: translateY(0)}
|
||||
}
|
||||
#tetsuohana.dance #tetsuo,
|
||||
#tetsuohana.dance #hana,
|
||||
#tetsuohana.dance2 #tetsuo,
|
||||
#tetsuohana.dance2 #hana{
|
||||
--frame: 1;
|
||||
transform: translateY(var(--low));
|
||||
animation: 0.5s ease-out tetsuohana-dance infinite forwards;
|
||||
}
|
||||
#tetsuohana.dance #tetsuo-in,
|
||||
#tetsuohana.dance #hana-in,
|
||||
#tetsuohana.dance2 #tetsuo-in,
|
||||
#tetsuohana.dance2 #hana-in{
|
||||
transform: translateY(0);
|
||||
animation: 0.5s ease-out tetsuohana-dance infinite forwards reverse;
|
||||
}
|
||||
#tetsuohana.dance #flowers1,
|
||||
#tetsuohana.dance #flowers2{
|
||||
transform: translateY(0) scaleX(var(--flip));
|
||||
transition: 0.34s transform ease-out;
|
||||
}
|
||||
#tetsuohana.dance2 #flowers1,
|
||||
#tetsuohana.dance2 #flowers2{
|
||||
transform: translateY(0) scaleX(var(--flip));
|
||||
}
|
||||
#tetsuohana.dance #flowers1-in,
|
||||
#tetsuohana.dance #flowers2-in{
|
||||
animation: 0.25s 0.4s step-end tetsuohana-flowers both;
|
||||
}
|
||||
#tetsuohana.dance2 #flowers1-in,
|
||||
#tetsuohana.dance2 #flowers2-in{
|
||||
background-position-y: calc(-318px * var(--scale));
|
||||
}
|
||||
#tetsuohana.dance #mikoshi-out{
|
||||
animation: 0.4s 0.4s ease-out tetsuohana-mikoshi both;
|
||||
}
|
||||
#tetsuohana.dance #mikoshi{
|
||||
transform: translateY(var(--low));
|
||||
animation: 0.5s 0.8s ease-out tetsuohana-dance infinite forwards;
|
||||
}
|
||||
#tetsuohana.dance #mikoshi-in{
|
||||
transform: translateY(0);
|
||||
animation: 0.5s 0.8s ease-out tetsuohana-dance infinite forwards reverse;
|
||||
}
|
||||
#tetsuohana.dance2 #mikoshi{
|
||||
transform: translateY(var(--low));
|
||||
animation: 0.5s -0.2s ease-out tetsuohana-dance infinite forwards;
|
||||
}
|
||||
#tetsuohana.dance2 #mikoshi-in{
|
||||
transform: translateY(0);
|
||||
animation: 0.5s -0.2s ease-out tetsuohana-dance infinite forwards reverse;
|
||||
}
|
||||
#tetsuohana.failed #tetsuo,
|
||||
#tetsuohana.failed #hana{
|
||||
--frame: 2;
|
||||
top: calc(26px * var(--scale));
|
||||
transform: translateY(calc(46px * var(--scale)));
|
||||
animation: 1.25s ease-out tetsuohana-failed1 forwards infinite;
|
||||
}
|
||||
#tetsuohana.failed #tetsuo-in,
|
||||
#tetsuohana.failed #hana-in{
|
||||
transform: translateY(0);
|
||||
animation: 1.25s ease-in tetsuohana-failed2 forwards infinite;
|
||||
}
|
||||
57
public/src/css/titlescreen.css
Normal file
57
public/src/css/titlescreen.css
Normal file
@@ -0,0 +1,57 @@
|
||||
@keyframes toggleFade{
|
||||
40%{
|
||||
opacity: 1;
|
||||
}
|
||||
70%{
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
#title-screen{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #1389f0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
cursor: pointer;
|
||||
}
|
||||
#logo{
|
||||
width: 100vmin;
|
||||
height: calc(100vmin / 1170 * 390);
|
||||
}
|
||||
.click-to-continue{
|
||||
position: absolute;
|
||||
bottom: 15%;
|
||||
color: #fff;
|
||||
font-size: 8vmin;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
animation: toggleFade 2s infinite ease-in-out;
|
||||
}
|
||||
.click-to-continue::before{
|
||||
-webkit-text-stroke: 0.25em #f00;
|
||||
filter: blur(0.3vmin);
|
||||
}
|
||||
#title-disclaimer {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
color: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
#title-disclaimer span {
|
||||
color: #fff;
|
||||
font-size: 2vmin;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
#title-disclaimer span:before {
|
||||
left: initial;
|
||||
filter: blur(0.1vmin);
|
||||
}
|
||||
472
public/src/css/view.css
Normal file
472
public/src/css/view.css
Normal file
@@ -0,0 +1,472 @@
|
||||
.view-outer{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: center;
|
||||
}
|
||||
.view{
|
||||
background: rgb(246, 234, 212);
|
||||
color: black;
|
||||
border: 0.25em black solid;
|
||||
border-radius: 0.5em;
|
||||
width: 800px;
|
||||
max-width: 40em;
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
font-size: 21px;
|
||||
position: relative;
|
||||
}
|
||||
@media (max-width: 950px){
|
||||
.view-outer:not(.touch-enabled) .view{
|
||||
font-size: 3vmin;
|
||||
}
|
||||
}
|
||||
@media (max-height: 650px){
|
||||
.view-outer:not(.touch-enabled) .view{
|
||||
font-size: 3vmin;
|
||||
}
|
||||
}
|
||||
.touch-enabled .view{
|
||||
font-size: 3vmin;
|
||||
}
|
||||
.view-title{
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
color: white;
|
||||
top: -0.7em;
|
||||
font-size: 1.65em;
|
||||
}
|
||||
.view-content{
|
||||
margin: 0.7em 0;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 14em);
|
||||
}
|
||||
kbd{
|
||||
font-family: inherit;
|
||||
padding: 0.1em 0.6em;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 0.6em;
|
||||
background-color: #f7f7f7;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.key-join{
|
||||
font-size: 0.6em;
|
||||
}
|
||||
.taibtn{
|
||||
display: inline-block;
|
||||
background: #f6ead4;
|
||||
padding: 0.4em 0.4em;
|
||||
border-radius: 0.5em;
|
||||
border: 0.1em rgba(218, 205, 178, 1) solid;
|
||||
cursor: pointer;
|
||||
font-size: 1.4em;
|
||||
box-sizing: border-box;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
}
|
||||
.view-end-button{
|
||||
float: right;
|
||||
padding: 0.4em 1.5em;
|
||||
font-weight: bold;
|
||||
border-color: #000;
|
||||
color: #000;
|
||||
z-index: 1;
|
||||
}
|
||||
.taibtn:hover,
|
||||
.taibtn.selected,
|
||||
.view-end-button:hover,
|
||||
.view-end-button.selected{
|
||||
position: relative;
|
||||
color: #fff;
|
||||
background: #ffb547;
|
||||
border-color: #fff;
|
||||
}
|
||||
.taibtn::before,
|
||||
.view-end-button::before{
|
||||
display: none;
|
||||
}
|
||||
.taibtn:hover::before,
|
||||
.taibtn.selected::before,
|
||||
.view-end-button:hover::before,
|
||||
.view-end-button.selected::before{
|
||||
display: block
|
||||
}
|
||||
.taibtn::before{
|
||||
padding-left: inherit;
|
||||
}
|
||||
.left-buttons{
|
||||
float: left;
|
||||
display: flex;
|
||||
}
|
||||
.left-buttons .taibtn{
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
.center-buttons{
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
.account-view .center-buttons{
|
||||
margin: 0.3em 0;
|
||||
}
|
||||
.center-buttons>div{
|
||||
text-align: center;
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
.center-buttons .taibtn{
|
||||
margin: 0 0.2em;
|
||||
}
|
||||
.diag-txt textarea,
|
||||
.diag-txt iframe{
|
||||
width: 100%;
|
||||
height: 5em;
|
||||
font-size: inherit;
|
||||
resize: none;
|
||||
word-break: break-all;
|
||||
margin-bottom: 1em;
|
||||
background: #fff;
|
||||
border: 1px solid #a9a9a9;
|
||||
user-select: all;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.text-warn{
|
||||
color: #d00;
|
||||
}
|
||||
.link-btn a{
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.nowrap{
|
||||
white-space: nowrap;
|
||||
}
|
||||
@keyframes border-pulse{
|
||||
0%{border-color: #ff0}
|
||||
50%{border-color: rgba(255, 255, 0, 0)}
|
||||
100%{border-color: #ff0}
|
||||
}
|
||||
@keyframes border-pulse2{
|
||||
0%{border-color: #e29e06}
|
||||
50%{border-color: rgba(226, 158, 6, 0)}
|
||||
100%{border-color: #e29e06}
|
||||
}
|
||||
.settings-outer{
|
||||
background-size: 50vh;
|
||||
}
|
||||
.setting-box{
|
||||
display: flex;
|
||||
height: 2em;
|
||||
margin-top: 1.2em;
|
||||
border: 0.25em solid #000;
|
||||
border-radius: 0.5em;
|
||||
padding: 0.3em;
|
||||
outline: none;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
}
|
||||
.setting-box:first-child{
|
||||
margin-top: 0;
|
||||
}
|
||||
.view-content:not(:hover) .setting-box.selected,
|
||||
.setting-box:hover{
|
||||
background: #ffb547;
|
||||
animation: 2s linear border-pulse infinite;
|
||||
}
|
||||
.bold-fonts .setting-box{
|
||||
line-height: 1em;
|
||||
}
|
||||
.setting-name{
|
||||
position: relative;
|
||||
width: 50%;
|
||||
padding: 0.3em;
|
||||
font-size: 1.3em;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.view-content:not(:hover) .setting-box.selected .setting-name,
|
||||
.setting-box:hover .setting-name,
|
||||
.setting-box:hover #gamepad-value{
|
||||
color: #fff;
|
||||
z-index: 0;
|
||||
}
|
||||
.setting-name::before{
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
.setting-name::after{
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(90deg, rgba(246, 234, 212, 0), #f6ead4 90%);
|
||||
}
|
||||
.view-content:not(:hover) .setting-box.selected .setting-name::after,
|
||||
.setting-box:hover .setting-name::after{
|
||||
background-image: linear-gradient(90deg, rgba(255, 181, 71, 0), #ffb547 90%);
|
||||
}
|
||||
.setting-value{
|
||||
display: flex;
|
||||
background: #fff;
|
||||
width: 50%;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.5em;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.setting-value.selected{
|
||||
width: calc(50% + 0.2em);
|
||||
margin: -0.1em;
|
||||
border: 0.2em solid #e29e06;
|
||||
padding: 0.4em;
|
||||
animation: 2s linear border-pulse2 infinite;
|
||||
}
|
||||
.setting-value>div{
|
||||
padding: 0 0.4em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.shadow-outer{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
#settings-gamepad,
|
||||
#settings-latency,
|
||||
#customsongs-error{
|
||||
display: none;
|
||||
}
|
||||
#settings-gamepad .view{
|
||||
width: 29.9em;
|
||||
max-width: 100vw;
|
||||
}
|
||||
#settings-gamepad .setting-box{
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
#gamepad-bg,
|
||||
#gamepad-buttons{
|
||||
background-size: 20.53em;
|
||||
}
|
||||
#gamepad-bg{
|
||||
position: relative;
|
||||
width: 20.53em;
|
||||
height: 11.83em;
|
||||
max-height: none;
|
||||
background-repeat: no-repeat;
|
||||
text-align: center;
|
||||
font-size: 1.4em;
|
||||
cursor: pointer;
|
||||
}
|
||||
#gamepad-buttons{
|
||||
position: absolute;
|
||||
left: 5.26em;
|
||||
top: 4.48em;
|
||||
width: 10.52em;
|
||||
height: 4.89em;
|
||||
background-position: 0 -11.87em;
|
||||
background-repeat: no-repeat;
|
||||
pointer-events: none;
|
||||
}
|
||||
#gamepad-value{
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
}
|
||||
#gamepad-value::before{
|
||||
left: auto;
|
||||
}
|
||||
#settings-latency .view{
|
||||
width: 30em;
|
||||
}
|
||||
.setting-value{
|
||||
position: relative;
|
||||
}
|
||||
.setting-value:not(.selected) .latency-buttons{
|
||||
display: none;
|
||||
}
|
||||
.setting-value .latency-buttons{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.latency-buttons span{
|
||||
display: inline-block;
|
||||
width: 2em;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: #c3862a;
|
||||
color: #fff;
|
||||
line-height: 2em;
|
||||
outline: none;
|
||||
}
|
||||
.latency-buttons span:hover,
|
||||
.latency-buttons span:active{
|
||||
background-color: #946013;
|
||||
}
|
||||
.left-buttons .taibtn,
|
||||
.center-buttons .taibtn{
|
||||
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;
|
||||
cursor: pointer;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
#customsongs-error .view,
|
||||
#dropzone .view{
|
||||
width: 600px;
|
||||
}
|
||||
#dropzone{
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
#dropzone .view-content{
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
#dropzone.dragover{
|
||||
opacity: 1;
|
||||
}
|
||||
.plugin-browse-button{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#plugin-browse{
|
||||
position: absolute;
|
||||
font-size: inherit;
|
||||
top: -0.1em;
|
||||
left: -0.1em;
|
||||
right: -0.1em;
|
||||
bottom: -0.1em;
|
||||
border-radius: 0.5em;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
#plugin-browse::-webkit-file-upload-button{
|
||||
cursor: pointer;
|
||||
}
|
||||
253
public/src/js/about.js
Normal file
253
public/src/js/about.js
Normal file
@@ -0,0 +1,253 @@
|
||||
class About{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(touchEnabled){
|
||||
this.touchEnabled = touchEnabled
|
||||
loader.changePage("about", true)
|
||||
cancelTouch = false
|
||||
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
this.diagTxt = this.getElement("diag-txt")
|
||||
this.version = document.getElementById("version-link").href
|
||||
this.tutorialOuter = this.getElement("view-outer")
|
||||
if(touchEnabled){
|
||||
this.tutorialOuter.classList.add("touch-enabled")
|
||||
}
|
||||
this.linkIssues = document.getElementById("link-issues")
|
||||
this.linkEmail = document.getElementById("link-email")
|
||||
|
||||
var tutorialTitle = this.getElement("view-title")
|
||||
tutorialTitle.innerText = strings.aboutSimulator
|
||||
tutorialTitle.setAttribute("alt", strings.aboutSimulator)
|
||||
var tutorialContent = this.getElement("view-content")
|
||||
strings.about.bugReporting.forEach(string => {
|
||||
tutorialContent.appendChild(document.createTextNode(string))
|
||||
tutorialContent.appendChild(document.createElement("br"))
|
||||
})
|
||||
var span = document.createElement("span")
|
||||
span.classList.add("text-warn")
|
||||
span.innerText = strings.about.diagnosticWarning
|
||||
tutorialContent.appendChild(span)
|
||||
this.endButton.innerText = strings.tutorial.ok
|
||||
this.endButton.setAttribute("alt", strings.tutorial.ok)
|
||||
|
||||
this.items = []
|
||||
|
||||
this.getLink(this.linkIssues).innerText = strings.about.issues
|
||||
this.linkIssues.setAttribute("alt", strings.about.issues)
|
||||
var versionUrl = gameConfig._version.url
|
||||
this.getLink(this.linkIssues).href = versionUrl + "issues"
|
||||
this.items.push(this.linkIssues)
|
||||
|
||||
var contactEmail = gameConfig.email
|
||||
this.hasEmail = typeof contactEmail === "string"
|
||||
if(this.hasEmail){
|
||||
this.linkEmail.setAttribute("alt", contactEmail)
|
||||
this.getLink(this.linkEmail).href = "mailto:" + contactEmail
|
||||
this.getLink(this.linkEmail).innerText = contactEmail
|
||||
this.items.push(this.linkEmail)
|
||||
}else{
|
||||
this.linkEmail.parentNode.removeChild(this.linkEmail)
|
||||
}
|
||||
|
||||
pageEvents.add(this.linkIssues, ["click", "touchend"], this.linkButton.bind(this))
|
||||
if(this.hasEmail){
|
||||
pageEvents.add(this.linkEmail, ["click", "touchend"], this.linkButton.bind(this))
|
||||
}
|
||||
pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
|
||||
this.items.push(this.endButton)
|
||||
this.selected = this.items.length - 1
|
||||
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["enter", "space", "don_l", "don_r"],
|
||||
previous: ["left", "up", "ka_l"],
|
||||
next: ["right", "down", "ka_r"],
|
||||
back: ["escape"]
|
||||
}, this.keyPressed.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
"confirm": ["b", "ls", "rs"],
|
||||
"previous": ["u", "l", "lb", "lt", "lsu", "lsl"],
|
||||
"next": ["d", "r", "rb", "rt", "lsd", "lsr"],
|
||||
"back": ["start", "a"]
|
||||
}, this.keyPressed.bind(this))
|
||||
|
||||
pageEvents.send("about", this.addDiag())
|
||||
}
|
||||
getElement(name){
|
||||
return loader.screen.getElementsByClassName(name)[0]
|
||||
}
|
||||
keyPressed(pressed, name){
|
||||
if(!pressed){
|
||||
return
|
||||
}
|
||||
var selected = this.items[this.selected]
|
||||
if(name === "confirm"){
|
||||
if(selected === this.endButton){
|
||||
this.onEnd()
|
||||
}else{
|
||||
this.getLink(selected).click()
|
||||
pageEvents.send("about-link", selected)
|
||||
assets.sounds["se_don"].play()
|
||||
}
|
||||
}else if(name === "previous" || name === "next"){
|
||||
selected.classList.remove("selected")
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
this.items[this.selected].classList.add("selected")
|
||||
assets.sounds["se_ka"].play()
|
||||
}else if(name === "back"){
|
||||
this.onEnd()
|
||||
}
|
||||
}
|
||||
mod(length, index){
|
||||
return ((index % length) + length) % length
|
||||
}
|
||||
onEnd(event){
|
||||
var touched = false
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
touched = true
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
this.clean()
|
||||
assets.sounds["se_don"].play()
|
||||
localStorage.setItem("tutorial", "true")
|
||||
setTimeout(() => {
|
||||
new SongSelect("about", false, touched)
|
||||
}, 500)
|
||||
}
|
||||
addDiag(){
|
||||
var diag = []
|
||||
|
||||
diag.push("```")
|
||||
diag.push("Taiko-Web version: " + this.version)
|
||||
diag.push("URL: " + location.href)
|
||||
diag.push("User agent: " + navigator.userAgent)
|
||||
diag.push("Screen size: " + innerWidth + "x" + innerHeight + ", outer: " + outerWidth + "x" + outerHeight + ", ratio: " + (window.devicePixelRatio || 1).toFixed(2))
|
||||
if(this.touchEnabled){
|
||||
diag.push("Touch enabled: true")
|
||||
}
|
||||
if(!fullScreenSupported){
|
||||
diag.push("Full screen supported: false")
|
||||
}
|
||||
diag.push("Blur performance: " + perf.blur + "ms, all images: " + perf.allImg + "ms")
|
||||
diag.push("Page load: " + (perf.load / 1000).toFixed(1) + "s")
|
||||
if("getGamepads" in navigator){
|
||||
var gamepads = navigator.getGamepads()
|
||||
for(var i = 0; i < gamepads.length; i++){
|
||||
if(gamepads[i]){
|
||||
var gamepadDiag = []
|
||||
gamepadDiag.push(gamepads[i].id)
|
||||
gamepadDiag.push("buttons: " + gamepads[i].buttons.length)
|
||||
gamepadDiag.push("axes: " + gamepads[i].axes.length)
|
||||
diag.push("Gamepad #" + (i + 1) + ": " + gamepadDiag.join(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
var userLangStr = " (none)"
|
||||
if("languages" in navigator){
|
||||
var userLang = navigator.languages.slice()
|
||||
if(userLang[0] !== navigator.language){
|
||||
userLang.unshift(navigator.language)
|
||||
}
|
||||
if(userLang.length !== 0){
|
||||
userLangStr = " (" + userLang.join(", ") + ")"
|
||||
}
|
||||
}
|
||||
diag.push("Language: " + strings.id + userLangStr)
|
||||
var latency = settings.getItem("latency")
|
||||
diag.push("Audio Latency: " + (latency.audio > 0 ? "+" : "") + latency.audio.toString() + "ms, Video Latency: " + (latency.video > 0 ? "+" : "") + latency.video.toString() + "ms")
|
||||
var pluginList = plugins.allPlugins.map(pluginLoader => {
|
||||
return (pluginLoader.plugin.module && pluginLoader.plugin.module.name || pluginLoader.name) + (pluginLoader.plugin.started ? " (started)" : "")
|
||||
})
|
||||
diag.push("Plugins: " + pluginList.join(", "))
|
||||
var errorObj = {}
|
||||
if(localStorage["lastError"]){
|
||||
try{
|
||||
errorObj = JSON.parse(localStorage["lastError"])
|
||||
}catch(e){}
|
||||
}
|
||||
if(errorObj.timestamp && errorObj.stack){
|
||||
if(errorObj.timestamp + 1000 * 60 * 60 * 24 > Date.now()){
|
||||
diag.push("Last error: " + errorObj.stack)
|
||||
diag.push("Error date: " + new Date(errorObj.timestamp).toGMTString())
|
||||
}else{
|
||||
localStorage.removeItem("lastError")
|
||||
}
|
||||
}
|
||||
diag.push("```")
|
||||
var diag = diag.join("\n")
|
||||
|
||||
if(navigator.userAgent.indexOf("Android") >= 0){
|
||||
var iframe = document.createElement("iframe")
|
||||
this.diagTxt.appendChild(iframe)
|
||||
var body = iframe.contentWindow.document.body
|
||||
body.innerText = diag
|
||||
|
||||
body.setAttribute("style", `
|
||||
font-family: monospace;
|
||||
margin: 2px 0 0 2px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
cursor: text;
|
||||
`)
|
||||
body.setAttribute("onblur", `
|
||||
getSelection().removeAllRanges()
|
||||
`)
|
||||
}else{
|
||||
this.textarea = document.createElement("textarea")
|
||||
this.textarea.readOnly = true
|
||||
this.textarea.value = diag
|
||||
this.diagTxt.appendChild(this.textarea)
|
||||
if(!this.touchEnabled){
|
||||
pageEvents.add(this.textarea, "focus", () => {
|
||||
this.textarea.select()
|
||||
})
|
||||
pageEvents.add(this.textarea, "blur", () => {
|
||||
getSelection().removeAllRanges()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var issueBody = strings.about.issueTemplate + "\n\n\n\n" + diag
|
||||
if(this.hasEmail){
|
||||
this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "<br>\r\n"))
|
||||
}
|
||||
|
||||
return diag
|
||||
}
|
||||
getLink(target){
|
||||
return target.getElementsByTagName("a")[0]
|
||||
}
|
||||
linkButton(event){
|
||||
if(event.target === event.currentTarget){
|
||||
this.getLink(event.currentTarget).click()
|
||||
pageEvents.send("about-link", event.currentTarget)
|
||||
assets.sounds["se_don"].play()
|
||||
}
|
||||
}
|
||||
clean(){
|
||||
cancelTouch = true
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
pageEvents.remove(this.linkIssues, ["click", "touchend"])
|
||||
if(this.hasEmail){
|
||||
pageEvents.remove(this.linkEmail, ["click", "touchend"])
|
||||
}
|
||||
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
|
||||
if(this.textarea){
|
||||
pageEvents.remove(this.textarea, ["focus", "blur"])
|
||||
}
|
||||
pageEvents.keyRemove(this, "all")
|
||||
delete this.endButton
|
||||
delete this.diagTxt
|
||||
delete this.version
|
||||
delete this.tutorialOuter
|
||||
delete this.linkIssues
|
||||
delete this.linkEmail
|
||||
delete this.textarea
|
||||
}
|
||||
}
|
||||
151
public/src/js/abstractfile.js
Normal file
151
public/src/js/abstractfile.js
Normal file
@@ -0,0 +1,151 @@
|
||||
function readFile(file, arrayBuffer, encoding){
|
||||
var reader = new FileReader()
|
||||
var promise = pageEvents.load(reader).then(event => event.target.result)
|
||||
reader[arrayBuffer ? "readAsArrayBuffer" : "readAsText"](file, encoding)
|
||||
return promise
|
||||
}
|
||||
function filePermission(file){
|
||||
return file.queryPermission().then(response => {
|
||||
if(response === "granted"){
|
||||
return file
|
||||
}else{
|
||||
return file.requestPermission().then(response => {
|
||||
if(response === "granted"){
|
||||
return file
|
||||
}else{
|
||||
return Promise.reject(strings.accessNotGrantedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
class RemoteFile{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(url){
|
||||
this.url = url
|
||||
try{
|
||||
this.path = new URL(url).pathname
|
||||
}catch(e){
|
||||
this.path = url
|
||||
}
|
||||
if(this.path.startsWith("/")){
|
||||
this.path = this.path.slice(1)
|
||||
}
|
||||
if(this.url.startsWith("data:")){
|
||||
this.name = "datauri"
|
||||
if(this.url.startsWith("data:audio/ogg")){
|
||||
this.name += ".ogg"
|
||||
}
|
||||
}else{
|
||||
this.name = this.path
|
||||
var index = this.name.lastIndexOf("/")
|
||||
if(index !== -1){
|
||||
this.name = this.name.slice(index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
arrayBuffer(){
|
||||
return loader.ajax(this.url, request => {
|
||||
request.responseType = "arraybuffer"
|
||||
})
|
||||
}
|
||||
read(encoding){
|
||||
if(encoding){
|
||||
return this.blob().then(blob => readFile(blob, false, encoding))
|
||||
}else{
|
||||
return loader.ajax(this.url)
|
||||
}
|
||||
}
|
||||
blob(){
|
||||
return loader.ajax(this.url, request => {
|
||||
request.responseType = "blob"
|
||||
})
|
||||
}
|
||||
}
|
||||
class LocalFile{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(file, path){
|
||||
this.file = file
|
||||
this.path = path || file.webkitRelativePath
|
||||
this.url = this.path
|
||||
this.name = file.name
|
||||
}
|
||||
arrayBuffer(){
|
||||
return readFile(this.file, true)
|
||||
}
|
||||
read(encoding){
|
||||
return readFile(this.file, false, encoding)
|
||||
}
|
||||
blob(){
|
||||
return Promise.resolve(this.file)
|
||||
}
|
||||
}
|
||||
class FilesystemFile{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(file, path){
|
||||
this.file = file
|
||||
this.path = path
|
||||
this.url = this.path
|
||||
this.name = file.name
|
||||
}
|
||||
arrayBuffer(){
|
||||
return this.blob().then(blob => blob.arrayBuffer())
|
||||
}
|
||||
read(encoding){
|
||||
return this.blob().then(blob => readFile(blob, false, encoding))
|
||||
}
|
||||
blob(){
|
||||
return filePermission(this.file).then(file => file.getFile())
|
||||
}
|
||||
}
|
||||
class GdriveFile{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(fileObj){
|
||||
this.path = fileObj.path
|
||||
this.name = fileObj.name
|
||||
this.id = fileObj.id
|
||||
this.url = gpicker.filesUrl + this.id + "?alt=media"
|
||||
}
|
||||
arrayBuffer(){
|
||||
return gpicker.downloadFile(this.id, "arraybuffer")
|
||||
}
|
||||
read(encoding){
|
||||
if(encoding){
|
||||
return this.blob().then(blob => readFile(blob, false, encoding))
|
||||
}else{
|
||||
return gpicker.downloadFile(this.id)
|
||||
}
|
||||
}
|
||||
blob(){
|
||||
return gpicker.downloadFile(this.id, "blob")
|
||||
}
|
||||
}
|
||||
class CachedFile{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(contents, oldFile){
|
||||
this.contents = contents
|
||||
this.oldFile = oldFile
|
||||
this.path = oldFile.path
|
||||
this.name = oldFile.name
|
||||
this.url = oldFile.url
|
||||
}
|
||||
arrayBuffer(){
|
||||
return Promise.resolve(this.contents)
|
||||
}
|
||||
read(encoding){
|
||||
return this.arrayBuffer()
|
||||
}
|
||||
blob(){
|
||||
return this.arrayBuffer()
|
||||
}
|
||||
}
|
||||
682
public/src/js/account.js
Normal file
682
public/src/js/account.js
Normal file
@@ -0,0 +1,682 @@
|
||||
class Account{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(touchEnabled){
|
||||
this.touchEnabled = touchEnabled
|
||||
cancelTouch = false
|
||||
this.locked = false
|
||||
|
||||
if(account.loggedIn){
|
||||
this.accountForm()
|
||||
}else{
|
||||
this.loginForm()
|
||||
}
|
||||
this.selected = this.items.length - 1
|
||||
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["enter", "space", "don_l", "don_r"],
|
||||
previous: ["left", "up", "ka_l"],
|
||||
next: ["right", "down", "ka_r"],
|
||||
back: ["escape"]
|
||||
}, this.keyPressed.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
"confirm": ["b", "ls", "rs"],
|
||||
"previous": ["u", "l", "lb", "lt", "lsu", "lsl"],
|
||||
"next": ["d", "r", "rb", "rt", "lsd", "lsr"],
|
||||
"back": ["start", "a"]
|
||||
}, this.keyPressed.bind(this))
|
||||
|
||||
pageEvents.send("account", account.loggedIn)
|
||||
}
|
||||
accountForm(){
|
||||
loader.changePage("account", true)
|
||||
this.mode = "account"
|
||||
|
||||
this.setAltText(this.getElement("view-title"), account.username)
|
||||
this.items = []
|
||||
this.inputForms = []
|
||||
this.shownDiv = ""
|
||||
|
||||
this.errorDiv = this.getElement("error-div")
|
||||
this.getElement("displayname-hint").innerText = strings.account.displayName
|
||||
this.displayname = this.getElement("displayname")
|
||||
this.displayname.placeholder = strings.account.displayName
|
||||
this.displayname.value = account.displayName
|
||||
this.inputForms.push(this.displayname)
|
||||
|
||||
this.redrawRunning = true
|
||||
this.redrawPaused = matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
this.redrawForce = true
|
||||
this.customdonRedrawBind = this.customdonRedraw.bind(this)
|
||||
this.start = new Date().getTime()
|
||||
this.frames = [
|
||||
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
|
||||
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
|
||||
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,7 ,8 ,9 ,10,
|
||||
11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17
|
||||
]
|
||||
this.customdonCache = new CanvasCache()
|
||||
this.customdonCache.resize(723 * 2, 1858, 1)
|
||||
this.customdonCanvas = this.getElement("customdon-canvas")
|
||||
pageEvents.add(this.customdonCanvas, "click", this.customdonPause.bind(this))
|
||||
this.customdonCtx = this.customdonCanvas.getContext("2d")
|
||||
this.customdonBodyFill = this.getElement("customdon-bodyfill")
|
||||
this.customdonBodyFill.value = account.don.body_fill
|
||||
var parent = this.customdonBodyFill.parentNode
|
||||
parent.insertBefore(document.createTextNode(strings.account.customdon.bodyFill), parent.firstChild)
|
||||
pageEvents.add(this.customdonBodyFill, ["change", "input"], this.customdonChange.bind(this))
|
||||
this.customdonFaceFill = this.getElement("customdon-facefill")
|
||||
this.customdonFaceFill.value = account.don.face_fill
|
||||
var parent = this.customdonFaceFill.parentNode
|
||||
parent.insertBefore(document.createTextNode(strings.account.customdon.faceFill), parent.firstChild)
|
||||
pageEvents.add(this.customdonFaceFill, ["change", "input"], this.customdonChange.bind(this))
|
||||
this.customdonResetBtn = this.getElement("customdon-reset")
|
||||
this.customdonResetBtn.value = strings.account.customdon.reset
|
||||
pageEvents.add(this.customdonResetBtn, ["click", "touchstart"], this.customdonReset.bind(this))
|
||||
this.customdonChange()
|
||||
this.customdonRedraw()
|
||||
|
||||
this.accountPassButton = this.getElement("accountpass-btn")
|
||||
this.setAltText(this.accountPassButton, strings.account.changePassword)
|
||||
pageEvents.add(this.accountPassButton, ["click", "touchstart"], event => {
|
||||
this.showDiv(event, "pass")
|
||||
})
|
||||
this.accountPass = this.getElement("accountpass-form")
|
||||
for(var i = 0; i < this.accountPass.length; i++){
|
||||
this.accountPass[i].placeholder = strings.account.currentNewRepeat[i]
|
||||
this.inputForms.push(this.accountPass[i])
|
||||
}
|
||||
this.accountPassDiv = this.getElement("accountpass-div")
|
||||
|
||||
this.accountDelButton = this.getElement("accountdel-btn")
|
||||
this.setAltText(this.accountDelButton, strings.account.deleteAccount)
|
||||
pageEvents.add(this.accountDelButton, ["click", "touchstart"], event => {
|
||||
this.showDiv(event, "del")
|
||||
})
|
||||
this.accountDel = this.getElement("accountdel-form")
|
||||
this.accountDel.password.placeholder = strings.account.verifyPassword
|
||||
this.inputForms.push(this.accountDel.password)
|
||||
this.accountDelDiv = this.getElement("accountdel-div")
|
||||
|
||||
this.linkPrivacy = this.getElement("privacy-btn")
|
||||
this.setAltText(this.linkPrivacy, strings.account.privacy)
|
||||
pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this))
|
||||
this.items.push(this.linkPrivacy)
|
||||
|
||||
this.logoutButton = this.getElement("logout-btn")
|
||||
this.setAltText(this.logoutButton, strings.account.logout)
|
||||
pageEvents.add(this.logoutButton, ["mousedown", "touchstart"], this.onLogout.bind(this))
|
||||
this.items.push(this.logoutButton)
|
||||
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
this.setAltText(this.endButton, strings.account.cancel)
|
||||
pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
|
||||
this.items.push(this.endButton)
|
||||
|
||||
this.saveButton = this.getElement("save-btn")
|
||||
this.setAltText(this.saveButton, strings.account.save)
|
||||
pageEvents.add(this.saveButton, ["mousedown", "touchstart"], this.onSave.bind(this))
|
||||
this.items.push(this.saveButton)
|
||||
|
||||
for(var i = 0; i < this.inputForms.length; i++){
|
||||
pageEvents.add(this.inputForms[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this))
|
||||
}
|
||||
}
|
||||
customdonPause(){
|
||||
this.redrawPaused = !this.redrawPaused
|
||||
this.redrawForce = true
|
||||
this.start = new Date().getTime()
|
||||
}
|
||||
customdonChange(){
|
||||
var ctx = this.customdonCtx
|
||||
this.customdonCache.clear()
|
||||
var w = 722
|
||||
var h = 1858
|
||||
this.customdonCache.set({
|
||||
w: w, h: h, id: "bodyFill"
|
||||
}, ctx => {
|
||||
ctx.drawImage(assets.image["don_anim_normal_b1"], 0, 0)
|
||||
ctx.globalCompositeOperation = "source-atop"
|
||||
ctx.fillStyle = this.customdonBodyFill.value
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
})
|
||||
this.customdonCache.set({
|
||||
w: w, h: h, id: "faceFill"
|
||||
}, ctx => {
|
||||
ctx.drawImage(assets.image["don_anim_normal_b2"], 0, 0)
|
||||
ctx.globalCompositeOperation = "source-atop"
|
||||
ctx.fillStyle = this.customdonFaceFill.value
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
|
||||
ctx.globalCompositeOperation = "source-over"
|
||||
this.customdonCache.get({
|
||||
ctx: ctx,
|
||||
x: 0, y: 0, w: w, h: h,
|
||||
id: "bodyFill"
|
||||
})
|
||||
})
|
||||
this.redrawForce = true
|
||||
}
|
||||
customdonReset(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}
|
||||
this.customdonBodyFill.value = defaultDon.body_fill
|
||||
this.customdonFaceFill.value = defaultDon.face_fill
|
||||
this.customdonChange()
|
||||
}
|
||||
customdonRedraw(){
|
||||
if(!this.redrawRunning){
|
||||
return
|
||||
}
|
||||
requestAnimationFrame(this.customdonRedrawBind)
|
||||
if(!document.hasFocus() || this.redrawPaused && !this.redrawForce){
|
||||
return
|
||||
}
|
||||
var ms = new Date().getTime()
|
||||
var ctx = this.customdonCtx
|
||||
if(this.redrawPaused){
|
||||
var frame = 0
|
||||
}else{
|
||||
var frame = this.frames[Math.floor((ms - this.start) / 30) % this.frames.length]
|
||||
}
|
||||
var w = 360
|
||||
var h = 184
|
||||
var sx = Math.floor(frame / 10) * (w + 2)
|
||||
var sy = (frame % 10) * (h + 2)
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
this.customdonCache.get({
|
||||
ctx: ctx,
|
||||
sx: sx, sy: sy, sw: w, sh: h,
|
||||
x: -26, y: 0, w: w, h: h,
|
||||
id: "faceFill"
|
||||
})
|
||||
ctx.drawImage(assets.image["don_anim_normal_a"],
|
||||
sx, sy, w, h,
|
||||
-26, 0, w, h
|
||||
)
|
||||
this.redrawForce = false
|
||||
}
|
||||
showDiv(event, div){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
var otherDiv = this.shownDiv && this.shownDiv !== div
|
||||
var display = this.shownDiv === div ? "" : "block"
|
||||
this.shownDiv = display ? div : ""
|
||||
switch(div){
|
||||
case "pass":
|
||||
if(otherDiv){
|
||||
this.accountDelDiv.style.display = ""
|
||||
}
|
||||
this.accountPassDiv.style.display = display
|
||||
break
|
||||
case "del":
|
||||
if(otherDiv){
|
||||
this.accountPassDiv.style.display = ""
|
||||
}
|
||||
this.accountDelDiv.style.display = display
|
||||
break
|
||||
}
|
||||
}
|
||||
loginForm(register, fromSwitch){
|
||||
loader.changePage("login", true)
|
||||
this.mode = register ? "register" : "login"
|
||||
|
||||
this.setAltText(this.getElement("view-title"), strings.account[this.mode])
|
||||
|
||||
this.errorDiv = this.getElement("error-div")
|
||||
this.items = []
|
||||
this.form = this.getElement("login-form")
|
||||
this.getElement("username-hint").innerText = strings.account.username
|
||||
this.form.username.placeholder = strings.account.enterUsername
|
||||
this.getElement("password-hint").innerText = strings.account.password
|
||||
this.form.password.placeholder = strings.account.enterPassword
|
||||
this.password2 = this.getElement("password2-div")
|
||||
this.remember = this.getElement("remember-div")
|
||||
this.getElement("remember-label").appendChild(document.createTextNode(strings.account.remember))
|
||||
this.loginButton = this.getElement("login-btn")
|
||||
this.registerButton = this.getElement("register-btn")
|
||||
|
||||
if(register){
|
||||
var pass2 = document.createElement("input")
|
||||
pass2.type = "password"
|
||||
pass2.name = "password2"
|
||||
pass2.required = true
|
||||
pass2.placeholder = strings.account.repeatPassword
|
||||
this.password2.appendChild(pass2)
|
||||
this.password2.style.display = "block"
|
||||
this.remember.style.display = "none"
|
||||
this.setAltText(this.loginButton, strings.account.registerAccount)
|
||||
this.setAltText(this.registerButton, strings.account.login)
|
||||
}else{
|
||||
this.setAltText(this.loginButton, strings.account.login)
|
||||
this.setAltText(this.registerButton, strings.account.register)
|
||||
}
|
||||
|
||||
pageEvents.add(this.form, "submit", this.onLogin.bind(this))
|
||||
pageEvents.add(this.loginButton, ["mousedown", "touchstart"], this.onLogin.bind(this))
|
||||
|
||||
pageEvents.add(this.registerButton, ["mousedown", "touchstart"], this.onSwitchMode.bind(this))
|
||||
this.items.push(this.registerButton)
|
||||
|
||||
this.linkPrivacy = this.getElement("privacy-btn")
|
||||
this.setAltText(this.linkPrivacy, strings.account.privacy)
|
||||
pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this))
|
||||
this.items.push(this.linkPrivacy)
|
||||
|
||||
if(!register){
|
||||
this.items.push(this.loginButton)
|
||||
}
|
||||
|
||||
for(var i = 0; i < this.form.length; i++){
|
||||
pageEvents.add(this.form[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this))
|
||||
}
|
||||
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
this.setAltText(this.endButton, strings.account.back)
|
||||
pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
|
||||
this.items.push(this.endButton)
|
||||
if(fromSwitch){
|
||||
this.selected = 0
|
||||
this.endButton.classList.remove("selected")
|
||||
this.registerButton.classList.add("selected")
|
||||
}
|
||||
}
|
||||
getElement(name){
|
||||
return loader.screen.getElementsByClassName(name)[0]
|
||||
}
|
||||
setAltText(element, text){
|
||||
element.innerText = text
|
||||
element.setAttribute("alt", text)
|
||||
}
|
||||
keyPressed(pressed, name){
|
||||
if(!pressed || this.locked){
|
||||
return
|
||||
}
|
||||
var selected = this.items[this.selected]
|
||||
if(name === "confirm"){
|
||||
if(selected === this.endButton){
|
||||
this.onEnd()
|
||||
}else if(selected === this.registerButton){
|
||||
this.onSwitchMode()
|
||||
}else if(selected === this.loginButton){
|
||||
this.onLogin()
|
||||
}else if(selected === this.linkPrivacy){
|
||||
assets.sounds["se_don"].play()
|
||||
this.openPrivacy()
|
||||
}
|
||||
}else if(name === "previous" || name === "next"){
|
||||
selected.classList.remove("selected")
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
this.items[this.selected].classList.add("selected")
|
||||
if(this.items[this.selected] === this.linkPrivacy){
|
||||
this.items[this.selected].scrollIntoView()
|
||||
}
|
||||
assets.sounds["se_ka"].play()
|
||||
}else if(name === "back"){
|
||||
this.onEnd()
|
||||
}
|
||||
}
|
||||
mod(length, index){
|
||||
return ((index % length) + length) % length
|
||||
}
|
||||
onFormPress(event){
|
||||
event.stopPropagation()
|
||||
if(event.type === "keypress" && event.keyCode === 13){
|
||||
event.preventDefault()
|
||||
if(this.mode === "account"){
|
||||
this.onSave()
|
||||
}else{
|
||||
this.onLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
onSwitchMode(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
this.clean(true)
|
||||
this.loginForm(this.mode === "login", true)
|
||||
}
|
||||
onLogin(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
var obj = {
|
||||
username: this.form.username.value,
|
||||
password: this.form.password.value
|
||||
}
|
||||
if(!obj.username || !obj.password){
|
||||
this.error(strings.account.cannotBeEmpty.replace("%s", strings.account[!obj.username ? "username" : "password"]))
|
||||
return
|
||||
}
|
||||
if(this.mode === "login"){
|
||||
obj.remember = this.form.remember.checked
|
||||
}else{
|
||||
if(obj.password !== this.form.password2.value){
|
||||
this.error(strings.account.passwordsDoNotMatch)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.request(this.mode, obj).then(response => {
|
||||
account.loggedIn = true
|
||||
account.username = response.username
|
||||
account.displayName = response.display_name
|
||||
account.don = response.don
|
||||
var loadScores = scores => {
|
||||
scoreStorage.load(scores)
|
||||
this.onEnd(false, true, true)
|
||||
pageEvents.send("login", account.username)
|
||||
}
|
||||
if(this.mode === "login"){
|
||||
this.request("scores/get", false, true).then(response => {
|
||||
loadScores(response.scores)
|
||||
}, () => {
|
||||
loadScores({})
|
||||
})
|
||||
}else{
|
||||
scoreStorage.save().catch(() => {}).finally(() => {
|
||||
this.onEnd(false, true, true)
|
||||
pageEvents.send("login", account.username)
|
||||
})
|
||||
}
|
||||
}, response => {
|
||||
if(response && response.status === "error" && response.message){
|
||||
if(response.message in strings.serverError){
|
||||
this.error(strings.serverError[response.message])
|
||||
}else{
|
||||
this.error(response.message)
|
||||
}
|
||||
}else{
|
||||
this.error(strings.account.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
openPrivacy(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
open("privacy")
|
||||
}
|
||||
onLogout(){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
account.loggedIn = false
|
||||
delete account.username
|
||||
delete account.displayName
|
||||
delete account.don
|
||||
var loadScores = () => {
|
||||
scoreStorage.load()
|
||||
this.onEnd(false, true)
|
||||
pageEvents.send("logout")
|
||||
}
|
||||
this.request("logout").then(loadScores, loadScores)
|
||||
}
|
||||
onSave(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
this.clearError()
|
||||
var promises = []
|
||||
var noNameChange = false
|
||||
if(this.shownDiv === "pass"){
|
||||
var passwords = []
|
||||
for(var i = 0; i < this.accountPass.length; i++){
|
||||
passwords.push(this.accountPass[i].value)
|
||||
}
|
||||
if(passwords[1] === passwords[2]){
|
||||
promises.push(this.request("account/password", {
|
||||
current_password: passwords[0],
|
||||
new_password: passwords[1]
|
||||
}))
|
||||
}else{
|
||||
this.error(strings.account.newPasswordsDoNotMatch)
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.shownDiv === "del" && this.accountDel.password.value){
|
||||
noNameChange = true
|
||||
promises.push(this.request("account/remove", {
|
||||
password: this.accountDel.password.value
|
||||
}).then(() => {
|
||||
account.loggedIn = false
|
||||
delete account.username
|
||||
delete account.displayName
|
||||
delete account.don
|
||||
scoreStorage.load()
|
||||
pageEvents.send("logout")
|
||||
return Promise.resolve
|
||||
}))
|
||||
}
|
||||
var newName = this.displayname.value.trim()
|
||||
if(!noNameChange && newName !== account.displayName){
|
||||
promises.push(this.request("account/display_name", {
|
||||
display_name: newName
|
||||
}).then(response => {
|
||||
account.displayName = response.display_name
|
||||
}))
|
||||
}
|
||||
var bodyFill = this.customdonBodyFill.value
|
||||
var faceFill = this.customdonFaceFill.value
|
||||
if(!noNameChange && (bodyFill !== account.body_fill || this.customdonFaceFill.value !== account.face_fill)){
|
||||
promises.push(this.request("account/don", {
|
||||
body_fill: bodyFill,
|
||||
face_fill: faceFill
|
||||
}).then(response => {
|
||||
account.don = response.don
|
||||
}))
|
||||
}
|
||||
var error = false
|
||||
var errorFunc = response => {
|
||||
if(error){
|
||||
return
|
||||
}
|
||||
if(response && response.message){
|
||||
if(response.message in strings.serverError){
|
||||
this.error(strings.serverError[response.message])
|
||||
}else{
|
||||
this.error(response.message)
|
||||
}
|
||||
}else{
|
||||
this.error(strings.account.error)
|
||||
}
|
||||
}
|
||||
Promise.all(promises).then(() => {
|
||||
this.onEnd(false, true)
|
||||
}, errorFunc).catch(errorFunc)
|
||||
}
|
||||
onEnd(event, noSound, noReset){
|
||||
var touched = false
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
touched = true
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
this.clean(false, noReset)
|
||||
assets.sounds["se_don"].play()
|
||||
setTimeout(() => {
|
||||
new SongSelect(false, false, touched)
|
||||
}, 500)
|
||||
}
|
||||
request(url, obj, get){
|
||||
this.lock(true)
|
||||
var doRequest = token => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = new XMLHttpRequest()
|
||||
request.open(get ? "GET" : "POST", "api/" + url)
|
||||
pageEvents.load(request).then(() => {
|
||||
this.lock(false)
|
||||
if(request.status !== 200){
|
||||
reject()
|
||||
return
|
||||
}
|
||||
try{
|
||||
var json = JSON.parse(request.response)
|
||||
}catch(e){
|
||||
reject()
|
||||
return
|
||||
}
|
||||
if(json.status === "ok"){
|
||||
resolve(json)
|
||||
}else{
|
||||
reject(json)
|
||||
}
|
||||
}, () => {
|
||||
this.lock(false)
|
||||
reject()
|
||||
})
|
||||
if(!get){
|
||||
request.setRequestHeader("X-CSRFToken", token)
|
||||
}
|
||||
if(obj){
|
||||
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
|
||||
request.send(JSON.stringify(obj))
|
||||
}else{
|
||||
request.send()
|
||||
}
|
||||
})
|
||||
}
|
||||
if(get){
|
||||
return doRequest()
|
||||
}else{
|
||||
return loader.getCsrfToken().then(doRequest)
|
||||
}
|
||||
}
|
||||
lock(isLocked){
|
||||
this.locked = isLocked
|
||||
if(this.mode === "login" || this.mode === "register"){
|
||||
for(var i = 0; i < this.form.length; i++){
|
||||
this.form[i].disabled = isLocked
|
||||
}
|
||||
}else if(this.mode === "account"){
|
||||
for(var i = 0; i < this.inputForms.length; i++){
|
||||
this.inputForms[i].disabled = isLocked
|
||||
}
|
||||
}
|
||||
}
|
||||
error(text){
|
||||
this.errorDiv.innerText = text
|
||||
this.errorDiv.style.display = "block"
|
||||
}
|
||||
clearError(){
|
||||
this.errorDiv.innerText = ""
|
||||
this.errorDiv.style.display = ""
|
||||
}
|
||||
clean(eventsOnly, noReset){
|
||||
if(!eventsOnly){
|
||||
cancelTouch = true
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
}
|
||||
if(this.mode === "account"){
|
||||
if(!noReset){
|
||||
this.accountPass.reset()
|
||||
this.accountDel.reset()
|
||||
}
|
||||
this.redrawRunning = false
|
||||
this.customdonCache.clean()
|
||||
pageEvents.remove(this.customdonCanvas, "click")
|
||||
pageEvents.remove(this.customdonBodyFill, ["change", "input"])
|
||||
pageEvents.remove(this.customdonFaceFill, ["change", "input"])
|
||||
pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"])
|
||||
pageEvents.remove(this.accounPassButton, ["click", "touchstart"])
|
||||
pageEvents.remove(this.accountDelButton, ["click", "touchstart"])
|
||||
pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.saveButton, ["mousedown", "touchstart"])
|
||||
for(var i = 0; i < this.inputForms.length; i++){
|
||||
pageEvents.remove(this.inputForms[i], ["keydown", "keyup", "keypress"])
|
||||
}
|
||||
delete this.errorDiv
|
||||
delete this.displayname
|
||||
delete this.frames
|
||||
delete this.customdonCanvas
|
||||
delete this.customdonCtx
|
||||
delete this.customdonBodyFill
|
||||
delete this.customdonFaceFill
|
||||
delete this.customdonResetBtn
|
||||
delete this.accountPassButton
|
||||
delete this.accountPass
|
||||
delete this.accountPassDiv
|
||||
delete this.accountDelButton
|
||||
delete this.accountDel
|
||||
delete this.accountDelDiv
|
||||
delete this.linkPrivacy
|
||||
delete this.logoutButton
|
||||
delete this.saveButton
|
||||
delete this.inputForms
|
||||
}else if(this.mode === "login" || this.mode === "register"){
|
||||
if(!eventsOnly && !noReset){
|
||||
this.form.reset()
|
||||
}
|
||||
pageEvents.remove(this.form, "submit")
|
||||
pageEvents.remove(this.loginButton, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.registerButton, ["mousedown", "touchstart"])
|
||||
for(var i = 0; i < this.form.length; i++){
|
||||
pageEvents.remove(this.registerButton, ["keydown", "keyup", "keypress"])
|
||||
}
|
||||
pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"])
|
||||
delete this.errorDiv
|
||||
delete this.form
|
||||
delete this.password2
|
||||
delete this.remember
|
||||
delete this.loginButton
|
||||
delete this.registerButton
|
||||
delete this.linkPrivacy
|
||||
}
|
||||
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
|
||||
delete this.endButton
|
||||
delete this.items
|
||||
}
|
||||
}
|
||||
164
public/src/js/assets.js
Normal file
164
public/src/js/assets.js
Normal file
@@ -0,0 +1,164 @@
|
||||
var assets = {
|
||||
"js": [
|
||||
"lib/md5.min.js",
|
||||
"lib/fuzzysort.js",
|
||||
"loadsong.js",
|
||||
"parseosu.js",
|
||||
"titlescreen.js",
|
||||
"scoresheet.js",
|
||||
"songselect.js",
|
||||
"keyboard.js",
|
||||
"gameinput.js",
|
||||
"game.js",
|
||||
"controller.js",
|
||||
"circle.js",
|
||||
"view.js",
|
||||
"mekadon.js",
|
||||
"gamepad.js",
|
||||
"tutorial.js",
|
||||
"soundbuffer.js",
|
||||
"p2.js",
|
||||
"canvasasset.js",
|
||||
"viewassets.js",
|
||||
"gamerules.js",
|
||||
"canvasdraw.js",
|
||||
"canvastest.js",
|
||||
"canvascache.js",
|
||||
"parsetja.js",
|
||||
"autoscore.js",
|
||||
"about.js",
|
||||
"debug.js",
|
||||
"session.js",
|
||||
"importsongs.js",
|
||||
"logo.js",
|
||||
"settings.js",
|
||||
"scorestorage.js",
|
||||
"account.js",
|
||||
"lyrics.js",
|
||||
"customsongs.js",
|
||||
"abstractfile.js",
|
||||
"idb.js",
|
||||
"plugins.js",
|
||||
"search.js"
|
||||
],
|
||||
"css": [
|
||||
"main.css",
|
||||
"titlescreen.css",
|
||||
"loadsong.css",
|
||||
"game.css",
|
||||
"debug.css",
|
||||
"songbg.css",
|
||||
"view.css",
|
||||
"search.css"
|
||||
],
|
||||
"img": [
|
||||
"notes.png",
|
||||
"notes_drumroll.png",
|
||||
"notes_hit.png",
|
||||
"notes_explosion.png",
|
||||
"balloon.png",
|
||||
"taiko.png",
|
||||
"don_anim_normal_a.png",
|
||||
"don_anim_normal_b1.png",
|
||||
"don_anim_normal_b2.png",
|
||||
"don_anim_10combo_a.png",
|
||||
"don_anim_10combo_b1.png",
|
||||
"don_anim_10combo_b2.png",
|
||||
"don_anim_gogo_a.png",
|
||||
"don_anim_gogo_b1.png",
|
||||
"don_anim_gogo_b2.png",
|
||||
"don_anim_gogostart_a.png",
|
||||
"don_anim_gogostart_b1.png",
|
||||
"don_anim_gogostart_b2.png",
|
||||
"don_anim_clear_a.png",
|
||||
"don_anim_clear_b1.png",
|
||||
"don_anim_clear_b2.png",
|
||||
"fire_anim.png",
|
||||
"fireworks_anim.png",
|
||||
"bg_score_p1.png",
|
||||
"bg_score_p2.png",
|
||||
"bg_pause.png",
|
||||
"badge_auto.png",
|
||||
"mimizu.png"
|
||||
],
|
||||
"cssBackground": {
|
||||
"#title-screen": "title-screen.png",
|
||||
"#loading-don": "dancing-don.gif",
|
||||
".pattern-bg": "bg-pattern-1.png",
|
||||
".song-search-result-course::before": "difficulty.png",
|
||||
"#song-select": "bg_genre_def.png",
|
||||
".settings-outer": "bg_settings.png",
|
||||
"#touch-pause-btn": "touch_pause.png",
|
||||
"#touch-full-btn": "touch_fullscreen.png",
|
||||
"#gamepad-bg, #gamepad-buttons": "settings_gamepad.png",
|
||||
".song-search-result-crown": "crown.png",
|
||||
".song-search-tip-error": "miss.png",
|
||||
"#song-search": "bg_search.png"
|
||||
},
|
||||
"audioSfx": [
|
||||
"se_pause.ogg",
|
||||
"se_calibration.ogg",
|
||||
|
||||
"v_results.ogg",
|
||||
"v_sanka.ogg",
|
||||
"v_songsel.ogg",
|
||||
"v_start.ogg",
|
||||
"v_title.ogg"
|
||||
],
|
||||
"audioSfxLR": [
|
||||
"neiro_1_don.ogg",
|
||||
"neiro_1_ka.ogg",
|
||||
"se_cancel.ogg",
|
||||
"se_don.ogg",
|
||||
"se_ka.ogg",
|
||||
"se_jump.ogg",
|
||||
|
||||
"se_balloon.ogg",
|
||||
"se_gameclear.ogg",
|
||||
"se_gamefail.ogg",
|
||||
"se_gamefullcombo.ogg",
|
||||
"se_results_countup.ogg",
|
||||
"se_results_crown.ogg",
|
||||
|
||||
"v_fullcombo.ogg",
|
||||
"v_renda.ogg",
|
||||
"v_results_fullcombo.ogg",
|
||||
"v_results_fullcombo2.ogg"
|
||||
],
|
||||
"audioSfxLoud": [
|
||||
"v_diffsel.ogg"
|
||||
],
|
||||
"audioMusic": [
|
||||
"bgm_songsel.mp3",
|
||||
"bgm_result.mp3",
|
||||
"bgm_setsume.mp3",
|
||||
"bgm_settings.mp3"
|
||||
],
|
||||
"fonts": {
|
||||
"Kozuka": "Kozuka.otf",
|
||||
"TnT": "TnT.ttf"
|
||||
},
|
||||
"views": [
|
||||
"game.html",
|
||||
"loadsong.html",
|
||||
"songselect.html",
|
||||
"titlescreen.html",
|
||||
"tutorial.html",
|
||||
"about.html",
|
||||
"debug.html",
|
||||
"session.html",
|
||||
"settings.html",
|
||||
"account.html",
|
||||
"login.html",
|
||||
"customsongs.html",
|
||||
"search.html"
|
||||
],
|
||||
|
||||
"songs": [],
|
||||
"sounds": {},
|
||||
"image": {},
|
||||
"pages": {},
|
||||
"categories": []
|
||||
}
|
||||
|
||||
var gameConfig = {}
|
||||
212
public/src/js/autoscore.js
Normal file
212
public/src/js/autoscore.js
Normal file
@@ -0,0 +1,212 @@
|
||||
class AutoScore {
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(difficulty, level, scoremode, circles) {
|
||||
this.scoremode = scoremode;
|
||||
this.circles = circles;
|
||||
this.basic_max_score_list = {
|
||||
oni: [
|
||||
1200000,
|
||||
700000,
|
||||
750000,
|
||||
800000,
|
||||
850000,
|
||||
900000,
|
||||
950000,
|
||||
1000000,
|
||||
1050000,
|
||||
1100000,
|
||||
1200000
|
||||
],
|
||||
ura: [
|
||||
1200000,
|
||||
700000,
|
||||
750000,
|
||||
800000,
|
||||
850000,
|
||||
900000,
|
||||
950000,
|
||||
1000000,
|
||||
1050000,
|
||||
1100000,
|
||||
1200000
|
||||
],
|
||||
hard: [
|
||||
900000,
|
||||
550000,
|
||||
600000,
|
||||
650000,
|
||||
700000,
|
||||
750000,
|
||||
800000,
|
||||
850000,
|
||||
900000,
|
||||
],
|
||||
normal: [
|
||||
700000,
|
||||
400000,
|
||||
450000,
|
||||
500000,
|
||||
550000,
|
||||
600000,
|
||||
650000,
|
||||
700000,
|
||||
],
|
||||
easy: [
|
||||
380000,
|
||||
300000,
|
||||
320000,
|
||||
340000,
|
||||
360000,
|
||||
380000,
|
||||
]
|
||||
}
|
||||
if (this.GetMaxCombo() === 0) {
|
||||
this.ScoreDiff = 100;
|
||||
this.ScoreInit = 450;
|
||||
return;
|
||||
}
|
||||
const target = this.GetTargetScore(difficulty, level);
|
||||
this.Score = 0;
|
||||
this.ScoreDiff = 0;
|
||||
this.ScoreInit = 0;
|
||||
var max_init = this.GetMaxPossibleInit(target);
|
||||
var min_init = 0;
|
||||
while (true) {
|
||||
this.ScoreInit = (max_init + min_init) / 2;
|
||||
this.ScoreDiff = Math.round(this.ScoreInit / 4);
|
||||
this.Score = this.TryScore(this.ScoreInit, this.ScoreDiff);
|
||||
//console.log(min_init, max_init, this.ScoreInit, this.ScoreDiff, this.Score);
|
||||
if (this.ScoreInit === target) {
|
||||
this.ScoreInit = Math.floor(this.ScoreInit / 10) * 10;
|
||||
this.ScoreDiff = Math.round(this.ScoreInit / 4);
|
||||
this.Score = this.TryScore(this.ScoreInit, this.ScoreDiff);
|
||||
break;
|
||||
} else if (this.Score >= target) {
|
||||
max_init = this.ScoreInit;
|
||||
} else {
|
||||
min_init = this.ScoreInit;
|
||||
}
|
||||
if (max_init - min_init <= 10) {
|
||||
this.ScoreInit = Math.floor(this.ScoreInit / 10) * 10;
|
||||
this.ScoreDiff = Math.round(this.ScoreInit / 4);
|
||||
this.Score = this.TryScore(this.ScoreInit, this.ScoreDiff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (this.Score < target) {
|
||||
this.ScoreInit += 10;
|
||||
this.ScoreDiff = Math.round(this.ScoreInit / 4);
|
||||
this.Score = this.TryScore(this.ScoreInit, this.ScoreDiff);
|
||||
//console.log(this.ScoreInit, this.ScoreDiff, this.Score);
|
||||
}
|
||||
//console.log(this.ScoreInit, this.ScoreDiff, this.Score);
|
||||
}
|
||||
IsCommonCircle(circle) {
|
||||
const ty = circle.type;
|
||||
return ty === "don" || ty === "ka" || ty === "daiDon" || ty === "daiKa";
|
||||
}
|
||||
TryScore(init, diff) {
|
||||
var score = 0;
|
||||
var combo = 0;
|
||||
for (var circle of this.circles) {
|
||||
if (circle.branch && circle.branch.name !== "master") {
|
||||
continue;
|
||||
}
|
||||
if (this.IsCommonCircle(circle)) {
|
||||
combo++;
|
||||
if (combo % 100 === 0 && this.scoremode !== 1) {
|
||||
score += 10000;
|
||||
}
|
||||
}
|
||||
var diff_mul = 0;
|
||||
var multiplier = circle.gogoTime ? 1.2 : 1;
|
||||
if (this.scoremode === 1) {
|
||||
diff_mul = Math.max(0, Math.floor((Math.min(combo, 100) - 1) / 10));
|
||||
} else {
|
||||
if (combo >= 100) {
|
||||
diff_mul = 8;
|
||||
} else if (combo >= 50) {
|
||||
diff_mul = 4;
|
||||
} else if (combo >= 30) {
|
||||
diff_mul = 2;
|
||||
} else if (combo >= 10) {
|
||||
diff_mul = 1;
|
||||
}
|
||||
}
|
||||
switch (circle.type) {
|
||||
case "don":
|
||||
case "ka": {
|
||||
score += Math.floor((init + diff * diff_mul) * multiplier / 10) * 10;
|
||||
break;
|
||||
}
|
||||
case "daiDon":
|
||||
case "daiKa": {
|
||||
score += Math.floor((init + diff * diff_mul) * multiplier / 5) * 10;
|
||||
break;
|
||||
}
|
||||
case "balloon": {
|
||||
score += (5000 + 300 * circle.requiredHits) * multiplier;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
GetTargetScore(difficulty, level) {
|
||||
//console.log(difficulty, level)
|
||||
var ret = this.basic_max_score_list[difficulty][level];
|
||||
if (!ret) {
|
||||
ret = this.basic_max_score_list[difficulty][0];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
GetMaxCombo() {
|
||||
var combo = 0;
|
||||
for (var circle of this.circles) {
|
||||
if (this.IsCommonCircle(circle) && (!circle.branch || circle.branch.name === "master")) {
|
||||
combo++;
|
||||
}
|
||||
}
|
||||
return combo;
|
||||
}
|
||||
GetMaxPossibleInit(target) {
|
||||
var basic_score = 0;
|
||||
if (this.scoremode !== 1) {
|
||||
const max_combo = this.GetMaxCombo();
|
||||
basic_score += Math.floor(max_combo / 100);
|
||||
}
|
||||
var combo = 0;
|
||||
for (var circle of this.circles) {
|
||||
if (circle.branch && circle.branch.name !== "master") {
|
||||
continue;
|
||||
}
|
||||
var multiplier = circle.gogoTime ? 1.2 : 1;
|
||||
switch (circle.type) {
|
||||
case "don":
|
||||
case "ka": {
|
||||
combo += (1 * multiplier);
|
||||
break;
|
||||
}
|
||||
case "daiDon":
|
||||
case "daiKa": {
|
||||
combo += (2 * multiplier);
|
||||
break;
|
||||
}
|
||||
case "balloon": {
|
||||
basic_score += (5000 + 300 * circle.requiredHits) * multiplier;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
combo = Math.floor(combo);
|
||||
return Math.ceil((target - basic_score) / combo / 10) * 10;
|
||||
}
|
||||
}
|
||||
203
public/src/js/browsersupport.js
Normal file
203
public/src/js/browsersupport.js
Normal file
@@ -0,0 +1,203 @@
|
||||
function browserSupport(){
|
||||
var tests = {
|
||||
"Arrow function": function(){
|
||||
eval("()=>{}")
|
||||
return true
|
||||
},
|
||||
"AudioContext": function(){
|
||||
if("AudioContext" in window || "webkitAudioContext" in window){
|
||||
return typeof (window.AudioContext || window.webkitAudioContext) === "function"
|
||||
}
|
||||
return false
|
||||
},
|
||||
"Class": function(){
|
||||
eval("class a{}")
|
||||
return true
|
||||
},
|
||||
"Class field declarations": function(){
|
||||
eval("class a{a=1}")
|
||||
return true
|
||||
},
|
||||
"Array.find": function(){
|
||||
return "find" in Array.prototype && "findIndex" in Array.prototype
|
||||
},
|
||||
"Path2D SVG": function(){
|
||||
var canvas = document.createElement("canvas")
|
||||
canvas.width = 1
|
||||
canvas.height = 1
|
||||
var ctx = canvas.getContext("2d")
|
||||
var path = new Path2D("M0 0H1V1H0")
|
||||
ctx.fill(path)
|
||||
return ctx.getImageData(0, 0, 1, 1).data[3] !== 0
|
||||
},
|
||||
"Promise": function(){
|
||||
if("Promise" in window && "resolve" in window.Promise && "reject" in window.Promise && "all" in window.Promise && "race" in window.Promise){
|
||||
var resolve
|
||||
new window.Promise(function(r){resolve = r})
|
||||
return typeof resolve === "function"
|
||||
}
|
||||
return false
|
||||
},
|
||||
"CSS calc": function(){
|
||||
var el = document.createElement("a")
|
||||
el.style.width = "calc(1px)"
|
||||
return el.style.length !== 0
|
||||
},
|
||||
"let statement": function(){
|
||||
eval("let a")
|
||||
return true
|
||||
},
|
||||
"CSS custom property": function(){
|
||||
var el = document.createElement("a")
|
||||
el.style.setProperty("--a", 1)
|
||||
return el.style.length !== 0
|
||||
},
|
||||
"Font Loading API": function(){
|
||||
return typeof FontFace === "function"
|
||||
},
|
||||
"OGG or WebAssembly": function(){
|
||||
return new Audio().canPlayType("audio/ogg;codecs=vorbis") || "WebAssembly" in window
|
||||
},
|
||||
"KeyboardEvent.key": function(){
|
||||
return "key" in KeyboardEvent.prototype
|
||||
},
|
||||
"Module import": function(){
|
||||
eval("import('data:text/javascript,')")
|
||||
return true
|
||||
}
|
||||
}
|
||||
failedTests = []
|
||||
for(var name in tests){
|
||||
var result = false
|
||||
try{
|
||||
result = tests[name]()
|
||||
}catch(e){}
|
||||
if(result === false){
|
||||
failedTests.push(name)
|
||||
}
|
||||
}
|
||||
if(failedTests.length !== 0){
|
||||
showUnsupported()
|
||||
}
|
||||
}
|
||||
function showUnsupported(strings){
|
||||
if(!strings){
|
||||
var lang
|
||||
try{
|
||||
if("localStorage" in window && window.localStorage.lang && window.localStorage.lang in allStrings){
|
||||
lang = window.localStorage.lang
|
||||
}
|
||||
if(!lang && "languages" in navigator){
|
||||
var userLang = navigator.languages.slice()
|
||||
userLang.unshift(navigator.language)
|
||||
for(var i in userLang){
|
||||
for(var j in allStrings){
|
||||
if(allStrings[j].regex.test(userLang[i])){
|
||||
lang = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(e){}
|
||||
if(!lang){
|
||||
lang = "en"
|
||||
}
|
||||
strings = allStrings[lang]
|
||||
}
|
||||
|
||||
var div = document.getElementById("unsupportedBrowser")
|
||||
if(div){
|
||||
div.parentNode.removeChild(div)
|
||||
}
|
||||
div = document.createElement("div")
|
||||
div.id = "unsupportedBrowser"
|
||||
|
||||
var warn = document.createElement("div")
|
||||
warn.id = "unsupportedWarn"
|
||||
warn.innerText = "!"
|
||||
warn.textContent = "!"
|
||||
div.appendChild(warn)
|
||||
var hide = document.createElement("div")
|
||||
hide.id = "unsupportedHide"
|
||||
hide.innerText = "x"
|
||||
hide.textContent = "x"
|
||||
div.appendChild(hide)
|
||||
|
||||
var span = document.createElement("span")
|
||||
var browserWarning = strings.browserSupport.browserWarning.split("%s")
|
||||
for(var i = 0; i < browserWarning.length; i++){
|
||||
if(i !== 0){
|
||||
var link = document.createElement("a")
|
||||
link.innerText = strings.browserSupport.details
|
||||
link.textContent = strings.browserSupport.details
|
||||
span.appendChild(link)
|
||||
}
|
||||
span.appendChild(document.createTextNode(browserWarning[i]))
|
||||
}
|
||||
div.appendChild(span)
|
||||
|
||||
var details = document.createElement("div")
|
||||
details.id = "unsupportedDetails"
|
||||
details.appendChild(document.createTextNode(strings.browserSupport.failedTests))
|
||||
|
||||
var ul = document.createElement("ul")
|
||||
for(var i = 0; i < failedTests.length; i++){
|
||||
var li = document.createElement("li")
|
||||
li.innerText = failedTests[i]
|
||||
li.textContent = failedTests[i]
|
||||
ul.appendChild(li)
|
||||
}
|
||||
details.appendChild(ul)
|
||||
|
||||
var supportedBrowser = strings.browserSupport.supportedBrowser.split("%s")
|
||||
for(var i = 0; i < supportedBrowser.length; i++){
|
||||
if(i !== 0){
|
||||
var chrome = document.createElement("a")
|
||||
chrome.href = "https://www.google.com/chrome/"
|
||||
chrome.innerText = "Google Chrome"
|
||||
chrome.textContent = "Google Chrome"
|
||||
details.appendChild(chrome)
|
||||
}
|
||||
details.appendChild(document.createTextNode(supportedBrowser[i]))
|
||||
}
|
||||
|
||||
div.appendChild(details)
|
||||
|
||||
document.body.appendChild(div)
|
||||
var divClick = function(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
getSelection().removeAllRanges()
|
||||
}
|
||||
div.classList.remove("hidden")
|
||||
}
|
||||
div.addEventListener("click", divClick)
|
||||
div.addEventListener("touchstart", divClick)
|
||||
var toggleDetails = function(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}
|
||||
if(details.style.display === "block"){
|
||||
details.style.display = ""
|
||||
}else{
|
||||
details.style.display = "block"
|
||||
}
|
||||
}
|
||||
link.addEventListener("click", toggleDetails)
|
||||
link.addEventListener("touchstart", toggleDetails)
|
||||
var hideClick = function(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}
|
||||
event.stopPropagation()
|
||||
div.classList.add("hidden")
|
||||
}
|
||||
hide.addEventListener("click", hideClick)
|
||||
hide.addEventListener("touchstart", hideClick)
|
||||
chrome.addEventListener("touchend", function(event){
|
||||
event.preventDefault()
|
||||
chrome.click()
|
||||
})
|
||||
}
|
||||
var failedTests
|
||||
browserSupport()
|
||||
151
public/src/js/canvasasset.js
Normal file
151
public/src/js/canvasasset.js
Normal file
@@ -0,0 +1,151 @@
|
||||
class CanvasAsset{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(view, layer, position){
|
||||
this.ctx = view.ctx
|
||||
this.view = view
|
||||
this.position = position
|
||||
this.animationFrames = {}
|
||||
this.speed = 1000 / 60
|
||||
this.animationStart = 0
|
||||
this.layer = layer
|
||||
this.beatInterval = 468.75
|
||||
}
|
||||
draw(){
|
||||
if(this.animation){
|
||||
var u = (a, b) => typeof a === "undefined" ? b : a
|
||||
var frame = 0
|
||||
var ms = this.view.getMS()
|
||||
var beatInterval = this.frameSpeed ? 1000 / 60 : this.beatInterval
|
||||
|
||||
if(this.animationEnd){
|
||||
if(ms > this.animationStart + this.animationEnd.frameCount * this.speed * beatInterval){
|
||||
this.animationEnd.callback()
|
||||
this.animationEnd = false
|
||||
return this.draw()
|
||||
}
|
||||
}
|
||||
var index = Math.floor((ms - this.animationStart) / (this.speed * beatInterval))
|
||||
if(Array.isArray(this.animation)){
|
||||
frame = this.animation[this.mod(this.animation.length, index)]
|
||||
}else{
|
||||
frame = this.mod(this.animation, index)
|
||||
}
|
||||
this.ctx.save()
|
||||
var pos = this.position(frame)
|
||||
if(this.image){
|
||||
this.ctx.drawImage(this.image,
|
||||
u(pos.sx, pos.x), u(pos.sy, pos.y),
|
||||
u(pos.sw, pos.w), u(pos.sh, pos.h),
|
||||
pos.x, pos.y, pos.w, pos.h
|
||||
)
|
||||
}
|
||||
this.ctx.restore()
|
||||
}
|
||||
}
|
||||
mod(length, index){
|
||||
return ((index % length) + length) % length
|
||||
}
|
||||
addFrames(name, frames, image, don){
|
||||
var framesObj = {
|
||||
frames: frames,
|
||||
don: don
|
||||
}
|
||||
if(don){
|
||||
var img = assets.image[image + "_a"]
|
||||
var cache1 = new CanvasCache()
|
||||
var cache2 = new CanvasCache()
|
||||
var w = img.width
|
||||
var h = img.height
|
||||
cache1.resize(w, h, 1)
|
||||
cache2.resize(w, h, 1)
|
||||
cache1.set({
|
||||
w: w, h: h, id: "1"
|
||||
}, ctx => {
|
||||
ctx.drawImage(assets.image[image + "_b1"], 0, 0)
|
||||
ctx.globalCompositeOperation = "source-atop"
|
||||
ctx.fillStyle = don.body_fill
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
})
|
||||
cache2.set({
|
||||
w: w, h: h, id: "2"
|
||||
}, ctx => {
|
||||
ctx.drawImage(assets.image[image + "_b2"], 0, 0)
|
||||
ctx.globalCompositeOperation = "source-atop"
|
||||
ctx.fillStyle = don.face_fill
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
|
||||
ctx.globalCompositeOperation = "source-over"
|
||||
cache1.get({
|
||||
ctx: ctx,
|
||||
x: 0, y: 0, w: w, h: h,
|
||||
id: "1"
|
||||
})
|
||||
ctx.drawImage(img, 0, 0)
|
||||
})
|
||||
cache1.clean()
|
||||
framesObj.cache = cache2
|
||||
framesObj.image = cache2.canvas
|
||||
}else if(image){
|
||||
framesObj.image = assets.image[image]
|
||||
}
|
||||
this.animationFrames[name] = framesObj
|
||||
}
|
||||
setAnimation(name){
|
||||
var framesObj = this.animationFrames[name]
|
||||
this.animationName = name
|
||||
if(framesObj){
|
||||
this.animation = framesObj.frames
|
||||
if(framesObj.image){
|
||||
this.image = framesObj.image
|
||||
}
|
||||
this.don = framesObj.don
|
||||
}else{
|
||||
this.animation = false
|
||||
}
|
||||
}
|
||||
getAnimation(){
|
||||
return this.animationName
|
||||
}
|
||||
getAnimationLength(name){
|
||||
var frames = this.animationFrames[name].frames
|
||||
if(Array.isArray(frames)){
|
||||
return frames.length
|
||||
}else{
|
||||
return frames
|
||||
}
|
||||
}
|
||||
setUpdateSpeed(speed, frameSpeed){
|
||||
this.speed = speed
|
||||
this.frameSpeed = frameSpeed
|
||||
}
|
||||
setAnimationStart(ms){
|
||||
this.animationStart = ms
|
||||
}
|
||||
setAnimationEnd(frameCount, callback){
|
||||
if(typeof frameCount === "undefined"){
|
||||
this.animationEnd = false
|
||||
}else{
|
||||
this.animationEnd = {
|
||||
frameCount: frameCount,
|
||||
callback: callback
|
||||
}
|
||||
}
|
||||
}
|
||||
changeBeatInterval(beatMS, initial){
|
||||
if(!initial && !this.frameSpeed){
|
||||
var ms = this.view.getMS()
|
||||
this.animationStart = ms - (ms - this.animationStart) / this.beatInterval * beatMS
|
||||
}
|
||||
this.beatInterval = beatMS
|
||||
}
|
||||
clean(){
|
||||
for(var i in this.animationFrames){
|
||||
var frame = this.animationFrames[i]
|
||||
if(frame.cache){
|
||||
frame.cache.clean()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
129
public/src/js/canvascache.js
Normal file
129
public/src/js/canvascache.js
Normal file
@@ -0,0 +1,129 @@
|
||||
class CanvasCache{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(noSmoothing, w, h, scale){
|
||||
this.noSmoothing = noSmoothing
|
||||
if(w){
|
||||
this.resize(w, h, scale)
|
||||
}
|
||||
this.index = Number.MIN_SAFE_INTEGER
|
||||
}
|
||||
resize(w, h, scale){
|
||||
if(this.canvas){
|
||||
this.map.clear()
|
||||
}else{
|
||||
this.map = new Map()
|
||||
this.canvas = document.createElement("canvas")
|
||||
this.ctx = this.canvas.getContext("2d")
|
||||
if(this.noSmoothing){
|
||||
this.ctx.imageSmoothingEnabled = false
|
||||
}
|
||||
}
|
||||
this.scale = scale
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.w = w
|
||||
this.h = h
|
||||
this.lastW = 0
|
||||
this.lastH = 0
|
||||
this.canvas.width = Math.max(1, this.w * this.scale)
|
||||
this.canvas.height = Math.max(1, this.h * this.scale)
|
||||
this.ctx.scale(this.scale, this.scale)
|
||||
}
|
||||
get(config, callback, setOnly){
|
||||
var img = this.map.get(config.id)
|
||||
if(img && setOnly || !img && !callback){
|
||||
return
|
||||
}
|
||||
var saved = false
|
||||
var index = this.index++
|
||||
if(!img){
|
||||
var w = config.w
|
||||
var h = config.h
|
||||
this.x += this.lastW + (this.lastW ? 1 : 0)
|
||||
if(this.x + w > this.w){
|
||||
this.x = 0
|
||||
this.y += this.lastH + 1
|
||||
}
|
||||
if(this.y + h > this.h){
|
||||
var clear = true
|
||||
var oldest = {index: index}
|
||||
this.map.forEach((oldImg, id) => {
|
||||
if(oldImg.index < oldest.index){
|
||||
oldest.id = id
|
||||
oldest.index = oldImg.index
|
||||
}
|
||||
})
|
||||
var oldImg = this.map.get(oldest.id)
|
||||
this.map.delete(oldest.id)
|
||||
img = {
|
||||
x: oldImg.x,
|
||||
y: oldImg.y,
|
||||
w: w,
|
||||
h: h
|
||||
}
|
||||
}else{
|
||||
var clear = false
|
||||
this.lastW = w
|
||||
this.lastH = Math.max(this.lastH, h)
|
||||
img = {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
w: w,
|
||||
h: h
|
||||
}
|
||||
}
|
||||
|
||||
saved = true
|
||||
this.ctx.save()
|
||||
this.ctx.translate(img.x |0, img.y |0)
|
||||
if(clear){
|
||||
this.ctx.clearRect(0, 0, (img.w |0) + 1, (img.h |0) + 1)
|
||||
}
|
||||
this.ctx.beginPath()
|
||||
this.ctx.rect(0, 0, img.w |0, img.h |0)
|
||||
this.ctx.clip()
|
||||
|
||||
this.map.set(config.id, img)
|
||||
callback(this.ctx)
|
||||
}
|
||||
img.index = index
|
||||
if(setOnly){
|
||||
this.ctx.restore()
|
||||
return
|
||||
}
|
||||
var z = this.scale
|
||||
var sx = (img.x + (config.sx || 0)) * z |0
|
||||
var sy = (img.y + (config.sy || 0)) * z |0
|
||||
var sw = (config.sw || img.w) * z |0
|
||||
var sh = (config.sh || img.h) * z |0
|
||||
config.ctx.drawImage(this.canvas,
|
||||
sx, sy, sw, sh,
|
||||
config.x |0, config.y |0, config.w |0, config.h |0
|
||||
)
|
||||
if(saved){
|
||||
this.ctx.restore()
|
||||
}
|
||||
}
|
||||
set(config, callback){
|
||||
return this.get(config, callback, true)
|
||||
}
|
||||
clear(){
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.lastW = 0
|
||||
this.lastH = 0
|
||||
this.map.clear()
|
||||
this.ctx.clearRect(0, 0, this.w, this.h)
|
||||
}
|
||||
clean(){
|
||||
if(!this.canvas){
|
||||
return
|
||||
}
|
||||
this.resize(1, 1, 1)
|
||||
delete this.map
|
||||
delete this.ctx
|
||||
delete this.canvas
|
||||
}
|
||||
}
|
||||
1733
public/src/js/canvasdraw.js
Normal file
1733
public/src/js/canvasdraw.js
Normal file
File diff suppressed because it is too large
Load Diff
151
public/src/js/canvastest.js
Normal file
151
public/src/js/canvastest.js
Normal file
@@ -0,0 +1,151 @@
|
||||
class CanvasTest{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.canvas = document.createElement("canvas")
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
var width = innerWidth * pixelRatio
|
||||
var height = innerHeight * pixelRatio
|
||||
this.canvas.width = Math.max(1, width)
|
||||
this.canvas.height = Math.max(1, height)
|
||||
this.ctx = this.canvas.getContext("2d")
|
||||
this.ctx.scale(pixelRatio, pixelRatio)
|
||||
this.ratio = pixelRatio
|
||||
this.draw = new CanvasDraw()
|
||||
this.font = "serif"
|
||||
|
||||
this.songAsset = {
|
||||
marginLeft: 18,
|
||||
marginTop: 90,
|
||||
width: 82,
|
||||
height: 452,
|
||||
border: 6,
|
||||
innerBorder: 8
|
||||
}
|
||||
}
|
||||
blurPerformance(){
|
||||
return new Promise(resolve => {
|
||||
requestAnimationFrame(() => {
|
||||
var ctx = this.ctx
|
||||
ctx.save()
|
||||
var lastIteration = this.blurIteration()
|
||||
var frameTime = []
|
||||
|
||||
for(var i = 0; i < 10; i++){
|
||||
lastIteration = lastIteration.then(ms => {
|
||||
frameTime.push(ms)
|
||||
return this.blurIteration()
|
||||
})
|
||||
}
|
||||
|
||||
lastIteration.then(() => {
|
||||
ctx.restore()
|
||||
resolve(frameTime.reduce((a, b) => a + b) / frameTime.length)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
blurIteration(){
|
||||
return new Promise(resolve => {
|
||||
requestAnimationFrame(() => {
|
||||
var startTime = Date.now()
|
||||
var ctx = this.ctx
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
|
||||
for(var x = 0; x < this.canvas.width; x += this.songAsset.width + this.songAsset.marginLeft){
|
||||
this.draw.songFrame({
|
||||
ctx: ctx,
|
||||
x: x,
|
||||
y: this.songAsset.marginTop,
|
||||
width: this.songAsset.width,
|
||||
height: this.songAsset.height,
|
||||
border: this.songAsset.border,
|
||||
innerBorder: this.songAsset.innerBorder,
|
||||
background: "#efb058",
|
||||
borderStyle: ["#ffe7bd", "#c68229"],
|
||||
ratio: this.ratio,
|
||||
innerContent: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
for(var i = 0; i < 2; i++){
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: "I am a text",
|
||||
fontSize: 48,
|
||||
fontFamily: this.font,
|
||||
x: 23 + 300 * i,
|
||||
y: 15
|
||||
}, [
|
||||
{x: -2, y: -2, outline: "#000", letterBorder: 22},
|
||||
{},
|
||||
{x: 2, y: 2, shadow: [2, 2, 7]},
|
||||
{x: 2, y: 2, outline: "#ad1516", letterBorder: 10},
|
||||
{x: -2, y: -2, outline: "#ff797b"},
|
||||
{outline: "#f70808"},
|
||||
{fill: "#fff", shadow: [-1, 1, 3, 1.5]}
|
||||
])
|
||||
}
|
||||
resolve(Date.now() - startTime)
|
||||
})
|
||||
})
|
||||
}
|
||||
drawAllImages(){
|
||||
return new Promise(resolve => {
|
||||
requestAnimationFrame(() => {
|
||||
var startTime = Date.now()
|
||||
var ctx = this.ctx
|
||||
ctx.save()
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
|
||||
for(var name in assets.image){
|
||||
ctx.drawImage(assets.image[name], 0, 0)
|
||||
}
|
||||
|
||||
var comboCount = 765
|
||||
var comboX = 100
|
||||
var comboY = 100
|
||||
var fontSize = 120
|
||||
this.ctx.font = "normal " + fontSize + "px TnT, Meiryo, sans-serif"
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.strokeStyle = "#000"
|
||||
this.ctx.lineWidth = fontSize / 10
|
||||
var glyph = this.ctx.measureText("0").width
|
||||
var comboText = comboCount.toString().split("")
|
||||
for(var i in comboText){
|
||||
var textX = comboX + glyph * (i - (comboText.length - 1) / 2)
|
||||
if(comboCount >= 100){
|
||||
var grd = this.ctx.createLinearGradient(
|
||||
textX - glyph * 0.2,
|
||||
comboY - fontSize * 0.8,
|
||||
textX + glyph * 0.2,
|
||||
comboY - fontSize * 0.2
|
||||
)
|
||||
grd.addColorStop(0, "#f00")
|
||||
grd.addColorStop(1, "#fe0")
|
||||
this.ctx.fillStyle = grd
|
||||
}else{
|
||||
this.ctx.fillStyle = "#fff"
|
||||
}
|
||||
this.strokeFillText(comboText[i],
|
||||
textX,
|
||||
comboY
|
||||
)
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
resolve(Date.now() - startTime)
|
||||
})
|
||||
})
|
||||
}
|
||||
strokeFillText(text, x, y){
|
||||
this.ctx.strokeText(text, x, y)
|
||||
this.ctx.fillText(text, x, y)
|
||||
}
|
||||
clean(){
|
||||
this.draw.clean()
|
||||
delete this.ctx
|
||||
delete this.canvas
|
||||
}
|
||||
}
|
||||
45
public/src/js/circle.js
Normal file
45
public/src/js/circle.js
Normal file
@@ -0,0 +1,45 @@
|
||||
class Circle{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(config){
|
||||
this.id = config.id
|
||||
this.ms = config.start
|
||||
this.originalMS = this.ms
|
||||
this.type = config.type
|
||||
this.text = config.txt
|
||||
this.speed = config.speed
|
||||
this.endTime = config.endTime || this.ms
|
||||
this.originalEndTime = this.endTime
|
||||
this.isPlayed = 0
|
||||
this.animating = false
|
||||
this.animT = 0
|
||||
this.score = 0
|
||||
this.lastFrame = this.ms + 100
|
||||
this.animationEnded = false
|
||||
this.timesHit = 0
|
||||
this.timesKa = 0
|
||||
this.requiredHits = config.requiredHits || 0
|
||||
this.rendaPlayed = false
|
||||
this.gogoTime = config.gogoTime || false
|
||||
this.gogoChecked = false
|
||||
this.beatMS = config.beatMS
|
||||
this.fixedPos = config.fixedPos
|
||||
this.branch = config.branch
|
||||
this.section = config.section
|
||||
}
|
||||
animate(ms){
|
||||
this.animating = true
|
||||
this.animT = ms
|
||||
}
|
||||
played(score, big){
|
||||
this.score = score
|
||||
this.isPlayed = score <= 0 ? score - 1 : (big ? 2 : 1)
|
||||
}
|
||||
hit(keysKa){
|
||||
this.timesHit++
|
||||
if(keysKa){
|
||||
this.timesKa++
|
||||
}
|
||||
}
|
||||
}
|
||||
414
public/src/js/controller.js
Normal file
414
public/src/js/controller.js
Normal file
@@ -0,0 +1,414 @@
|
||||
class Controller{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(selectedSong, songData, autoPlayEnabled, multiplayer, touchEnabled){
|
||||
this.selectedSong = selectedSong
|
||||
this.songData = songData
|
||||
this.autoPlayEnabled = autoPlayEnabled
|
||||
this.saveScore = !autoPlayEnabled
|
||||
this.multiplayer = multiplayer
|
||||
this.touchEnabled = touchEnabled
|
||||
if(multiplayer === 2){
|
||||
this.snd = p2.player === 2 ? "_p1" : "_p2"
|
||||
this.don = p2.don || defaultDon
|
||||
}else{
|
||||
this.snd = multiplayer ? "_p" + p2.player : ""
|
||||
this.don = account.loggedIn ? account.don : defaultDon
|
||||
}
|
||||
if(this.snd === "_p2" && this.objEqual(defaultDon, this.don)){
|
||||
this.don = {
|
||||
body_fill: defaultDon.face_fill,
|
||||
face_fill: defaultDon.body_fill
|
||||
}
|
||||
}
|
||||
|
||||
this.calibrationMode = selectedSong.folder === "calibration"
|
||||
this.audioLatency = 0
|
||||
this.videoLatency = 0
|
||||
if(!this.calibrationMode){
|
||||
var latency = settings.getItem("latency")
|
||||
if(!autoPlayEnabled || this.multiplayer){
|
||||
this.audioLatency = Math.round(latency.audio) || 0
|
||||
}
|
||||
this.videoLatency = Math.round(latency.video) || 0 + this.audioLatency
|
||||
}
|
||||
if(this.multiplayer !== 2){
|
||||
loader.changePage("game", false)
|
||||
}
|
||||
|
||||
if(selectedSong.type === "tja"){
|
||||
this.parsedSongData = new ParseTja(songData, selectedSong.difficulty, selectedSong.stars, selectedSong.offset)
|
||||
}else{
|
||||
this.parsedSongData = new ParseOsu(songData, selectedSong.difficulty, selectedSong.stars, selectedSong.offset)
|
||||
}
|
||||
this.offset = this.parsedSongData.soundOffset
|
||||
|
||||
var maxCombo = this.parsedSongData.circles.filter(circle => ["don", "ka", "daiDon", "daiKa"].indexOf(circle.type) > -1 && (!circle.branch || circle.branch.name == "master")).length
|
||||
if (maxCombo >= 50) {
|
||||
var comboVoices = ["v_combo_50"].concat(Array.from(Array(Math.min(50, Math.floor(maxCombo / 100))), (d, i) => "v_combo_" + ((i + 1) * 100)))
|
||||
var promises = []
|
||||
|
||||
comboVoices.forEach(name => {
|
||||
if (!assets.sounds[name + "_p1"]) {
|
||||
promises.push(loader.loadSound(name + ".ogg", snd.sfxGain).then(sound => {
|
||||
assets.sounds[name + "_p1"] = assets.sounds[name].copy(snd.sfxGainL)
|
||||
assets.sounds[name + "_p2"] = assets.sounds[name].copy(snd.sfxGainR)
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
Promise.all(promises)
|
||||
}
|
||||
|
||||
if(this.calibrationMode){
|
||||
this.volume = 1
|
||||
}else{
|
||||
assets.songs.forEach(song => {
|
||||
if(song.id == this.selectedSong.folder){
|
||||
this.mainAsset = song.sound
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.game = new Game(this, this.selectedSong, this.parsedSongData)
|
||||
this.view = new View(this)
|
||||
if (parseFloat(localStorage.getItem("baisoku") ?? "1", 10) !== 1) {
|
||||
this.saveScore = false;
|
||||
}
|
||||
this.mekadon = new Mekadon(this, this.game)
|
||||
this.keyboard = new GameInput(this)
|
||||
if(!autoPlayEnabled && this.multiplayer !== 2){
|
||||
this.easierBigNotes = settings.getItem("easierBigNotes") || this.keyboard.keyboard.TaikoForceLv5
|
||||
}else{
|
||||
this.easierBigNotes = false
|
||||
}
|
||||
|
||||
this.drumSounds = settings.getItem("latency").drumSounds
|
||||
this.playedSounds = {}
|
||||
}
|
||||
run(syncWith){
|
||||
if(syncWith){
|
||||
this.syncWith = syncWith
|
||||
}
|
||||
if(this.multiplayer !== 2){
|
||||
snd.musicGain.setVolumeMul(this.volume)
|
||||
}
|
||||
this.game.run()
|
||||
this.view.run()
|
||||
if(this.multiplayer === 1){
|
||||
syncWith.run(this)
|
||||
syncWith.game.elapsedTime = this.game.elapsedTime
|
||||
syncWith.game.startDate = this.game.startDate
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
this.startMainLoop()
|
||||
if(!this.multiplayer){
|
||||
debugObj.controller = this
|
||||
if(debugObj.debug){
|
||||
debugObj.debug.updateStatus()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
startMainLoop(){
|
||||
this.mainLoopRunning = true
|
||||
window.gamestatus = 'start'
|
||||
this.gameLoop()
|
||||
this.viewLoop()
|
||||
if(this.multiplayer !== 2){
|
||||
this.gameInterval = setInterval(this.gameLoop.bind(this), 1000 / 60)
|
||||
pageEvents.send("game-start", {
|
||||
selectedSong: this.selectedSong,
|
||||
autoPlayEnabled: this.autoPlayEnabled,
|
||||
multiplayer: this.multiplayer,
|
||||
touchEnabled: this.touchEnabled
|
||||
})
|
||||
}
|
||||
}
|
||||
stopMainLoop(){
|
||||
this.mainLoopRunning = false
|
||||
window.gamestatus = 'stop'
|
||||
|
||||
if (window.videoElement) {
|
||||
// 停止视频播放
|
||||
window.videoElement.pause();
|
||||
|
||||
// 移除视频元素
|
||||
document.body.removeChild(window.videoElement);
|
||||
|
||||
// 解除引用,允许垃圾回收
|
||||
window.videoElement = null;
|
||||
}
|
||||
|
||||
|
||||
if(this.game.mainAsset){
|
||||
this.game.mainAsset.stop()
|
||||
}
|
||||
if(this.multiplayer !== 2){
|
||||
clearInterval(this.gameInterval)
|
||||
}
|
||||
}
|
||||
gameLoop(){
|
||||
if(this.mainLoopRunning){
|
||||
if(this.multiplayer === 1){
|
||||
this.syncWith.game.elapsedTime = this.game.elapsedTime
|
||||
this.syncWith.game.startDate = this.game.startDate
|
||||
}
|
||||
var ms = this.game.elapsedTime
|
||||
|
||||
if(this.game.musicFadeOut < 3){
|
||||
this.keyboard.checkMenuKeys()
|
||||
}
|
||||
if(this.calibrationMode){
|
||||
this.game.calibration()
|
||||
}
|
||||
if(!this.game.isPaused()){
|
||||
this.keyboard.checkGameKeys()
|
||||
|
||||
if(ms < 0){
|
||||
this.game.updateTime()
|
||||
}else{
|
||||
if(!this.calibrationMode){
|
||||
this.game.update()
|
||||
}
|
||||
if(!this.mainLoopRunning){
|
||||
return
|
||||
}
|
||||
this.game.playMainMusic()
|
||||
}
|
||||
}
|
||||
if(this.multiplayer === 1){
|
||||
this.syncWith.gameLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
viewLoop(){
|
||||
if(this.mainLoopRunning){
|
||||
if(this.multiplayer !== 2){
|
||||
requestAnimationFrame(() => {
|
||||
var player = this.multiplayer ? p2.player : 1
|
||||
if(player === 1){
|
||||
this.viewLoop()
|
||||
}
|
||||
if(this.multiplayer === 1){
|
||||
this.syncWith.viewLoop()
|
||||
}
|
||||
if(player === 2){
|
||||
this.viewLoop()
|
||||
}
|
||||
if(this.scoresheet){
|
||||
if(this.view.ctx){
|
||||
this.view.ctx.save()
|
||||
this.view.ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
}
|
||||
this.scoresheet.redraw()
|
||||
if(this.view.ctx){
|
||||
this.view.ctx.restore()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
this.view.refresh()
|
||||
}
|
||||
}
|
||||
gameEnded(){
|
||||
var score = this.getGlobalScore()
|
||||
var vp
|
||||
if(this.game.rules.clearReached(score.gauge)){
|
||||
if(score.bad === 0){
|
||||
vp = "fullcombo"
|
||||
this.playSound("v_fullcombo", 1.350)
|
||||
}else{
|
||||
vp = "clear"
|
||||
}
|
||||
}else{
|
||||
vp = "fail"
|
||||
}
|
||||
this.playSound("se_game" + vp)
|
||||
}
|
||||
displayResults(){
|
||||
if(this.multiplayer !== 2){
|
||||
if(this.view.cursorHidden){
|
||||
this.view.canvas.style.cursor = ""
|
||||
}
|
||||
this.scoresheet = new Scoresheet(this, this.getGlobalScore(), this.multiplayer, this.touchEnabled)
|
||||
}
|
||||
}
|
||||
displayScore(score, notPlayed, bigNote){
|
||||
this.view.displayScore(score, notPlayed, bigNote)
|
||||
}
|
||||
songSelection(fadeIn, showWarning){
|
||||
if(!fadeIn){
|
||||
if(this.cleaned){
|
||||
return
|
||||
}
|
||||
this.clean()
|
||||
}
|
||||
if(this.calibrationMode){
|
||||
new SettingsView(this.touchEnabled, false, null, "latency")
|
||||
}else{
|
||||
new SongSelect(false, fadeIn, this.touchEnabled, null, showWarning)
|
||||
}
|
||||
}
|
||||
restartSong(){
|
||||
if(this.cleaned){
|
||||
return
|
||||
}
|
||||
this.clean()
|
||||
if(this.multiplayer){
|
||||
new LoadSong(this.selectedSong, false, true, this.touchEnabled)
|
||||
}else{
|
||||
new Promise(resolve => {
|
||||
if(this.calibrationMode){
|
||||
resolve()
|
||||
}else{
|
||||
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
|
||||
var promises = []
|
||||
if(songObj.chart && songObj.chart !== "blank"){
|
||||
var chart = songObj.chart
|
||||
if(chart.separateDiff){
|
||||
var chartDiff = this.selectedSong.difficulty
|
||||
chart = chart[chartDiff]
|
||||
}
|
||||
this.addPromise(promises, chart.read(this.selectedSong.type === "tja" ? "utf-8" : undefined).then(data => {
|
||||
this.songData = data.replace(/\0/g, "").split("\n")
|
||||
return Promise.resolve()
|
||||
}), chart.url)
|
||||
}
|
||||
if(songObj.lyricsFile){
|
||||
this.addPromise(promises, songObj.lyricsFile.read().then(result => {
|
||||
songObj.lyricsData = result
|
||||
}, () => Promise.resolve()), songObj.lyricsFile.url)
|
||||
}
|
||||
if(songObj && songObj.category_id === 9){
|
||||
LoadSong.insertBackgroundVideo(songObj.id)
|
||||
}
|
||||
Promise.all(promises).then(resolve)
|
||||
}
|
||||
}).then(() => {
|
||||
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
|
||||
taikoGame.run()
|
||||
})
|
||||
}
|
||||
}
|
||||
addPromise(promises, promise, url){
|
||||
promises.push(promise.catch(error => {
|
||||
if(this.restartSongError){
|
||||
return
|
||||
}
|
||||
this.restartSongError = true
|
||||
if(url){
|
||||
error = (Array.isArray(error) ? error[0] + ": " : (error ? error + ": " : "")) + url
|
||||
}
|
||||
pageEvents.send("load-song-error", error)
|
||||
errorMessage(new Error(error).stack)
|
||||
var title = this.selectedSong.title
|
||||
if(title !== this.selectedSong.originalTitle){
|
||||
title += " (" + this.selectedSong.originalTitle + ")"
|
||||
}
|
||||
setTimeout(() => {
|
||||
new SongSelect(false, false, this.touchEnabled, null, {
|
||||
name: "loadSongError",
|
||||
title: title,
|
||||
id: this.selectedSong.folder,
|
||||
error: error
|
||||
})
|
||||
}, 500)
|
||||
return Promise.reject(error)
|
||||
}))
|
||||
}
|
||||
playSound(id, time, noSnd){
|
||||
if(!this.drumSounds && (id === "neiro_1_don" || id === "neiro_1_ka" || id === "se_don" || id === "se_ka")){
|
||||
return
|
||||
}
|
||||
var ms = Date.now() + (time || 0) * 1000
|
||||
if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
|
||||
assets.sounds[id + (noSnd ? "" : this.snd)].play(time)
|
||||
this.playedSounds[id] = ms
|
||||
}
|
||||
}
|
||||
togglePause(forcePause, pauseMove, noSound){
|
||||
if(this.multiplayer === 1){
|
||||
this.syncWith.game.togglePause(forcePause, pauseMove, noSound)
|
||||
}
|
||||
this.game.togglePause(forcePause, pauseMove, noSound)
|
||||
|
||||
|
||||
}
|
||||
getKeys(){
|
||||
return this.keyboard.getKeys()
|
||||
}
|
||||
setKey(pressed, name, ms){
|
||||
return this.keyboard.setKey(pressed, name, ms)
|
||||
}
|
||||
getElapsedTime(){
|
||||
return this.game.elapsedTime
|
||||
}
|
||||
getCircles(){
|
||||
return this.game.getCircles()
|
||||
}
|
||||
getCurrentCircle(){
|
||||
return this.game.getCurrentCircle()
|
||||
}
|
||||
isWaiting(key, type){
|
||||
return this.keyboard.isWaiting(key, type)
|
||||
}
|
||||
waitForKeyup(key, type){
|
||||
this.keyboard.waitForKeyup(key, type)
|
||||
}
|
||||
getKeyTime(){
|
||||
return this.keyboard.getKeyTime()
|
||||
}
|
||||
getCombo(){
|
||||
return this.game.getCombo()
|
||||
}
|
||||
getGlobalScore(){
|
||||
return this.game.getGlobalScore()
|
||||
}
|
||||
autoPlay(circle){
|
||||
if(this.multiplayer){
|
||||
p2.play(circle, this.mekadon)
|
||||
}else{
|
||||
return this.mekadon.play(circle)
|
||||
}
|
||||
}
|
||||
objEqual(a, b){
|
||||
for(var i in a){
|
||||
if(a[i] !== b[i]){
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
clean(){
|
||||
this.cleaned = true
|
||||
if(this.multiplayer === 1){
|
||||
this.syncWith.clean()
|
||||
}
|
||||
this.stopMainLoop()
|
||||
this.keyboard.clean()
|
||||
this.view.clean()
|
||||
snd.buffer.loadSettings()
|
||||
|
||||
if(!this.multiplayer){
|
||||
debugObj.controller = null
|
||||
if(debugObj.debug){
|
||||
debugObj.debug.updateStatus()
|
||||
}
|
||||
}
|
||||
if(this.lyrics){
|
||||
this.lyrics.clean()
|
||||
}
|
||||
}
|
||||
}
|
||||
551
public/src/js/customsongs.js
Normal file
551
public/src/js/customsongs.js
Normal file
@@ -0,0 +1,551 @@
|
||||
class CustomSongs{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(touchEnabled, noPage, noLoading){
|
||||
this.loaderDiv = document.createElement("div")
|
||||
this.loaderDiv.innerHTML = assets.pages["loadsong"]
|
||||
var loadingText = this.loaderDiv.querySelector("#loading-text")
|
||||
this.setAltText(loadingText, strings.loading)
|
||||
|
||||
this.locked = false
|
||||
this.mode = "main"
|
||||
|
||||
if(noPage){
|
||||
this.noPage = true
|
||||
this.noLoading = noLoading
|
||||
return
|
||||
}
|
||||
|
||||
this.touchEnabled = touchEnabled
|
||||
loader.changePage("customsongs", true)
|
||||
if(touchEnabled){
|
||||
this.getElement("view-outer").classList.add("touch-enabled")
|
||||
}
|
||||
|
||||
var tutorialTitle = this.getElement("view-title")
|
||||
this.setAltText(tutorialTitle, strings.customSongs.title)
|
||||
|
||||
var tutorialContent = this.getElement("view-content")
|
||||
strings.customSongs.description.forEach(string => {
|
||||
tutorialContent.appendChild(document.createTextNode(string))
|
||||
tutorialContent.appendChild(document.createElement("br"))
|
||||
})
|
||||
|
||||
this.items = []
|
||||
this.linkLocalFolder = document.getElementById("link-localfolder")
|
||||
this.hasLocal = (typeof showDirectoryPicker === "function" || "webkitdirectory" in HTMLInputElement.prototype) && !(/Android|iPhone|iPad/.test(navigator.userAgent))
|
||||
this.selected = -1
|
||||
|
||||
if(this.hasLocal){
|
||||
this.browse = document.getElementById("browse")
|
||||
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
|
||||
this.setAltText(this.linkLocalFolder, strings.customSongs.localFolder)
|
||||
pageEvents.add(this.linkLocalFolder, ["mousedown", "touchstart"], this.localFolder.bind(this))
|
||||
this.items.push(this.linkLocalFolder)
|
||||
if(this.selected === -1){
|
||||
this.linkLocalFolder.classList.add("selected")
|
||||
this.selected = this.items.length - 1
|
||||
}
|
||||
}else{
|
||||
this.linkLocalFolder.parentNode.removeChild(this.linkLocalFolder)
|
||||
}
|
||||
|
||||
var groupGdrive = document.getElementById("group-gdrive")
|
||||
this.linkGdriveFolder = document.getElementById("link-gdrivefolder")
|
||||
this.linkGdriveAccount = document.getElementById("link-gdriveaccount")
|
||||
this.linkPrivacy = document.getElementById("link-privacy")
|
||||
if(gameConfig.google_credentials.gdrive_enabled){
|
||||
this.setAltText(this.linkGdriveFolder, strings.customSongs.gdriveFolder)
|
||||
pageEvents.add(this.linkGdriveFolder, ["mousedown", "touchstart"], this.gdriveFolder.bind(this))
|
||||
this.items.push(this.linkGdriveFolder)
|
||||
if(this.selected === -1){
|
||||
this.linkGdriveFolder.classList.add("selected")
|
||||
this.selected = this.items.length - 1
|
||||
}
|
||||
this.setAltText(this.linkGdriveAccount, strings.customSongs.gdriveAccount)
|
||||
pageEvents.add(this.linkGdriveAccount, ["mousedown", "touchstart"], this.gdriveAccount.bind(this))
|
||||
this.items.push(this.linkGdriveAccount)
|
||||
this.setAltText(this.linkPrivacy, strings.account.privacy)
|
||||
pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this))
|
||||
this.items.push(this.linkPrivacy)
|
||||
}else{
|
||||
groupGdrive.style.display = "none"
|
||||
this.linkPrivacy.parentNode.removeChild(this.linkPrivacy)
|
||||
}
|
||||
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
this.setAltText(this.endButton, strings.session.cancel)
|
||||
pageEvents.add(this.endButton, ["mousedown", "touchstart"], event => this.onEnd(event, true))
|
||||
this.items.push(this.endButton)
|
||||
if(this.selected === -1){
|
||||
this.endButton.classList.add("selected")
|
||||
this.selected = this.items.length - 1
|
||||
}
|
||||
|
||||
this.fileSystem = location.protocol === "https:" && DataTransferItem.prototype.getAsFileSystemHandle
|
||||
if(this.fileSystem || DataTransferItem.prototype.webkitGetAsEntry){
|
||||
this.dropzone = document.getElementById("dropzone")
|
||||
var dropContent = this.dropzone.getElementsByClassName("view-content")[0]
|
||||
dropContent.innerText = strings.customSongs.dropzone
|
||||
this.dragging = false
|
||||
this.dragTarget = null
|
||||
pageEvents.add(document, "dragenter", event => {
|
||||
event.preventDefault()
|
||||
this.dragTarget = event.target
|
||||
})
|
||||
pageEvents.add(document, "dragover", event => {
|
||||
event.preventDefault()
|
||||
if(!this.locked){
|
||||
event.dataTransfer.dropEffect = "copy"
|
||||
this.dropzone.classList.add("dragover")
|
||||
this.dragging = true
|
||||
}else{
|
||||
event.dataTransfer.dropEffect = "none"
|
||||
}
|
||||
})
|
||||
pageEvents.add(document, "dragleave", () => {
|
||||
if(this.dragTarget === event.target){
|
||||
event.preventDefault()
|
||||
this.dropzone.classList.remove("dragover")
|
||||
this.dragging = false
|
||||
}
|
||||
})
|
||||
pageEvents.add(document, "drop", this.filesDropped.bind(this))
|
||||
}
|
||||
|
||||
this.errorDiv = document.getElementById("customsongs-error")
|
||||
pageEvents.add(this.errorDiv, ["mousedown", "touchstart"], event => {
|
||||
if(event.target === event.currentTarget){
|
||||
this.hideError()
|
||||
}
|
||||
})
|
||||
var errorTitle = this.errorDiv.getElementsByClassName("view-title")[0]
|
||||
this.setAltText(errorTitle, strings.customSongs.importError)
|
||||
this.errorContent = this.errorDiv.getElementsByClassName("view-content")[0]
|
||||
this.errorEnd = this.errorDiv.getElementsByClassName("view-end-button")[0]
|
||||
this.setAltText(this.errorEnd, strings.tutorial.ok)
|
||||
pageEvents.add(this.errorEnd, ["mousedown", "touchstart"], () => this.hideError(true))
|
||||
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["enter", "space", "don_l", "don_r"],
|
||||
previous: ["left", "up", "ka_l"],
|
||||
next: ["right", "down", "ka_r"],
|
||||
backEsc: ["escape"]
|
||||
}, this.keyPressed.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
confirmPad: ["b", "ls", "rs"],
|
||||
previous: ["u", "l", "lb", "lt", "lsu", "lsl"],
|
||||
next: ["d", "r", "rb", "rt", "lsd", "lsr"],
|
||||
back: ["start", "a"]
|
||||
}, this.keyPressed.bind(this))
|
||||
|
||||
pageEvents.send("custom-songs")
|
||||
}
|
||||
getElement(name){
|
||||
return loader.screen.getElementsByClassName(name)[0]
|
||||
}
|
||||
setAltText(element, text){
|
||||
element.innerText = text
|
||||
element.setAttribute("alt", text)
|
||||
}
|
||||
localFolder(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
this.changeSelected(this.linkLocalFolder)
|
||||
if(typeof showDirectoryPicker === "function" && !(/\bOPR\/|\bOPRGX\//.test(navigator.userAgent))){
|
||||
return showDirectoryPicker().then(file => {
|
||||
this.walkFilesystem(file).then(files => this.importLocal(files)).then(input => {
|
||||
if(input){
|
||||
db.setItem("customFolder", [file])
|
||||
}
|
||||
}).catch(e => {
|
||||
if(e !== "cancel"){
|
||||
return Promise.reject(e)
|
||||
}
|
||||
})
|
||||
}, () => {})
|
||||
}else{
|
||||
this.browse.click()
|
||||
}
|
||||
}
|
||||
browseChange(event){
|
||||
var files = []
|
||||
for(var i = 0; i < event.target.files.length; i++){
|
||||
files.push(new LocalFile(event.target.files[i]))
|
||||
}
|
||||
this.importLocal(files)
|
||||
}
|
||||
walkFilesystem(file, path="", output=[]){
|
||||
if(file.kind === "directory"){
|
||||
return filePermission(file).then(file => {
|
||||
var values = file.values()
|
||||
var walkValues = () => values.next().then(generator => {
|
||||
if(generator.done){
|
||||
return output
|
||||
}
|
||||
return this.walkFilesystem(generator.value, path + file.name + "/", output).then(() => walkValues())
|
||||
})
|
||||
return walkValues()
|
||||
}, () => Promise.resolve())
|
||||
}else{
|
||||
output.push(new FilesystemFile(file, path + file.name))
|
||||
return Promise.resolve(output)
|
||||
}
|
||||
}
|
||||
filesDropped(event){
|
||||
event.preventDefault()
|
||||
this.dropzone.classList.remove("dragover")
|
||||
this.dragging = false
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
var allFiles = []
|
||||
var dropPromises = []
|
||||
var dbItems = []
|
||||
for(var i = 0; i < event.dataTransfer.items.length; i++){
|
||||
var item = event.dataTransfer.items[i]
|
||||
let promise
|
||||
if(this.fileSystem){
|
||||
promise = item.getAsFileSystemHandle().then(file => {
|
||||
dbItems.push(file)
|
||||
return this.walkFilesystem(file)
|
||||
})
|
||||
}else{
|
||||
var entry = item.webkitGetAsEntry()
|
||||
if(entry){
|
||||
promise = this.walkEntry(entry)
|
||||
}
|
||||
}
|
||||
if(promise){
|
||||
dropPromises.push(promise.then(files => {
|
||||
allFiles = allFiles.concat(files)
|
||||
}))
|
||||
}
|
||||
}
|
||||
Promise.all(dropPromises).then(() => this.importLocal(allFiles)).then(input => {
|
||||
if(input && dbItems.length){
|
||||
db.setItem("customFolder", dbItems)
|
||||
}
|
||||
})
|
||||
}
|
||||
walkEntry(entry, path="", output=[]){
|
||||
return new Promise(resolve => {
|
||||
if(entry.isFile){
|
||||
entry.file(file => {
|
||||
output.push(new LocalFile(file, path + file.name))
|
||||
return resolve()
|
||||
}, resolve)
|
||||
}else if(entry.isDirectory){
|
||||
var dirReader = entry.createReader()
|
||||
dirReader.readEntries(entries => {
|
||||
var dirPromises = []
|
||||
for(var i = 0; i < entries.length; i++){
|
||||
dirPromises.push(this.walkEntry(entries[i], path + entry.name + "/", output))
|
||||
}
|
||||
return Promise.all(dirPromises).then(resolve)
|
||||
}, resolve)
|
||||
}else{
|
||||
return resolve()
|
||||
}
|
||||
}).then(() => output)
|
||||
}
|
||||
importLocal(files){
|
||||
if(!files.length){
|
||||
if(this.noPage){
|
||||
return Promise.reject("cancel")
|
||||
}else{
|
||||
return Promise.resolve("cancel")
|
||||
}
|
||||
}
|
||||
this.locked = true
|
||||
this.loading(true)
|
||||
|
||||
var importSongs = new ImportSongs()
|
||||
return importSongs.load(files).then(this.songsLoaded.bind(this), e => {
|
||||
if(!this.noPage){
|
||||
this.browse.form.reset()
|
||||
}
|
||||
this.locked = false
|
||||
this.loading(false)
|
||||
if(e === "nosongs"){
|
||||
this.showError(strings.customSongs.noSongs, "nosongs")
|
||||
}else if(e !== "cancel"){
|
||||
return Promise.reject(e)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
gdriveFolder(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
this.changeSelected(this.linkGdriveFolder)
|
||||
this.locked = true
|
||||
this.loading(true)
|
||||
var importSongs = new ImportSongs(true)
|
||||
if(!gpicker){
|
||||
var gpickerPromise = loader.loadScript("src/js/gpicker.js").then(() => {
|
||||
gpicker = new Gpicker()
|
||||
})
|
||||
}else{
|
||||
var gpickerPromise = Promise.resolve()
|
||||
}
|
||||
gpickerPromise.then(() => {
|
||||
return gpicker.browse(locked => {
|
||||
this.locked = locked
|
||||
this.loading(locked)
|
||||
}, error => {
|
||||
this.showError(error)
|
||||
})
|
||||
}).then(files => importSongs.load(files))
|
||||
.then(this.songsLoaded.bind(this))
|
||||
.catch(e => {
|
||||
this.locked = false
|
||||
this.loading(false)
|
||||
if(e === "nosongs"){
|
||||
this.showError(strings.customSongs.noSongs, "nosongs")
|
||||
}else if(e !== "cancel"){
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}).finally(() => {
|
||||
if(this.linkGdriveAccount){
|
||||
var addRemove = !gpicker || !gpicker.oauthToken ? "add" : "remove"
|
||||
this.linkGdriveAccount.classList[addRemove]("hiddenbtn")
|
||||
}
|
||||
})
|
||||
}
|
||||
gdriveAccount(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
this.changeSelected(this.linkGdriveAccount)
|
||||
this.locked = true
|
||||
this.loading(true)
|
||||
if(!gpicker){
|
||||
var gpickerPromise = loader.loadScript("/src/js/gpicker.js").then(() => {
|
||||
gpicker = new Gpicker()
|
||||
})
|
||||
}else{
|
||||
var gpickerPromise = Promise.resolve()
|
||||
}
|
||||
gpickerPromise.then(() => {
|
||||
return gpicker.switchAccounts(locked => {
|
||||
this.locked = locked
|
||||
this.loading(locked)
|
||||
}, error => {
|
||||
this.showError(error)
|
||||
})
|
||||
}).then(() => {
|
||||
this.locked = false
|
||||
this.loading(false)
|
||||
}).catch(error => {
|
||||
if(error !== "cancel"){
|
||||
this.showError(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
openPrivacy(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
this.changeSelected(this.linkPrivacy)
|
||||
open("privacy")
|
||||
}
|
||||
loading(show){
|
||||
if(this.noLoading){
|
||||
return
|
||||
}
|
||||
if(show){
|
||||
loader.screen.appendChild(this.loaderDiv)
|
||||
}else if(this.loaderDiv.parentNode){
|
||||
this.loaderDiv.parentNode.removeChild(this.loaderDiv)
|
||||
}
|
||||
}
|
||||
songsLoaded(songs){
|
||||
if(songs){
|
||||
var length = songs.length
|
||||
assets.songs = songs
|
||||
assets.customSongs = true
|
||||
assets.customSelected = this.noPage ? +localStorage.getItem("customSelected") : 0
|
||||
}
|
||||
if(this.noPage){
|
||||
pageEvents.send("import-songs", length)
|
||||
}else{
|
||||
assets.sounds["se_don"].play()
|
||||
setTimeout(() => {
|
||||
new SongSelect("customSongs", false, this.touchEnabled)
|
||||
pageEvents.send("import-songs", length)
|
||||
}, 500)
|
||||
}
|
||||
this.clean()
|
||||
return songs && songs.length
|
||||
}
|
||||
keyPressed(pressed, name){
|
||||
if(!pressed || this.locked){
|
||||
return
|
||||
}
|
||||
var selected = this.items[this.selected]
|
||||
if(this.mode === "main"){
|
||||
if(name === "confirm" || name === "confirmPad"){
|
||||
if(selected === this.endButton){
|
||||
this.onEnd(null, true)
|
||||
}else if(name !== "confirmPad"){
|
||||
if(selected === this.linkLocalFolder){
|
||||
assets.sounds["se_don"].play()
|
||||
this.localFolder()
|
||||
}else if(selected === this.linkGdriveFolder){
|
||||
assets.sounds["se_don"].play()
|
||||
this.gdriveFolder()
|
||||
}else if(selected === this.linkGdriveAccount){
|
||||
assets.sounds["se_don"].play()
|
||||
this.gdriveAccount()
|
||||
}else if(selected === this.linkPrivacy){
|
||||
assets.sounds["se_don"].play()
|
||||
this.openPrivacy()
|
||||
}
|
||||
}
|
||||
}else if(name === "previous" || name === "next"){
|
||||
selected.classList.remove("selected")
|
||||
do{
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
}while(this.items[this.selected] === this.linkPrivacy && name !== "previous")
|
||||
this.items[this.selected].classList.add("selected")
|
||||
assets.sounds["se_ka"].play()
|
||||
}else if(name === "back" || name === "backEsc"){
|
||||
if(!this.dragging || name !== "backEsc"){
|
||||
this.onEnd()
|
||||
}
|
||||
}
|
||||
}else if(this.mode === "error"){
|
||||
if(name === "confirm" || name === "confirmPad" || name === "back" || name === "backEsc"){
|
||||
this.hideError(name === "confirm" || name === "confirmPad")
|
||||
}
|
||||
}
|
||||
}
|
||||
changeSelected(button){
|
||||
var selected = this.items[this.selected]
|
||||
if(selected !== button){
|
||||
selected.classList.remove("selected")
|
||||
this.selected = this.items.findIndex(item => item === button)
|
||||
this.items[this.selected].classList.add("selected")
|
||||
}
|
||||
}
|
||||
mod(length, index){
|
||||
return ((index % length) + length) % length
|
||||
}
|
||||
onEnd(event, confirm){
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
var touched = false
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
touched = true
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}else{
|
||||
touched = this.touchEnabled
|
||||
}
|
||||
this.clean()
|
||||
if(!this.noPage){
|
||||
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
|
||||
}
|
||||
return new Promise(resolve => setTimeout(() => {
|
||||
new SongSelect("customSongs", false, touched)
|
||||
resolve()
|
||||
}, 500))
|
||||
}
|
||||
showError(text, errorName){
|
||||
this.locked = false
|
||||
this.loading(false)
|
||||
if(this.noPage){
|
||||
var error = new Error(text)
|
||||
error.name = errorName
|
||||
throw error
|
||||
}else if(this.mode === "error"){
|
||||
return
|
||||
}
|
||||
this.mode = "error"
|
||||
this.errorContent.innerText = text
|
||||
this.errorDiv.style.display = "flex"
|
||||
assets.sounds["se_pause"].play()
|
||||
}
|
||||
hideError(confirm){
|
||||
if(this.mode !== "error"){
|
||||
return
|
||||
}
|
||||
this.mode = "main"
|
||||
this.errorDiv.style.display = ""
|
||||
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
|
||||
}
|
||||
clean(){
|
||||
delete this.loaderDiv
|
||||
if(this.noPage){
|
||||
return
|
||||
}
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
pageEvents.remove(this.browse, "change")
|
||||
if(this.hasLocal){
|
||||
pageEvents.remove(this.linkLocalFolder, ["mousedown", "touchstart"])
|
||||
}
|
||||
if(gameConfig.google_credentials.gdrive_enabled){
|
||||
pageEvents.remove(this.linkGdriveFolder, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.linkGdriveAccount, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"])
|
||||
}
|
||||
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.errorDiv, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.errorEnd, ["mousedown", "touchstart"])
|
||||
if(DataTransferItem.prototype.webkitGetAsEntry){
|
||||
pageEvents.remove(document, ["dragenter", "dragover", "dragleave", "drop"])
|
||||
delete this.dropzone
|
||||
delete this.dragTarget
|
||||
}
|
||||
if(gpicker){
|
||||
gpicker.tokenResolve = null
|
||||
}
|
||||
delete this.browse
|
||||
delete this.linkLocalFolder
|
||||
delete this.linkGdriveFolder
|
||||
delete this.linkGdriveAccount
|
||||
delete this.linkPrivacy
|
||||
delete this.endButton
|
||||
delete this.items
|
||||
delete this.errorDiv
|
||||
delete this.errorContent
|
||||
delete this.errorEnd
|
||||
}
|
||||
}
|
||||
491
public/src/js/debug.js
Normal file
491
public/src/js/debug.js
Normal file
@@ -0,0 +1,491 @@
|
||||
class Debug{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
if(!assets.pages["debug"]){
|
||||
return
|
||||
}
|
||||
this.debugDiv = document.createElement("div")
|
||||
this.debugDiv.id = "debug"
|
||||
this.debugDiv.innerHTML = assets.pages["debug"]
|
||||
document.body.appendChild(this.debugDiv)
|
||||
|
||||
this.titleDiv = this.byClass("title")
|
||||
this.minimiseDiv = this.byClass("minimise")
|
||||
this.offsetDiv = this.byClass("offset")
|
||||
this.measureNumDiv = this.byClass("measure-num")
|
||||
this.branchHideDiv = this.byClass("branch-hide")
|
||||
this.branchSelectDiv = this.byClass("branch-select")
|
||||
this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0]
|
||||
this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0]
|
||||
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.restartCheckbox = this.byClass("change-restart")
|
||||
this.autoplayLabel = this.byClass("autoplay-label")
|
||||
this.autoplayCheckbox = this.byClass("autoplay")
|
||||
this.restartBtn = this.byClass("restart-btn")
|
||||
this.exitBtn = this.byClass("exit-btn")
|
||||
|
||||
this.moving = false
|
||||
this.windowSymbol = Symbol()
|
||||
pageEvents.add(window, ["mousedown", "mouseup", "touchstart", "touchend", "blur", "resize"], this.stopMove.bind(this), this.windowSymbol)
|
||||
pageEvents.mouseAdd(this, this.onMove.bind(this))
|
||||
pageEvents.add(window, "touchmove", this.onMove.bind(this))
|
||||
pageEvents.add(this.titleDiv, ["mousedown", "touchstart"], this.startMove.bind(this))
|
||||
pageEvents.add(this.minimiseDiv, ["click", "touchstart"], this.minimise.bind(this))
|
||||
pageEvents.add(this.restartBtn, ["click", "touchstart"], this.restartSong.bind(this))
|
||||
pageEvents.add(this.exitBtn, ["click", "touchstart"], this.clean.bind(this))
|
||||
pageEvents.add(this.restartLabel, "touchstart", this.touchBox.bind(this))
|
||||
pageEvents.add(this.autoplayLabel, "touchstart", this.touchBox.bind(this))
|
||||
pageEvents.add(this.autoplayCheckbox, "change", this.toggleAutoplay.bind(this))
|
||||
pageEvents.add(this.branchSelect, "change", this.branchChange.bind(this))
|
||||
pageEvents.add(this.branchResetBtn, ["click", "touchstart"], this.branchReset.bind(this))
|
||||
|
||||
this.offsetSlider = new InputSlider(this.offsetDiv, -60, 60, 3)
|
||||
this.offsetSlider.onchange(this.offsetChange.bind(this))
|
||||
|
||||
this.measureNumSlider = new InputSlider(this.measureNumDiv, 0, 1000, 0)
|
||||
this.measureNumSlider.onchange(this.measureNumChange.bind(this))
|
||||
this.measureNumSlider.set(0)
|
||||
|
||||
this.volumeSlider = new InputSlider(this.volumeDiv, 0, 3, 2)
|
||||
this.volumeSlider.onchange(this.volumeChange.bind(this))
|
||||
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.restore()
|
||||
this.updateStatus()
|
||||
pageEvents.send("debug")
|
||||
}
|
||||
byClass(name){
|
||||
return this.debugDiv.getElementsByClassName(name)[0]
|
||||
}
|
||||
startMove(event){
|
||||
if(event.which === 1 || event.type === "touchstart"){
|
||||
event.stopPropagation()
|
||||
var divPos = this.debugDiv.getBoundingClientRect()
|
||||
var click = event.type === "touchstart" ? event.changedTouches[0] : event
|
||||
var x = click.pageX - divPos.left
|
||||
var y = click.pageY - divPos.top
|
||||
this.moving = {x: x, y: y}
|
||||
}
|
||||
}
|
||||
onMove(event){
|
||||
if(this.moving){
|
||||
var click = event.type === "touchmove" ? event.changedTouches[0] : event
|
||||
var x = click.clientX - this.moving.x
|
||||
var y = click.clientY - this.moving.y
|
||||
this.moveTo(x, y)
|
||||
}
|
||||
}
|
||||
stopMove(event){
|
||||
if(this.debugDiv.style.display === "none"){
|
||||
return
|
||||
}
|
||||
if(!event || event.type === "resize"){
|
||||
var divPos = this.debugDiv.getBoundingClientRect()
|
||||
var x = divPos.left
|
||||
var y = divPos.top
|
||||
}else{
|
||||
var click = event.type === "touchstart" || event.type === "touchend" ? event.changedTouches[0] : event
|
||||
if(event.type == "blur"){
|
||||
var x = this.moving.x
|
||||
var y = this.moving.y
|
||||
}else{
|
||||
var x = click.clientX - this.moving.x
|
||||
var y = click.clientY - this.moving.y
|
||||
}
|
||||
}
|
||||
var w = this.debugDiv.offsetWidth
|
||||
var h = this.debugDiv.offsetHeight
|
||||
if(x + w > innerWidth){
|
||||
x = innerWidth - w
|
||||
}
|
||||
if(y + h > lastHeight){
|
||||
y = lastHeight - h
|
||||
}
|
||||
if(x < 0){
|
||||
x = 0
|
||||
}
|
||||
if(y < 0){
|
||||
y = 0
|
||||
}
|
||||
this.moveTo(x, y)
|
||||
this.moving = false
|
||||
}
|
||||
moveTo(x, y){
|
||||
this.debugDiv.style.transform = "translate(" + x + "px, " + y + "px)"
|
||||
}
|
||||
restore(){
|
||||
debugObj.state = "open"
|
||||
this.debugDiv.style.display = ""
|
||||
this.stopMove()
|
||||
}
|
||||
minimise(){
|
||||
debugObj.state = "minimised"
|
||||
this.debugDiv.style.display = "none"
|
||||
}
|
||||
updateStatus(){
|
||||
if(debugObj.controller && !this.controller){
|
||||
this.controller = debugObj.controller
|
||||
|
||||
this.restartBtn.style.display = "block"
|
||||
this.autoplayLabel.style.display = "block"
|
||||
if(this.controller.parsedSongData.branches){
|
||||
this.branchHideDiv.style.display = "block"
|
||||
}
|
||||
if(this.controller.lyrics){
|
||||
this.lyricsHideDiv.style.display = "block"
|
||||
}
|
||||
|
||||
var selectedSong = this.controller.selectedSong
|
||||
this.defaultOffset = selectedSong.offset || 0
|
||||
if(this.songHash === selectedSong.hash){
|
||||
this.offsetChange(this.offsetSlider.get(), true)
|
||||
this.branchChange(null, true)
|
||||
this.volumeChange(this.volumeSlider.get(), true)
|
||||
this.lyricsChange(this.lyricsSlider.get(), true)
|
||||
}else{
|
||||
this.songHash = selectedSong.hash
|
||||
this.offsetSlider.set(this.defaultOffset)
|
||||
this.branchReset(null, true)
|
||||
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) => {
|
||||
return i === 0 || Math.abs(measure.ms - array[i - 1].ms) > 0.01
|
||||
})
|
||||
this.measureNumSlider.setMinMax(0, measures.length - 1)
|
||||
if(this.measureNum > 0 && measures.length >= this.measureNum){
|
||||
var measureMS = measures[this.measureNum - 1].ms
|
||||
var game = this.controller.game
|
||||
game.started = true
|
||||
var timestamp = Date.now()
|
||||
var currentDate = timestamp - measureMS
|
||||
game.startDate = currentDate
|
||||
game.sndTime = timestamp - snd.buffer.getTime() * 1000
|
||||
var circles = game.songData.circles
|
||||
for(var i in circles){
|
||||
game.currentCircle = i
|
||||
if(circles[i].endTime >= measureMS){
|
||||
break
|
||||
}
|
||||
game.skipNote(circles[i])
|
||||
}
|
||||
if(game.mainMusicPlaying){
|
||||
game.mainMusicPlaying = false
|
||||
game.mainAsset.stop()
|
||||
}
|
||||
}
|
||||
this.autoplayCheckbox.checked = this.controller.autoPlayEnabled
|
||||
}
|
||||
if(this.controller && !debugObj.controller){
|
||||
this.restartBtn.style.display = ""
|
||||
this.autoplayLabel.style.display = ""
|
||||
this.branchHideDiv.style.display = ""
|
||||
this.lyricsHideDiv.style.display = ""
|
||||
this.controller = null
|
||||
}
|
||||
this.stopMove()
|
||||
}
|
||||
offsetChange(value, noRestart){
|
||||
if(this.controller){
|
||||
var offset = (this.defaultOffset - value) * 1000
|
||||
var songData = this.controller.parsedSongData
|
||||
songData.circles.forEach(circle => {
|
||||
circle.ms = circle.originalMS + offset
|
||||
circle.endTime = circle.originalEndTime + offset
|
||||
})
|
||||
songData.measures.forEach(measure => {
|
||||
measure.ms = measure.originalMS + offset
|
||||
})
|
||||
if(songData.branches){
|
||||
songData.branches.forEach(branch => {
|
||||
branch.ms = branch.originalMS + offset
|
||||
})
|
||||
}
|
||||
if(this.controller.lyrics){
|
||||
this.controller.lyrics.offsetChange(value * 1000)
|
||||
}
|
||||
if(this.restartCheckbox.checked && !noRestart){
|
||||
this.restartSong()
|
||||
}
|
||||
}
|
||||
}
|
||||
measureNumChange(value){
|
||||
this.measureNum = value
|
||||
if(this.restartCheckbox.checked){
|
||||
this.restartSong()
|
||||
}
|
||||
}
|
||||
volumeChange(value, noRestart){
|
||||
if(this.controller){
|
||||
snd.musicGain.setVolumeMul(value)
|
||||
}
|
||||
if(this.restartCheckbox.checked && !noRestart){
|
||||
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(){
|
||||
if(this.controller){
|
||||
this.controller.restartSong()
|
||||
}
|
||||
}
|
||||
toggleAutoplay(event){
|
||||
if(this.controller){
|
||||
this.controller.autoPlayEnabled = this.autoplayCheckbox.checked
|
||||
if(this.controller.autoPlayEnabled){
|
||||
this.controller.saveScore = false
|
||||
}else{
|
||||
var keyboard = debugObj.controller.keyboard
|
||||
keyboard.setKey(false, "don_l")
|
||||
keyboard.setKey(false, "don_r")
|
||||
keyboard.setKey(false, "ka_l")
|
||||
keyboard.setKey(false, "ka_r")
|
||||
}
|
||||
}
|
||||
}
|
||||
branchChange(event, noRestart){
|
||||
if(this.controller){
|
||||
var game = this.controller.game
|
||||
var name = this.branchSelect.value
|
||||
game.branch = name === "auto" ? false : name
|
||||
game.branchSet = name === "auto"
|
||||
if(noRestart){
|
||||
game.branchStatic = true
|
||||
}
|
||||
var selectedOption = this.branchSelect.selectedOptions[0]
|
||||
this.branchSelect.style.background = selectedOption.style.background
|
||||
if(this.restartCheckbox.checked && !noRestart){
|
||||
this.restartSong()
|
||||
}
|
||||
}
|
||||
}
|
||||
branchReset(event, noRestart){
|
||||
this.branchSelect.value = "auto"
|
||||
this.branchChange(null, noRestart)
|
||||
}
|
||||
touchBox(event){
|
||||
event.currentTarget.click()
|
||||
}
|
||||
clean(){
|
||||
this.offsetSlider.clean()
|
||||
this.measureNumSlider.clean()
|
||||
this.volumeSlider.clean()
|
||||
this.lyricsSlider.clean()
|
||||
|
||||
pageEvents.remove(window, ["mousedown", "mouseup", "touchstart", "touchend", "blur", "resize"], this.windowSymbol)
|
||||
pageEvents.mouseRemove(this)
|
||||
pageEvents.remove(window, "touchmove")
|
||||
pageEvents.remove(this.titleDiv, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.minimiseDiv, ["click", "touchstart"])
|
||||
pageEvents.remove(this.restartBtn, ["click", "touchstart"])
|
||||
pageEvents.remove(this.exitBtn, ["click", "touchstart"])
|
||||
pageEvents.remove(this.restartLabel, "touchstart")
|
||||
pageEvents.remove(this.autoplayLabel, "touchstart")
|
||||
pageEvents.remove(this.autoplayCheckbox, "change")
|
||||
pageEvents.remove(this.branchSelect, "change")
|
||||
pageEvents.remove(this.branchResetBtn, ["click", "touchstart"])
|
||||
|
||||
delete this.offsetSlider
|
||||
delete this.measureNumSlider
|
||||
delete this.volumeSlider
|
||||
delete this.titleDiv
|
||||
delete this.minimiseDiv
|
||||
delete this.offsetDiv
|
||||
delete this.measureNumDiv
|
||||
delete this.branchHideDiv
|
||||
delete this.branchSelectDiv
|
||||
delete this.branchSelect
|
||||
delete this.branchResetBtn
|
||||
delete this.volumeDiv
|
||||
delete this.lyricsHideDiv
|
||||
delete this.lyricsOffsetDiv
|
||||
delete this.restartCheckbox
|
||||
delete this.autoplayLabel
|
||||
delete this.autoplayCheckbox
|
||||
delete this.restartBtn
|
||||
delete this.exitBtn
|
||||
delete this.controller
|
||||
delete this.windowSymbol
|
||||
|
||||
debugObj.state = "closed"
|
||||
debugObj.debug = null
|
||||
document.body.removeChild(this.debugDiv)
|
||||
|
||||
delete this.debugDiv
|
||||
}
|
||||
}
|
||||
class InputSlider{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(sliderDiv, min, max, fixedPoint){
|
||||
this.fixedPoint = fixedPoint
|
||||
this.mul = Math.pow(10, fixedPoint)
|
||||
this.min = min * this.mul
|
||||
this.max = max * this.mul
|
||||
|
||||
this.input = sliderDiv.getElementsByTagName("input")[0]
|
||||
this.reset = sliderDiv.getElementsByClassName("reset")[0]
|
||||
this.plus = sliderDiv.getElementsByClassName("plus")[0]
|
||||
this.minus = sliderDiv.getElementsByClassName("minus")[0]
|
||||
this.value = null
|
||||
this.defaultValue = null
|
||||
this.callbacks = []
|
||||
this.touchEnd = []
|
||||
this.windowSymbol = Symbol()
|
||||
pageEvents.add(this.input, ["touchstart", "touchend"], event => {
|
||||
event.stopPropagation()
|
||||
})
|
||||
pageEvents.add(window, ["mouseup", "touchstart", "touchend", "blur"], event => {
|
||||
if(event.type !== "touchstart"){
|
||||
this.touchEnd.forEach(func => func(event))
|
||||
}else if(event.target !== this.input){
|
||||
this.input.blur()
|
||||
}
|
||||
}, this.windowSymbol)
|
||||
|
||||
this.addTouchRepeat(this.plus, this.add.bind(this))
|
||||
this.addTouchRepeat(this.minus, this.subtract.bind(this))
|
||||
this.addTouch(this.reset, this.resetValue.bind(this))
|
||||
pageEvents.add(this.input, "change", this.manualSet.bind(this))
|
||||
pageEvents.add(this.input, "keydown", this.captureKeys.bind(this))
|
||||
}
|
||||
update(noCallback, force){
|
||||
var oldValue = this.input.value
|
||||
if(this.value === null){
|
||||
this.input.value = ""
|
||||
this.input.readOnly = true
|
||||
}else{
|
||||
if(this.value > this.max){
|
||||
this.value = this.max
|
||||
}
|
||||
if(this.value < this.min){
|
||||
this.value = this.min
|
||||
}
|
||||
this.input.value = this.get().toFixed(this.fixedPoint)
|
||||
this.input.readOnly = false
|
||||
}
|
||||
if(force || !noCallback && oldValue !== this.input.value){
|
||||
this.callbacks.forEach(callback => {
|
||||
callback(this.get())
|
||||
})
|
||||
}
|
||||
}
|
||||
set(number){
|
||||
this.value = Math.floor(number * this.mul)
|
||||
this.defaultValue = this.value
|
||||
this.update(true)
|
||||
}
|
||||
setMinMax(min, max){
|
||||
this.min = min
|
||||
this.max = max
|
||||
this.update()
|
||||
}
|
||||
get(){
|
||||
if(this.value === null){
|
||||
return null
|
||||
}else{
|
||||
return Math.floor(this.value) / this.mul
|
||||
}
|
||||
}
|
||||
add(event){
|
||||
if(this.value !== null){
|
||||
var newValue = this.value + this.eventNumber(event)
|
||||
if(newValue <= this.max){
|
||||
this.value = newValue
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
subtract(event){
|
||||
if(this.value !== null){
|
||||
var newValue = this.value - this.eventNumber(event)
|
||||
if(newValue >= this.min){
|
||||
this.value = newValue
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
eventNumber(event){
|
||||
return (event.ctrlKey ? 10 : 1) * (event.shiftKey ? 10 : 1) * (event.altKey ? 10 : 1) * 1
|
||||
}
|
||||
resetValue(){
|
||||
this.value = this.defaultValue
|
||||
this.update()
|
||||
}
|
||||
onchange(callback){
|
||||
this.callbacks.push(callback)
|
||||
}
|
||||
manualSet(){
|
||||
var number = parseFloat(this.input.value) * this.mul
|
||||
if(Number.isFinite(number) && this.min <= number && number <= this.max){
|
||||
this.value = number
|
||||
}
|
||||
this.update(false, true)
|
||||
}
|
||||
captureKeys(event){
|
||||
event.stopPropagation()
|
||||
}
|
||||
addTouch(element, callback){
|
||||
pageEvents.add(element, ["mousedown", "touchstart"], event => {
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
callback(event)
|
||||
})
|
||||
}
|
||||
addTouchRepeat(element, callback){
|
||||
this.addTouch(element, event => {
|
||||
var active = true
|
||||
var func = () => {
|
||||
active = false
|
||||
this.touchEnd.splice(this.touchEnd.indexOf(func), 1)
|
||||
}
|
||||
this.touchEnd.push(func)
|
||||
var repeat = delay => {
|
||||
if(active && this.touchEnd){
|
||||
callback(event)
|
||||
setTimeout(() => repeat(50), delay)
|
||||
}
|
||||
}
|
||||
repeat(400)
|
||||
})
|
||||
}
|
||||
removeTouch(element){
|
||||
pageEvents.remove(element, ["mousedown", "touchstart"])
|
||||
}
|
||||
clean(){
|
||||
this.removeTouch(this.plus)
|
||||
this.removeTouch(this.minus)
|
||||
this.removeTouch(this.reset)
|
||||
pageEvents.remove(this.input, ["touchstart", "touchend"])
|
||||
pageEvents.remove(window, ["mouseup", "touchstart", "touchend", "blur"], this.windowSymbol)
|
||||
pageEvents.remove(this.input, ["touchstart", "change", "keydown"])
|
||||
|
||||
delete this.input
|
||||
delete this.reset
|
||||
delete this.plus
|
||||
delete this.minus
|
||||
delete this.windowSymbol
|
||||
delete this.touchEnd
|
||||
}
|
||||
}
|
||||
862
public/src/js/game.js
Normal file
862
public/src/js/game.js
Normal file
@@ -0,0 +1,862 @@
|
||||
class Game{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(controller, selectedSong, songData){
|
||||
this.controller = controller
|
||||
this.selectedSong = selectedSong
|
||||
this.songData = songData
|
||||
this.elapsedTime = 0
|
||||
this.currentCircle = -1
|
||||
this.currentEvent = 0
|
||||
this.updateCurrentCircle()
|
||||
this.combo = 0
|
||||
this.rules = new GameRules(this)
|
||||
this.globalScore = {
|
||||
points: 0,
|
||||
good: 0,
|
||||
ok: 0,
|
||||
bad: 0,
|
||||
maxCombo: 0,
|
||||
drumroll: 0,
|
||||
gauge: 0,
|
||||
title: selectedSong.title,
|
||||
difficulty: this.rules.difficulty
|
||||
}
|
||||
var combo = this.songData.circles.filter(circle => {
|
||||
var type = circle.type
|
||||
return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active)
|
||||
}).length
|
||||
this.soulPoints = this.rules.soulPoints(combo)
|
||||
this.paused = false
|
||||
this.started = false
|
||||
this.mainMusicPlaying = false
|
||||
this.musicFadeOut = 0
|
||||
this.fadeOutStarted = false
|
||||
this.currentTimingPoint = 0
|
||||
this.branchNames = ["normal", "advanced", "master"]
|
||||
this.resetSection()
|
||||
this.gameLagSync = !this.controller.touchEnabled && !(/Firefox/.test(navigator.userAgent))
|
||||
|
||||
assets.songs.forEach(song => {
|
||||
if(song.id == selectedSong.folder){
|
||||
this.mainAsset = song.sound
|
||||
}
|
||||
})
|
||||
}
|
||||
run(){
|
||||
this.timeForDistanceCircle = 2500
|
||||
this.initTiming()
|
||||
this.view = this.controller.view
|
||||
}
|
||||
initTiming(){
|
||||
// Date when the chrono is started (before the game begins)
|
||||
var firstCircle = this.songData.circles[0]
|
||||
if(this.controller.calibrationMode){
|
||||
var offsetTime = 0
|
||||
}else{
|
||||
var offsetTime = Math.max(0, this.timeForDistanceCircle - (firstCircle ? firstCircle.ms : 0)) |0
|
||||
}
|
||||
if(this.controller.multiplayer){
|
||||
var syncWith = this.controller.syncWith
|
||||
var syncCircles = syncWith.game.songData.circles
|
||||
var syncOffsetTime = Math.max(0, this.timeForDistanceCircle - syncCircles[0].ms) |0
|
||||
offsetTime = Math.max(offsetTime, syncOffsetTime)
|
||||
}
|
||||
this.elapsedTime = -offsetTime
|
||||
// The real start for the game will start when chrono will reach 0
|
||||
this.startDate = Date.now() + offsetTime
|
||||
}
|
||||
update(){
|
||||
this.updateTime()
|
||||
// Main operations
|
||||
this.updateCirclesStatus()
|
||||
this.checkPlays()
|
||||
// Event operations
|
||||
this.whenFadeoutMusic()
|
||||
if(this.controller.multiplayer !== 2){
|
||||
this.whenLastCirclePlayed()
|
||||
}
|
||||
}
|
||||
getCircles(){
|
||||
return this.songData.circles
|
||||
}
|
||||
updateCirclesStatus(){
|
||||
var nextSet = false
|
||||
var ms = this.elapsedTime
|
||||
var circles = this.songData.circles
|
||||
var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1
|
||||
var index = 0
|
||||
|
||||
for(var i = startIndex; i < circles.length; i++){
|
||||
var circle = circles[i]
|
||||
if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){
|
||||
var type = circle.type
|
||||
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
|
||||
var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) + this.controller.audioLatency
|
||||
|
||||
if(ms >= circle.ms + this.controller.audioLatency){
|
||||
if(drumrollNotes && !circle.rendaPlayed && ms < endTime + this.controller.audioLatency){
|
||||
circle.rendaPlayed = true
|
||||
if(this.rules.difficulty === "easy"){
|
||||
assets.sounds["v_renda" + this.controller.snd].stop()
|
||||
this.controller.playSound("v_renda")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(circle.daiFailed && (ms >= circle.daiFailed.ms + this.rules.daiLeniency || ms > endTime)){
|
||||
this.checkScore(circle, circle.daiFailed.check)
|
||||
}else if(ms > endTime){
|
||||
if(!this.controller.autoPlayEnabled){
|
||||
if(drumrollNotes){
|
||||
if(circle.section && circle.timesHit === 0){
|
||||
this.resetSection()
|
||||
}
|
||||
circle.played(-1, false)
|
||||
this.updateCurrentCircle()
|
||||
if(this.controller.multiplayer === 1){
|
||||
var value = {
|
||||
pace: (ms - circle.ms - this.controller.audioLatency) / circle.timesHit
|
||||
}
|
||||
if(type === "drumroll" || type === "daiDrumroll"){
|
||||
value.kaAmount = circle.timesKa / circle.timesHit
|
||||
}
|
||||
p2.send("drumroll", value)
|
||||
}
|
||||
}else{
|
||||
this.skipNote(circle)
|
||||
this.updateCurrentCircle()
|
||||
}
|
||||
}
|
||||
}else if(!this.controller.autoPlayEnabled && !nextSet){
|
||||
nextSet = true
|
||||
this.currentCircle = i
|
||||
}
|
||||
if(index++ > 1){
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var branches = this.songData.branches
|
||||
if(branches){
|
||||
var force = this.controller.multiplayer === 2 ? p2 : this
|
||||
var measures = this.songData.measures
|
||||
if(this.controller.multiplayer === 2 || force.branch){
|
||||
if(!force.branchSet){
|
||||
force.branchSet = true
|
||||
if(branches.length){
|
||||
this.setBranch(branches[0], force.branch)
|
||||
}
|
||||
var view = this.controller.view
|
||||
var currentMeasure = view.branch
|
||||
for(var i = measures.length; i--;){
|
||||
var measure = measures[i]
|
||||
if(measure.nextBranch && measure.ms <= ms){
|
||||
currentMeasure = measure.nextBranch.active
|
||||
}
|
||||
}
|
||||
if(view.branch !== currentMeasure){
|
||||
if(!this.branchStatic){
|
||||
view.branchAnimate = {
|
||||
ms: ms,
|
||||
fromBranch: view.branch
|
||||
}
|
||||
}
|
||||
this.branchStatic = false
|
||||
view.branch = currentMeasure
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var i = 0; i < measures.length; i++){
|
||||
var measure = measures[i]
|
||||
if(measure.ms > ms){
|
||||
break
|
||||
}else{
|
||||
if(measure.nextBranch && !measure.gameChecked){
|
||||
measure.gameChecked = true
|
||||
var branch = measure.nextBranch
|
||||
if(branch.type){
|
||||
var accuracy = 0
|
||||
if(branch.type === "drumroll"){
|
||||
if(force.branch){
|
||||
var accuracy = Math.max(0, branch.requirement[force.branch])
|
||||
}else{
|
||||
var accuracy = this.sectionDrumroll
|
||||
}
|
||||
}else if(this.sectionNotes.length !== 0){
|
||||
if(force.branch){
|
||||
var accuracy = Math.max(0, Math.min(100, branch.requirement[force.branch]))
|
||||
}else{
|
||||
var accuracy = this.sectionNotes.reduce((a, b) => a + b) / this.sectionNotes.length * 100
|
||||
}
|
||||
}
|
||||
if(accuracy >= branch.requirement.master){
|
||||
this.setBranch(branch, "master")
|
||||
}else if(accuracy >= branch.requirement.advanced){
|
||||
this.setBranch(branch, "advanced")
|
||||
}else{
|
||||
this.setBranch(branch, "normal")
|
||||
}
|
||||
}else if(this.controller.multiplayer === 1){
|
||||
p2.send("branch", "normal")
|
||||
}
|
||||
}
|
||||
if(this.controller.lyrics){
|
||||
if(!measure.branch){
|
||||
this.controller.lyrics.branch = null
|
||||
}else if(measure.branch.active){
|
||||
this.controller.lyrics.branch = measure.branch.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.controller.lyrics){
|
||||
this.controller.lyrics.update(ms)
|
||||
}
|
||||
}
|
||||
fixNoteStream(keysDon){
|
||||
var circleIsNote = circle => {
|
||||
var type = circle.type
|
||||
return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa"
|
||||
}
|
||||
var correctNote = circle => {
|
||||
var type = circle.type
|
||||
return keysDon ? (type === "don" || type === "daiDon") : (type === "ka" || type === "daiKa")
|
||||
}
|
||||
var ms = this.elapsedTime
|
||||
var circles = this.songData.circles
|
||||
|
||||
for(var i = this.currentCircle + 1; i < circles.length; i++){
|
||||
var circle = circles[i]
|
||||
var relative = ms - circle.ms - this.controller.audioLatency
|
||||
if(!circle.branch || circle.branch.active){
|
||||
if((!circleIsNote(circle) || relative < -this.rules.bad)){
|
||||
break
|
||||
}else if(Math.abs(relative) < this.rules.ok && correctNote(circle)){
|
||||
for(var j = this.currentCircle; j < i; j++){
|
||||
var circle = circles[j]
|
||||
if(circle && !circle.branch || circle.branch.active){
|
||||
this.skipNote(circles[j])
|
||||
}
|
||||
}
|
||||
this.currentCircle = i
|
||||
return circles[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skipNote(circle){
|
||||
if(circle.section){
|
||||
this.resetSection()
|
||||
}
|
||||
circle.played(-1, circle.type === "daiDon" || circle.type === "daiKa")
|
||||
this.sectionNotes.push(0)
|
||||
this.controller.displayScore(0, true)
|
||||
this.updateCombo(0)
|
||||
this.updateGlobalScore(0, 1)
|
||||
if(this.controller.multiplayer === 1){
|
||||
p2.send("note", {
|
||||
score: -1
|
||||
})
|
||||
}
|
||||
}
|
||||
checkPlays(){
|
||||
var circles = this.songData.circles
|
||||
var circle = circles[this.currentCircle]
|
||||
|
||||
if(this.controller.autoPlayEnabled){
|
||||
while(circle && this.controller.autoPlay(circle)){
|
||||
circle = circles[this.currentCircle]
|
||||
}
|
||||
return
|
||||
}
|
||||
var keys = this.controller.getKeys()
|
||||
|
||||
var don_l = keys["don_l"] && !this.controller.isWaiting("don_l", "score")
|
||||
var don_r = keys["don_r"] && !this.controller.isWaiting("don_r", "score")
|
||||
var ka_l = keys["ka_l"] && !this.controller.isWaiting("ka_l", "score")
|
||||
var ka_r = keys["ka_r"] && !this.controller.isWaiting("ka_r", "score")
|
||||
|
||||
var checkDon = () => {
|
||||
if(don_l && don_r){
|
||||
this.checkKey(["don_l", "don_r"], circle, "daiDon")
|
||||
}else if(don_l){
|
||||
this.checkKey(["don_l"], circle, "don")
|
||||
}else if(don_r){
|
||||
this.checkKey(["don_r"], circle, "don")
|
||||
}
|
||||
}
|
||||
var checkKa = () => {
|
||||
if(ka_l && ka_r){
|
||||
this.checkKey(["ka_l", "ka_r"], circle, "daiKa")
|
||||
}else if(ka_l){
|
||||
this.checkKey(["ka_l"], circle, "ka")
|
||||
}else if(ka_r){
|
||||
this.checkKey(["ka_r"], circle, "ka")
|
||||
}
|
||||
}
|
||||
var keyTime = this.controller.getKeyTime()
|
||||
if(keyTime["don"] >= keyTime["ka"]){
|
||||
checkDon()
|
||||
checkKa()
|
||||
}else{
|
||||
checkKa()
|
||||
checkDon()
|
||||
}
|
||||
}
|
||||
checkKey(keyCodes, circle, check){
|
||||
if(circle && !circle.isPlayed){
|
||||
if(!this.checkScore(circle, check)){
|
||||
return
|
||||
}
|
||||
}
|
||||
keyCodes.forEach(keyCode => {
|
||||
this.controller.waitForKeyup(keyCode, "score")
|
||||
})
|
||||
}
|
||||
checkScore(circle, check){
|
||||
var ms = this.elapsedTime
|
||||
var type = circle.type
|
||||
|
||||
var keysDon = check === "don" || check === "daiDon"
|
||||
var keysKa = check === "ka" || check === "daiKa"
|
||||
var keyDai = check === "daiDon" || check === "daiKa"
|
||||
var typeDon = type === "don" || type === "daiDon"
|
||||
var typeKa = type === "ka" || type === "daiKa"
|
||||
var typeDai = type === "daiDon" || type === "daiKa"
|
||||
|
||||
var keyTime = this.controller.getKeyTime()
|
||||
var currentTime = circle.daiFailed ? circle.daiFailed.ms : keysDon ? keyTime["don"] : keyTime["ka"]
|
||||
var relative = currentTime - circle.ms - this.controller.audioLatency
|
||||
|
||||
if(relative >= this.rules.ok){
|
||||
var fixedNote = this.fixNoteStream(keysDon)
|
||||
if(fixedNote){
|
||||
return this.checkScore(fixedNote, check)
|
||||
}
|
||||
}
|
||||
|
||||
if(typeDon || typeKa){
|
||||
if(-this.rules.bad >= relative || relative >= this.rules.bad){
|
||||
return true
|
||||
}
|
||||
var score = 0
|
||||
if(keysDon && typeDon || keysKa && typeKa){
|
||||
var circleStatus = -1
|
||||
relative = Math.abs(relative)
|
||||
if(relative < this.rules.good){
|
||||
circleStatus = 450
|
||||
}else if(relative < this.rules.ok){
|
||||
circleStatus = 230
|
||||
}else if(relative < this.rules.bad){
|
||||
circleStatus = 0
|
||||
}
|
||||
if(typeDai && !keyDai){
|
||||
if(this.controller.easierBigNotes){
|
||||
// Taiko Force Lv5 can't hit both Dons at the same time, so dai offered
|
||||
keyDai = true
|
||||
}else if(!circle.daiFailed){
|
||||
circle.daiFailed = {
|
||||
ms: ms,
|
||||
status: circleStatus,
|
||||
check: check
|
||||
}
|
||||
return false
|
||||
}else if(ms < circle.daiFailed.ms + this.rules.daiLeniency){
|
||||
return false
|
||||
}else{
|
||||
circleStatus = circle.daiFailed.status
|
||||
}
|
||||
}
|
||||
if(circleStatus === 230 || circleStatus === 450){
|
||||
score = circleStatus
|
||||
}
|
||||
circle.played(score, score === 0 ? typeDai : keyDai)
|
||||
this.controller.displayScore(score, false, typeDai && keyDai)
|
||||
}else{
|
||||
var keyTime = this.controller.getKeyTime()
|
||||
var keyTimeRelative = Math.abs(keyTime.don - keyTime.ka)
|
||||
if(Math.abs(relative) >= (keyTimeRelative <= 25 ? this.rules.bad : this.rules.good)){
|
||||
return true
|
||||
}
|
||||
circle.played(-1, typeDai)
|
||||
this.controller.displayScore(score, true, false)
|
||||
}
|
||||
this.updateCombo(score)
|
||||
this.updateGlobalScore(score, typeDai && keyDai ? 2 : 1, circle.gogoTime)
|
||||
this.updateCurrentCircle()
|
||||
if(circle.section){
|
||||
this.resetSection()
|
||||
}
|
||||
this.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0))
|
||||
if(this.controller.multiplayer === 1){
|
||||
var value = {
|
||||
score: score,
|
||||
ms: circle.ms - currentTime - this.controller.audioLatency,
|
||||
dai: typeDai ? (keyDai ? 2 : 1) : 0
|
||||
}
|
||||
if((!keysDon || !typeDon) && (!keysKa || !typeKa)){
|
||||
value.reverse = true
|
||||
}
|
||||
p2.send("note", value)
|
||||
}
|
||||
}else{
|
||||
if(circle.ms + this.controller.audioLatency > currentTime || currentTime > circle.endTime + this.controller.audioLatency){
|
||||
return true
|
||||
}
|
||||
if(keysDon && type === "balloon"){
|
||||
this.checkBalloon(circle)
|
||||
if(check === "daiDon" && !circle.isPlayed){
|
||||
this.checkBalloon(circle)
|
||||
}
|
||||
}else if((keysDon || keysKa) && (type === "drumroll" || type === "daiDrumroll")){
|
||||
this.checkDrumroll(circle, keysKa)
|
||||
if(keyDai){
|
||||
this.checkDrumroll(circle, keysKa)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
checkBalloon(circle){
|
||||
if(circle.timesHit >= circle.requiredHits - 1){
|
||||
var score = 5000
|
||||
this.updateCurrentCircle()
|
||||
circle.hit()
|
||||
circle.played(score)
|
||||
if(this.controller.multiplayer == 1){
|
||||
p2.send("drumroll", {
|
||||
pace: (this.elapsedTime - circle.ms + this.controller.audioLatency) / circle.timesHit
|
||||
})
|
||||
}
|
||||
}else{
|
||||
var score = 300
|
||||
circle.hit()
|
||||
}
|
||||
this.globalScore.drumroll++
|
||||
this.sectionDrumroll++
|
||||
this.globalScore.points += score
|
||||
this.view.setDarkBg(false)
|
||||
}
|
||||
checkDrumroll(circle, keysKa){
|
||||
var ms = this.elapsedTime
|
||||
var dai = circle.type === "daiDrumroll"
|
||||
var score = 100
|
||||
if(circle.section && circle.timesHit === 0){
|
||||
this.resetSection()
|
||||
}
|
||||
circle.hit(keysKa)
|
||||
var keyTime = this.controller.getKeyTime()
|
||||
if(circle.type === "drumroll"){
|
||||
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
|
||||
}else{
|
||||
var sound = keyTime["don"] > keyTime["ka"] ? "daiDon" : "daiKa"
|
||||
}
|
||||
var circleAnim = new Circle({
|
||||
id: 0,
|
||||
start: ms,
|
||||
type: sound,
|
||||
txt: "",
|
||||
speed: circle.speed,
|
||||
gogoTime: circle.gogoTime,
|
||||
fixedPos: document.hasFocus()
|
||||
})
|
||||
circleAnim.played(score, dai)
|
||||
circleAnim.animate(ms)
|
||||
this.view.drumroll.push(circleAnim)
|
||||
this.globalScore.drumroll++
|
||||
this.sectionDrumroll++
|
||||
this.globalScore.points += score * (dai ? 2 : 1)
|
||||
this.view.setDarkBg(false)
|
||||
}
|
||||
getLastCircle(circles){
|
||||
for(var i = circles.length; i--;){
|
||||
return circles[i]
|
||||
}
|
||||
}
|
||||
whenLastCirclePlayed(){
|
||||
var ms = this.elapsedTime
|
||||
if(!this.lastCircle){
|
||||
var circles = this.songData.circles
|
||||
var circle = this.getLastCircle(circles)
|
||||
this.lastCircle = circle ? circle.endTime : 0
|
||||
if(this.controller.multiplayer){
|
||||
var syncWith = this.controller.syncWith
|
||||
var syncCircles = syncWith.game.songData.circles
|
||||
circle = this.getLastCircle(syncCircles)
|
||||
var syncLastCircle = circle ? circle.endTime : 0
|
||||
if(syncLastCircle > this.lastCircle){
|
||||
this.lastCircle = syncLastCircle
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!this.fadeOutStarted && ms >= this.lastCircle + 2000 + this.controller.audioLatency){
|
||||
this.fadeOutStarted = ms
|
||||
if(this.controller.multiplayer){
|
||||
this.controller.syncWith.game.fadeOutStarted = ms
|
||||
}
|
||||
}
|
||||
}
|
||||
whenFadeoutMusic(){
|
||||
var started = this.fadeOutStarted
|
||||
if(started){
|
||||
var ms = this.elapsedTime
|
||||
var duration = this.mainAsset ? this.mainAsset.duration : 0
|
||||
var musicDuration = duration * 1000 - this.controller.offset
|
||||
if(this.musicFadeOut === 0){
|
||||
if(this.controller.multiplayer === 1){
|
||||
var obj = this.getGlobalScore()
|
||||
obj.name = account.loggedIn ? account.displayName : null
|
||||
p2.send("gameresults", obj)
|
||||
}
|
||||
this.musicFadeOut++
|
||||
}else if(this.musicFadeOut === 1 && ms >= started + 1600){
|
||||
this.controller.gameEnded()
|
||||
if(!p2.session && this.controller.multiplayer === 1){
|
||||
p2.send("gameend")
|
||||
}
|
||||
this.musicFadeOut++
|
||||
}else if(this.musicFadeOut === 2 && (ms >= Math.max(started + 8600, Math.min(started + 8600 + 5000, musicDuration + 250)))){
|
||||
this.controller.displayResults()
|
||||
this.musicFadeOut++
|
||||
}else if(this.musicFadeOut === 3 && (ms >= Math.max(started + 9600, Math.min(started + 9600 + 5000, musicDuration + 1250)))){
|
||||
this.controller.clean()
|
||||
if(this.controller.scoresheet){
|
||||
this.controller.scoresheet.startRedraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
playMainMusic(){
|
||||
var ms = this.elapsedTime + this.controller.offset
|
||||
if(!this.mainMusicPlaying && (!this.fadeOutStarted || ms < this.fadeOutStarted + 1600)){
|
||||
if(this.calibrationState === "audio"){
|
||||
var beatInterval = this.controller.view.beatInterval
|
||||
var startAt = ms % beatInterval
|
||||
var duration = this.mainAsset.duration * 1000
|
||||
if(startAt < duration){
|
||||
this.mainAsset.playLoop(0, false, startAt / 1000, 0, beatInterval / 1000)
|
||||
}else{
|
||||
this.mainAsset.playLoop((startAt - duration) / 1000, false, 0, 0, beatInterval / 1000)
|
||||
}
|
||||
}else if(this.controller.multiplayer !== 2 && this.mainAsset){
|
||||
this.mainAsset.play((ms < 0 ? -ms : 0) / 1000, false, Math.max(0, ms / 1000))
|
||||
}
|
||||
this.mainMusicPlaying = true
|
||||
}
|
||||
}
|
||||
togglePause(forcePause, pauseMove, noSound){
|
||||
if(!this.paused){
|
||||
if(forcePause === false){
|
||||
return
|
||||
}
|
||||
if(!noSound){
|
||||
this.controller.playSound("se_pause", 0, true)
|
||||
}
|
||||
this.paused = true
|
||||
this.latestDate = Date.now()
|
||||
if(this.mainAsset){
|
||||
this.mainAsset.stop()
|
||||
}
|
||||
this.mainMusicPlaying = false
|
||||
this.view.pauseMove(pauseMove || 0, true)
|
||||
this.view.gameDiv.classList.add("game-paused")
|
||||
this.view.lastMousemove = this.view.getMS()
|
||||
this.view.cursorHidden = false
|
||||
pageEvents.send("pause")
|
||||
}else if(!forcePause){
|
||||
if(forcePause !== false && this.calibrationState && ["audioHelp", "audioComplete", "videoHelp", "videoComplete", "results"].indexOf(this.calibrationState) !== -1){
|
||||
return
|
||||
}
|
||||
if(this.calibrationState === "audioHelp" || this.calibrationState === "videoHelp"){
|
||||
this.calibrationState = this.calibrationState === "audioHelp" ? "audio" : "video"
|
||||
this.controller.view.pauseOptions = strings.pauseOptions
|
||||
this.controller.playSound("se_don", 0, true)
|
||||
}else if(!noSound){
|
||||
this.controller.playSound("se_cancel", 0, true)
|
||||
}
|
||||
this.paused = false
|
||||
var currentDate = Date.now()
|
||||
this.startDate += currentDate - this.latestDate
|
||||
this.sndTime = currentDate - snd.buffer.getTime() * 1000
|
||||
this.view.gameDiv.classList.remove("game-paused")
|
||||
this.view.pointer()
|
||||
pageEvents.send("unpause", currentDate - this.latestDate)
|
||||
}
|
||||
}
|
||||
isPaused(){
|
||||
return this.paused
|
||||
}
|
||||
updateTime(){
|
||||
// Refreshed date
|
||||
var ms = this.elapsedTime
|
||||
if(ms >= 0 && !this.started){
|
||||
this.startDate = Date.now()
|
||||
this.elapsedTime = this.getAccurateTime()
|
||||
this.started = true
|
||||
this.sndTime = this.startDate - snd.buffer.getTime() * 1000
|
||||
}else if(ms < 0 || ms >= 0 && this.started){
|
||||
var currentDate = Date.now()
|
||||
if(this.gameLagSync){
|
||||
var sndTime = currentDate - snd.buffer.getTime() * 1000
|
||||
var lag = sndTime - this.sndTime
|
||||
if(Math.abs(lag) >= 50){
|
||||
this.startDate += lag
|
||||
this.sndTime = sndTime
|
||||
pageEvents.send("game-lag", lag)
|
||||
}
|
||||
}
|
||||
this.elapsedTime = currentDate - this.startDate
|
||||
}
|
||||
}
|
||||
getAccurateTime(){
|
||||
if(this.isPaused()){
|
||||
return this.elapsedTime
|
||||
}else{
|
||||
return Date.now() - this.startDate
|
||||
}
|
||||
}
|
||||
getCircles(){
|
||||
return this.songData.circles
|
||||
}
|
||||
updateCurrentCircle(){
|
||||
var circles = this.songData.circles
|
||||
do{
|
||||
var circle = circles[++this.currentCircle]
|
||||
}while(circle && (circle.branch && !circle.branch.active))
|
||||
}
|
||||
getCurrentCircle(){
|
||||
return this.currentCircle
|
||||
}
|
||||
updateCombo(score){
|
||||
if(score !== 0){
|
||||
this.combo++
|
||||
}else{
|
||||
this.combo = 0
|
||||
}
|
||||
if(this.combo > this.globalScore.maxCombo){
|
||||
this.globalScore.maxCombo = this.combo
|
||||
}
|
||||
if(this.combo === 50 || this.combo > 0 && this.combo % 100 === 0 && this.combo <= 5000){
|
||||
this.controller.playSound("v_combo_" + this.combo)
|
||||
}
|
||||
if (this.songData.scoremode == 2 && this.combo > 0 && this.combo % 100 == 0) {
|
||||
this.globalScore.points += 10000;
|
||||
}
|
||||
this.view.updateCombo(this.combo)
|
||||
}
|
||||
getCombo(){
|
||||
return this.combo
|
||||
}
|
||||
getGlobalScore(){
|
||||
return this.globalScore
|
||||
}
|
||||
updateGlobalScore(score, multiplier, gogoTime){
|
||||
// Circle score
|
||||
switch(score){
|
||||
case 450:
|
||||
this.globalScore.good++
|
||||
this.globalScore.gauge += this.soulPoints.good
|
||||
break
|
||||
case 230:
|
||||
this.globalScore.ok++
|
||||
this.globalScore.gauge += this.soulPoints.ok
|
||||
break
|
||||
case 0:
|
||||
this.globalScore.bad++
|
||||
this.globalScore.gauge += this.soulPoints.bad
|
||||
break
|
||||
}
|
||||
if (this.songData.scoremode) {
|
||||
switch (score) {
|
||||
case 450:
|
||||
score = this.songData.scoreinit;
|
||||
break;
|
||||
case 230:
|
||||
score = Math.floor(this.songData.scoreinit / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Gauge update
|
||||
if(this.globalScore.gauge < 0){
|
||||
this.globalScore.gauge = 0
|
||||
}else if(this.globalScore.gauge > 10000){
|
||||
this.globalScore.gauge = 10000
|
||||
}
|
||||
// Points update
|
||||
if (this.songData.scoremode == 2) {
|
||||
var diff_mul = 0;
|
||||
if (this.combo >= 100) {
|
||||
diff_mul = 8;
|
||||
} else if (this.combo >= 50) {
|
||||
diff_mul = 4;
|
||||
} else if (this.combo >= 30) {
|
||||
diff_mul = 2;
|
||||
} else if (this.combo >= 10) {
|
||||
diff_mul = 1;
|
||||
}
|
||||
score += this.songData.scorediff * diff_mul;
|
||||
} else {
|
||||
score += Math.max(0, Math.floor((Math.min(this.combo, 100) - 1) / 10) * (this.songData.scoremode ? this.songData.scorediff : 100));
|
||||
}
|
||||
|
||||
if(gogoTime){
|
||||
multiplier *= 1.2
|
||||
}
|
||||
this.globalScore.points += Math.floor(score * multiplier / 10) * 10
|
||||
}
|
||||
setBranch(currentBranch, activeName){
|
||||
var pastActive = currentBranch.active
|
||||
var ms = currentBranch.ms
|
||||
for(var i = 0; i < this.songData.branches.length; i++){
|
||||
var branch = this.songData.branches[i]
|
||||
if(branch.ms >= ms){
|
||||
var relevantName = activeName
|
||||
var req = branch.requirement
|
||||
var noNormal = req.advanced <= 0
|
||||
var noAdvanced = req.master <= 0 || req.advanced >= req.master || branch.type === "accuracy" && req.advanced > 100
|
||||
var noMaster = branch.type === "accuracy" && req.master > 100
|
||||
if(relevantName === "normal" && noNormal){
|
||||
relevantName = noAdvanced ? "master" : "advanced"
|
||||
}
|
||||
if(relevantName === "advanced" && noAdvanced){
|
||||
relevantName = noMaster ? "normal" : "master"
|
||||
}
|
||||
if(relevantName === "master" && noMaster){
|
||||
relevantName = noAdvanced ? "normal" : "advanced"
|
||||
}
|
||||
for(var j in this.branchNames){
|
||||
var name = this.branchNames[j]
|
||||
if(name in branch){
|
||||
branch[name].active = name === relevantName
|
||||
}
|
||||
}
|
||||
if(branch === currentBranch){
|
||||
activeName = relevantName
|
||||
}
|
||||
branch.active = relevantName
|
||||
}
|
||||
}
|
||||
var circles = this.songData.circles
|
||||
var circle = circles[this.currentCircle]
|
||||
if(!circle || circle.branch === currentBranch[pastActive]){
|
||||
var ms = this.elapsedTime
|
||||
var closestCircle = circles.findIndex(circle => {
|
||||
return (!circle.branch || circle.branch.active) && circle.endTime + this.controller.audioLatency >= ms
|
||||
})
|
||||
if(closestCircle !== -1){
|
||||
this.currentCircle = closestCircle
|
||||
}
|
||||
}
|
||||
if(this.controller.multiplayer === 1){
|
||||
p2.send("branch", activeName)
|
||||
}
|
||||
}
|
||||
resetSection(){
|
||||
this.sectionNotes = []
|
||||
this.sectionDrumroll = 0
|
||||
}
|
||||
clearKeyTime(){
|
||||
var keyboard = this.controller.keyboard
|
||||
for(var key in keyboard.keyTime){
|
||||
keyboard.keys[key] = null
|
||||
keyboard.keyTime[key] = -Infinity
|
||||
}
|
||||
}
|
||||
calibration(){
|
||||
var view = this.controller.view
|
||||
if(!this.calibrationState){
|
||||
this.controller.parsedSongData.measures = []
|
||||
this.calibrationProgress = {
|
||||
audio: 0,
|
||||
video: 0,
|
||||
requirement: 40
|
||||
}
|
||||
this.calibrationReset("audio", true)
|
||||
}
|
||||
var progress = this.calibrationProgress
|
||||
var state = this.calibrationState
|
||||
switch(state){
|
||||
case "audio":
|
||||
case "video":
|
||||
if(state === "audio" && !this.mainAsset){
|
||||
this.mainAsset = assets.sounds["se_calibration"]
|
||||
this.mainMusicPlaying = false
|
||||
}
|
||||
if(progress.hit >= progress.requirement){
|
||||
var reduced = 0
|
||||
for(var i = 2; i < progress.offsets.length; i++){
|
||||
reduced += progress.offsets[i]
|
||||
}
|
||||
progress[state] = Math.max(0, Math.round(reduced / progress.offsets.length - 2))
|
||||
this.calibrationState += "Complete"
|
||||
view.pauseOptions = []
|
||||
this.clearKeyTime()
|
||||
this.togglePause(true, 1)
|
||||
this.mainAsset = null
|
||||
}
|
||||
break
|
||||
case "audioComplete":
|
||||
case "videoComplete":
|
||||
if(Date.now() - this.latestDate > 3000){
|
||||
var audioComplete = this.calibrationState === "audioComplete"
|
||||
this.controller.playSound("se_pause", 0, true)
|
||||
if(audioComplete){
|
||||
this.calibrationReset("video")
|
||||
}else{
|
||||
view.pauseOptions = [
|
||||
strings.calibration.retryPrevious,
|
||||
strings.calibration.finish
|
||||
]
|
||||
}
|
||||
this.calibrationState = audioComplete ? "videoHelp" : "results"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
calibrationHit(ms){
|
||||
var progress = this.calibrationProgress
|
||||
var beatInterval = this.controller.view.beatInterval
|
||||
var current = Math.floor((ms + 100) / beatInterval)
|
||||
if(current !== progress.last){
|
||||
var offset = ((ms + 100) % beatInterval) - 100
|
||||
var offsets = progress.offsets
|
||||
if(offsets.length >= progress.requirement){
|
||||
offsets.shift()
|
||||
}
|
||||
offsets.push(offset)
|
||||
progress.hit++
|
||||
progress.last = current
|
||||
this.globalScore.gauge = 10000 / (progress.requirement / progress.hit)
|
||||
}
|
||||
}
|
||||
calibrationReset(to, togglePause){
|
||||
var view = this.controller.view
|
||||
this.songData.circles = []
|
||||
view.pauseOptions = [
|
||||
to === "audio" ? strings.calibration.back : strings.calibration.retryPrevious,
|
||||
strings.calibration.start
|
||||
]
|
||||
this.calibrationState = to + "Help"
|
||||
var progress = this.calibrationProgress
|
||||
progress.offsets = []
|
||||
progress.hit = 0
|
||||
progress.last = null
|
||||
this.globalScore.gauge = 0
|
||||
if(to === "video"){
|
||||
this.clearKeyTime()
|
||||
this.initTiming()
|
||||
this.latestDate = this.startDate
|
||||
this.elapsedTime = 0
|
||||
view.ms = 0
|
||||
}
|
||||
if(togglePause){
|
||||
this.togglePause(true, 1, true)
|
||||
}else{
|
||||
view.pauseMove(1, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
253
public/src/js/gameinput.js
Normal file
253
public/src/js/gameinput.js
Normal file
@@ -0,0 +1,253 @@
|
||||
class GameInput{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(controller){
|
||||
this.controller = controller
|
||||
this.game = this.controller.game
|
||||
|
||||
this.keyboard = new Keyboard({
|
||||
ka_l: ["ka_l"],
|
||||
don_l: ["don_l"],
|
||||
don_r: ["don_r"],
|
||||
ka_r: ["ka_r"],
|
||||
pause: ["q", "esc"],
|
||||
back: ["backspace"],
|
||||
previous: ["left", "up"],
|
||||
next: ["right", "down"],
|
||||
confirm: ["enter", "space"]
|
||||
}, this.keyPress.bind(this))
|
||||
this.keys = {}
|
||||
this.waitKeyupScore = {}
|
||||
this.waitKeyupSound = {}
|
||||
this.waitKeyupMenu = {}
|
||||
this.keyTime = {
|
||||
"don": -Infinity,
|
||||
"ka": -Infinity
|
||||
}
|
||||
this.keyboardEvents = 0
|
||||
|
||||
var layout = settings.getItem("gamepadLayout")
|
||||
if(layout === "b"){
|
||||
var gameBtn = {
|
||||
don_l: ["d", "r", "ls"],
|
||||
don_r: ["a", "x", "rs"],
|
||||
ka_l: ["u", "l", "lb", "lt"],
|
||||
ka_r: ["b", "y", "rb", "rt"]
|
||||
}
|
||||
}else if(layout === "c"){
|
||||
var gameBtn = {
|
||||
don_l: ["d", "l", "ls"],
|
||||
don_r: ["a", "b", "rs"],
|
||||
ka_l: ["u", "r", "lb", "lt"],
|
||||
ka_r: ["x", "y", "rb", "rt"]
|
||||
}
|
||||
}else{
|
||||
var gameBtn = {
|
||||
don_l: ["u", "d", "l", "r", "ls"],
|
||||
don_r: ["a", "b", "x", "y", "rs"],
|
||||
ka_l: ["lb", "lt"],
|
||||
ka_r: ["rb", "rt"]
|
||||
}
|
||||
}
|
||||
this.gamepad = new Gamepad(gameBtn)
|
||||
this.gamepadInterval = setInterval(this.gamepadKeys.bind(this), 1000 / 60 / 2)
|
||||
|
||||
this.gamepadMenu = new Gamepad({
|
||||
cancel: ["a"],
|
||||
confirm: ["b", "ls", "rs"],
|
||||
previous: ["u", "l", "lb", "lt", "lsu", "lsl"],
|
||||
next: ["d", "r", "rb", "rt", "lsd", "lsr"],
|
||||
pause: ["start"]
|
||||
})
|
||||
|
||||
if(controller.multiplayer === 1){
|
||||
pageEvents.add(window, "beforeunload", event => {
|
||||
if(p2.otherConnected){
|
||||
pageEvents.send("p2-abandoned", event)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
keyPress(pressed, name){
|
||||
if(!this.controller.autoPlayEnabled || this.game.isPaused() || name !== "don_l" && name !== "don_r" && name !== "ka_l" && name !== "ka_r"){
|
||||
this.setKey(pressed, name, this.game.getAccurateTime())
|
||||
}
|
||||
}
|
||||
checkGameKeys(){
|
||||
if(this.controller.autoPlayEnabled){
|
||||
this.checkKeySound("don_l", "don")
|
||||
this.checkKeySound("don_r", "don")
|
||||
this.checkKeySound("ka_l", "ka")
|
||||
this.checkKeySound("ka_r", "ka")
|
||||
}
|
||||
}
|
||||
gamepadKeys(){
|
||||
if(!this.game.isPaused() && !this.controller.autoPlayEnabled){
|
||||
this.gamepad.play((pressed, name) => {
|
||||
if(pressed){
|
||||
if(this.keys[name]){
|
||||
this.setKey(false, name)
|
||||
}
|
||||
this.setKey(true, name, this.game.getAccurateTime())
|
||||
}else{
|
||||
this.setKey(false, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
checkMenuKeys(){
|
||||
if(!this.controller.multiplayer && !this.locked && this.controller.view.pauseOptions.length !== 0){
|
||||
var moveMenu = 0
|
||||
var ms = this.game.getAccurateTime()
|
||||
this.gamepadMenu.play((pressed, name) => {
|
||||
if(pressed){
|
||||
if(this.game.isPaused()){
|
||||
if(name === "cancel"){
|
||||
this.locked = true
|
||||
return setTimeout(() => {
|
||||
this.controller.togglePause()
|
||||
this.locked = false
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
if(this.keys[name]){
|
||||
this.setKey(false, name)
|
||||
}
|
||||
this.setKey(true, name, ms)
|
||||
}else{
|
||||
this.setKey(false, name)
|
||||
}
|
||||
})
|
||||
this.checkKey("pause", "menu", () => {
|
||||
this.controller.togglePause()
|
||||
for(var key in this.keyTime){
|
||||
this.keys[key] = null
|
||||
this.keyTime[key] = -Infinity
|
||||
}
|
||||
})
|
||||
var moveMenuMinus = () => {
|
||||
moveMenu = -1
|
||||
}
|
||||
var moveMenuPlus = () => {
|
||||
moveMenu = 1
|
||||
}
|
||||
var moveMenuConfirm = () => {
|
||||
if(this.game.isPaused()){
|
||||
this.locked = true
|
||||
setTimeout(() => {
|
||||
this.controller.view.pauseConfirm()
|
||||
this.locked = false
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
this.checkKey("previous", "menu", moveMenuMinus)
|
||||
this.checkKey("ka_l", "menu", moveMenuMinus)
|
||||
this.checkKey("next", "menu", moveMenuPlus)
|
||||
this.checkKey("ka_r", "menu", moveMenuPlus)
|
||||
this.checkKey("confirm", "menu", moveMenuConfirm)
|
||||
this.checkKey("don_l", "menu", moveMenuConfirm)
|
||||
this.checkKey("don_r", "menu", moveMenuConfirm)
|
||||
if(moveMenu && this.game.isPaused()){
|
||||
this.controller.playSound("se_ka", 0, true)
|
||||
this.controller.view.pauseMove(moveMenu)
|
||||
}
|
||||
}
|
||||
if(this.controller.multiplayer !== 2){
|
||||
this.checkKey("back", "menu", () => {
|
||||
if(this.controller.multiplayer === 1 && p2.otherConnected){
|
||||
p2.send("gameend")
|
||||
pageEvents.send("p2-abandoned")
|
||||
}
|
||||
this.controller.togglePause()
|
||||
this.controller.songSelection()
|
||||
})
|
||||
}
|
||||
}
|
||||
checkKey(name, type, callback){
|
||||
if(this.keys[name] && !this.isWaiting(name, type)){
|
||||
this.waitForKeyup(name, type)
|
||||
callback()
|
||||
}
|
||||
}
|
||||
checkKeySound(name, sound){
|
||||
this.checkKey(name, "sound", () => {
|
||||
var circles = this.controller.getCircles()
|
||||
var circle = circles[this.controller.getCurrentCircle()]
|
||||
var currentTime = this.keyTime[name]
|
||||
this.keyTime[sound] = currentTime
|
||||
if(circle && !circle.isPlayed){
|
||||
if(circle.type === "balloon"){
|
||||
if(sound === "don" && circle.requiredHits - circle.timesHit <= 1){
|
||||
this.controller.playSound("se_balloon")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
this.controller.playSound("neiro_1_" + sound)
|
||||
})
|
||||
}
|
||||
getKeys(){
|
||||
return this.keys
|
||||
}
|
||||
setKey(pressed, name, ms){
|
||||
if(pressed){
|
||||
this.keys[name] = true
|
||||
this.waitKeyupScore[name] = false
|
||||
this.waitKeyupSound[name] = false
|
||||
this.waitKeyupMenu[name] = false
|
||||
if(this.game.isPaused()){
|
||||
return
|
||||
}
|
||||
this.keyTime[name] = ms
|
||||
var calibrationState = this.game.calibrationState
|
||||
var calibration = calibrationState && !this.game.paused
|
||||
if(name == "don_l" || name == "don_r"){
|
||||
if(calibration){
|
||||
this.game.calibrationHit(ms)
|
||||
}else{
|
||||
this.checkKeySound(name, "don")
|
||||
}
|
||||
this.keyboardEvents++
|
||||
}else if(name == "ka_l" || name == "ka_r"){
|
||||
if(!calibration){
|
||||
this.checkKeySound(name, "ka")
|
||||
}
|
||||
this.keyboardEvents++
|
||||
}
|
||||
}
|
||||
}
|
||||
isWaiting(name, type){
|
||||
if(type === "score"){
|
||||
return this.waitKeyupScore[name]
|
||||
}else if(type === "sound"){
|
||||
return this.waitKeyupSound[name]
|
||||
}else if(type === "menu"){
|
||||
return this.waitKeyupMenu[name]
|
||||
}
|
||||
}
|
||||
waitForKeyup(name, type){
|
||||
if(!this.keys[name]){
|
||||
return
|
||||
}
|
||||
if(type === "score"){
|
||||
this.waitKeyupScore[name] = true
|
||||
}else if(type === "sound"){
|
||||
this.waitKeyupSound[name] = true
|
||||
}else if(type === "menu"){
|
||||
this.waitKeyupMenu[name] = true
|
||||
}
|
||||
}
|
||||
getKeyTime(){
|
||||
return this.keyTime
|
||||
}
|
||||
clean(){
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
this.gamepadMenu.clean()
|
||||
clearInterval(this.gamepadInterval)
|
||||
if(this.controller.multiplayer === 1){
|
||||
pageEvents.remove(window, "beforeunload")
|
||||
}
|
||||
}
|
||||
}
|
||||
153
public/src/js/gamepad.js
Normal file
153
public/src/js/gamepad.js
Normal file
@@ -0,0 +1,153 @@
|
||||
class Gamepad{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(bindings, callback){
|
||||
this.bindings = bindings
|
||||
this.callback = !!callback
|
||||
this.b = {
|
||||
"a": 0,
|
||||
"b": 1,
|
||||
"x": 2,
|
||||
"y": 3,
|
||||
"lb": 4,
|
||||
"rb": 5,
|
||||
"lt": 6,
|
||||
"rt": 7,
|
||||
"back": 8,
|
||||
"start": 9,
|
||||
"ls": 10,
|
||||
"rs": 11,
|
||||
"u": 12,
|
||||
"d": 13,
|
||||
"l": 14,
|
||||
"r": 15,
|
||||
"guide": 16,
|
||||
"lsu": "lsu",
|
||||
"lsr": "lsr",
|
||||
"lsd": "lsd",
|
||||
"lsl": "lsl"
|
||||
}
|
||||
this.btn = {}
|
||||
this.gamepadEvents = 0
|
||||
if(callback){
|
||||
this.interval = setInterval(() => {
|
||||
this.play(callback)
|
||||
}, 1000 / 60)
|
||||
}
|
||||
}
|
||||
play(callback){
|
||||
if("getGamepads" in navigator){
|
||||
var gamepads = navigator.getGamepads()
|
||||
if(gamepads.length === 0){
|
||||
return
|
||||
}
|
||||
}else{
|
||||
return
|
||||
}
|
||||
if(pageEvents.lastKeyEvent + 5000 > Date.now()){
|
||||
return
|
||||
}
|
||||
|
||||
var bindings = this.bindings
|
||||
var force = {
|
||||
lsu: false,
|
||||
lsr: false,
|
||||
lsd: false,
|
||||
lsl: false
|
||||
}
|
||||
|
||||
for(var i = 0; i < gamepads.length; i++){
|
||||
if(gamepads[i]){
|
||||
var axes = gamepads[i].axes
|
||||
if(axes.length >= 2){
|
||||
force.lsl = force.lsl || axes[0] <= -0.5
|
||||
force.lsr = force.lsr || axes[0] >= 0.5
|
||||
force.lsu = force.lsu || axes[1] <= -0.5
|
||||
force.lsd = force.lsd || axes[1] >= 0.5
|
||||
}
|
||||
if(axes.length >= 10){
|
||||
// TaTaCon left D-Pad, DualSense D-Pad
|
||||
for(var pov = 0; pov < 8; pov++){
|
||||
if(Math.abs(axes[9] - (pov / 3.5 - 1)) <= 0.01){
|
||||
force.u = force.u || pov === 7 || pov === 0 || pov === 1
|
||||
force.r = force.r || pov === 1 || pov === 2 || pov === 3
|
||||
force.d = force.d || pov === 3 || pov === 4 || pov === 5
|
||||
force.l = force.l || pov === 5 || pov === 6 || pov === 7
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < gamepads.length; i++){
|
||||
if(gamepads[i]){
|
||||
this.toRelease = {}
|
||||
for(var bind in bindings){
|
||||
this.toRelease[bind] = bindings[bind].length
|
||||
}
|
||||
for(var bind in bindings){
|
||||
for(var name in bindings[bind]){
|
||||
var bindName = bindings[bind][name]
|
||||
this.checkButton(gamepads, this.b[bindName], bind, callback, force[bindName])
|
||||
if(!this.b){
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
checkButton(gamepads, btnName, keyCode, callback, force){
|
||||
var button = false
|
||||
|
||||
if(typeof force === "undefined"){
|
||||
for(var i = 0; i < gamepads.length; i++){
|
||||
if(gamepads[i]){
|
||||
var btn = gamepads[i].buttons[btnName]
|
||||
if(btn){
|
||||
var btnPressed = btn.pressed || btn.value >= 0.5
|
||||
if(btnPressed){
|
||||
button = btnPressed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pressed = !this.btn[btnName] && button
|
||||
var released = this.btn[btnName] && !button
|
||||
}else{
|
||||
var pressed = !this.btn[btnName] && force
|
||||
var released = this.btn[btnName] && !force
|
||||
}
|
||||
|
||||
if(pressed){
|
||||
this.btn[btnName] = true
|
||||
}else if(released){
|
||||
this.btn[btnName] = false
|
||||
}
|
||||
|
||||
if(pressed){
|
||||
callback(true, keyCode)
|
||||
this.gamepadEvents++
|
||||
}else if(!button){
|
||||
if(released){
|
||||
this.toRelease[keyCode + "released"] = true
|
||||
}
|
||||
this.toRelease[keyCode]--
|
||||
if(this.toRelease[keyCode] === 0 && this.toRelease[keyCode + "released"]){
|
||||
callback(false, keyCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
clean(){
|
||||
if(this.callback){
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
delete this.bindings
|
||||
delete this.b
|
||||
delete this.btn
|
||||
}
|
||||
}
|
||||
77
public/src/js/gamerules.js
Normal file
77
public/src/js/gamerules.js
Normal file
@@ -0,0 +1,77 @@
|
||||
class GameRules{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(game){
|
||||
this.difficulty = game.controller.selectedSong.difficulty
|
||||
var frame = 1000 / 60
|
||||
|
||||
switch(this.difficulty){
|
||||
case "easy":
|
||||
case "normal":
|
||||
this.good = 5 / 2 * frame
|
||||
this.ok = 13 / 2 * frame
|
||||
this.bad = 15 / 2 * frame
|
||||
break
|
||||
case "hard":
|
||||
case "oni":
|
||||
case "ura":
|
||||
default:
|
||||
this.good = 3 / 2 * frame
|
||||
this.ok = 9 / 2 * frame
|
||||
this.bad = 13 / 2 * frame
|
||||
break
|
||||
}
|
||||
switch(this.difficulty){
|
||||
case "easy":
|
||||
this.gaugeClear = 30 / 50
|
||||
break
|
||||
case "normal":
|
||||
case "hard":
|
||||
this.gaugeClear = 35 / 50
|
||||
break
|
||||
case "oni":
|
||||
case "ura":
|
||||
this.gaugeClear = 40 / 50
|
||||
break
|
||||
default:
|
||||
this.gaugeClear = 51 / 50
|
||||
break
|
||||
}
|
||||
|
||||
this.daiLeniency = 2 * frame
|
||||
}
|
||||
soulPoints(combo){
|
||||
var good, ok, bad
|
||||
switch(this.difficulty){
|
||||
case "easy":
|
||||
good = Math.floor(10000 / combo * 1.575)
|
||||
ok = Math.floor(good * 0.75)
|
||||
bad = Math.ceil(good / -2)
|
||||
break
|
||||
case "normal":
|
||||
good = Math.floor(10000 / combo / 0.7)
|
||||
ok = Math.floor(good * 0.75)
|
||||
bad = Math.ceil(good / -0.75)
|
||||
break
|
||||
case "hard":
|
||||
good = Math.floor(10000 / combo * 1.5)
|
||||
ok = Math.floor(good * 0.75)
|
||||
bad = Math.ceil(good / -0.8)
|
||||
break
|
||||
case "oni":
|
||||
case "ura":
|
||||
good = Math.floor(10000 / combo / 0.7)
|
||||
ok = Math.floor(good * 0.5)
|
||||
bad = Math.ceil(good * -1.6)
|
||||
break
|
||||
}
|
||||
return {good: good, ok: ok, bad: bad}
|
||||
}
|
||||
gaugePercent(gauge){
|
||||
return Math.floor(gauge / 200) / 50
|
||||
}
|
||||
clearReached(gauge){
|
||||
return this.gaugePercent(gauge) >= this.gaugeClear
|
||||
}
|
||||
}
|
||||
285
public/src/js/gpicker.js
Normal file
285
public/src/js/gpicker.js
Normal file
@@ -0,0 +1,285 @@
|
||||
class Gpicker{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.apiKey = gameConfig.google_credentials.api_key
|
||||
this.oauthClientId = gameConfig.google_credentials.oauth_client_id
|
||||
this.projectNumber = gameConfig.google_credentials.project_number
|
||||
this.scope = "https://www.googleapis.com/auth/drive.readonly"
|
||||
this.folder = "application/vnd.google-apps.folder"
|
||||
this.filesUrl = "https://www.googleapis.com/drive/v3/files/"
|
||||
this.resolveQueue = []
|
||||
this.queueActive = false
|
||||
this.clientCallbackBind = this.clientCallback.bind(this)
|
||||
}
|
||||
browse(lockedCallback, errorCallback){
|
||||
return this.loadApi(lockedCallback, errorCallback)
|
||||
.then(() => this.getToken(lockedCallback, errorCallback))
|
||||
.then(() => new Promise((resolve, reject) => {
|
||||
this.displayPicker(data => {
|
||||
if(data.action === "picked"){
|
||||
var file = data.docs[0]
|
||||
var folders = []
|
||||
var rateLimit = -1
|
||||
var lastBatch = 0
|
||||
var walk = (files, output=[]) => {
|
||||
for(var i = 0; i < files.length; i++){
|
||||
var path = files[i].path ? files[i].path + "/" : ""
|
||||
var list = files[i].list
|
||||
if(!list){
|
||||
continue
|
||||
}
|
||||
for(var j = 0; j < list.length; j++){
|
||||
var file = list[j]
|
||||
if(file.mimeType === this.folder){
|
||||
folders.push({
|
||||
path: path + file.name,
|
||||
id: file.id
|
||||
})
|
||||
}else{
|
||||
output.push(new GdriveFile({
|
||||
path: path + file.name,
|
||||
name: file.name,
|
||||
id: file.id
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
var batchList = []
|
||||
for(var i = 0; i < folders.length && batchList.length < 100; i++){
|
||||
if(!folders[i].listed){
|
||||
folders[i].pos = i
|
||||
folders[i].listed = true
|
||||
batchList.push(folders[i])
|
||||
}
|
||||
}
|
||||
if(batchList.length){
|
||||
var batch = gapi.client.newBatch()
|
||||
batchList.forEach(folder => {
|
||||
var req = {
|
||||
q: "'" + folder.id + "' in parents and trashed = false",
|
||||
orderBy: "name_natural"
|
||||
}
|
||||
if(folder.pageToken){
|
||||
req.pageToken = folder.pageToken
|
||||
}
|
||||
batch.add(gapi.client.drive.files.list(req), {id: folder.pos})
|
||||
})
|
||||
if(lastBatch + batchList.length > 100){
|
||||
var waitPromise = this.sleep(1000)
|
||||
}else{
|
||||
var waitPromise = Promise.resolve()
|
||||
}
|
||||
return waitPromise.then(() => this.queue()).then(() => batch.then(responses => {
|
||||
var files = []
|
||||
var rateLimited = false
|
||||
for(var i in responses.result){
|
||||
var result = responses.result[i].result
|
||||
if(result.error){
|
||||
if(result.error.errors[0].domain !== "usageLimits"){
|
||||
console.warn(result)
|
||||
}else if(!rateLimited){
|
||||
rateLimited = true
|
||||
rateLimit++
|
||||
folders.push({
|
||||
path: folders[i].path,
|
||||
id: folders[i].id,
|
||||
pageToken: folders[i].pageToken
|
||||
})
|
||||
}
|
||||
}else{
|
||||
if(result.nextPageToken){
|
||||
folders.push({
|
||||
path: folders[i].path,
|
||||
id: folders[i].id,
|
||||
pageToken: result.nextPageToken
|
||||
})
|
||||
}
|
||||
files.push({path: folders[i].path, list: result.files})
|
||||
}
|
||||
}
|
||||
if(rateLimited){
|
||||
return this.sleep(Math.pow(2, rateLimit) * 1000).then(() => walk(files, output))
|
||||
}else{
|
||||
return walk(files, output)
|
||||
}
|
||||
}))
|
||||
}else{
|
||||
return output
|
||||
}
|
||||
}
|
||||
if(file.mimeType === this.folder){
|
||||
return walk([{list: [file]}]).then(resolve, reject)
|
||||
}else{
|
||||
return reject("cancel")
|
||||
}
|
||||
}else if(data.action === "cancel"){
|
||||
return reject("cancel")
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
loadApi(lockedCallback=()=>{}, errorCallback=()=>{}){
|
||||
if(window.gapi && gapi.client && gapi.client.drive){
|
||||
return Promise.resolve()
|
||||
}
|
||||
var promises = [
|
||||
loader.loadScript("https://apis.google.com/js/api.js"),
|
||||
loader.loadScript("https://accounts.google.com/gsi/client")
|
||||
]
|
||||
var apiLoaded = false
|
||||
return Promise.all(promises).then(() => new Promise((resolve, reject) =>
|
||||
gapi.load("picker:client", {
|
||||
callback: resolve,
|
||||
onerror: reject
|
||||
})
|
||||
))
|
||||
.then(() => new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if(!apiLoaded){
|
||||
lockedCallback(false)
|
||||
}
|
||||
}, 3000)
|
||||
return gapi.client.load("drive", "v3").then(resolve, reject)
|
||||
})).then(() => {
|
||||
apiLoaded = true
|
||||
lockedCallback(true)
|
||||
}).catch(e => {
|
||||
errorCallback(Array.isArray(e) ? e[0] : e)
|
||||
return Promise.reject("cancel")
|
||||
})
|
||||
}
|
||||
getClient(errorCallback=()=>{}, force){
|
||||
var obj = {
|
||||
client_id: this.oauthClientId,
|
||||
scope: this.scope,
|
||||
callback: this.clientCallbackBind
|
||||
}
|
||||
if(force){
|
||||
if(!this.clientForce){
|
||||
obj.select_account = true
|
||||
this.clientForce = google.accounts.oauth2.initTokenClient(obj)
|
||||
}
|
||||
return this.clientForce
|
||||
}else{
|
||||
if(!this.client){
|
||||
this.client = google.accounts.oauth2.initTokenClient(obj)
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
}
|
||||
clientCallback(tokenResponse){
|
||||
this.tokenResponse = tokenResponse
|
||||
this.oauthToken = tokenResponse && tokenResponse.access_token
|
||||
if(this.oauthToken && this.tokenResolve){
|
||||
this.tokenResolve()
|
||||
}
|
||||
}
|
||||
getToken(lockedCallback=()=>{}, errorCallback=()=>{}, force){
|
||||
if(this.oauthToken && !force){
|
||||
return Promise.resolve()
|
||||
}
|
||||
var client = this.getClient(errorCallback, force)
|
||||
var promise = new Promise(resolve => {
|
||||
this.tokenResolve = resolve
|
||||
})
|
||||
lockedCallback(false)
|
||||
client.requestAccessToken()
|
||||
return promise.then(() => {
|
||||
this.tokenResolve = null
|
||||
if(this.checkScope()){
|
||||
lockedCallback(true)
|
||||
}else{
|
||||
return Promise.reject("cancel")
|
||||
}
|
||||
})
|
||||
}
|
||||
checkScope(){
|
||||
return google.accounts.oauth2.hasGrantedAnyScope(this.tokenResponse, this.scope)
|
||||
}
|
||||
switchAccounts(lockedCallback, errorCallback){
|
||||
return this.loadApi().then(() => this.getToken(lockedCallback, errorCallback, true))
|
||||
}
|
||||
displayPicker(callback){
|
||||
var picker = gapi.picker.api
|
||||
new picker.PickerBuilder()
|
||||
.setDeveloperKey(this.apiKey)
|
||||
.setAppId(this.projectNumber)
|
||||
.setOAuthToken(this.oauthToken)
|
||||
.setLocale(strings.gpicker.locale)
|
||||
.hideTitleBar()
|
||||
.addView(new picker.DocsView("folders")
|
||||
.setLabel(strings.gpicker.myDrive)
|
||||
.setParent("root")
|
||||
.setSelectFolderEnabled(true)
|
||||
.setMode("grid")
|
||||
)
|
||||
.addView(new picker.DocsView("folders")
|
||||
.setLabel(strings.gpicker.starred)
|
||||
.setStarred(true)
|
||||
.setSelectFolderEnabled(true)
|
||||
.setMode("grid")
|
||||
)
|
||||
.addView(new picker.DocsView("folders")
|
||||
.setLabel(strings.gpicker.sharedWithMe)
|
||||
.setOwnedByMe(false)
|
||||
.setSelectFolderEnabled(true)
|
||||
.setMode("list")
|
||||
)
|
||||
.setCallback(callback)
|
||||
.setSize(Infinity, Infinity)
|
||||
.build()
|
||||
.setVisible(true)
|
||||
}
|
||||
downloadFile(id, responseType, retry){
|
||||
var url = this.filesUrl + id + "?alt=media"
|
||||
return this.queue().then(this.getToken.bind(this)).then(() =>
|
||||
loader.ajax(url, request => {
|
||||
if(responseType){
|
||||
request.responseType = responseType
|
||||
}
|
||||
request.setRequestHeader("Authorization", "Bearer " + this.oauthToken)
|
||||
}, true).then(event => {
|
||||
var request = event.target
|
||||
var reject = () => Promise.reject(`${url} (${request.status})`)
|
||||
if(request.status === 200){
|
||||
return request.response
|
||||
}else if(request.status === 401 && !retry){
|
||||
return new Response(request.response).json().then(response => {
|
||||
var e = response.error
|
||||
if(e && e.errors[0].reason === "authError"){
|
||||
delete this.oauthToken
|
||||
return this.downloadFile(id, responseType, true)
|
||||
}else{
|
||||
return reject()
|
||||
}
|
||||
}, reject)
|
||||
}
|
||||
return reject()
|
||||
})
|
||||
)
|
||||
}
|
||||
sleep(time){
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
}
|
||||
queue(){
|
||||
return new Promise(resolve => {
|
||||
this.resolveQueue.push(resolve)
|
||||
if(!this.queueActive){
|
||||
this.queueActive = true
|
||||
this.queueTimer = setInterval(this.parseQueue.bind(this), 100)
|
||||
this.parseQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
parseQueue(){
|
||||
if(this.resolveQueue.length){
|
||||
var resolve = this.resolveQueue.shift()
|
||||
resolve()
|
||||
}else{
|
||||
this.queueActive = false
|
||||
clearInterval(this.queueTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
54
public/src/js/idb.js
Normal file
54
public/src/js/idb.js
Normal file
@@ -0,0 +1,54 @@
|
||||
class IDB{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(name, store){
|
||||
this.name = name
|
||||
this.store = store
|
||||
}
|
||||
start(){
|
||||
if(this.db){
|
||||
return Promise.resolve(this.db)
|
||||
}
|
||||
var request = indexedDB.open(this.name)
|
||||
request.onupgradeneeded = event => {
|
||||
var db = event.target.result
|
||||
db.createObjectStore(this.store)
|
||||
}
|
||||
return this.promise(request).then(result => {
|
||||
this.db = result
|
||||
return this.db
|
||||
}, target =>
|
||||
console.warn("DB error", target)
|
||||
)
|
||||
}
|
||||
promise(request){
|
||||
return new Promise((resolve, reject) => {
|
||||
return pageEvents.race(request, "success", "error").then(response => {
|
||||
if(response.type === "success"){
|
||||
return resolve(event.target.result)
|
||||
}else{
|
||||
return reject(event.target)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
transaction(method, ...args){
|
||||
return this.start().then(db =>
|
||||
db.transaction(this.store, "readwrite").objectStore(this.store)[method](...args)
|
||||
).then(this.promise.bind(this))
|
||||
}
|
||||
getItem(name){
|
||||
return this.transaction("get", name)
|
||||
}
|
||||
setItem(name, value){
|
||||
return this.transaction("put", value, name)
|
||||
}
|
||||
removeItem(name){
|
||||
return this.transaction("delete", name)
|
||||
}
|
||||
removeDB(){
|
||||
delete this.db
|
||||
return indexedDB.deleteDatabase(this.name)
|
||||
}
|
||||
}
|
||||
658
public/src/js/importsongs.js
Normal file
658
public/src/js/importsongs.js
Normal file
@@ -0,0 +1,658 @@
|
||||
class ImportSongs{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(limited, otherFiles, noPlugins, pluginAmount){
|
||||
this.limited = limited
|
||||
this.tjaFiles = []
|
||||
this.osuFiles = []
|
||||
this.assetFiles = {}
|
||||
this.pluginFiles = []
|
||||
this.otherFiles = otherFiles || {}
|
||||
this.noPlugins = noPlugins
|
||||
this.pluginAmount = pluginAmount
|
||||
this.songs = []
|
||||
this.stylesheet = []
|
||||
this.plugins = []
|
||||
this.songTitle = this.otherFiles.songTitle || {}
|
||||
this.uraRegex = /\s*[\((]裏[\))]$/
|
||||
this.courseTypes = {
|
||||
"easy": 0,
|
||||
"normal": 1,
|
||||
"hard": 2,
|
||||
"oni": 3,
|
||||
"ura": 4
|
||||
}
|
||||
this.categoryAliases = {}
|
||||
assets.categories.forEach(cat => {
|
||||
this.categoryAliases[cat.title.toLowerCase()] = cat.id
|
||||
if(cat.aliases){
|
||||
cat.aliases.forEach(alias => {
|
||||
this.categoryAliases[alias.toLowerCase()] = cat.id
|
||||
})
|
||||
}
|
||||
if(cat.title_lang){
|
||||
for(var i in cat.title_lang){
|
||||
this.categoryAliases[cat.title_lang[i].toLowerCase()] = cat.id
|
||||
}
|
||||
}
|
||||
})
|
||||
this.assetSelectors = {}
|
||||
for(var selector in assets.cssBackground){
|
||||
var filename = assets.cssBackground[selector]
|
||||
var index = filename.lastIndexOf(".")
|
||||
if(index !== -1){
|
||||
filename = filename.slice(0, index)
|
||||
}
|
||||
this.assetSelectors[filename] = selector
|
||||
}
|
||||
this.comboVoices = ["v_combo_50"].concat(Array.from(Array(50), (d, i) => "v_combo_" + ((i + 1) * 100)))
|
||||
}
|
||||
load(files){
|
||||
var extensionRegex = /\.[^\/]+$/
|
||||
files.sort((a, b) => {
|
||||
var path1 = a.path.replace(extensionRegex, "")
|
||||
var path2 = b.path.replace(extensionRegex, "")
|
||||
return path1 > path2 ? 1 : -1
|
||||
})
|
||||
|
||||
var metaFiles = []
|
||||
for(var i = 0; i < files.length; i++){
|
||||
var file = files[i]
|
||||
var name = file.name.toLowerCase()
|
||||
var path = file.path.toLowerCase()
|
||||
if(name.endsWith(".tja") || name.endsWith(".tjf")){
|
||||
this.tjaFiles.push({
|
||||
file: file,
|
||||
index: i
|
||||
})
|
||||
}else if(name.endsWith(".osu")){
|
||||
this.osuFiles.push({
|
||||
file: file,
|
||||
index: i
|
||||
})
|
||||
}else if(!this.limited && (name === "genre.ini" || name === "box.def") || name === "songtitle.txt"){
|
||||
var level = (file.path.match(/\//g) || []).length
|
||||
metaFiles.push({
|
||||
file: file,
|
||||
level: (level * 2) + (name === "genre.ini" ? 1 : 0)
|
||||
})
|
||||
}else if(!this.limited && (path.indexOf("/taiko-web assets/") !== -1 || path.indexOf("taiko-web assets/") === 0)){
|
||||
if(!(name in this.assetFiles)){
|
||||
this.assetFiles[name] = file
|
||||
}
|
||||
}else if(name.endsWith(".taikoweb.js")){
|
||||
this.pluginFiles.push({
|
||||
file: file,
|
||||
index: i
|
||||
})
|
||||
}else{
|
||||
this.otherFiles[path] = file
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.noPlugins && this.pluginFiles.length){
|
||||
var pluginPromises = []
|
||||
this.pluginFiles.forEach(fileObj => {
|
||||
pluginPromises.push(this.addPlugin(fileObj).catch(e => console.warn(e)))
|
||||
})
|
||||
return Promise.all(pluginPromises).then(() => {
|
||||
var startPromises = []
|
||||
var pluginAmount = 0
|
||||
if(this.plugins.length && confirm(strings.plugins.warning.replace("%s",
|
||||
strings.plugins.plugin[strings.plural.select(this.plugins.length)].replace("%s",
|
||||
this.plugins.length.toString()
|
||||
)
|
||||
))){
|
||||
this.plugins.forEach(obj => {
|
||||
var plugin = plugins.add(obj.data, {
|
||||
name: obj.name,
|
||||
raw: true
|
||||
})
|
||||
if(plugin){
|
||||
pluginAmount++
|
||||
plugin.imported = true
|
||||
startPromises.push(plugin.start())
|
||||
}
|
||||
})
|
||||
}
|
||||
return Promise.all(startPromises).then(() => {
|
||||
var importSongs = new ImportSongs(this.limited, this.otherFiles, true, pluginAmount)
|
||||
return importSongs.load(files)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var metaPromises = []
|
||||
metaFiles.forEach(fileObj => {
|
||||
metaPromises.push(this.addMeta(fileObj))
|
||||
})
|
||||
|
||||
return Promise.all(metaPromises).then(() => {
|
||||
var songPromises = []
|
||||
|
||||
this.tjaFiles.forEach(fileObj => {
|
||||
songPromises.push(this.addTja(fileObj).catch(e => console.warn(e)))
|
||||
})
|
||||
this.osuFiles.forEach(fileObj => {
|
||||
songPromises.push(this.addOsu(fileObj).catch(e => console.warn(e)))
|
||||
})
|
||||
songPromises.push(this.addAssets())
|
||||
return Promise.all(songPromises)
|
||||
}).then(this.loaded.bind(this))
|
||||
}
|
||||
|
||||
addMeta(fileObj){
|
||||
var file = fileObj.file
|
||||
var level = fileObj.level
|
||||
var name = file.name.toLowerCase()
|
||||
return file.read(name === "songtitle.txt" ? undefined : "utf-8").then(data => {
|
||||
var data = data.replace(/\0/g, "").split("\n")
|
||||
var category
|
||||
if(name === "genre.ini"){
|
||||
var key
|
||||
for(var i = 0; i < data.length; i++){
|
||||
var line = data[i].trim().toLowerCase()
|
||||
if(line.startsWith("[") && line.endsWith("]")){
|
||||
key = line.slice(1, -1)
|
||||
}else if(key === "genre"){
|
||||
var equalsPos = line.indexOf("=")
|
||||
if(equalsPos !== -1 && line.slice(0, equalsPos).trim() === "genrename"){
|
||||
var value = line.slice(equalsPos + 1).trim()
|
||||
if(value.toLowerCase() in this.categoryAliases){
|
||||
category = value
|
||||
}else{
|
||||
category = data[i].trim().slice(equalsPos + 1).trim()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}else if(name === "box.def"){
|
||||
for(var i = 0; i < data.length; i++){
|
||||
var line = data[i].trim().toLowerCase()
|
||||
if(line.startsWith("#title:")){
|
||||
var value = line.slice(7).trim()
|
||||
if(value.toLowerCase() in this.categoryAliases){
|
||||
category = value
|
||||
}
|
||||
}else if(line.startsWith("#genre:")){
|
||||
var value = line.slice(7).trim()
|
||||
if(value.toLowerCase() in this.categoryAliases){
|
||||
category = value
|
||||
}else{
|
||||
category = data[i].trim().slice(7).trim()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}else if(name === "songtitle.txt"){
|
||||
var lastTitle
|
||||
for(var i = 0; i < data.length; i++){
|
||||
var line = data[i].trim()
|
||||
if(line){
|
||||
var lang = line.slice(0, 2)
|
||||
if(line.charAt(2) !== " " || !(lang in allStrings)){
|
||||
this.songTitle[line] = {}
|
||||
lastTitle = line
|
||||
}else if(lastTitle){
|
||||
this.songTitle[lastTitle][lang] = line.slice(3).trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(category){
|
||||
var metaPath = file.path.toLowerCase().slice(0, file.name.length * -1)
|
||||
var filesLoop = fileObj => {
|
||||
var tjaPath = fileObj.file.path.toLowerCase().slice(0, fileObj.file.name.length * -1)
|
||||
if(tjaPath.startsWith(metaPath) && (!("categoryLevel" in fileObj) || fileObj.categoryLevel < level)){
|
||||
if(category.toLowerCase() in this.categoryAliases){
|
||||
fileObj.category_id = this.categoryAliases[category.toLowerCase()]
|
||||
}else{
|
||||
fileObj.category = category
|
||||
}
|
||||
fileObj.categoryLevel = level
|
||||
}
|
||||
}
|
||||
this.tjaFiles.forEach(filesLoop)
|
||||
this.osuFiles.forEach(filesLoop)
|
||||
}
|
||||
}).catch(e => {
|
||||
console.warn(e)
|
||||
})
|
||||
}
|
||||
|
||||
addTja(fileObj){
|
||||
var file = fileObj.file
|
||||
var index = fileObj.index
|
||||
var category = fileObj.category
|
||||
var category_id = fileObj.category_id
|
||||
if(!this.limited){
|
||||
var filePromise = file.read(prompt("太鼓さん次郎のファイルは\"sjis\"、TJAPlayer3のファイルは\"utf-8\"と入力してください。"))
|
||||
}else{
|
||||
var filePromise = Promise.resolve()
|
||||
}
|
||||
return filePromise.then(dataRaw => {
|
||||
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
|
||||
var tja = new ParseTja(data, "oni", 0, 0, true)
|
||||
var songObj = {
|
||||
id: index + 1,
|
||||
order: index + 1,
|
||||
title: file.name.slice(0, file.name.lastIndexOf(".")),
|
||||
type: "tja",
|
||||
chart: file,
|
||||
courses: {},
|
||||
music: "muted",
|
||||
custom: true
|
||||
}
|
||||
if(this.limited){
|
||||
songObj.unloaded = true
|
||||
}
|
||||
var coursesAdded = false
|
||||
var titleLang = {}
|
||||
var titleLangAdded = false
|
||||
var subtitleLangAdded = false
|
||||
var subtitleLang = {}
|
||||
var dir = file.path.toLowerCase()
|
||||
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
|
||||
for(var diff in tja.metadata){
|
||||
var meta = tja.metadata[diff]
|
||||
songObj.title = meta.title || file.name.slice(0, file.name.lastIndexOf("."))
|
||||
var subtitle = meta.subtitle || ""
|
||||
if(subtitle.startsWith("--") || subtitle.startsWith("++")){
|
||||
subtitle = subtitle.slice(2).trim()
|
||||
}
|
||||
songObj.subtitle = subtitle
|
||||
songObj.preview = meta.demostart || 0
|
||||
songObj.courses[diff] = {
|
||||
stars: meta.level || 0,
|
||||
branch: !!meta.branch
|
||||
}
|
||||
coursesAdded = true
|
||||
if(meta.wave){
|
||||
songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music
|
||||
}
|
||||
if(meta.genre){
|
||||
if(meta.genre.toLowerCase() in this.categoryAliases){
|
||||
songObj.category_id = this.categoryAliases[meta.genre.toLowerCase()]
|
||||
}else{
|
||||
songObj.category = meta.genre
|
||||
}
|
||||
}
|
||||
if(meta.taikowebskin){
|
||||
songObj.song_skin = this.getSkin(dir, meta.taikowebskin)
|
||||
}
|
||||
if(meta.maker){
|
||||
var maker = meta.maker
|
||||
var url = null
|
||||
var gt = maker.lastIndexOf(">")
|
||||
if(gt === maker.length - 1){
|
||||
var lt = maker.lastIndexOf("<")
|
||||
if(lt !== -1 && lt !== gt - 2){
|
||||
url = maker.slice(lt + 1, gt).trim()
|
||||
if(url.startsWith("http://") || url.startsWith("https://")){
|
||||
maker = maker.slice(0, lt)
|
||||
}else{
|
||||
url = null
|
||||
}
|
||||
}
|
||||
}
|
||||
songObj.maker = {
|
||||
name: maker,
|
||||
url: url,
|
||||
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){
|
||||
var songTitle = songObj.title
|
||||
var ura = ""
|
||||
if(songTitle){
|
||||
var uraPos = songTitle.search(this.uraRegex)
|
||||
if(uraPos !== -1){
|
||||
ura = songTitle.slice(uraPos)
|
||||
songTitle = songTitle.slice(0, uraPos)
|
||||
}
|
||||
}
|
||||
if(meta["title" + id]){
|
||||
titleLang[id] = meta["title" + id]
|
||||
titleLangAdded = true
|
||||
}else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){
|
||||
titleLang[id] = this.songTitle[songTitle][id] + ura
|
||||
titleLangAdded = true
|
||||
}
|
||||
if(meta["subtitle" + id]){
|
||||
subtitleLang[id] = meta["subtitle" + id]
|
||||
subtitleLangAdded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if(titleLangAdded){
|
||||
songObj.title_lang = titleLang
|
||||
}
|
||||
if(subtitleLangAdded){
|
||||
songObj.subtitle_lang = subtitleLang
|
||||
}
|
||||
if(!songObj.category_id && !songObj.category){
|
||||
if(!category && category_id === undefined){
|
||||
songObj.category_id = this.getCategory(file, [songTitle || songObj.title, file.name.slice(0, file.name.lastIndexOf("."))])
|
||||
}else if(category){
|
||||
songObj.category = category
|
||||
songObj.orginalCategory = category
|
||||
}else{
|
||||
songObj.category_id = category_id
|
||||
}
|
||||
}
|
||||
if(coursesAdded || songObj.unloaded){
|
||||
this.songs[index] = songObj
|
||||
}
|
||||
if(!this.limited){
|
||||
var hash = md5.base64(dataRaw).slice(0, -2)
|
||||
songObj.hash = hash
|
||||
scoreStorage.songTitles[songObj.title] = hash
|
||||
var score = scoreStorage.get(hash, false, true)
|
||||
if(score){
|
||||
score.title = songObj.title
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addOsu(fileObj){
|
||||
var file = fileObj.file
|
||||
var index = fileObj.index
|
||||
var category = fileObj.category
|
||||
var category_id = fileObj.category_id
|
||||
if(!this.limited){
|
||||
var filePromise = file.read()
|
||||
}else{
|
||||
var filePromise = Promise.resolve()
|
||||
}
|
||||
return filePromise.then(dataRaw => {
|
||||
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
|
||||
var osu = new ParseOsu(data, "oni", 0, 0, true)
|
||||
var dir = file.path.toLowerCase()
|
||||
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
|
||||
var songObj = {
|
||||
id: index + 1,
|
||||
order: index + 1,
|
||||
type: "osu",
|
||||
chart: file,
|
||||
subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist,
|
||||
subtitle_lang: {
|
||||
en: osu.metadata.Artist || osu.metadata.ArtistUnicode
|
||||
},
|
||||
preview: osu.generalInfo.PreviewTime ? osu.generalInfo.PreviewTime / 1000 : 0,
|
||||
courses: {},
|
||||
music: (osu.generalInfo.AudioFilename ? this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] : "") || "muted"
|
||||
}
|
||||
if(!this.limited){
|
||||
songObj.courses.oni = {
|
||||
stars: parseInt(osu.difficulty.overallDifficulty) || 0,
|
||||
branch: false
|
||||
}
|
||||
}else{
|
||||
songObj.unloaded = true
|
||||
}
|
||||
var filename = file.name.slice(0, file.name.lastIndexOf("."))
|
||||
var title = osu.metadata.TitleUnicode || osu.metadata.Title || file.name.slice(0, file.name.lastIndexOf("."))
|
||||
if(title){
|
||||
var suffix = ""
|
||||
var matches = filename.match(/\[.+?\]$/)
|
||||
if(matches){
|
||||
suffix = " " + matches[0]
|
||||
}
|
||||
songObj.title = title + suffix
|
||||
songObj.title_lang = {
|
||||
en: (osu.metadata.Title || osu.metadata.TitleUnicode || title) + suffix
|
||||
}
|
||||
}else{
|
||||
songObj.title = filename
|
||||
}
|
||||
this.songs[index] = songObj
|
||||
if(!category && category_id === undefined){
|
||||
songObj.category_id = this.getCategory(file, [osu.metadata.TitleUnicode, osu.metadata.Title, file.name.slice(0, file.name.lastIndexOf("."))])
|
||||
}else if(category){
|
||||
songObj.category = category
|
||||
songObj.orginalCategory = category
|
||||
}else{
|
||||
songObj.category_id = category_id
|
||||
}
|
||||
if(!this.limited){
|
||||
var hash = md5.base64(dataRaw).slice(0, -2)
|
||||
songObj.hash = hash
|
||||
scoreStorage.songTitles[songObj.title] = hash
|
||||
var score = scoreStorage.get(hash, false, true)
|
||||
if(score){
|
||||
score.title = songObj.title
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addAssets(){
|
||||
var promises = []
|
||||
for(let name in this.assetFiles){
|
||||
let id = this.getFilename(name)
|
||||
var file = this.assetFiles[name]
|
||||
var index = name.lastIndexOf(".")
|
||||
if(name === "vectors.json"){
|
||||
promises.push(file.read().then(response => {
|
||||
vectors = JSON.parse(response)
|
||||
}))
|
||||
}
|
||||
if(name.endsWith(".png") || name.endsWith(".gif")){
|
||||
let image = document.createElement("img")
|
||||
promises.push(pageEvents.load(image).then(() => {
|
||||
if(id in this.assetSelectors){
|
||||
var selector = this.assetSelectors[id]
|
||||
var gradient = ""
|
||||
if(selector === "#song-search"){
|
||||
gradient = loader.songSearchGradient
|
||||
}
|
||||
this.stylesheet.push(loader.cssRuleset({
|
||||
[selector]: {
|
||||
"background-image": gradient + "url(\"" + image.src + "\")"
|
||||
}
|
||||
}))
|
||||
}
|
||||
}))
|
||||
image.id = name
|
||||
promises.push(file.blob().then(blob => {
|
||||
image.src = URL.createObjectURL(blob)
|
||||
}))
|
||||
loader.assetsDiv.appendChild(image)
|
||||
var oldImage = assets.image[id]
|
||||
if(oldImage && oldImage.parentNode){
|
||||
URL.revokeObjectURL(oldImage.src)
|
||||
oldImage.parentNode.removeChild(oldImage)
|
||||
}
|
||||
assets.image[id] = image
|
||||
}
|
||||
if(assets.audioSfx.indexOf(name) !== -1){
|
||||
assets.sounds[id].clean()
|
||||
promises.push(this.loadSound(file, name, snd.sfxGain))
|
||||
}
|
||||
if(assets.audioMusic.indexOf(name) !== -1){
|
||||
assets.sounds[id].clean()
|
||||
promises.push(this.loadSound(file, name, snd.musicGain))
|
||||
}
|
||||
if(assets.audioSfxLR.indexOf(name) !== -1){
|
||||
assets.sounds[id + "_p1"].clean()
|
||||
assets.sounds[id + "_p2"].clean()
|
||||
promises.push(this.loadSound(file, name, snd.sfxGain).then(sound => {
|
||||
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
|
||||
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
|
||||
}))
|
||||
}
|
||||
if(assets.audioSfxLoud.indexOf(name) !== -1){
|
||||
assets.sounds[id].clean()
|
||||
promises.push(this.loadSound(file, name, snd.sfxLoudGain))
|
||||
}
|
||||
if(this.comboVoices.indexOf(id) !== -1){
|
||||
promises.push(snd.sfxGain.load(file).then(sound => {
|
||||
assets.sounds[id] = sound
|
||||
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
|
||||
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
|
||||
}))
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
loadSound(file, name, gain){
|
||||
var id = this.getFilename(name)
|
||||
return gain.load(file).then(sound => {
|
||||
assets.sounds[id] = sound
|
||||
})
|
||||
}
|
||||
getFilename(name){
|
||||
return name.slice(0, name.lastIndexOf("."))
|
||||
}
|
||||
|
||||
addPlugin(fileObj){
|
||||
var file = fileObj.file
|
||||
var filePromise = file.read()
|
||||
return filePromise.then(dataRaw => {
|
||||
var name = file.name.slice(0, file.name.lastIndexOf(".taikoweb.js"))
|
||||
this.plugins.push({
|
||||
name: name,
|
||||
data: dataRaw
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getCategory(file, exclude){
|
||||
var path = file.path.toLowerCase().split("/")
|
||||
for(var i = path.length - 2; i >= 0; i--){
|
||||
var hasTitle = false
|
||||
for(var j in exclude){
|
||||
if(exclude[j] && path[i].indexOf(exclude[j].toLowerCase()) !== -1){
|
||||
hasTitle = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if(!hasTitle){
|
||||
for(var cat in this.categoryAliases){
|
||||
if(path[i].indexOf(cat) !== -1){
|
||||
return this.categoryAliases[cat]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSkin(dir, config){
|
||||
var configArray = config.toLowerCase().split(",")
|
||||
var configObj = {}
|
||||
for(var i in configArray){
|
||||
var string = configArray[i].trim()
|
||||
var space = string.indexOf(" ")
|
||||
if(space !== -1){
|
||||
configObj[string.slice(0, space).trim()] = string.slice(space + 1).trim()
|
||||
}
|
||||
}
|
||||
if(!configObj.dir){
|
||||
configObj.dir = ""
|
||||
}
|
||||
configObj.prefix = "custom "
|
||||
var skinnable = ["song", "stage", "don"]
|
||||
for(var i in skinnable){
|
||||
var skinName = skinnable[i]
|
||||
var skinValue = configObj[skinName]
|
||||
if(skinValue && skinValue !== "none"){
|
||||
var fileName = "bg_" + skinName + "_" + configObj.name
|
||||
var skinPath = this.joinPath(dir, configObj.dir, fileName)
|
||||
for(var j = 0; j < 2; j++){
|
||||
if(skinValue !== "static"){
|
||||
var suffix = (j === 0 ? "_a" : "_b") + ".png"
|
||||
}else{
|
||||
var suffix = ".png"
|
||||
}
|
||||
var skinFull = this.normPath(skinPath + suffix)
|
||||
if(skinFull in this.otherFiles){
|
||||
configObj[fileName + suffix] = this.otherFiles[skinFull]
|
||||
}else{
|
||||
configObj[skinName] = null
|
||||
}
|
||||
if(skinValue === "static"){
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return configObj
|
||||
}
|
||||
|
||||
loaded(){
|
||||
this.songs = this.songs.filter(song => typeof song !== "undefined")
|
||||
if(this.stylesheet.length){
|
||||
var style = document.createElement("style")
|
||||
style.appendChild(document.createTextNode(this.stylesheet.join("\n")))
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
if(this.songs.length){
|
||||
if(this.limited){
|
||||
assets.otherFiles = this.otherFiles
|
||||
assets.otherFiles.songTitle = this.songTitle
|
||||
}
|
||||
return Promise.resolve(this.songs)
|
||||
}else{
|
||||
if(this.noPlugins && this.pluginAmount || Object.keys(this.assetFiles).length){
|
||||
return Promise.resolve()
|
||||
}else{
|
||||
return Promise.reject("nosongs")
|
||||
}
|
||||
}
|
||||
this.clean()
|
||||
}
|
||||
|
||||
joinPath(){
|
||||
var resultPath = arguments[0]
|
||||
for(var i = 1; i < arguments.length; i++){
|
||||
var pPath = arguments[i]
|
||||
if(pPath && (pPath[0] === "/" || pPath[0] === "\\")){
|
||||
resultPath = pPath
|
||||
}else{
|
||||
var lastChar = resultPath.slice(-1)
|
||||
if(resultPath && (lastChar !== "/" || lastChar !== "\\")){
|
||||
resultPath = resultPath + "/"
|
||||
}
|
||||
resultPath = resultPath + pPath
|
||||
}
|
||||
}
|
||||
return resultPath
|
||||
}
|
||||
normPath(path){
|
||||
path = path.replace(/\\/g, "/").toLowerCase()
|
||||
while(path[0] === "/"){
|
||||
path = path.slice(1)
|
||||
}
|
||||
var comps = path.split("/")
|
||||
for(var i = 0; i < comps.length; i++){
|
||||
if(comps[i] === "." || comps[i] === ""){
|
||||
comps.splice(i, 1)
|
||||
i--
|
||||
}else if(i !== 0 && comps[i] === ".." && comps[i - 1] !== ".."){
|
||||
comps.splice(i - 1, 2)
|
||||
i -= 2
|
||||
}
|
||||
}
|
||||
return comps.join("/")
|
||||
}
|
||||
|
||||
clean(){
|
||||
delete this.songs
|
||||
delete this.tjaFiles
|
||||
delete this.osuFiles
|
||||
delete this.assetFiles
|
||||
delete this.otherFiles
|
||||
}
|
||||
}
|
||||
124
public/src/js/keyboard.js
Normal file
124
public/src/js/keyboard.js
Normal file
@@ -0,0 +1,124 @@
|
||||
class Keyboard{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(bindings, callback){
|
||||
this.bindings = bindings
|
||||
this.callback = callback
|
||||
this.wildcard = false
|
||||
this.substitute = {
|
||||
"up": "arrowup",
|
||||
"right": "arrowright",
|
||||
"down": "arrowdown",
|
||||
"left": "arrowleft",
|
||||
"space": " ",
|
||||
"esc": "escape",
|
||||
"ctrl": "control",
|
||||
"altgr": "altgraph"
|
||||
}
|
||||
this.btn = {}
|
||||
this.TaikoForceLv5 = false;
|
||||
this.update()
|
||||
pageEvents.keyAdd(this, "all", "both", this.keyEvent.bind(this))
|
||||
pageEvents.blurAdd(this, this.blurEvent.bind(this))
|
||||
}
|
||||
isTaikoForceLv5(kbdSettings){
|
||||
// the key of Taiko Force Lv5's PC module looks like this
|
||||
return (kbdSettings.ka_l[0] === "f") && (kbdSettings.ka_r[0] === "e") && (kbdSettings.don_l[0] === "i") && (kbdSettings.don_r[0] === "j");
|
||||
}
|
||||
update(){
|
||||
var kbdSettings = settings.getItem("keyboardSettings")
|
||||
this.TaikoForceLv5 = this.isTaikoForceLv5(kbdSettings)
|
||||
var drumKeys = {}
|
||||
for(var name in kbdSettings){
|
||||
var keys = kbdSettings[name]
|
||||
for(var i in keys){
|
||||
drumKeys[keys[i]] = name
|
||||
}
|
||||
}
|
||||
this.kbd = {}
|
||||
for(var name in this.bindings){
|
||||
var keys = this.bindings[name]
|
||||
for(var i in keys){
|
||||
var key = keys[i]
|
||||
if(key in drumKeys){
|
||||
continue
|
||||
}
|
||||
if(key in kbdSettings){
|
||||
var keyArray = kbdSettings[key]
|
||||
for(var j in keyArray){
|
||||
key = keyArray[j]
|
||||
if(!(key in this.kbd)){
|
||||
this.kbd[key] = name
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(key in this.substitute){
|
||||
key = this.substitute[key]
|
||||
}
|
||||
if(!(key in this.kbd)){
|
||||
if(key === "wildcard"){
|
||||
this.wildcard = true
|
||||
}
|
||||
this.kbd[key] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keyEvent(event){
|
||||
var key = event.key.toLowerCase()
|
||||
if(event.target.tagName === "INPUT"){
|
||||
if(key === "escape"){
|
||||
event.preventDefault()
|
||||
}
|
||||
}else if(
|
||||
key === "escape" ||
|
||||
key === "backspace" ||
|
||||
key === "tab" ||
|
||||
key === "contextmenu" ||
|
||||
key === "alt"
|
||||
){
|
||||
event.preventDefault()
|
||||
}
|
||||
if(!event.repeat){
|
||||
var pressed = event.type === "keydown"
|
||||
if(pressed){
|
||||
this.btn[key] = true
|
||||
}else{
|
||||
delete this.btn[key]
|
||||
if(key in this.kbd){
|
||||
for(var i in this.btn){
|
||||
if(this.kbd[i] === this.kbd[key]){
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(key in this.kbd){
|
||||
this.callback(pressed, this.kbd[key], event)
|
||||
}else if(this.wildcard){
|
||||
this.callback(pressed, this.kbd["wildcard"], event)
|
||||
}
|
||||
}
|
||||
}
|
||||
blurEvent(){
|
||||
for(var key in this.btn){
|
||||
if(this.btn[key]){
|
||||
delete this.btn[key]
|
||||
var name = this.kbd[key] || (this.wildcard ? "wildcard" : false)
|
||||
if(name){
|
||||
this.callback(false, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clean(){
|
||||
pageEvents.keyRemove(this, "all")
|
||||
pageEvents.blurRemove(this)
|
||||
delete this.bindings
|
||||
delete this.callback
|
||||
delete this.kbd
|
||||
delete this.btn
|
||||
}
|
||||
}
|
||||
636
public/src/js/lib/fuzzysort.js
Normal file
636
public/src/js/lib/fuzzysort.js
Normal file
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
fuzzysort.js https://github.com/farzher/fuzzysort
|
||||
SublimeText-like Fuzzy Search
|
||||
|
||||
fuzzysort.single('fs', 'Fuzzy Search') // {score: -16}
|
||||
fuzzysort.single('test', 'test') // {score: 0}
|
||||
fuzzysort.single('doesnt exist', 'target') // null
|
||||
|
||||
fuzzysort.go('mr', [{file:'Monitor.cpp'}, {file:'MeshRenderer.cpp'}], {key:'file'})
|
||||
// [{score:-18, obj:{file:'MeshRenderer.cpp'}}, {score:-6009, obj:{file:'Monitor.cpp'}}]
|
||||
|
||||
fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp'])
|
||||
// [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}]
|
||||
|
||||
fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '<b>', '</b>')
|
||||
// <b>F</b>uzzy <b>S</b>earch
|
||||
*/
|
||||
|
||||
// UMD (Universal Module Definition) for fuzzysort
|
||||
;(function(root, UMD) {
|
||||
if(typeof define === 'function' && define.amd) define([], UMD)
|
||||
else if(typeof module === 'object' && module.exports) module.exports = UMD()
|
||||
else root.fuzzysort = UMD()
|
||||
})(this, function UMD() { function fuzzysortNew(instanceOptions) {
|
||||
|
||||
var fuzzysort = {
|
||||
|
||||
single: function(search, target, options) { ;if(search=='farzher')return{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6]}
|
||||
if(!search) return null
|
||||
if(!isObj(search)) search = fuzzysort.getPreparedSearch(search)
|
||||
|
||||
if(!target) return null
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
|
||||
: instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
|
||||
: true
|
||||
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
|
||||
return algorithm(search, target, search[0])
|
||||
},
|
||||
|
||||
go: function(search, targets, options) { ;if(search=='farzher')return[{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}]
|
||||
if(!search) return noResults
|
||||
search = fuzzysort.prepareSearch(search)
|
||||
var searchLowerCode = search[0]
|
||||
|
||||
var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
|
||||
var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991
|
||||
var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
|
||||
: instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
|
||||
: true
|
||||
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
|
||||
var resultsLen = 0; var limitedCount = 0
|
||||
var targetsLen = targets.length
|
||||
|
||||
// This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
|
||||
|
||||
// options.keys
|
||||
if(options && options.keys) {
|
||||
var scoreFn = options.scoreFn || defaultScoreFn
|
||||
var keys = options.keys
|
||||
var keysLen = keys.length
|
||||
for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i]
|
||||
var objResults = new Array(keysLen)
|
||||
for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
|
||||
var key = keys[keyI]
|
||||
var target = getValue(obj, key)
|
||||
if(!target) { objResults[keyI] = null; continue }
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
objResults[keyI] = algorithm(search, target, searchLowerCode)
|
||||
}
|
||||
objResults.obj = obj // before scoreFn so scoreFn can use it
|
||||
var score = scoreFn(objResults)
|
||||
if(score === null) continue
|
||||
if(score < threshold) continue
|
||||
objResults.score = score
|
||||
if(resultsLen < limit) { q.add(objResults); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(score > q.peek().score) q.replaceTop(objResults)
|
||||
}
|
||||
}
|
||||
|
||||
// options.key
|
||||
} else if(options && options.key) {
|
||||
var key = options.key
|
||||
for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i]
|
||||
var target = getValue(obj, key)
|
||||
if(!target) continue
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
var result = algorithm(search, target, searchLowerCode)
|
||||
if(result === null) continue
|
||||
if(result.score < threshold) continue
|
||||
|
||||
// have to clone result so duplicate targets from different obj can each reference the correct obj
|
||||
result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden
|
||||
|
||||
if(resultsLen < limit) { q.add(result); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(result.score > q.peek().score) q.replaceTop(result)
|
||||
}
|
||||
}
|
||||
|
||||
// no keys
|
||||
} else {
|
||||
for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i]
|
||||
if(!target) continue
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
var result = algorithm(search, target, searchLowerCode)
|
||||
if(result === null) continue
|
||||
if(result.score < threshold) continue
|
||||
if(resultsLen < limit) { q.add(result); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(result.score > q.peek().score) q.replaceTop(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(resultsLen === 0) return noResults
|
||||
var results = new Array(resultsLen)
|
||||
for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
|
||||
results.total = resultsLen + limitedCount
|
||||
return results
|
||||
},
|
||||
|
||||
goAsync: function(search, targets, options) {
|
||||
var canceled = false
|
||||
var p = new Promise(function(resolve, reject) { ;if(search=='farzher')return resolve([{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}])
|
||||
if(!search) return resolve(noResults)
|
||||
search = fuzzysort.prepareSearch(search)
|
||||
var searchLowerCode = search[0]
|
||||
|
||||
var q = fastpriorityqueue()
|
||||
var iCurrent = targets.length - 1
|
||||
var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
|
||||
var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991
|
||||
var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
|
||||
: instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
|
||||
: true
|
||||
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
|
||||
var resultsLen = 0; var limitedCount = 0
|
||||
function step() {
|
||||
if(canceled) return reject('canceled')
|
||||
|
||||
var startMs = Date.now()
|
||||
|
||||
// This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
|
||||
|
||||
// options.keys
|
||||
if(options && options.keys) {
|
||||
var scoreFn = options.scoreFn || defaultScoreFn
|
||||
var keys = options.keys
|
||||
var keysLen = keys.length
|
||||
for(; iCurrent >= 0; --iCurrent) {
|
||||
if(iCurrent%1000/*itemsPerCheck*/ === 0) {
|
||||
if(Date.now() - startMs >= 10/*asyncInterval*/) {
|
||||
isNode?setImmediate(step):setTimeout(step)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var obj = targets[iCurrent]
|
||||
var objResults = new Array(keysLen)
|
||||
for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
|
||||
var key = keys[keyI]
|
||||
var target = getValue(obj, key)
|
||||
if(!target) { objResults[keyI] = null; continue }
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
objResults[keyI] = algorithm(search, target, searchLowerCode)
|
||||
}
|
||||
objResults.obj = obj // before scoreFn so scoreFn can use it
|
||||
var score = scoreFn(objResults)
|
||||
if(score === null) continue
|
||||
if(score < threshold) continue
|
||||
objResults.score = score
|
||||
if(resultsLen < limit) { q.add(objResults); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(score > q.peek().score) q.replaceTop(objResults)
|
||||
}
|
||||
}
|
||||
|
||||
// options.key
|
||||
} else if(options && options.key) {
|
||||
var key = options.key
|
||||
for(; iCurrent >= 0; --iCurrent) {
|
||||
if(iCurrent%1000/*itemsPerCheck*/ === 0) {
|
||||
if(Date.now() - startMs >= 10/*asyncInterval*/) {
|
||||
isNode?setImmediate(step):setTimeout(step)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var obj = targets[iCurrent]
|
||||
var target = getValue(obj, key)
|
||||
if(!target) continue
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
var result = algorithm(search, target, searchLowerCode)
|
||||
if(result === null) continue
|
||||
if(result.score < threshold) continue
|
||||
|
||||
// have to clone result so duplicate targets from different obj can each reference the correct obj
|
||||
result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden
|
||||
|
||||
if(resultsLen < limit) { q.add(result); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(result.score > q.peek().score) q.replaceTop(result)
|
||||
}
|
||||
}
|
||||
|
||||
// no keys
|
||||
} else {
|
||||
for(; iCurrent >= 0; --iCurrent) {
|
||||
if(iCurrent%1000/*itemsPerCheck*/ === 0) {
|
||||
if(Date.now() - startMs >= 10/*asyncInterval*/) {
|
||||
isNode?setImmediate(step):setTimeout(step)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var target = targets[iCurrent]
|
||||
if(!target) continue
|
||||
if(!isObj(target)) target = fuzzysort.getPrepared(target)
|
||||
|
||||
var result = algorithm(search, target, searchLowerCode)
|
||||
if(result === null) continue
|
||||
if(result.score < threshold) continue
|
||||
if(resultsLen < limit) { q.add(result); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(result.score > q.peek().score) q.replaceTop(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(resultsLen === 0) return resolve(noResults)
|
||||
var results = new Array(resultsLen)
|
||||
for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
|
||||
results.total = resultsLen + limitedCount
|
||||
resolve(results)
|
||||
}
|
||||
|
||||
isNode?setImmediate(step):step() //setTimeout here is too slow
|
||||
})
|
||||
p.cancel = function() { canceled = true }
|
||||
return p
|
||||
},
|
||||
|
||||
highlight: function(result, hOpen, hClose) {
|
||||
if(typeof hOpen == 'function') return fuzzysort.highlightCallback(result, hOpen)
|
||||
if(result === null) return null
|
||||
if(hOpen === undefined) hOpen = '<b>'
|
||||
if(hClose === undefined) hClose = '</b>'
|
||||
var highlighted = ''
|
||||
var matchesIndex = 0
|
||||
var opened = false
|
||||
var target = result.target
|
||||
var targetLen = target.length
|
||||
var matchesBest = result.indexes
|
||||
for(var i = 0; i < targetLen; ++i) { var char = target[i]
|
||||
if(matchesBest[matchesIndex] === i) {
|
||||
++matchesIndex
|
||||
if(!opened) { opened = true
|
||||
highlighted += hOpen
|
||||
}
|
||||
|
||||
if(matchesIndex === matchesBest.length) {
|
||||
highlighted += char + hClose + target.substr(i+1)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if(opened) { opened = false
|
||||
highlighted += hClose
|
||||
}
|
||||
}
|
||||
highlighted += char
|
||||
}
|
||||
|
||||
return highlighted
|
||||
},
|
||||
highlightCallback: function(result, cb) {
|
||||
if(result === null) return null
|
||||
var target = result.target
|
||||
var targetLen = target.length
|
||||
var indexes = result.indexes
|
||||
var highlighted = ''
|
||||
var matchI = 0
|
||||
var indexesI = 0
|
||||
var opened = false
|
||||
var result = []
|
||||
for(var i = 0; i < targetLen; ++i) { var char = target[i]
|
||||
if(indexes[indexesI] === i) {
|
||||
++indexesI
|
||||
if(!opened) { opened = true
|
||||
result.push(highlighted); highlighted = ''
|
||||
}
|
||||
|
||||
if(indexesI === indexes.length) {
|
||||
highlighted += char
|
||||
result.push(cb(highlighted, matchI++)); highlighted = ''
|
||||
result.push(target.substr(i+1))
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if(opened) { opened = false
|
||||
result.push(cb(highlighted, matchI++)); highlighted = ''
|
||||
}
|
||||
}
|
||||
highlighted += char
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
prepare: function(target) {
|
||||
if(!target) return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null} // hidden
|
||||
return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null} // hidden
|
||||
},
|
||||
prepareSlow: function(target) {
|
||||
if(!target) return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null} // hidden
|
||||
return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null} // hidden
|
||||
},
|
||||
prepareSearch: function(search) {
|
||||
if(!search) search = ''
|
||||
return fuzzysort.prepareLowerCodes(search)
|
||||
},
|
||||
|
||||
|
||||
|
||||
// Below this point is only internal code
|
||||
// Below this point is only internal code
|
||||
// Below this point is only internal code
|
||||
// Below this point is only internal code
|
||||
|
||||
|
||||
|
||||
getPrepared: function(target) {
|
||||
if(target.length > 999) return fuzzysort.prepare(target) // don't cache huge targets
|
||||
var targetPrepared = preparedCache.get(target)
|
||||
if(targetPrepared !== undefined) return targetPrepared
|
||||
targetPrepared = fuzzysort.prepare(target)
|
||||
preparedCache.set(target, targetPrepared)
|
||||
return targetPrepared
|
||||
},
|
||||
getPreparedSearch: function(search) {
|
||||
if(search.length > 999) return fuzzysort.prepareSearch(search) // don't cache huge searches
|
||||
var searchPrepared = preparedSearchCache.get(search)
|
||||
if(searchPrepared !== undefined) return searchPrepared
|
||||
searchPrepared = fuzzysort.prepareSearch(search)
|
||||
preparedSearchCache.set(search, searchPrepared)
|
||||
return searchPrepared
|
||||
},
|
||||
|
||||
algorithm: function(searchLowerCodes, prepared, searchLowerCode) {
|
||||
var targetLowerCodes = prepared._targetLowerCodes
|
||||
var searchLen = searchLowerCodes.length
|
||||
var targetLen = targetLowerCodes.length
|
||||
var searchI = 0 // where we at
|
||||
var targetI = 0 // where you at
|
||||
var typoSimpleI = 0
|
||||
var matchesSimpleLen = 0
|
||||
|
||||
// very basic fuzzy match; to remove non-matching targets ASAP!
|
||||
// walk through target. find sequential matches.
|
||||
// if all chars aren't found then exit
|
||||
for(;;) {
|
||||
var isMatch = searchLowerCode === targetLowerCodes[targetI]
|
||||
if(isMatch) {
|
||||
matchesSimple[matchesSimpleLen++] = targetI
|
||||
++searchI; if(searchI === searchLen) break
|
||||
searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))]
|
||||
}
|
||||
|
||||
++targetI; if(targetI >= targetLen) { // Failed to find searchI
|
||||
// Check for typo or exit
|
||||
// we go as far as possible before trying to transpose
|
||||
// then we transpose backwards until we reach the beginning
|
||||
for(;;) {
|
||||
if(searchI <= 1) return null // not allowed to transpose first char
|
||||
if(typoSimpleI === 0) { // we haven't tried to transpose yet
|
||||
--searchI
|
||||
var searchLowerCodeNew = searchLowerCodes[searchI]
|
||||
if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char
|
||||
typoSimpleI = searchI
|
||||
} else {
|
||||
if(typoSimpleI === 1) return null // reached the end of the line for transposing
|
||||
--typoSimpleI
|
||||
searchI = typoSimpleI
|
||||
searchLowerCode = searchLowerCodes[searchI + 1]
|
||||
var searchLowerCodeNew = searchLowerCodes[searchI]
|
||||
if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char
|
||||
}
|
||||
matchesSimpleLen = searchI
|
||||
targetI = matchesSimple[matchesSimpleLen - 1] + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var searchI = 0
|
||||
var typoStrictI = 0
|
||||
var successStrict = false
|
||||
var matchesStrictLen = 0
|
||||
|
||||
var nextBeginningIndexes = prepared._nextBeginningIndexes
|
||||
if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target)
|
||||
var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
|
||||
|
||||
// Our target string successfully matched all characters in sequence!
|
||||
// Let's try a more advanced and strict test to improve the score
|
||||
// only count it as a match if it's consecutive or a beginning character!
|
||||
if(targetI !== targetLen) for(;;) {
|
||||
if(targetI >= targetLen) {
|
||||
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
|
||||
if(searchI <= 0) { // We failed to push chars forward for a better match
|
||||
// transpose, starting from the beginning
|
||||
++typoStrictI; if(typoStrictI > searchLen-2) break
|
||||
if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) continue // doesn't make sense to transpose a repeat char
|
||||
targetI = firstPossibleI
|
||||
continue
|
||||
}
|
||||
|
||||
--searchI
|
||||
var lastMatch = matchesStrict[--matchesStrictLen]
|
||||
targetI = nextBeginningIndexes[lastMatch]
|
||||
|
||||
} else {
|
||||
var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI]
|
||||
if(isMatch) {
|
||||
matchesStrict[matchesStrictLen++] = targetI
|
||||
++searchI; if(searchI === searchLen) { successStrict = true; break }
|
||||
++targetI
|
||||
} else {
|
||||
targetI = nextBeginningIndexes[targetI]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // tally up the score & keep track of matches for highlighting later
|
||||
if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
|
||||
else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
|
||||
var score = 0
|
||||
var lastTargetI = -1
|
||||
for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i]
|
||||
// score only goes down if they're not consecutive
|
||||
if(lastTargetI !== targetI - 1) score -= targetI
|
||||
lastTargetI = targetI
|
||||
}
|
||||
if(!successStrict) {
|
||||
score *= 1000
|
||||
if(typoSimpleI !== 0) score += -20/*typoPenalty*/
|
||||
} else {
|
||||
if(typoStrictI !== 0) score += -20/*typoPenalty*/
|
||||
}
|
||||
score -= targetLen - searchLen
|
||||
prepared.score = score
|
||||
prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i]
|
||||
|
||||
return prepared
|
||||
}
|
||||
},
|
||||
|
||||
algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) {
|
||||
var targetLowerCodes = prepared._targetLowerCodes
|
||||
var searchLen = searchLowerCodes.length
|
||||
var targetLen = targetLowerCodes.length
|
||||
var searchI = 0 // where we at
|
||||
var targetI = 0 // where you at
|
||||
var matchesSimpleLen = 0
|
||||
|
||||
// very basic fuzzy match; to remove non-matching targets ASAP!
|
||||
// walk through target. find sequential matches.
|
||||
// if all chars aren't found then exit
|
||||
for(;;) {
|
||||
var isMatch = searchLowerCode === targetLowerCodes[targetI]
|
||||
if(isMatch) {
|
||||
matchesSimple[matchesSimpleLen++] = targetI
|
||||
++searchI; if(searchI === searchLen) break
|
||||
searchLowerCode = searchLowerCodes[searchI]
|
||||
}
|
||||
++targetI; if(targetI >= targetLen) return null // Failed to find searchI
|
||||
}
|
||||
|
||||
var searchI = 0
|
||||
var successStrict = false
|
||||
var matchesStrictLen = 0
|
||||
|
||||
var nextBeginningIndexes = prepared._nextBeginningIndexes
|
||||
if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target)
|
||||
var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
|
||||
|
||||
// Our target string successfully matched all characters in sequence!
|
||||
// Let's try a more advanced and strict test to improve the score
|
||||
// only count it as a match if it's consecutive or a beginning character!
|
||||
if(targetI !== targetLen) for(;;) {
|
||||
if(targetI >= targetLen) {
|
||||
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
|
||||
if(searchI <= 0) break // We failed to push chars forward for a better match
|
||||
|
||||
--searchI
|
||||
var lastMatch = matchesStrict[--matchesStrictLen]
|
||||
targetI = nextBeginningIndexes[lastMatch]
|
||||
|
||||
} else {
|
||||
var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]
|
||||
if(isMatch) {
|
||||
matchesStrict[matchesStrictLen++] = targetI
|
||||
++searchI; if(searchI === searchLen) { successStrict = true; break }
|
||||
++targetI
|
||||
} else {
|
||||
targetI = nextBeginningIndexes[targetI]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // tally up the score & keep track of matches for highlighting later
|
||||
if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
|
||||
else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
|
||||
var score = 0
|
||||
var lastTargetI = -1
|
||||
for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i]
|
||||
// score only goes down if they're not consecutive
|
||||
if(lastTargetI !== targetI - 1) score -= targetI
|
||||
lastTargetI = targetI
|
||||
}
|
||||
if(!successStrict) score *= 1000
|
||||
score -= targetLen - searchLen
|
||||
prepared.score = score
|
||||
prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i]
|
||||
|
||||
return prepared
|
||||
}
|
||||
},
|
||||
|
||||
prepareLowerCodes: function(str) {
|
||||
var strLen = str.length
|
||||
var lowerCodes = [] // new Array(strLen) sparse array is too slow
|
||||
var lower = str.toLowerCase()
|
||||
for(var i = 0; i < strLen; ++i) lowerCodes[i] = lower.charCodeAt(i)
|
||||
return lowerCodes
|
||||
},
|
||||
prepareBeginningIndexes: function(target) {
|
||||
var targetLen = target.length
|
||||
var beginningIndexes = []; var beginningIndexesLen = 0
|
||||
var wasUpper = false
|
||||
var wasAlphanum = false
|
||||
for(var i = 0; i < targetLen; ++i) {
|
||||
var targetCode = target.charCodeAt(i)
|
||||
var isUpper = targetCode>=65&&targetCode<=90
|
||||
var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57
|
||||
var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum
|
||||
wasUpper = isUpper
|
||||
wasAlphanum = isAlphanum
|
||||
if(isBeginning) beginningIndexes[beginningIndexesLen++] = i
|
||||
}
|
||||
return beginningIndexes
|
||||
},
|
||||
prepareNextBeginningIndexes: function(target) {
|
||||
var targetLen = target.length
|
||||
var beginningIndexes = fuzzysort.prepareBeginningIndexes(target)
|
||||
var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
|
||||
var lastIsBeginning = beginningIndexes[0]
|
||||
var lastIsBeginningI = 0
|
||||
for(var i = 0; i < targetLen; ++i) {
|
||||
if(lastIsBeginning > i) {
|
||||
nextBeginningIndexes[i] = lastIsBeginning
|
||||
} else {
|
||||
lastIsBeginning = beginningIndexes[++lastIsBeginningI]
|
||||
nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning
|
||||
}
|
||||
}
|
||||
return nextBeginningIndexes
|
||||
},
|
||||
|
||||
cleanup: cleanup,
|
||||
new: fuzzysortNew,
|
||||
}
|
||||
return fuzzysort
|
||||
} // fuzzysortNew
|
||||
|
||||
// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
|
||||
var isNode = typeof require !== 'undefined' && typeof window === 'undefined'
|
||||
var MyMap = typeof Map === 'function' ? Map : function(){var s=Object.create(null);this.get=function(k){return s[k]};this.set=function(k,val){s[k]=val;return this};this.clear=function(){s=Object.create(null)}}
|
||||
var preparedCache = new MyMap()
|
||||
var preparedSearchCache = new MyMap()
|
||||
var noResults = []; noResults.total = 0
|
||||
var matchesSimple = []; var matchesStrict = []
|
||||
function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = [] }
|
||||
function defaultScoreFn(a) {
|
||||
var max = -9007199254740991
|
||||
for (var i = a.length - 1; i >= 0; --i) {
|
||||
var result = a[i]; if(result === null) continue
|
||||
var score = result.score
|
||||
if(score > max) max = score
|
||||
}
|
||||
if(max === -9007199254740991) return null
|
||||
return max
|
||||
}
|
||||
|
||||
// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
|
||||
// prop = 'key1.key2' 10ms
|
||||
// prop = ['key1', 'key2'] 27ms
|
||||
function getValue(obj, prop) {
|
||||
var tmp = obj[prop]; if(tmp !== undefined) return tmp
|
||||
var segs = prop
|
||||
if(!Array.isArray(prop)) segs = prop.split('.')
|
||||
var len = segs.length
|
||||
var i = -1
|
||||
while (obj && (++i < len)) obj = obj[segs[i]]
|
||||
return obj
|
||||
}
|
||||
|
||||
function isObj(x) { return typeof x === 'object' } // faster as a function
|
||||
|
||||
// Hacked version of https://github.com/lemire/FastPriorityQueue.js
|
||||
var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c<o;){var f=c+1;e=c,f<o&&r[f].score<r[c].score&&(e=f),r[e-1>>1]=r[e],c=1+(e<<1)}for(var a=e-1>>1;e>0&&n.score<r[a].score;a=(e=a)-1>>1)r[e]=r[a];r[e]=n}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score<r[c].score;c=(n=c)-1>>1)r[n]=r[c];r[n]=e},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e}},e.peek=function(e){if(0!==o)return r[0]},e.replaceTop=function(o){r[0]=o,n()},e};
|
||||
var q = fastpriorityqueue() // reuse this, except for async, it needs to make its own
|
||||
|
||||
return fuzzysortNew()
|
||||
}) // UMD
|
||||
|
||||
// TODO: (performance) wasm version!?
|
||||
// TODO: (performance) threads?
|
||||
// TODO: (performance) avoid cache misses
|
||||
// TODO: (performance) preparedCache is a memory leak
|
||||
// TODO: (like sublime) backslash === forwardslash
|
||||
// TODO: (like sublime) spaces: "a b" should do 2 searches 1 for a and 1 for b
|
||||
// TODO: (scoring) garbage in targets that allows most searches to strict match need a penality
|
||||
// TODO: (performance) idk if allowTypo is optimized
|
||||
199
public/src/js/lib/jszip.js
Normal file
199
public/src/js/lib/jszip.js
Normal file
@@ -0,0 +1,199 @@
|
||||
|
||||
/*!
|
||||
|
||||
JSZip v3.7.1 - A JavaScript class for generating and reading zip files
|
||||
<http://stuartk.com/jszip>
|
||||
|
||||
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||||
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
|
||||
|
||||
JSZip uses the library pako released under the MIT license :
|
||||
https://github.com/nodeca/pako/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
function JSZip(){return function s(a,o,h){function u(r,t){if(!o[r]){if(!a[r]){var e="function"==typeof require&&require;if(!t&&e)return e(r,!0);if(l)return l(r,!0);var i=new Error("Cannot find module '"+r+"'");throw i.code="MODULE_NOT_FOUND",i;}var n=o[r]={exports:{}};a[r][0].call(n.exports,function(t){var e=a[r][1][t];return u(e||t)},n,n.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,t=0;t<h.length;t++)u(h[t]);return u}({1:[function(t,e,r){var c=t("./utils"),d=
|
||||
t("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(t){for(var e,r,i,n,s,a,o,h=[],u=0,l=t.length,f=l,d="string"!==c.getTypeOf(t);u<t.length;)f=l-u,i=d?(e=t[u++],r=u<l?t[u++]:0,u<l?t[u++]:0):(e=t.charCodeAt(u++),r=u<l?t.charCodeAt(u++):0,u<l?t.charCodeAt(u++):0),n=e>>2,s=(3&e)<<4|r>>4,a=1<f?(15&r)<<2|i>>6:64,o=2<f?63&i:64,h.push(p.charAt(n)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(t){var e,r,i,n,s,a,o=0,h=0,u="data:";
|
||||
if(t.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(t=t.replace(/[^A-Za-z0-9\+\/=]/g,"")).length/4;if(t.charAt(t.length-1)===p.charAt(64)&&f--,t.charAt(t.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=d.uint8array?new Uint8Array(0|f):new Array(0|f);o<t.length;)e=p.indexOf(t.charAt(o++))<<2|(n=p.indexOf(t.charAt(o++)))>>4,r=(15&n)<<4|(s=p.indexOf(t.charAt(o++)))>>2,i=(3&s)<<6|(a=p.indexOf(t.charAt(o++))),
|
||||
l[h++]=e,64!==s&&(l[h++]=r),64!==a&&(l[h++]=i);return l}},{"./support":30,"./utils":32}],2:[function(t,e,r){var i=t("./external"),n=t("./stream/DataWorker"),s=t("./stream/Crc32Probe"),a=t("./stream/DataLengthProbe");function o(t,e,r,i,n){this.compressedSize=t,this.uncompressedSize=e,this.crc32=r,this.compression=i,this.compressedContent=n}o.prototype={getContentWorker:function(){var t=(new n(i.Promise.resolve(this.compressedContent))).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),
|
||||
e=this;return t.on("end",function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch");}),t},getCompressedWorker:function(){return(new n(i.Promise.resolve(this.compressedContent))).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(t,e,r){return t.pipe(new s).pipe(new a("uncompressedSize")).pipe(e.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",
|
||||
e)},e.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(t,e,r){var i=t("./stream/GenericWorker");r.STORE={magic:"\x00\x00",compressWorker:function(t){return new i("STORE compression")},uncompressWorker:function(){return new i("STORE decompression")}},r.DEFLATE=t("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(t,e,r){var i=t("./utils");var o=function(){for(var t,e=[],r=0;r<256;r++){t=r;for(var i=0;i<8;i++)t=1&
|
||||
t?3988292384^t>>>1:t>>>1;e[r]=t}return e}();e.exports=function(t,e){return void 0!==t&&t.length?"string"!==i.getTypeOf(t)?function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;a<s;a++)t=t>>>8^n[255&(t^e[a])];return-1^t}(0|e,t,t.length,0):function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;a<s;a++)t=t>>>8^n[255&(t^e.charCodeAt(a))];return-1^t}(0|e,t,t.length,0):0}},{"./utils":32}],5:[function(t,e,r){r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,
|
||||
r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(t,e,r){var i=null;i="undefined"!=typeof Promise?Promise:t("lie"),e.exports={Promise:i}},{lie:37}],7:[function(t,e,r){var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,n=t("pako"),s=t("./utils"),a=t("./stream/GenericWorker"),o=i?"uint8array":"array";function h(t,e){a.call(this,"FlateWorker/"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}r.magic=
|
||||
"\b\x00",s.inherits(h,a),h.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,t.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new n[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=
|
||||
function(t){e.push({data:t,meta:e.meta})}},r.compressWorker=function(t){return new h("Deflate",t)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(t,e,r){function A(t,e){var r,i="";for(r=0;r<e;r++)i+=String.fromCharCode(255&t),t>>>=8;return i}function i(t,e,r,i,n,s){var a,o,h=t.file,u=t.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),d=I.transformTo("string",O.utf8encode(h.name)),c=h.comment,p=I.transformTo("string",
|
||||
s(c)),m=I.transformTo("string",O.utf8encode(c)),_=d.length!==h.name.length,g=m.length!==c.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};e&&!r||(x.crc32=t.crc32,x.compressedSize=t.compressedSize,x.uncompressedSize=t.uncompressedSize);var S=0;e&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===n?(C=798,z|=function(t,e){var r=t;return t||(r=e?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(t){return 63&(t||0)}(h.dosPermissions)),
|
||||
a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+d,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\x00",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+
|
||||
A(p.length,2)+"\x00\x00\x00\x00"+A(z,4)+A(i,4)+f+b+p}}var I=t("../utils"),n=t("../stream/GenericWorker"),O=t("../utf8"),B=t("../crc32"),R=t("../signature");function s(t,e,r,i){n.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=e,this.zipPlatform=r,this.encodeFileName=i,this.streamFiles=t,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,n),s.prototype.push=function(t){var e=t.meta.percent||
|
||||
0,r=this.entriesCount,i=this._sources.length;this.accumulate?this.contentBuffer.push(t):(this.bytesWritten+=t.data.length,n.prototype.push.call(this,{data:t.data,meta:{currentFile:this.currentFile,percent:r?(e+100*(r-i-1))/r:100}}))},s.prototype.openedSource=function(t){this.currentSourceOffset=this.bytesWritten,this.currentFile=t.file.name;var e=this.streamFiles&&!t.file.dir;if(e){var r=i(t,e,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=
|
||||
!0},s.prototype.closedSource=function(t){this.accumulate=!1;var e=this.streamFiles&&!t.file.dir,r=i(t,e,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),e)this.push({data:function(t){return R.DATA_DESCRIPTOR+A(t.crc32,4)+A(t.compressedSize,4)+A(t.uncompressedSize,4)}(t),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=
|
||||
function(){for(var t=this.bytesWritten,e=0;e<this.dirRecords.length;e++)this.push({data:this.dirRecords[e],meta:{percent:100}});var r=this.bytesWritten-t,i=function(t,e,r,i,n){var s=I.transformTo("string",n(i));return R.CENTRAL_DIRECTORY_END+"\x00\x00\x00\x00"+A(t,2)+A(t,2)+A(e,4)+A(r,4)+A(s.length,2)+s}(this.dirRecords.length,r,t,this.zipComment,this.encodeFileName);this.push({data:i,meta:{percent:100}})},s.prototype.prepareNextSource=function(){this.previous=this._sources.shift(),this.openedSource(this.previous.streamInfo),
|
||||
this.isPaused?this.previous.pause():this.previous.resume()},s.prototype.registerPrevious=function(t){this._sources.push(t);var e=this;return t.on("data",function(t){e.processChunk(t)}),t.on("end",function(){e.closedSource(e.previous.streamInfo),e._sources.length?e.prepareNextSource():e.end()}),t.on("error",function(t){e.error(t)}),this},s.prototype.resume=function(){return!!n.prototype.resume.call(this)&&(!this.previous&&this._sources.length?(this.prepareNextSource(),!0):this.previous||this._sources.length||
|
||||
this.generatedError?void 0:(this.end(),!0))},s.prototype.error=function(t){var e=this._sources;if(!n.prototype.error.call(this,t))return!1;for(var r=0;r<e.length;r++)try{e[r].error(t)}catch(t$0){}return!0},s.prototype.lock=function(){n.prototype.lock.call(this);for(var t=this._sources,e=0;e<t.length;e++)t[e].lock()},e.exports=s},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(t,e,r){var u=t("../compressions"),i=t("./ZipFileWorker");r.generateWorker=
|
||||
function(t,a,e){var o=new i(a.streamFiles,e,a.platform,a.encodeFileName),h=0;try{t.forEach(function(t,e){h++;var r=function(t,e){var r=t||e,i=u[r];if(!i)throw new Error(r+" is not a valid compression method !");return i}(e.options.compression,a.compression),i=e.options.compressionOptions||a.compressionOptions||{},n=e.dir,s=e.date;e._compressWorker(r,i).withStreamInfo("file",{name:t,dir:n,date:s,comment:e.comment||"",unixPermissions:e.unixPermissions,dosPermissions:e.dosPermissions}).pipe(o)}),o.entriesCount=
|
||||
h}catch(t$1){o.error(t$1)}return o}},{"../compressions":3,"./ZipFileWorker":8}],10:[function(t,e,r){function i(){if(!(this instanceof i))return new i;if(arguments.length)throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");this.files=Object.create(null),this.comment=null,this.root="",this.clone=function(){var t=new i;for(var e in this)"function"!=typeof this[e]&&(t[e]=this[e]);return t}}(i.prototype=t("./object")).loadAsync=t("./load"),
|
||||
i.support=t("./support"),i.defaults=t("./defaults"),i.version="3.7.1",i.loadAsync=function(t,e){return(new i).loadAsync(t,e)},i.external=t("./external"),e.exports=i},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(t,e,r){var i=t("./utils"),n=t("./external"),o=t("./utf8"),h=t("./zipEntries"),s=t("./stream/Crc32Probe"),u=t("./nodejsUtils");function l(i){return new n.Promise(function(t,e){var r=i.decompressed.getContentWorker().pipe(new s);r.on("error",function(t){e(t)}).on("end",
|
||||
function(){r.streamInfo.crc32!==i.decompressed.crc32?e(new Error("Corrupted zip : CRC32 mismatch")):t()}).resume()})}e.exports=function(t,s){var a=this;return s=i.extend(s||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:o.utf8decode}),u.isNode&&u.isStream(t)?n.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")):i.prepareContent("the loaded zip file",t,!0,s.optimizedBinaryString,s.base64).then(function(t){var e=new h(s);return e.load(t),
|
||||
e}).then(function(t){var e=[n.Promise.resolve(t)],r=t.files;if(s.checkCRC32)for(var i=0;i<r.length;i++)e.push(l(r[i]));return n.Promise.all(e)}).then(function(t){for(var e=t.shift(),r=e.files,i=0;i<r.length;i++){var n=r[i];a.file(n.fileNameStr,n.decompressed,{binary:!0,optimizedBinaryString:!0,date:n.date,dir:n.dir,comment:n.fileCommentStr.length?n.fileCommentStr:null,unixPermissions:n.unixPermissions,dosPermissions:n.dosPermissions,createFolders:s.createFolders})}return e.zipComment.length&&(a.comment=
|
||||
e.zipComment),a})}},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(t,e,r){var i=t("../utils"),n=t("../stream/GenericWorker");function s(t,e){n.call(this,"Nodejs stream input adapter for "+t),this._upstreamEnded=!1,this._bindStream(e)}i.inherits(s,n),s.prototype._bindStream=function(t){var e=this;(this._stream=t).pause(),t.on("data",function(t){e.push({data:t,meta:{percent:0}})}).on("error",function(t){e.isPaused?this.generatedError=
|
||||
t:e.error(t)}).on("end",function(){e.isPaused?e._upstreamEnded=!0:e.end()})},s.prototype.pause=function(){return!!n.prototype.pause.call(this)&&(this._stream.pause(),!0)},s.prototype.resume=function(){return!!n.prototype.resume.call(this)&&(this._upstreamEnded?this.end():this._stream.resume(),!0)},e.exports=s},{"../stream/GenericWorker":28,"../utils":32}],13:[function(t,e,r){var n=t("readable-stream").Readable;function i(t,e,r){n.call(this,e),this._helper=t;var i=this;t.on("data",function(t,e){i.push(t)||
|
||||
i._helper.pause(),r&&r(e)}).on("error",function(t){i.emit("error",t)}).on("end",function(){i.push(null)})}t("../utils").inherits(i,n),i.prototype._read=function(){this._helper.resume()},e.exports=i},{"../utils":32,"readable-stream":16}],14:[function(t,e,r){e.exports={isNode:"undefined"!=typeof Buffer,newBufferFrom:function(t,e){if(Buffer.from&&Buffer.from!==Uint8Array.from)return Buffer.from(t,e);if("number"==typeof t)throw new Error('The "data" argument must not be a number');return new Buffer(t,
|
||||
e)},allocBuffer:function(t){if(Buffer.alloc)return Buffer.alloc(t);var e=new Buffer(t);return e.fill(0),e},isBuffer:function(t){return Buffer.isBuffer(t)},isStream:function(t){return t&&"function"==typeof t.on&&"function"==typeof t.pause&&"function"==typeof t.resume}}},{}],15:[function(t,e,r){function s(t,e,r){var i,n=u.getTypeOf(e),s=u.extend(r||{},f);s.date=s.date||new Date,null!==s.compression&&(s.compression=s.compression.toUpperCase()),"string"==typeof s.unixPermissions&&(s.unixPermissions=parseInt(s.unixPermissions,
|
||||
8)),s.unixPermissions&&16384&s.unixPermissions&&(s.dir=!0),s.dosPermissions&&16&s.dosPermissions&&(s.dir=!0),s.dir&&(t=g(t)),s.createFolders&&(i=_(t))&&b.call(this,i,!0);var a="string"===n&&!1===s.binary&&!1===s.base64;r&&void 0!==r.binary||(s.binary=!a),(e instanceof d&&0===e.uncompressedSize||s.dir||!e||0===e.length)&&(s.base64=!1,s.binary=!0,e="",s.compression="STORE",n="string");var o=null;o=e instanceof d||e instanceof l?e:p.isNode&&p.isStream(e)?new m(t,e):u.prepareContent(t,e,s.binary,s.optimizedBinaryString,
|
||||
s.base64);var h=new c(t,o,s);this.files[t]=h}var n=t("./utf8"),u=t("./utils"),l=t("./stream/GenericWorker"),a=t("./stream/StreamHelper"),f=t("./defaults"),d=t("./compressedObject"),c=t("./zipObject"),o=t("./generate"),p=t("./nodejsUtils"),m=t("./nodejs/NodejsStreamInputAdapter"),_=function(t){"/"===t.slice(-1)&&(t=t.substring(0,t.length-1));var e=t.lastIndexOf("/");return 0<e?t.substring(0,e):""},g=function(t){return"/"!==t.slice(-1)&&(t+="/"),t},b=function(t,e){return e=void 0!==e?e:f.createFolders,
|
||||
t=g(t),this.files[t]||s.call(this,t,null,{dir:!0,createFolders:e}),this.files[t]};function h(t){return"[object RegExp]"===Object.prototype.toString.call(t)}var i={load:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");},forEach:function(t){var e,r,i;for(e in this.files)i=this.files[e],(r=e.slice(this.root.length,e.length))&&e.slice(0,this.root.length)===this.root&&t(r,i)},filter:function(r){var i=[];return this.forEach(function(t,e){r(t,e)&&i.push(e)}),
|
||||
i},file:function(t,e,r){if(1!==arguments.length)return t=this.root+t,s.call(this,t,e,r),this;if(h(t)){var i=t;return this.filter(function(t,e){return!e.dir&&i.test(t)})}var n=this.files[this.root+t];return n&&!n.dir?n:null},folder:function(r){if(!r)return this;if(h(r))return this.filter(function(t,e){return e.dir&&r.test(t)});var t=this.root+r,e=b.call(this,t),i=this.clone();return i.root=e.name,i},remove:function(r){r=this.root+r;var t=this.files[r];if(t||("/"!==r.slice(-1)&&(r+="/"),t=this.files[r]),
|
||||
t&&!t.dir)delete this.files[r];else for(var e=this.filter(function(t,e){return e.name.slice(0,r.length)===r}),i=0;i<e.length;i++)delete this.files[e[i].name];return this},generate:function(t){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");},generateInternalStream:function(t){var e,r={};try{if((r=u.extend(t||{},{streamFiles:!1,compression:"STORE",compressionOptions:null,type:"",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:n.utf8encode})).type=
|
||||
r.type.toLowerCase(),r.compression=r.compression.toUpperCase(),"binarystring"===r.type&&(r.type="string"),!r.type)throw new Error("No output type specified.");u.checkSupport(r.type),"darwin"!==r.platform&&"freebsd"!==r.platform&&"linux"!==r.platform&&"sunos"!==r.platform||(r.platform="UNIX"),"win32"===r.platform&&(r.platform="DOS");var i=r.comment||this.comment||"";e=o.generateWorker(this,r,i)}catch(t$2){(e=new l("error")).error(t$2)}return new a(e,r.type||"string",r.mimeType)},generateAsync:function(t,
|
||||
e){return this.generateInternalStream(t).accumulate(e)},generateNodeStream:function(t,e){return(t=t||{}).type||(t.type="nodebuffer"),this.generateInternalStream(t).toNodejsStream(e)}};e.exports=i},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(t,e,r){e.exports=t("stream")},{stream:void 0}],17:[function(t,e,r){var i=t("./DataReader");
|
||||
function n(t){i.call(this,t);for(var e=0;e<this.data.length;e++)t[e]=255&t[e]}t("../utils").inherits(n,i),n.prototype.byteAt=function(t){return this.data[this.zero+t]},n.prototype.lastIndexOfSignature=function(t){for(var e=t.charCodeAt(0),r=t.charCodeAt(1),i=t.charCodeAt(2),n=t.charCodeAt(3),s=this.length-4;0<=s;--s)if(this.data[s]===e&&this.data[s+1]===r&&this.data[s+2]===i&&this.data[s+3]===n)return s-this.zero;return-1},n.prototype.readAndCheckSignature=function(t){var e=t.charCodeAt(0),r=t.charCodeAt(1),
|
||||
i=t.charCodeAt(2),n=t.charCodeAt(3),s=this.readData(4);return e===s[0]&&r===s[1]&&i===s[2]&&n===s[3]},n.prototype.readData=function(t){if(this.checkOffset(t),0===t)return[];var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./DataReader":18}],18:[function(t,e,r){var i=t("../utils");function n(t){this.data=t,this.length=t.length,this.index=0,this.zero=0}n.prototype={checkOffset:function(t){this.checkIndex(this.index+t)},checkIndex:function(t){if(this.length<
|
||||
this.zero+t||t<0)throw new Error("End of data reached (data length = "+this.length+", asked index = "+t+"). Corrupted zip ?");},setIndex:function(t){this.checkIndex(t),this.index=t},skip:function(t){this.setIndex(this.index+t)},byteAt:function(t){},readInt:function(t){var e,r=0;for(this.checkOffset(t),e=this.index+t-1;e>=this.index;e--)r=(r<<8)+this.byteAt(e);return this.index+=t,r},readString:function(t){return i.transformTo("string",this.readData(t))},readData:function(t){},lastIndexOfSignature:function(t){},
|
||||
readAndCheckSignature:function(t){},readDate:function(){var t=this.readInt(4);return new Date(Date.UTC(1980+(t>>25&127),(t>>21&15)-1,t>>16&31,t>>11&31,t>>5&63,(31&t)<<1))}},e.exports=n},{"../utils":32}],19:[function(t,e,r){var i=t("./Uint8ArrayReader");function n(t){i.call(this,t)}t("../utils").inherits(n,i),n.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./Uint8ArrayReader":21}],
|
||||
20:[function(t,e,r){var i=t("./DataReader");function n(t){i.call(this,t)}t("../utils").inherits(n,i),n.prototype.byteAt=function(t){return this.data.charCodeAt(this.zero+t)},n.prototype.lastIndexOfSignature=function(t){return this.data.lastIndexOf(t)-this.zero},n.prototype.readAndCheckSignature=function(t){return t===this.readData(4)},n.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,
|
||||
"./DataReader":18}],21:[function(t,e,r){var i=t("./ArrayReader");function n(t){i.call(this,t)}t("../utils").inherits(n,i),n.prototype.readData=function(t){if(this.checkOffset(t),0===t)return new Uint8Array(0);var e=this.data.subarray(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./ArrayReader":17}],22:[function(t,e,r){var i=t("../utils"),n=t("../support"),s=t("./ArrayReader"),a=t("./StringReader"),o=t("./NodeBufferReader"),h=t("./Uint8ArrayReader");
|
||||
e.exports=function(t){var e=i.getTypeOf(t);return i.checkSupport(e),"string"!==e||n.uint8array?"nodebuffer"===e?new o(t):n.uint8array?new h(i.transformTo("uint8array",t)):new s(i.transformTo("array",t)):new a(t)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(t,e,r){r.LOCAL_FILE_HEADER="PK\u0003\u0004",r.CENTRAL_FILE_HEADER="PK\u0001\u0002",r.CENTRAL_DIRECTORY_END="PK\u0005\u0006",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR=
|
||||
"PK\u0006\u0007",r.ZIP64_CENTRAL_DIRECTORY_END="PK\u0006\u0006",r.DATA_DESCRIPTOR="PK\u0007\b"},{}],24:[function(t,e,r){var i=t("./GenericWorker"),n=t("../utils");function s(t){i.call(this,"ConvertWorker to "+t),this.destType=t}n.inherits(s,i),s.prototype.processChunk=function(t){this.push({data:n.transformTo(this.destType,t.data),meta:t.meta})},e.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(t,e,r){var i=t("./GenericWorker"),n=t("../crc32");function s(){i.call(this,"Crc32Probe"),
|
||||
this.withStreamInfo("crc32",0)}t("../utils").inherits(s,i),s.prototype.processChunk=function(t){this.streamInfo.crc32=n(t.data,this.streamInfo.crc32||0),this.push(t)},e.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(t,e,r){var i=t("../utils"),n=t("./GenericWorker");function s(t){n.call(this,"DataLengthProbe for "+t),this.propName=t,this.withStreamInfo(t,0)}i.inherits(s,n),s.prototype.processChunk=function(t){if(t){var e=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=
|
||||
e+t.data.length}n.prototype.processChunk.call(this,t)},e.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(t,e,r){var i=t("../utils"),n=t("./GenericWorker");function s(t){n.call(this,"DataWorker");var e=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,t.then(function(t){e.dataIsReady=!0,e.data=t,e.max=t&&t.length||0,e.type=i.getTypeOf(t),e.isPaused||e._tickAndRepeat()},function(t){e.error(t)})}i.inherits(s,n),s.prototype.cleanUp=function(){n.prototype.cleanUp.call(this),
|
||||
this.data=null},s.prototype.resume=function(){return!!n.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,i.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(i.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var t=null,e=Math.min(this.max,this.index+16384);
|
||||
if(this.index>=this.max)return this.end();switch(this.type){case "string":t=this.data.substring(this.index,e);break;case "uint8array":t=this.data.subarray(this.index,e);break;case "array":case "nodebuffer":t=this.data.slice(this.index,e)}return this.index=e,this.push({data:t,meta:{percent:this.max?this.index/this.max*100:0}})},e.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(t,e,r){function i(t){this.name=t||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo=
|
||||
{},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}i.prototype={push:function(t){this.emit("data",t)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(t$3){this.emit("error",t$3)}return!0},error:function(t){return!this.isFinished&&(this.isPaused?this.generatedError=t:(this.isFinished=!0,this.emit("error",t),this.previous&&this.previous.error(t),this.cleanUp()),!0)},
|
||||
on:function(t,e){return this._listeners[t].push(e),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(t,e){if(this._listeners[t])for(var r=0;r<this._listeners[t].length;r++)this._listeners[t][r].call(this,e)},pipe:function(t){return t.registerPrevious(this)},registerPrevious:function(t){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.streamInfo=t.streamInfo,this.mergeStreamInfo(),this.previous=
|
||||
t;var e=this;return t.on("data",function(t){e.processChunk(t)}),t.on("end",function(){e.end()}),t.on("error",function(t){e.error(t)}),this},pause:function(){return!this.isPaused&&!this.isFinished&&(this.isPaused=!0,this.previous&&this.previous.pause(),!0)},resume:function(){if(!this.isPaused||this.isFinished)return!1;var t=this.isPaused=!1;return this.generatedError&&(this.error(this.generatedError),t=!0),this.previous&&this.previous.resume(),!t},flush:function(){},processChunk:function(t){this.push(t)},
|
||||
withStreamInfo:function(t,e){return this.extraStreamInfo[t]=e,this.mergeStreamInfo(),this},mergeStreamInfo:function(){for(var t in this.extraStreamInfo)this.extraStreamInfo.hasOwnProperty(t)&&(this.streamInfo[t]=this.extraStreamInfo[t])},lock:function(){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.isLocked=!0,this.previous&&this.previous.lock()},toString:function(){var t="Worker "+this.name;return this.previous?this.previous+" -> "+t:t}},e.exports=i},{}],29:[function(t,
|
||||
e,r){var h=t("../utils"),n=t("./ConvertWorker"),s=t("./GenericWorker"),u=t("../base64"),i=t("../support"),a=t("../external"),o=null;if(i.nodestream)try{o=t("../nodejs/NodejsStreamOutputAdapter")}catch(t$4){}function l(t,o){return new a.Promise(function(e,r){var i=[],n=t._internalType,s=t._outputType,a=t._mimeType;t.on("data",function(t,e){i.push(t),o&&o(e)}).on("error",function(t){i=[],r(t)}).on("end",function(){try{var t=function(t,e,r){switch(t){case "blob":return h.newBlob(h.transformTo("arraybuffer",
|
||||
e),r);case "base64":return u.encode(e);default:return h.transformTo(t,e)}}(s,function(t,e){var r,i=0,n=null,s=0;for(r=0;r<e.length;r++)s+=e[r].length;switch(t){case "string":return e.join("");case "array":return Array.prototype.concat.apply([],e);case "uint8array":for(n=new Uint8Array(s),r=0;r<e.length;r++)n.set(e[r],i),i+=e[r].length;return n;case "nodebuffer":return Buffer.concat(e);default:throw new Error("concat : unsupported type '"+t+"'");}}(n,i),a);e(t)}catch(t$5){r(t$5)}i=[]}).resume()})}
|
||||
function f(t,e,r){var i=e;switch(e){case "blob":case "arraybuffer":i="uint8array";break;case "base64":i="string"}try{this._internalType=i,this._outputType=e,this._mimeType=r,h.checkSupport(i),this._worker=t.pipe(new n(i)),t.lock()}catch(t$6){this._worker=new s("error"),this._worker.error(t$6)}}f.prototype={accumulate:function(t){return l(this,t)},on:function(t,e){var r=this;return"data"===t?this._worker.on(t,function(t){e.call(r,t.data,t.meta)}):this._worker.on(t,function(){h.delay(e,arguments,r)}),
|
||||
this},resume:function(){return h.delay(this._worker.resume,[],this._worker),this},pause:function(){return this._worker.pause(),this},toNodejsStream:function(t){if(h.checkSupport("nodestream"),"nodebuffer"!==this._outputType)throw new Error(this._outputType+" is not supported by this method");return new o(this,{objectMode:"nodebuffer"!==this._outputType},t)}},e.exports=f},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],
|
||||
30:[function(t,e,r){if(r.base64=!0,r.array=!0,r.string=!0,r.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,r.nodebuffer="undefined"!=typeof Buffer,r.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)r.blob=!1;else{var i=new ArrayBuffer(0);try{r.blob=0===(new Blob([i],{type:"application/zip"})).size}catch(t$8){try{var n=new (self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);n.append(i),r.blob=0===n.getBlob("application/zip").size}catch(t$7){r.blob=
|
||||
!1}}}try{r.nodestream=!!t("readable-stream").Readable}catch(t$9){r.nodestream=!1}},{"readable-stream":16}],31:[function(t,e,s){for(var o=t("./utils"),h=t("./support"),r=t("./nodejsUtils"),i=t("./stream/GenericWorker"),u=new Array(256),n=0;n<256;n++)u[n]=252<=n?6:248<=n?5:240<=n?4:224<=n?3:192<=n?2:1;u[254]=u[254]=1;function a(){i.call(this,"utf-8 decode"),this.leftOver=null}function l(){i.call(this,"utf-8 encode")}s.utf8encode=function(t){return h.nodebuffer?r.newBufferFrom(t,"utf-8"):function(t){var e,
|
||||
r,i,n,s,a=t.length,o=0;for(n=0;n<a;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),o+=r<128?1:r<2048?2:r<65536?3:4;for(e=h.uint8array?new Uint8Array(o):new Array(o),n=s=0;s<o;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),r<128?e[s++]=r:(r<2048?e[s++]=192|r>>>6:(r<65536?e[s++]=224|r>>>12:(e[s++]=240|r>>>18,e[s++]=128|r>>>12&63),e[s++]=128|r>>>6&63),
|
||||
e[s++]=128|63&r);return e}(t)},s.utf8decode=function(t){return h.nodebuffer?o.transformTo("nodebuffer",t).toString("utf-8"):function(t){var e,r,i,n,s=t.length,a=new Array(2*s);for(e=r=0;e<s;)if((i=t[e++])<128)a[r++]=i;else if(4<(n=u[i]))a[r++]=65533,e+=n-1;else{for(i&=2===n?31:3===n?15:7;1<n&&e<s;)i=i<<6|63&t[e++],n--;1<n?a[r++]=65533:i<65536?a[r++]=i:(i-=65536,a[r++]=55296|i>>10&1023,a[r++]=56320|1023&i)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(t=o.transformTo(h.uint8array?
|
||||
"uint8array":"array",t))},o.inherits(a,i),a.prototype.processChunk=function(t){var e=o.transformTo(h.uint8array?"uint8array":"array",t.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=e;(e=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),e.set(r,this.leftOver.length)}else e=this.leftOver.concat(e);this.leftOver=null}var i=function(t,e){var r;for((e=e||t.length)>t.length&&(e=t.length),r=e-1;0<=r&&128==(192&t[r]);)r--;return r<0?e:0===r?e:r+u[t[r]]>e?r:e}(e),n=
|
||||
e;i!==e.length&&(h.uint8array?(n=e.subarray(0,i),this.leftOver=e.subarray(i,e.length)):(n=e.slice(0,i),this.leftOver=e.slice(i,e.length))),this.push({data:s.utf8decode(n),meta:t.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,i),l.prototype.processChunk=function(t){this.push({data:s.utf8encode(t.data),meta:t.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,
|
||||
"./support":30,"./utils":32}],32:[function(t,e,a){var o=t("./support"),h=t("./base64"),r=t("./nodejsUtils"),i=t("set-immediate-shim"),u=t("./external");function n(t){return t}function l(t,e){for(var r=0;r<t.length;++r)e[r]=255&t.charCodeAt(r);return e}a.newBlob=function(e,r){a.checkSupport("blob");try{return new Blob([e],{type:r})}catch(t$11){try{var i=new (self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);return i.append(e),i.getBlob(r)}catch(t$10){throw new Error("Bug : can't construct the Blob.");
|
||||
}}};var s={stringifyByChunk:function(t,e,r){var i=[],n=0,s=t.length;if(s<=r)return String.fromCharCode.apply(null,t);for(;n<s;)"array"===e||"nodebuffer"===e?i.push(String.fromCharCode.apply(null,t.slice(n,Math.min(n+r,s)))):i.push(String.fromCharCode.apply(null,t.subarray(n,Math.min(n+r,s)))),n+=r;return i.join("")},stringifyByChar:function(t){for(var e="",r=0;r<t.length;r++)e+=String.fromCharCode(t[r]);return e},applyCanBeUsed:{uint8array:function(){try{return o.uint8array&&1===String.fromCharCode.apply(null,
|
||||
new Uint8Array(1)).length}catch(t$12){return!1}}(),nodebuffer:function(){try{return o.nodebuffer&&1===String.fromCharCode.apply(null,r.allocBuffer(1)).length}catch(t$13){return!1}}()}};function f(t){var e=65536,r=a.getTypeOf(t),i=!0;if("uint8array"===r?i=s.applyCanBeUsed.uint8array:"nodebuffer"===r&&(i=s.applyCanBeUsed.nodebuffer),i)for(;1<e;)try{return s.stringifyByChunk(t,r,e)}catch(t$14){e=Math.floor(e/2)}return s.stringifyByChar(t)}function d(t,e){for(var r=0;r<t.length;r++)e[r]=t[r];return e}
|
||||
a.applyFromCharCode=f;var c={};c.string={string:n,array:function(t){return l(t,new Array(t.length))},arraybuffer:function(t){return c.string.uint8array(t).buffer},uint8array:function(t){return l(t,new Uint8Array(t.length))},nodebuffer:function(t){return l(t,r.allocBuffer(t.length))}},c.array={string:f,array:n,arraybuffer:function(t){return(new Uint8Array(t)).buffer},uint8array:function(t){return new Uint8Array(t)},nodebuffer:function(t){return r.newBufferFrom(t)}},c.arraybuffer={string:function(t){return f(new Uint8Array(t))},
|
||||
array:function(t){return d(new Uint8Array(t),new Array(t.byteLength))},arraybuffer:n,uint8array:function(t){return new Uint8Array(t)},nodebuffer:function(t){return r.newBufferFrom(new Uint8Array(t))}},c.uint8array={string:f,array:function(t){return d(t,new Array(t.length))},arraybuffer:function(t){return t.buffer},uint8array:n,nodebuffer:function(t){return r.newBufferFrom(t)}},c.nodebuffer={string:f,array:function(t){return d(t,new Array(t.length))},arraybuffer:function(t){return c.nodebuffer.uint8array(t).buffer},
|
||||
uint8array:function(t){return d(t,new Uint8Array(t.length))},nodebuffer:n},a.transformTo=function(t,e){if(e=e||"",!t)return e;a.checkSupport(t);var r=a.getTypeOf(e);return c[r][t](e)},a.getTypeOf=function(t){return"string"==typeof t?"string":"[object Array]"===Object.prototype.toString.call(t)?"array":o.nodebuffer&&r.isBuffer(t)?"nodebuffer":o.uint8array&&t instanceof Uint8Array?"uint8array":o.arraybuffer&&t instanceof ArrayBuffer?"arraybuffer":void 0},a.checkSupport=function(t){if(!o[t.toLowerCase()])throw new Error(t+
|
||||
" is not supported by this platform");},a.MAX_VALUE_16BITS=65535,a.MAX_VALUE_32BITS=-1,a.pretty=function(t){var e,r,i="";for(r=0;r<(t||"").length;r++)i+="\\x"+((e=t.charCodeAt(r))<16?"0":"")+e.toString(16).toUpperCase();return i},a.delay=function(t,e,r){i(function(){t.apply(r||null,e||[])})},a.inherits=function(t,e){function r(){}r.prototype=e.prototype,t.prototype=new r},a.extend=function(){var t,e,r={};for(t=0;t<arguments.length;t++)for(e in arguments[t])arguments[t].hasOwnProperty(e)&&void 0===
|
||||
r[e]&&(r[e]=arguments[t][e]);return r},a.prepareContent=function(r,t,i,n,s){return u.Promise.resolve(t).then(function(i){return o.blob&&(i instanceof Blob||-1!==["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(i)))&&"undefined"!=typeof FileReader?new u.Promise(function(e,r){var t=new FileReader;t.onload=function(t){e(t.target.result)},t.onerror=function(t){r(t.target.error)},t.readAsArrayBuffer(i)}):i}).then(function(t){var e=a.getTypeOf(t);return e?("arraybuffer"===e?t=a.transformTo("uint8array",
|
||||
t):"string"===e&&(s?t=h.decode(t):i&&!0!==n&&(t=function(t){return l(t,o.uint8array?new Uint8Array(t.length):new Array(t.length))}(t))),t):u.Promise.reject(new Error("Can't read the data of '"+r+"'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"set-immediate-shim":54}],33:[function(t,e,r){var i=t("./reader/readerFor"),n=t("./utils"),s=t("./signature"),a=t("./zipEntry"),o=(t("./utf8"),t("./support"));function h(t){this.files=
|
||||
[],this.loadOptions=t}h.prototype={checkSignature:function(t){if(!this.reader.readAndCheckSignature(t)){this.reader.index-=4;var e=this.reader.readString(4);throw new Error("Corrupted zip or bug: unexpected signature ("+n.pretty(e)+", expected "+n.pretty(t)+")");}},isSignature:function(t,e){var r=this.reader.index;this.reader.setIndex(t);var i=this.reader.readString(4)===e;return this.reader.setIndex(r),i},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=
|
||||
this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var t=this.reader.readData(this.zipCommentLength),e=o.uint8array?"uint8array":"array",r=n.transformTo(e,t);this.zipComment=this.loadOptions.decodeFileName(r)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),
|
||||
this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var t,e,r,i=this.zip64EndOfCentralSize-44;0<i;)t=this.reader.readInt(2),e=this.reader.readInt(4),r=this.reader.readData(e),this.zip64ExtensibleData[t]={id:t,length:e,value:r}},
|
||||
readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),1<this.disksCount)throw new Error("Multi-volumes zip are not supported");},readLocalFiles:function(){var t,e;for(t=0;t<this.files.length;t++)e=this.files[t],this.reader.setIndex(e.localHeaderOffset),this.checkSignature(s.LOCAL_FILE_HEADER),e.readLocalPart(this.reader),e.handleUTF8(),e.processAttributes()},
|
||||
readCentralDir:function(){var t;for(this.reader.setIndex(this.centralDirOffset);this.reader.readAndCheckSignature(s.CENTRAL_FILE_HEADER);)(t=new a({zip64:this.zip64},this.loadOptions)).readCentralPart(this.reader),this.files.push(t);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length);},readEndOfCentral:function(){var t=this.reader.lastIndexOfSignature(s.CENTRAL_DIRECTORY_END);
|
||||
if(t<0)throw!this.isSignature(0,s.LOCAL_FILE_HEADER)?new Error("Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip: can't find end of central directory");this.reader.setIndex(t);var e=t;if(this.checkSignature(s.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===n.MAX_VALUE_16BITS||this.diskWithCentralDirStart===n.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===n.MAX_VALUE_16BITS||
|
||||
this.centralDirRecords===n.MAX_VALUE_16BITS||this.centralDirSize===n.MAX_VALUE_32BITS||this.centralDirOffset===n.MAX_VALUE_32BITS){if(this.zip64=!0,(t=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR))<0)throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(t),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,s.ZIP64_CENTRAL_DIRECTORY_END)&&
|
||||
(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var r=this.centralDirOffset+this.centralDirSize;this.zip64&&(r+=20,r+=12+this.zip64EndOfCentralSize);var i=e-r;if(0<i)this.isSignature(e,
|
||||
s.CENTRAL_FILE_HEADER)||(this.reader.zero=i);else if(i<0)throw new Error("Corrupted zip: missing "+Math.abs(i)+" bytes.");},prepareReader:function(t){this.reader=i(t)},load:function(t){this.prepareReader(t),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},e.exports=h},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utf8":31,"./utils":32,"./zipEntry":34}],34:[function(t,e,r){var i=t("./reader/readerFor"),s=t("./utils"),n=t("./compressedObject"),a=t("./crc32"),o=t("./utf8"),
|
||||
h=t("./compressions"),u=t("./support");function l(t,e){this.options=t,this.loadOptions=e}l.prototype={isEncrypted:function(){return 1==(1&this.bitFlag)},useUTF8:function(){return 2048==(2048&this.bitFlag)},readLocalPart:function(t){var e,r;if(t.skip(22),this.fileNameLength=t.readInt(2),r=t.readInt(2),this.fileName=t.readData(this.fileNameLength),t.skip(r),-1===this.compressedSize||-1===this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough information from the central directory (compressedSize === -1 || uncompressedSize === -1)");
|
||||
if(null===(e=function(t){for(var e in h)if(h.hasOwnProperty(e)&&h[e].magic===t)return h[e];return null}(this.compressionMethod)))throw new Error("Corrupted zip : compression "+s.pretty(this.compressionMethod)+" unknown (inner file : "+s.transformTo("string",this.fileName)+")");this.decompressed=new n(this.compressedSize,this.uncompressedSize,this.crc32,e,t.readData(this.compressedSize))},readCentralPart:function(t){this.versionMadeBy=t.readInt(2),t.skip(2),this.bitFlag=t.readInt(2),this.compressionMethod=
|
||||
t.readString(2),this.date=t.readDate(),this.crc32=t.readInt(4),this.compressedSize=t.readInt(4),this.uncompressedSize=t.readInt(4);var e=t.readInt(2);if(this.extraFieldsLength=t.readInt(2),this.fileCommentLength=t.readInt(2),this.diskNumberStart=t.readInt(2),this.internalFileAttributes=t.readInt(2),this.externalFileAttributes=t.readInt(4),this.localHeaderOffset=t.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");t.skip(e),this.readExtraFields(t),this.parseZIP64ExtraField(t),
|
||||
this.fileComment=t.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var t=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),0==t&&(this.dosPermissions=63&this.externalFileAttributes),3==t&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(t){if(this.extraFields[1]){var e=i(this.extraFields[1].value);this.uncompressedSize===
|
||||
s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(t){var e,r,i,n=t.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});t.index+4<n;)e=t.readInt(2),r=t.readInt(2),i=t.readData(r),this.extraFields[e]={id:e,length:r,
|
||||
value:i};t.setIndex(n)},handleUTF8:function(){var t=u.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=o.utf8decode(this.fileName),this.fileCommentStr=o.utf8decode(this.fileComment);else{var e=this.findExtraFieldUnicodePath();if(null!==e)this.fileNameStr=e;else{var r=s.transformTo(t,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(r)}var i=this.findExtraFieldUnicodeComment();if(null!==i)this.fileCommentStr=i;else{var n=s.transformTo(t,this.fileComment);this.fileCommentStr=
|
||||
this.loadOptions.decodeFileName(n)}}},findExtraFieldUnicodePath:function(){var t=this.extraFields[28789];if(t){var e=i(t.value);return 1!==e.readInt(1)?null:a(this.fileName)!==e.readInt(4)?null:o.utf8decode(e.readData(t.length-5))}return null},findExtraFieldUnicodeComment:function(){var t=this.extraFields[25461];if(t){var e=i(t.value);return 1!==e.readInt(1)?null:a(this.fileComment)!==e.readInt(4)?null:o.utf8decode(e.readData(t.length-5))}return null}},e.exports=l},{"./compressedObject":2,"./compressions":3,
|
||||
"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(t,e,r){function i(t,e,r){this.name=t,this.dir=r.dir,this.date=r.date,this.comment=r.comment,this.unixPermissions=r.unixPermissions,this.dosPermissions=r.dosPermissions,this._data=e,this._dataBinary=r.binary,this.options={compression:r.compression,compressionOptions:r.compressionOptions}}var s=t("./stream/StreamHelper"),n=t("./stream/DataWorker"),a=t("./utf8"),o=t("./compressedObject"),h=t("./stream/GenericWorker");
|
||||
i.prototype={internalStream:function(t){var e=null,r="string";try{if(!t)throw new Error("No output type specified.");var i="string"===(r=t.toLowerCase())||"text"===r;"binarystring"!==r&&"text"!==r||(r="string"),e=this._decompressWorker();var n=!this._dataBinary;n&&!i&&(e=e.pipe(new a.Utf8EncodeWorker)),!n&&i&&(e=e.pipe(new a.Utf8DecodeWorker))}catch(t$15){(e=new h("error")).error(t$15)}return new s(e,r,"")},async:function(t,e){return this.internalStream(t).accumulate(e)},nodeStream:function(t,e){return this.internalStream(t||
|
||||
"nodebuffer").toNodejsStream(e)},_compressWorker:function(t,e){if(this._data instanceof o&&this._data.compression.magic===t.magic)return this._data.getCompressedWorker();var r=this._decompressWorker();return this._dataBinary||(r=r.pipe(new a.Utf8EncodeWorker)),o.createWorkerFrom(r,t,e)},_decompressWorker:function(){return this._data instanceof o?this._data.getContentWorker():this._data instanceof h?this._data:new n(this._data)}};for(var u=["asText","asBinary","asNodeBuffer","asUint8Array","asArrayBuffer"],
|
||||
l=function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");},f=0;f<u.length;f++)i.prototype[u[f]]=l;e.exports=i},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(t,l,e){(function(e){var r,i,t=e.MutationObserver||e.WebKitMutationObserver;if(t){var n=0,s=new t(u),a=e.document.createTextNode("");s.observe(a,{characterData:!0}),r=function(){a.data=n=++n%2}}else if(e.setImmediate||
|
||||
void 0===e.MessageChannel)r="document"in e&&"onreadystatechange"in e.document.createElement("script")?function(){var t=e.document.createElement("script");t.onreadystatechange=function(){u(),t.onreadystatechange=null,t.parentNode.removeChild(t),t=null},e.document.documentElement.appendChild(t)}:function(){setTimeout(u,0)};else{var o=new e.MessageChannel;o.port1.onmessage=u,r=function(){o.port2.postMessage(0)}}var h=[];function u(){var t,e;i=!0;for(var r=h.length;r;){for(e=h,h=[],t=-1;++t<r;)e[t]();
|
||||
r=h.length}i=!1}l.exports=function(t){1!==h.push(t)||i||r()}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],37:[function(t,e,r){var n=t("immediate");function u(){}var l={},s=["REJECTED"],a=["FULFILLED"],i=["PENDING"];function o(t){if("function"!=typeof t)throw new TypeError("resolver must be a function");this.state=i,this.queue=[],this.outcome=void 0,t!==u&&c(this,t)}function h(t,e,r){this.promise=t,"function"==typeof e&&(this.onFulfilled=
|
||||
e,this.callFulfilled=this.otherCallFulfilled),"function"==typeof r&&(this.onRejected=r,this.callRejected=this.otherCallRejected)}function f(e,r,i){n(function(){var t;try{t=r(i)}catch(t$16){return l.reject(e,t$16)}t===e?l.reject(e,new TypeError("Cannot resolve promise with itself")):l.resolve(e,t)})}function d(t){var e=t&&t.then;if(t&&("object"==typeof t||"function"==typeof t)&&"function"==typeof e)return function(){e.apply(t,arguments)}}function c(e,t){var r=!1;function i(t){r||(r=!0,l.reject(e,t))}
|
||||
function n(t){r||(r=!0,l.resolve(e,t))}var s=p(function(){t(n,i)});"error"===s.status&&i(s.value)}function p(t,e){var r={};try{r.value=t(e),r.status="success"}catch(t$17){r.status="error",r.value=t$17}return r}(e.exports=o).prototype["finally"]=function(e){if("function"!=typeof e)return this;var r=this.constructor;return this.then(function(t){return r.resolve(e()).then(function(){return t})},function(t){return r.resolve(e()).then(function(){throw t;})})},o.prototype["catch"]=function(t){return this.then(null,
|
||||
t)},o.prototype.then=function(t,e){if("function"!=typeof t&&this.state===a||"function"!=typeof e&&this.state===s)return this;var r=new this.constructor(u);this.state!==i?f(r,this.state===a?t:e,this.outcome):this.queue.push(new h(r,t,e));return r},h.prototype.callFulfilled=function(t){l.resolve(this.promise,t)},h.prototype.otherCallFulfilled=function(t){f(this.promise,this.onFulfilled,t)},h.prototype.callRejected=function(t){l.reject(this.promise,t)},h.prototype.otherCallRejected=function(t){f(this.promise,
|
||||
this.onRejected,t)},l.resolve=function(t,e){var r=p(d,e);if("error"===r.status)return l.reject(t,r.value);var i=r.value;if(i)c(t,i);else{t.state=a,t.outcome=e;for(var n=-1,s=t.queue.length;++n<s;)t.queue[n].callFulfilled(e)}return t},l.reject=function(t,e){t.state=s,t.outcome=e;for(var r=-1,i=t.queue.length;++r<i;)t.queue[r].callRejected(e);return t},o.resolve=function(t){if(t instanceof this)return t;return l.resolve(new this(u),t)},o.reject=function(t){var e=new this(u);return l.reject(e,t)},o.all=
|
||||
function(t){var r=this;if("[object Array]"!==Object.prototype.toString.call(t))return this.reject(new TypeError("must be an array"));var i=t.length,n=!1;if(!i)return this.resolve([]);var s=new Array(i),a=0,e=-1,o=new this(u);for(;++e<i;)h(t[e],e);return o;function h(t,e){r.resolve(t).then(function(t){s[e]=t,++a!==i||n||(n=!0,l.resolve(o,s))},function(t){n||(n=!0,l.reject(o,t))})}},o.race=function(t){var e=this;if("[object Array]"!==Object.prototype.toString.call(t))return this.reject(new TypeError("must be an array"));
|
||||
var r=t.length,i=!1;if(!r)return this.resolve([]);var n=-1,s=new this(u);for(;++n<r;)a=t[n],e.resolve(a).then(function(t){i||(i=!0,l.resolve(s,t))},function(t){i||(i=!0,l.reject(s,t))});var a;return s}},{immediate:36}],38:[function(t,e,r){var i={};(0,t("./lib/utils/common").assign)(i,t("./lib/deflate"),t("./lib/inflate"),t("./lib/zlib/constants")),e.exports=i},{"./lib/deflate":39,"./lib/inflate":40,"./lib/utils/common":41,"./lib/zlib/constants":44}],39:[function(t,e,r){var a=t("./zlib/deflate"),o=
|
||||
t("./utils/common"),h=t("./utils/strings"),n=t("./zlib/messages"),s=t("./zlib/zstream"),u=Object.prototype.toString,l=0,f=-1,d=0,c=8;function p(t){if(!(this instanceof p))return new p(t);this.options=o.assign({level:f,method:c,chunkSize:16384,windowBits:15,memLevel:8,strategy:d,to:""},t||{});var e=this.options;e.raw&&0<e.windowBits?e.windowBits=-e.windowBits:e.gzip&&0<e.windowBits&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new s,this.strm.avail_out=
|
||||
0;var r=a.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(r!==l)throw new Error(n[r]);if(e.header&&a.deflateSetHeader(this.strm,e.header),e.dictionary){var i;if(i="string"==typeof e.dictionary?h.string2buf(e.dictionary):"[object ArrayBuffer]"===u.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,(r=a.deflateSetDictionary(this.strm,i))!==l)throw new Error(n[r]);this._dict_set=!0}}function i(t,e){var r=new p(e);if(r.push(t,!0),r.err)throw r.msg||n[r.err];return r.result}
|
||||
p.prototype.push=function(t,e){var r,i,n=this.strm,s=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:!0===e?4:0,"string"==typeof t?n.input=h.string2buf(t):"[object ArrayBuffer]"===u.call(t)?n.input=new Uint8Array(t):n.input=t,n.next_in=0,n.avail_in=n.input.length;do{if(0===n.avail_out&&(n.output=new o.Buf8(s),n.next_out=0,n.avail_out=s),1!==(r=a.deflate(n,i))&&r!==l)return this.onEnd(r),!(this.ended=!0);0!==n.avail_out&&(0!==n.avail_in||4!==i&&2!==i)||("string"===this.options.to?this.onData(h.buf2binstring(o.shrinkBuf(n.output,
|
||||
n.next_out))):this.onData(o.shrinkBuf(n.output,n.next_out)))}while((0<n.avail_in||0===n.avail_out)&&1!==r);return 4===i?(r=a.deflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===l):2!==i||(this.onEnd(l),!(n.avail_out=0))},p.prototype.onData=function(t){this.chunks.push(t)},p.prototype.onEnd=function(t){t===l&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=o.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},r.Deflate=p,r.deflate=i,r.deflateRaw=
|
||||
function(t,e){return(e=e||{}).raw=!0,i(t,e)},r.gzip=function(t,e){return(e=e||{}).gzip=!0,i(t,e)}},{"./utils/common":41,"./utils/strings":42,"./zlib/deflate":46,"./zlib/messages":51,"./zlib/zstream":53}],40:[function(t,e,r){var d=t("./zlib/inflate"),c=t("./utils/common"),p=t("./utils/strings"),m=t("./zlib/constants"),i=t("./zlib/messages"),n=t("./zlib/zstream"),s=t("./zlib/gzheader"),_=Object.prototype.toString;function a(t){if(!(this instanceof a))return new a(t);this.options=c.assign({chunkSize:16384,
|
||||
windowBits:0,to:""},t||{});var e=this.options;e.raw&&0<=e.windowBits&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(0<=e.windowBits&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),15<e.windowBits&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new n,this.strm.avail_out=0;var r=d.inflateInit2(this.strm,e.windowBits);if(r!==m.Z_OK)throw new Error(i[r]);this.header=new s,d.inflateGetHeader(this.strm,
|
||||
this.header)}function o(t,e){var r=new a(e);if(r.push(t,!0),r.err)throw r.msg||i[r.err];return r.result}a.prototype.push=function(t,e){var r,i,n,s,a,o,h=this.strm,u=this.options.chunkSize,l=this.options.dictionary,f=!1;if(this.ended)return!1;i=e===~~e?e:!0===e?m.Z_FINISH:m.Z_NO_FLUSH,"string"==typeof t?h.input=p.binstring2buf(t):"[object ArrayBuffer]"===_.call(t)?h.input=new Uint8Array(t):h.input=t,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new c.Buf8(u),h.next_out=0,h.avail_out=
|
||||
u),(r=d.inflate(h,m.Z_NO_FLUSH))===m.Z_NEED_DICT&&l&&(o="string"==typeof l?p.string2buf(l):"[object ArrayBuffer]"===_.call(l)?new Uint8Array(l):l,r=d.inflateSetDictionary(this.strm,o)),r===m.Z_BUF_ERROR&&!0===f&&(r=m.Z_OK,f=!1),r!==m.Z_STREAM_END&&r!==m.Z_OK)return this.onEnd(r),!(this.ended=!0);h.next_out&&(0!==h.avail_out&&r!==m.Z_STREAM_END&&(0!==h.avail_in||i!==m.Z_FINISH&&i!==m.Z_SYNC_FLUSH)||("string"===this.options.to?(n=p.utf8border(h.output,h.next_out),s=h.next_out-n,a=p.buf2string(h.output,
|
||||
n),h.next_out=s,h.avail_out=u-s,s&&c.arraySet(h.output,h.output,n,s,0),this.onData(a)):this.onData(c.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(f=!0)}while((0<h.avail_in||0===h.avail_out)&&r!==m.Z_STREAM_END);return r===m.Z_STREAM_END&&(i=m.Z_FINISH),i===m.Z_FINISH?(r=d.inflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===m.Z_OK):i!==m.Z_SYNC_FLUSH||(this.onEnd(m.Z_OK),!(h.avail_out=0))},a.prototype.onData=function(t){this.chunks.push(t)},a.prototype.onEnd=function(t){t===
|
||||
m.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=c.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},r.Inflate=a,r.inflate=o,r.inflateRaw=function(t,e){return(e=e||{}).raw=!0,o(t,e)},r.ungzip=o},{"./utils/common":41,"./utils/strings":42,"./zlib/constants":44,"./zlib/gzheader":47,"./zlib/inflate":49,"./zlib/messages":51,"./zlib/zstream":53}],41:[function(t,e,r){var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=
|
||||
typeof Int32Array;r.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var r=e.shift();if(r){if("object"!=typeof r)throw new TypeError(r+"must be non-object");for(var i in r)r.hasOwnProperty(i)&&(t[i]=r[i])}}return t},r.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,r,i,n){if(e.subarray&&t.subarray)t.set(e.subarray(r,r+i),n);else for(var s=0;s<i;s++)t[n+s]=e[r+s]},flattenChunks:function(t){var e,r,
|
||||
i,n,s,a;for(e=i=0,r=t.length;e<r;e++)i+=t[e].length;for(a=new Uint8Array(i),e=n=0,r=t.length;e<r;e++)s=t[e],a.set(s,n),n+=s.length;return a}},s={arraySet:function(t,e,r,i,n){for(var s=0;s<i;s++)t[n+s]=e[r+s]},flattenChunks:function(t){return[].concat.apply([],t)}};r.setTyped=function(t){t?(r.Buf8=Uint8Array,r.Buf16=Uint16Array,r.Buf32=Int32Array,r.assign(r,n)):(r.Buf8=Array,r.Buf16=Array,r.Buf32=Array,r.assign(r,s))},r.setTyped(i)},{}],42:[function(t,e,r){var h=t("./common"),n=!0,s=!0;try{String.fromCharCode.apply(null,
|
||||
[0])}catch(t$18){n=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(t$19){s=!1}for(var u=new h.Buf8(256),i=0;i<256;i++)u[i]=252<=i?6:248<=i?5:240<=i?4:224<=i?3:192<=i?2:1;function l(t,e){if(e<65537&&(t.subarray&&s||!t.subarray&&n))return String.fromCharCode.apply(null,h.shrinkBuf(t,e));for(var r="",i=0;i<e;i++)r+=String.fromCharCode(t[i]);return r}u[254]=u[254]=1,r.string2buf=function(t){var e,r,i,n,s,a=t.length,o=0;for(n=0;n<a;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&
|
||||
(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),o+=r<128?1:r<2048?2:r<65536?3:4;for(e=new h.Buf8(o),n=s=0;s<o;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),r<128?e[s++]=r:(r<2048?e[s++]=192|r>>>6:(r<65536?e[s++]=224|r>>>12:(e[s++]=240|r>>>18,e[s++]=128|r>>>12&63),e[s++]=128|r>>>6&63),e[s++]=128|63&r);return e},r.buf2binstring=function(t){return l(t,t.length)},r.binstring2buf=function(t){for(var e=new h.Buf8(t.length),
|
||||
r=0,i=e.length;r<i;r++)e[r]=t.charCodeAt(r);return e},r.buf2string=function(t,e){var r,i,n,s,a=e||t.length,o=new Array(2*a);for(r=i=0;r<a;)if((n=t[r++])<128)o[i++]=n;else if(4<(s=u[n]))o[i++]=65533,r+=s-1;else{for(n&=2===s?31:3===s?15:7;1<s&&r<a;)n=n<<6|63&t[r++],s--;1<s?o[i++]=65533:n<65536?o[i++]=n:(n-=65536,o[i++]=55296|n>>10&1023,o[i++]=56320|1023&n)}return l(o,i)},r.utf8border=function(t,e){var r;for((e=e||t.length)>t.length&&(e=t.length),r=e-1;0<=r&&128==(192&t[r]);)r--;return r<0?e:0===r?e:
|
||||
r+u[t[r]]>e?r:e}},{"./common":41}],43:[function(t,e,r){e.exports=function(t,e,r,i){for(var n=65535&t|0,s=t>>>16&65535|0,a=0;0!==r;){for(r-=a=2E3<r?2E3:r;s=s+(n=n+e[i++]|0)|0,--a;);n%=65521,s%=65521}return n|s<<16|0}},{}],44:[function(t,e,r){e.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,
|
||||
Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],45:[function(t,e,r){var o=function(){for(var t,e=[],r=0;r<256;r++){t=r;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[r]=t}return e}();e.exports=function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;a<s;a++)t=t>>>8^n[255&(t^e[a])];return-1^t}},{}],46:[function(t,e,r){var h,d=t("../utils/common"),u=t("./trees"),c=t("./adler32"),p=t("./crc32"),i=t("./messages"),l=0,f=4,m=0,_=-2,g=
|
||||
-1,b=4,n=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(t,e){return t.msg=i[e],e}function T(t){return(t<<1)-(4<t?9:0)}function D(t){for(var e=t.length;0<=--e;)t[e]=0}function F(t){var e=t.state,r=e.pending;r>t.avail_out&&(r=t.avail_out),0!==r&&(d.arraySet(t.output,e.pending_buf,e.pending_out,r,t.next_out),t.next_out+=r,e.pending_out+=r,t.total_out+=r,t.avail_out-=r,e.pending-=r,0===e.pending&&(e.pending_out=0))}function N(t,e){u._tr_flush_block(t,0<=
|
||||
t.block_start?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,F(t.strm)}function U(t,e){t.pending_buf[t.pending++]=e}function P(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function L(t,e){var r,i,n=t.max_chain_length,s=t.strstart,a=t.prev_length,o=t.nice_match,h=t.strstart>t.w_size-z?t.strstart-(t.w_size-z):0,u=t.window,l=t.w_mask,f=t.prev,d=t.strstart+S,c=u[s+a-1],p=u[s+a];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do if(u[(r=
|
||||
e)+a]===p&&u[r+a-1]===c&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do;while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&s<d);if(i=S-(d-s),s=d-S,a<i){if(t.match_start=e,o<=(a=i))break;c=u[s+a-1],p=u[s+a]}}while((e=f[e&l])>h&&0!=--n);return a<=t.lookahead?a:t.lookahead}function j(t){var e,r,i,n,s,a,o,h,u,l,f=t.w_size;do{if(n=t.window_size-t.lookahead-t.strstart,t.strstart>=f+(f-z)){for(d.arraySet(t.window,t.window,
|
||||
f,f,0),t.match_start-=f,t.strstart-=f,t.block_start-=f,e=r=t.hash_size;i=t.head[--e],t.head[e]=f<=i?i-f:0,--r;);for(e=r=f;i=t.prev[--e],t.prev[e]=f<=i?i-f:0,--r;);n+=f}if(0===t.strm.avail_in)break;if(a=t.strm,o=t.window,h=t.strstart+t.lookahead,u=n,l=void 0,l=a.avail_in,u<l&&(l=u),r=0===l?0:(a.avail_in-=l,d.arraySet(o,a.input,a.next_in,l,h),1===a.state.wrap?a.adler=c(a.adler,o,l,h):2===a.state.wrap&&(a.adler=p(a.adler,o,l,h)),a.next_in+=l,a.total_in+=l,l),t.lookahead+=r,t.lookahead+t.insert>=x)for(s=
|
||||
t.strstart-t.insert,t.ins_h=t.window[s],t.ins_h=(t.ins_h<<t.hash_shift^t.window[s+1])&t.hash_mask;t.insert&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[s+x-1])&t.hash_mask,t.prev[s&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=s,s++,t.insert--,!(t.lookahead+t.insert<x)););}while(t.lookahead<z&&0!==t.strm.avail_in)}function Z(t,e){for(var r,i;;){if(t.lookahead<z){if(j(t),t.lookahead<z&&e===l)return A;if(0===t.lookahead)break}if(r=0,t.lookahead>=x&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&
|
||||
t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==r&&t.strstart-r<=t.w_size-z&&(t.match_length=L(t,r)),t.match_length>=x)if(i=u._tr_tally(t,t.strstart-t.match_start,t.match_length-x),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=x){for(t.match_length--;t.strstart++,t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart,0!=--t.match_length;);t.strstart++}else t.strstart+=
|
||||
t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+1])&t.hash_mask;else i=u._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=t.strstart<x-1?t.strstart:x-1,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}function W(t,e){for(var r,i,n;;){if(t.lookahead<z){if(j(t),t.lookahead<z&&e===l)return A;if(0===t.lookahead)break}if(r=0,
|
||||
t.lookahead>=x&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=x-1,0!==r&&t.prev_length<t.max_lazy_match&&t.strstart-r<=t.w_size-z&&(t.match_length=L(t,r),t.match_length<=5&&(1===t.strategy||t.match_length===x&&4096<t.strstart-t.match_start)&&(t.match_length=x-1)),t.prev_length>=x&&t.match_length<=t.prev_length){for(n=t.strstart+t.lookahead-
|
||||
x,i=u._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-x),t.lookahead-=t.prev_length-1,t.prev_length-=2;++t.strstart<=n&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!=--t.prev_length;);if(t.match_available=0,t.match_length=x-1,t.strstart++,i&&(N(t,!1),0===t.strm.avail_out))return A}else if(t.match_available){if((i=u._tr_tally(t,0,t.window[t.strstart-1]))&&N(t,!1),t.strstart++,t.lookahead--,0===
|
||||
t.strm.avail_out)return A}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=u._tr_tally(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<x-1?t.strstart:x-1,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}function M(t,e,r,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=r,this.max_chain=i,this.func=n}function H(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=
|
||||
0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=v,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=
|
||||
0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new d.Buf16(2*w),this.dyn_dtree=new d.Buf16(2*(2*a+1)),this.bl_tree=new d.Buf16(2*(2*o+1)),D(this.dyn_ltree),D(this.dyn_dtree),D(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new d.Buf16(k+1),this.heap=new d.Buf16(2*s+1),D(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new d.Buf16(2*s+1),D(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=
|
||||
0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function G(t){var e;return t&&t.state?(t.total_in=t.total_out=0,t.data_type=n,(e=t.state).pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=e.wrap?C:E,t.adler=2===e.wrap?0:1,e.last_flush=l,u._tr_init(e),m):R(t,_)}function K(t){var e=G(t);return e===m&&function(t){t.window_size=2*t.w_size,D(t.head),t.max_lazy_match=h[t.level].max_lazy,t.good_match=h[t.level].good_length,t.nice_match=h[t.level].nice_length,t.max_chain_length=h[t.level].max_chain,
|
||||
t.strstart=0,t.block_start=0,t.lookahead=0,t.insert=0,t.match_length=t.prev_length=x-1,t.match_available=0,t.ins_h=0}(t.state),e}function Y(t,e,r,i,n,s){if(!t)return _;var a=1;if(e===g&&(e=6),i<0?(a=0,i=-i):15<i&&(a=2,i-=16),n<1||y<n||r!==v||i<8||15<i||e<0||9<e||s<0||b<s)return R(t,_);8===i&&(i=9);var o=new H;return(t.state=o).strm=t,o.wrap=a,o.gzhead=null,o.w_bits=i,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=n+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+
|
||||
x-1)/x),o.window=new d.Buf8(2*o.w_size),o.head=new d.Buf16(o.hash_size),o.prev=new d.Buf16(o.w_size),o.lit_bufsize=1<<n+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new d.Buf8(o.pending_buf_size),o.d_buf=1*o.lit_bufsize,o.l_buf=3*o.lit_bufsize,o.level=e,o.strategy=s,o.method=r,K(t)}h=[new M(0,0,0,0,function(t,e){var r=65535;for(r>t.pending_buf_size-5&&(r=t.pending_buf_size-5);;){if(t.lookahead<=1){if(j(t),0===t.lookahead&&e===l)return A;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=
|
||||
0;var i=t.block_start+r;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,N(t,!1),0===t.strm.avail_out))return A;if(t.strstart-t.block_start>=t.w_size-z&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):(t.strstart>t.block_start&&(N(t,!1),t.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,
|
||||
258,258,4096,W)],r.deflateInit=function(t,e){return Y(t,e,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(t,e){return t&&t.state?2!==t.state.wrap?_:(t.state.gzhead=e,m):_},r.deflate=function(t,e){var r,i,n,s;if(!t||!t.state||5<e||e<0)return t?R(t,_):_;if(i=t.state,!t.output||!t.input&&0!==t.avail_in||666===i.status&&e!==f)return R(t,0===t.avail_out?-5:_);if(i.strm=t,r=i.last_flush,i.last_flush=e,i.status===C)if(2===i.wrap)t.adler=0,U(i,31),U(i,139),U(i,
|
||||
8),i.gzhead?(U(i,(i.gzhead.text?1:0)+(i.gzhead.hcrc?2:0)+(i.gzhead.extra?4:0)+(i.gzhead.name?8:0)+(i.gzhead.comment?16:0)),U(i,255&i.gzhead.time),U(i,i.gzhead.time>>8&255),U(i,i.gzhead.time>>16&255),U(i,i.gzhead.time>>24&255),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&(U(i,255&i.gzhead.extra.length),U(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(t.adler=p(t.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=69):(U(i,0),U(i,0),
|
||||
U(i,0),U(i,0),U(i,0),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,3),i.status=E);else{var a=v+(i.w_bits-8<<4)<<8;a|=(2<=i.strategy||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(a|=32),a+=31-a%31,i.status=E,P(i,a),0!==i.strstart&&(P(i,t.adler>>>16),P(i,65535&t.adler)),t.adler=1}if(69===i.status)if(i.gzhead.extra){for(n=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,
|
||||
n)),F(t),n=i.pending,i.pending!==i.pending_buf_size));)U(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=73)}else i.status=73;if(73===i.status)if(i.gzhead.name){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending===i.pending_buf_size)){s=1;break}s=i.gzindex<i.gzhead.name.length?
|
||||
255&i.gzhead.name.charCodeAt(i.gzindex++):0,U(i,s)}while(0!==s);i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),0===s&&(i.gzindex=0,i.status=91)}else i.status=91;if(91===i.status)if(i.gzhead.comment){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending===i.pending_buf_size)){s=1;break}s=i.gzindex<i.gzhead.comment.length?255&i.gzhead.comment.charCodeAt(i.gzindex++):0,U(i,
|
||||
s)}while(0!==s);i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),0===s&&(i.status=103)}else i.status=103;if(103===i.status&&(i.gzhead.hcrc?(i.pending+2>i.pending_buf_size&&F(t),i.pending+2<=i.pending_buf_size&&(U(i,255&t.adler),U(i,t.adler>>8&255),t.adler=0,i.status=E)):i.status=E),0!==i.pending){if(F(t),0===t.avail_out)return i.last_flush=-1,m}else if(0===t.avail_in&&T(e)<=T(r)&&e!==f)return R(t,-5);if(666===i.status&&0!==t.avail_in)return R(t,-5);if(0!==t.avail_in||0!==
|
||||
i.lookahead||e!==l&&666!==i.status){var o=2===i.strategy?function(t,e){for(var r;;){if(0===t.lookahead&&(j(t),0===t.lookahead)){if(e===l)return A;break}if(t.match_length=0,r=u._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,r&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}(i,e):3===i.strategy?function(t,e){for(var r,i,n,s,a=t.window;;){if(t.lookahead<=S){if(j(t),t.lookahead<=S&&e===l)return A;
|
||||
if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=x&&0<t.strstart&&(i=a[n=t.strstart-1])===a[++n]&&i===a[++n]&&i===a[++n]){s=t.strstart+S;do;while(i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&n<s);t.match_length=S-(s-n),t.match_length>t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=x?(r=u._tr_tally(t,1,t.match_length-x),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(r=u._tr_tally(t,0,t.window[t.strstart]),
|
||||
t.lookahead--,t.strstart++),r&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}(i,e):h[i.level].func(i,e);if(o!==O&&o!==B||(i.status=666),o===A||o===O)return 0===t.avail_out&&(i.last_flush=-1),m;if(o===I&&(1===e?u._tr_align(i):5!==e&&(u._tr_stored_block(i,0,0,!1),3===e&&(D(i.head),0===i.lookahead&&(i.strstart=0,i.block_start=0,i.insert=0))),F(t),0===t.avail_out))return i.last_flush=-1,m}return e!==f?
|
||||
m:i.wrap<=0?1:(2===i.wrap?(U(i,255&t.adler),U(i,t.adler>>8&255),U(i,t.adler>>16&255),U(i,t.adler>>24&255),U(i,255&t.total_in),U(i,t.total_in>>8&255),U(i,t.total_in>>16&255),U(i,t.total_in>>24&255)):(P(i,t.adler>>>16),P(i,65535&t.adler)),F(t),0<i.wrap&&(i.wrap=-i.wrap),0!==i.pending?m:1)},r.deflateEnd=function(t){var e;return t&&t.state?(e=t.state.status)!==C&&69!==e&&73!==e&&91!==e&&103!==e&&e!==E&&666!==e?R(t,_):(t.state=null,e===E?R(t,-3):m):_},r.deflateSetDictionary=function(t,e){var r,i,n,s,a,
|
||||
o,h,u,l=e.length;if(!t||!t.state)return _;if(2===(s=(r=t.state).wrap)||1===s&&r.status!==C||r.lookahead)return _;for(1===s&&(t.adler=c(t.adler,e,l,0)),r.wrap=0,l>=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new d.Buf8(r.w_size),d.arraySet(u,e,l-r.w_size,r.w_size,0),e=u,l=r.w_size),a=t.avail_in,o=t.next_in,h=t.input,t.avail_in=l,t.next_in=0,t.input=e,j(r);r.lookahead>=x;){for(i=r.strstart,n=r.lookahead-(x-1);r.ins_h=(r.ins_h<<r.hash_shift^r.window[i+x-1])&r.hash_mask,r.prev[i&
|
||||
r.w_mask]=r.head[r.ins_h],r.head[r.ins_h]=i,i++,--n;);r.strstart=i,r.lookahead=x-1,j(r)}return r.strstart+=r.lookahead,r.block_start=r.strstart,r.insert=r.lookahead,r.lookahead=0,r.match_length=r.prev_length=x-1,r.match_available=0,t.next_in=o,t.input=h,t.avail_in=a,r.wrap=s,m},r.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./messages":51,"./trees":52}],47:[function(t,e,r){e.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,
|
||||
this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},{}],48:[function(t,e,r){e.exports=function(t,e){var r,i,n,s,a,o,h,u,l,f,d,c,p,m,_,g,b,v,y,w,k,x,S,z,C;r=t.state,i=t.next_in,z=t.input,n=i+(t.avail_in-5),s=t.next_out,C=t.output,a=s-(e-t.avail_out),o=s+(t.avail_out-257),h=r.dmax,u=r.wsize,l=r.whave,f=r.wnext,d=r.window,c=r.hold,p=r.bits,m=r.lencode,_=r.distcode,g=(1<<r.lenbits)-1,b=(1<<r.distbits)-1;t:do{p<15&&(c+=z[i++]<<p,p+=8,c+=z[i++]<<p,p+=8),v=m[c&g];e:for(;;){if(c>>>=
|
||||
y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(c&(1<<y)-1)];continue e}if(32&y){r.mode=12;break t}t.msg="invalid literal/length code",r.mode=30;break t}w=65535&v,(y&=15)&&(p<y&&(c+=z[i++]<<p,p+=8),w+=c&(1<<y)-1,c>>>=y,p-=y),p<15&&(c+=z[i++]<<p,p+=8,c+=z[i++]<<p,p+=8),v=_[c&b];r:for(;;){if(c>>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(c&(1<<y)-1)];continue r}t.msg="invalid distance code",r.mode=30;break t}if(k=65535&v,p<(y&=15)&&
|
||||
(c+=z[i++]<<p,(p+=8)<y&&(c+=z[i++]<<p,p+=8)),h<(k+=c&(1<<y)-1)){t.msg="invalid distance too far back",r.mode=30;break t}if(c>>>=y,p-=y,(y=s-a)<k){if(l<(y=k-y)&&r.sane){t.msg="invalid distance too far back",r.mode=30;break t}if(S=d,(x=0)===f){if(x+=u-y,y<w){for(w-=y;C[s++]=d[x++],--y;);x=s-k,S=C}}else if(f<y){if(x+=u+f-y,(y-=f)<w){for(w-=y;C[s++]=d[x++],--y;);if(x=0,f<w){for(w-=y=f;C[s++]=d[x++],--y;);x=s-k,S=C}}}else if(x+=f-y,y<w){for(w-=y;C[s++]=d[x++],--y;);x=s-k,S=C}for(;2<w;)C[s++]=S[x++],C[s++]=
|
||||
S[x++],C[s++]=S[x++],w-=3;w&&(C[s++]=S[x++],1<w&&(C[s++]=S[x++]))}else{for(x=s-k;C[s++]=C[x++],C[s++]=C[x++],C[s++]=C[x++],2<(w-=3););w&&(C[s++]=C[x++],1<w&&(C[s++]=C[x++]))}break}}break}}while(i<n&&s<o);i-=w=p>>3,c&=(1<<(p-=w<<3))-1,t.next_in=i,t.next_out=s,t.avail_in=i<n?n-i+5:5-(i-n),t.avail_out=s<o?o-s+257:257-(s-o),r.hold=c,r.bits=p}},{}],49:[function(t,e,r){var I=t("../utils/common"),O=t("./adler32"),B=t("./crc32"),R=t("./inffast"),T=t("./inftrees"),D=1,F=2,N=0,U=-2,P=1,i=852,n=592;function L(t){return(t>>>
|
||||
24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),
|
||||
this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=P,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new I.Buf32(i),e.distcode=e.distdyn=new I.Buf32(n),e.sane=1,e.back=-1,N):U}function o(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,a(t)):U}function h(t,e){var r,i;return t&&t.state?(i=t.state,e<0?(r=
|
||||
0,e=-e):(r=1+(e>>4),e<48&&(e&=15)),e&&(e<8||15<e)?U:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=r,i.wbits=e,o(t))):U}function u(t,e){var r,i;return t?(i=new s,(t.state=i).window=null,(r=h(t,e))!==N&&(t.state=null),r):U}var l,f,d=!0;function j(t){if(d){var e;for(l=new I.Buf32(512),f=new I.Buf32(32),e=0;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(T(D,t.lens,0,288,l,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;T(F,t.lens,0,32,f,0,t.work,
|
||||
{bits:5}),d=!1}t.lencode=l,t.lenbits=9,t.distcode=f,t.distbits=5}function Z(t,e,r,i){var n,s=t.state;return null===s.window&&(s.wsize=1<<s.wbits,s.wnext=0,s.whave=0,s.window=new I.Buf8(s.wsize)),i>=s.wsize?(I.arraySet(s.window,e,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(i<(n=s.wsize-s.wnext)&&(n=i),I.arraySet(s.window,e,r-i,n,s.wnext),(i-=n)?(I.arraySet(s.window,e,r-i,i,0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whave<s.wsize&&(s.whave+=n))),0}r.inflateReset=
|
||||
o,r.inflateReset2=h,r.inflateResetKeep=a,r.inflateInit=function(t){return u(t,15)},r.inflateInit2=u,r.inflate=function(t,e){var r,i,n,s,a,o,h,u,l,f,d,c,p,m,_,g,b,v,y,w,k,x,S,z,C=0,E=new I.Buf8(4),A=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!t||!t.state||!t.output||!t.input&&0!==t.avail_in)return U;12===(r=t.state).mode&&(r.mode=13),a=t.next_out,n=t.output,h=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,u=r.hold,l=r.bits,f=o,d=h,x=N;t:for(;;)switch(r.mode){case P:if(0===r.wrap){r.mode=13;
|
||||
break}for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(2&r.wrap&&35615===u){E[r.check=0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){t.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){t.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){t.msg="invalid window size",r.mode=30;break}r.dmax=1<<k,t.adler=r.check=1,r.mode=512&
|
||||
u?10:12,l=u=0;break;case 2:for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(r.flags=u,8!=(255&r.flags)){t.msg="unknown compression method",r.mode=30;break}if(57344&r.flags){t.msg="unknown header flags set",r.mode=30;break}r.head&&(r.head.text=u>>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.head&&(r.head.time=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,
|
||||
E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.head&&(r.head.xflags=255&u,r.head.os=u>>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.length=u,r.head&&(r.head.extra_len=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(c=r.length)&&(c=o),c&&(r.head&&(k=r.head.extra_len-
|
||||
r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,i,s,c,k)),512&r.flags&&(r.check=B(r.check,i,c,s)),o-=c,s+=c,r.length-=c),r.length))break t;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break t;for(c=0;k=i[s+c++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&c<o;);if(512&r.flags&&(r.check=B(r.check,i,c,s)),o-=c,s+=c,k)break t}else r.head&&(r.head.name=null);r.length=0,r.mode=8;case 8:if(4096&r.flags){if(0===o)break t;for(c=0;k=
|
||||
i[s+c++],r.head&&k&&r.length<65536&&(r.head.comment+=String.fromCharCode(k)),k&&c<o;);if(512&r.flags&&(r.check=B(r.check,i,c,s)),o-=c,s+=c,k)break t}else r.head&&(r.head.comment=null);r.mode=9;case 9:if(512&r.flags){for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(u!==(65535&r.check)){t.msg="header crc mismatch",r.mode=30;break}l=u=0}r.head&&(r.head.hcrc=r.flags>>9&1,r.head.done=!0),t.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}t.adler=r.check=L(u),
|
||||
l=u=0,r.mode=11;case 11:if(0===r.havedict)return t.next_out=a,t.avail_out=h,t.next_in=s,t.avail_in=o,r.hold=u,r.bits=l,2;t.adler=r.check=1,r.mode=12;case 12:if(5===e||6===e)break t;case 13:if(r.last){u>>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}switch(r.last=1&u,l-=1,3&(u>>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==e)break;u>>>=2,l-=2;break t;case 2:r.mode=17;break;case 3:t.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&
|
||||
l,l-=7&l;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if((65535&u)!=(u>>>16^65535)){t.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===e)break t;case 15:r.mode=16;case 16:if(c=r.length){if(o<c&&(c=o),h<c&&(c=h),0===c)break t;I.arraySet(n,i,s,c,a),o-=c,s+=c,h-=c,a+=c,r.length-=c;break}r.mode=12;break;case 17:for(;l<14;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(r.nlen=257+(31&u),u>>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286<r.nlen||
|
||||
30<r.ndist){t.msg="too many length or distance symbols",r.mode=30;break}r.have=0,r.mode=18;case 18:for(;r.have<r.ncode;){for(;l<3;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.lens[A[r.have++]]=7&u,u>>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){t.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have<r.nlen+r.ndist;){for(;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>
|
||||
16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(b<16)u>>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(u>>>=_,l-=_,0===r.have){t.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],c=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}l-=_,k=0,c=3+(7&(u>>>=_)),u>>>=3,l-=3}else{for(z=_+7;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}l-=_,k=0,c=11+(127&(u>>>=_)),
|
||||
u>>>=7,l-=7}if(r.have+c>r.nlen+r.ndist){t.msg="invalid bit length repeat",r.mode=30;break}for(;c--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){t.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){t.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){t.msg=
|
||||
"invalid distances set",r.mode=30;break}if(r.mode=20,6===e)break t;case 20:r.mode=21;case 21:if(6<=o&&258<=h){t.next_out=a,t.avail_out=h,t.next_in=s,t.avail_in=o,r.hold=u,r.bits=l,R(t,d),a=t.next_out,n=t.output,h=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(g&&0==(240&g)){for(v=_,y=g,w=b;g=(C=r.lencode[w+((u&(1<<v+y)-1)>>
|
||||
v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){t.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.length+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<<r.distbits)-
|
||||
1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(0==(240&g)){for(v=_,y=g,w=b;g=(C=r.distcode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){t.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.offset+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=
|
||||
r.extra}if(r.offset>r.dmax){t.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break t;if(c=d-h,r.offset>c){if((c=r.offset-c)>r.whave&&r.sane){t.msg="invalid distance too far back",r.mode=30;break}p=c>r.wnext?(c-=r.wnext,r.wsize-c):r.wnext-c,c>r.length&&(c=r.length),m=r.window}else m=n,p=a-r.offset,c=r.length;for(h<c&&(c=h),h-=c,r.length-=c;n[a++]=m[p++],--c;);0===r.length&&(r.mode=21);break;case 26:if(0===h)break t;n[a++]=r.length,h--,r.mode=21;break;case 27:if(r.wrap){for(;l<
|
||||
32;){if(0===o)break t;o--,u|=i[s++]<<l,l+=8}if(d-=h,t.total_out+=d,r.total+=d,d&&(t.adler=r.check=r.flags?B(r.check,n,d,a-d):O(r.check,n,d,a-d)),d=h,(r.flags?u:L(u))!==r.check){t.msg="incorrect data check",r.mode=30;break}l=u=0}r.mode=28;case 28:if(r.wrap&&r.flags){for(;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(u!==(4294967295&r.total)){t.msg="incorrect length check",r.mode=30;break}l=u=0}r.mode=29;case 29:x=1;break t;case 30:x=-3;break t;case 31:return-4;case 32:default:return U}return t.next_out=
|
||||
a,t.avail_out=h,t.next_in=s,t.avail_in=o,r.hold=u,r.bits=l,(r.wsize||d!==t.avail_out&&r.mode<30&&(r.mode<27||4!==e))&&Z(t,t.output,t.next_out,d-t.avail_out)?(r.mode=31,-4):(f-=t.avail_in,d-=t.avail_out,t.total_in+=f,t.total_out+=d,r.total+=d,r.wrap&&d&&(t.adler=r.check=r.flags?B(r.check,n,d,t.next_out-d):O(r.check,n,d,t.next_out-d)),t.data_type=r.bits+(r.last?64:0)+(12===r.mode?128:0)+(20===r.mode||15===r.mode?256:0),(0==f&&0===d||4===e)&&x===N&&(x=-5),x)},r.inflateEnd=function(t){if(!t||!t.state)return U;
|
||||
var e=t.state;return e.window&&(e.window=null),t.state=null,N},r.inflateGetHeader=function(t,e){var r;return t&&t.state?0==(2&(r=t.state).wrap)?U:((r.head=e).done=!1,N):U},r.inflateSetDictionary=function(t,e){var r,i=e.length;return t&&t.state?0!==(r=t.state).wrap&&11!==r.mode?U:11===r.mode&&O(1,e,i,0)!==r.check?-3:Z(t,e,i,i)?(r.mode=31,-4):(r.havedict=1,N):U},r.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./inffast":48,"./inftrees":50}],50:[function(t,
|
||||
e,r){var D=t("../utils/common"),F=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],N=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],U=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],P=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];e.exports=function(t,e,r,i,n,s,a,o){var h,u,l,f,d,c,p,m,_,
|
||||
g=o.bits,b=0,v=0,y=0,w=0,k=0,x=0,S=0,z=0,C=0,E=0,A=null,I=0,O=new D.Buf16(16),B=new D.Buf16(16),R=null,T=0;for(b=0;b<=15;b++)O[b]=0;for(v=0;v<i;v++)O[e[r+v]]++;for(k=g,w=15;1<=w&&0===O[w];w--);if(w<k&&(k=w),0===w)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(y=1;y<w&&0===O[y];y++);for(k<y&&(k=y),b=z=1;b<=15;b++)if(z<<=1,(z-=O[b])<0)return-1;if(0<z&&(0===t||1!==w))return-1;for(B[1]=0,b=1;b<15;b++)B[b+1]=B[b]+O[b];for(v=0;v<i;v++)0!==e[r+v]&&(a[B[e[r+v]]++]=v);if(c=0===t?(A=R=a,19):1===t?(A=
|
||||
F,I-=257,R=N,T-=257,256):(A=U,R=P,-1),b=y,d=s,S=v=E=0,l=-1,f=(C=1<<(x=k))-1,1===t&&852<C||2===t&&592<C)return 1;for(;;){for(p=b-S,_=a[v]<c?(m=0,a[v]):a[v]>c?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<<b-S,y=u=1<<x;n[d+(E>>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<<b-1;E&h;)h>>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=e[r+a[v]]}if(k<b&&(E&f)!==l){for(0===S&&(S=k),d+=y,z=1<<(x=b-S);x+S<w&&!((z-=O[x+S])<=0);)x++,z<<=1;if(C+=1<<x,1===t&&852<C||2===t&&592<C)return 1;n[l=E&f]=k<<24|x<<16|
|
||||
d-s|0}}return 0!==E&&(n[d+E]=b-S<<24|64<<16|0),o.bits=k,0}},{"../utils/common":41}],51:[function(t,e,r){e.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],52:[function(t,e,r){var n=t("../utils/common"),o=0,h=1;function i(t){for(var e=t.length;0<=--e;)t[e]=0}var s=0,a=29,u=256,l=u+1+a,f=30,d=19,_=2*l+1,g=15,c=16,p=7,m=256,b=16,v=17,y=18,w=[0,0,0,0,0,0,0,0,1,1,1,1,
|
||||
2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],k=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],x=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],z=new Array(2*(l+2));i(z);var C=new Array(2*f);i(C);var E=new Array(512);i(E);var A=new Array(256);i(A);var I=new Array(a);i(I);var O,B,R,T=new Array(f);function D(t,e,r,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=r,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}function F(t,
|
||||
e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}function N(t){return t<256?E[t]:E[256+(t>>>7)]}function U(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function P(t,e,r){t.bi_valid>c-r?(t.bi_buf|=e<<t.bi_valid&65535,U(t,t.bi_buf),t.bi_buf=e>>c-t.bi_valid,t.bi_valid+=r-c):(t.bi_buf|=e<<t.bi_valid&65535,t.bi_valid+=r)}function L(t,e,r){P(t,r[2*e],r[2*e+1])}function j(t,e){for(var r=0;r|=1&t,t>>>=1,r<<=1,0<--e;);return r>>>1}function Z(t,e,r){var i,n,s=new Array(g+1),
|
||||
a=0;for(i=1;i<=g;i++)s[i]=a=a+r[i-1]<<1;for(n=0;n<=e;n++){var o=t[2*n+1];0!==o&&(t[2*n]=j(s[o]++,o))}}function W(t){var e;for(e=0;e<l;e++)t.dyn_ltree[2*e]=0;for(e=0;e<f;e++)t.dyn_dtree[2*e]=0;for(e=0;e<d;e++)t.bl_tree[2*e]=0;t.dyn_ltree[2*m]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function M(t){8<t.bi_valid?U(t,t.bi_buf):0<t.bi_valid&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function H(t,e,r,i){var n=2*e,s=2*r;return t[n]<t[s]||t[n]===t[s]&&i[e]<=i[r]}function G(t,e,
|
||||
r){for(var i=t.heap[r],n=r<<1;n<=t.heap_len&&(n<t.heap_len&&H(e,t.heap[n+1],t.heap[n],t.depth)&&n++,!H(e,i,t.heap[n],t.depth));)t.heap[r]=t.heap[n],r=n,n<<=1;t.heap[r]=i}function K(t,e,r){var i,n,s,a,o=0;if(0!==t.last_lit)for(;i=t.pending_buf[t.d_buf+2*o]<<8|t.pending_buf[t.d_buf+2*o+1],n=t.pending_buf[t.l_buf+o],o++,0===i?L(t,n,e):(L(t,(s=A[n])+u+1,e),0!==(a=w[s])&&P(t,n-=I[s],a),L(t,s=N(--i),r),0!==(a=k[s])&&P(t,i-=T[s],a)),o<t.last_lit;);L(t,m,e)}function Y(t,e){var r,i,n,s=e.dyn_tree,a=e.stat_desc.static_tree,
|
||||
o=e.stat_desc.has_stree,h=e.stat_desc.elems,u=-1;for(t.heap_len=0,t.heap_max=_,r=0;r<h;r++)0!==s[2*r]?(t.heap[++t.heap_len]=u=r,t.depth[r]=0):s[2*r+1]=0;for(;t.heap_len<2;)s[2*(n=t.heap[++t.heap_len]=u<2?++u:0)]=1,t.depth[n]=0,t.opt_len--,o&&(t.static_len-=a[2*n+1]);for(e.max_code=u,r=t.heap_len>>1;1<=r;r--)G(t,s,r);for(n=h;r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],G(t,s,1),i=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=i,s[2*n]=s[2*r]+s[2*i],t.depth[n]=(t.depth[r]>=t.depth[i]?t.depth[r]:
|
||||
t.depth[i])+1,s[2*r+1]=s[2*i+1]=n,t.heap[1]=n++,G(t,s,1),2<=t.heap_len;);t.heap[--t.heap_max]=t.heap[1],function(t,e){var r,i,n,s,a,o,h=e.dyn_tree,u=e.max_code,l=e.stat_desc.static_tree,f=e.stat_desc.has_stree,d=e.stat_desc.extra_bits,c=e.stat_desc.extra_base,p=e.stat_desc.max_length,m=0;for(s=0;s<=g;s++)t.bl_count[s]=0;for(h[2*t.heap[t.heap_max]+1]=0,r=t.heap_max+1;r<_;r++)p<(s=h[2*h[2*(i=t.heap[r])+1]+1]+1)&&(s=p,m++),h[2*i+1]=s,u<i||(t.bl_count[s]++,a=0,c<=i&&(a=d[i-c]),o=h[2*i],t.opt_len+=o*(s+
|
||||
a),f&&(t.static_len+=o*(l[2*i+1]+a)));if(0!==m){do{for(s=p-1;0===t.bl_count[s];)s--;t.bl_count[s]--,t.bl_count[s+1]+=2,t.bl_count[p]--,m-=2}while(0<m);for(s=p;0!==s;s--)for(i=t.bl_count[s];0!==i;)u<(n=t.heap[--r])||(h[2*n+1]!==s&&(t.opt_len+=(s-h[2*n+1])*h[2*n],h[2*n+1]=s),i--)}}(t,e),Z(s,u,t.bl_count)}function X(t,e,r){var i,n,s=-1,a=e[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),e[2*(r+1)+1]=65535,i=0;i<=r;i++)n=a,a=e[2*(i+1)+1],++o<h&&n===a||(o<u?t.bl_tree[2*n]+=o:0!==n?(n!==s&&t.bl_tree[2*n]++,t.bl_tree[2*
|
||||
b]++):o<=10?t.bl_tree[2*v]++:t.bl_tree[2*y]++,s=n,u=(o=0)===a?(h=138,3):n===a?(h=6,3):(h=7,4))}function V(t,e,r){var i,n,s=-1,a=e[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),i=0;i<=r;i++)if(n=a,a=e[2*(i+1)+1],!(++o<h&&n===a)){if(o<u)for(;L(t,n,t.bl_tree),0!=--o;);else 0!==n?(n!==s&&(L(t,n,t.bl_tree),o--),L(t,b,t.bl_tree),P(t,o-3,2)):o<=10?(L(t,v,t.bl_tree),P(t,o-3,3)):(L(t,y,t.bl_tree),P(t,o-11,7));s=n,u=(o=0)===a?(h=138,3):n===a?(h=6,3):(h=7,4)}}i(T);var q=!1;function J(t,e,r,i){P(t,(s<<1)+(i?1:0),3),
|
||||
function(t,e,r,i){M(t),i&&(U(t,r),U(t,~r)),n.arraySet(t.pending_buf,t.window,e,r,t.pending),t.pending+=r}(t,e,r,!0)}r._tr_init=function(t){q||(function(){var t,e,r,i,n,s=new Array(g+1);for(i=r=0;i<a-1;i++)for(I[i]=r,t=0;t<1<<w[i];t++)A[r++]=i;for(A[r-1]=i,i=n=0;i<16;i++)for(T[i]=n,t=0;t<1<<k[i];t++)E[n++]=i;for(n>>=7;i<f;i++)for(T[i]=n<<7,t=0;t<1<<k[i]-7;t++)E[256+n++]=i;for(e=0;e<=g;e++)s[e]=0;for(t=0;t<=143;)z[2*t+1]=8,t++,s[8]++;for(;t<=255;)z[2*t+1]=9,t++,s[9]++;for(;t<=279;)z[2*t+1]=7,t++,s[7]++;
|
||||
for(;t<=287;)z[2*t+1]=8,t++,s[8]++;for(Z(z,l+1,s),t=0;t<f;t++)C[2*t+1]=5,C[2*t]=j(t,5);O=new D(z,w,u+1,l,g),B=new D(C,k,0,f,g),R=new D(new Array(0),x,0,d,p)}(),q=!0),t.l_desc=new F(t.dyn_ltree,O),t.d_desc=new F(t.dyn_dtree,B),t.bl_desc=new F(t.bl_tree,R),t.bi_buf=0,t.bi_valid=0,W(t)},r._tr_stored_block=J,r._tr_flush_block=function(t,e,r,i){var n,s,a=0;0<t.level?(2===t.strm.data_type&&(t.strm.data_type=function(t){var e,r=4093624447;for(e=0;e<=31;e++,r>>>=1)if(1&r&&0!==t.dyn_ltree[2*e])return o;if(0!==
|
||||
t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return h;for(e=32;e<u;e++)if(0!==t.dyn_ltree[2*e])return h;return o}(t)),Y(t,t.l_desc),Y(t,t.d_desc),a=function(t){var e;for(X(t,t.dyn_ltree,t.l_desc.max_code),X(t,t.dyn_dtree,t.d_desc.max_code),Y(t,t.bl_desc),e=d-1;3<=e&&0===t.bl_tree[2*S[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),n=t.opt_len+3+7>>>3,(s=t.static_len+3+7>>>3)<=n&&(n=s)):n=s=r+5,r+4<=n&&-1!==e?J(t,e,r,i):4===t.strategy||s===n?(P(t,2+(i?1:0),3),K(t,z,C)):(P(t,4+(i?1:0),
|
||||
3),function(t,e,r,i){var n;for(P(t,e-257,5),P(t,r-1,5),P(t,i-4,4),n=0;n<i;n++)P(t,t.bl_tree[2*S[n]+1],3);V(t,t.dyn_ltree,e-1),V(t,t.dyn_dtree,r-1)}(t,t.l_desc.max_code+1,t.d_desc.max_code+1,a+1),K(t,t.dyn_ltree,t.dyn_dtree)),W(t),i&&M(t)},r._tr_tally=function(t,e,r){return t.pending_buf[t.d_buf+2*t.last_lit]=e>>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&r,t.last_lit++,0===e?t.dyn_ltree[2*r]++:(t.matches++,e--,t.dyn_ltree[2*(A[r]+u+1)]++,t.dyn_dtree[2*
|
||||
N(e)]++),t.last_lit===t.lit_bufsize-1},r._tr_align=function(t){P(t,2,3),L(t,m,z),function(t){16===t.bi_valid?(U(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):8<=t.bi_valid&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}(t)}},{"../utils/common":41}],53:[function(t,e,r){e.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(t,
|
||||
e,r){e.exports="function"==typeof setImmediate?setImmediate:function(){var t=[].slice.apply(arguments);t.splice(1,0,0),setTimeout.apply(null,t)}},{}]},{},[10])(10)};
|
||||
10
public/src/js/lib/md5.min.js
vendored
Normal file
10
public/src/js/lib/md5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
46
public/src/js/lib/oggmented-wasm.js
Normal file
46
public/src/js/lib/oggmented-wasm.js
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
var Oggmented = (function() {
|
||||
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
|
||||
|
||||
return (
|
||||
function(Oggmented) {
|
||||
Oggmented = Oggmented || {};
|
||||
|
||||
(function(b,c){function m(h){delete k[h]}function l(h){if(e)setTimeout(l,0,h);else{var t=k[h];if(t){e=!0;try{var p=t.A,y=t.v;switch(y.length){case 0:p();break;case 1:p(y[0]);break;case 2:p(y[0],y[1]);break;case 3:p(y[0],y[1],y[2]);break;default:p.apply(c,y)}}finally{m(h),e=!1}}}}function q(){r=function(h){process.C(function(){l(h)})}}function u(){if(b.postMessage&&!b.importScripts){var h=!0,t=b.onmessage;b.onmessage=function(){h=!1};b.postMessage("","*");b.onmessage=t;return h}}function w(){function h(p){p.source===
|
||||
b&&"string"===typeof p.data&&0===p.data.indexOf(t)&&l(+p.data.slice(t.length))}var t="setImmediate$"+Math.random()+"$";b.addEventListener?b.addEventListener("message",h,!1):b.attachEvent("onmessage",h);r=function(p){b.postMessage(t+p,"*")}}function v(){var h=new MessageChannel;h.port1.onmessage=function(t){l(t.data)};r=function(t){h.port2.postMessage(t)}}function A(){var h=f.documentElement;r=function(t){var p=f.createElement("script");p.onreadystatechange=function(){l(t);p.onreadystatechange=null;
|
||||
h.removeChild(p);p=null};h.appendChild(p)}}function d(){r=function(h){setTimeout(l,0,h)}}if(!b.setImmediate){var g=1,k={},e=!1,f=b.document,r,n=Object.getPrototypeOf&&Object.getPrototypeOf(b);n=n&&n.setTimeout?n:b;"[object process]"==={}.toString.call(b.process)?q():u()?w():b.MessageChannel?v():f&&"onreadystatechange"in f.createElement("script")?A():d();n.setImmediate=function(h){"function"!==typeof h&&(h=new Function(""+h));for(var t=Array(arguments.length-1),p=0;p<t.length;p++)t[p]=arguments[p+
|
||||
1];k[g]={A:h,v:t};r(g);return g++};n.clearImmediate=m}})("undefined"===typeof self?"undefined"===typeof global?this:global:self);
|
||||
null;var a;a||(a=typeof Oggmented !== 'undefined' ? Oggmented : {});var x,z;a.ready=new Promise(function(b,c){x=b;z=c});const aa=new (window.AudioContext||window.webkitAudioContext);
|
||||
a.decodeOggData=(b,c,m)=>{try{var {channels:l,length:q,rate:u}=(g=>{const k=g.byteLength,e=B(k);g=new Int8Array(g);C.set(g,e);D("open_buffer","number",["number","number"],[e,k]);return{channels:ba(),length:ca(),rate:da()}})(b),w=aa.createBuffer(l,q,u),v=B(Uint32Array.BYTES_PER_ELEMENT),A=0}catch(g){return m&&m(g)}const d=()=>{try{const g=Date.now();let k;for(;k=ea(v);){const e=fa(v,"*"),f=new Uint32Array(ha.buffer,e,l);for(let r=0;r<l;r++){const n=new Float32Array(E.buffer,f[r],k);w.getChannelData(r).set(n,
|
||||
A)}A+=k;if(g+8<Date.now()){setImmediate(d);break}}0===k&&(ia(),F(b),F(v),c&&c(w))}catch(g){return m&&m(g)}};setImmediate(d);return w};var G={},H;for(H in a)a.hasOwnProperty(H)&&(G[H]=a[H]);function ja(b,c){throw c;}var I="";"undefined"!==typeof document&&document.currentScript&&(I=document.currentScript.src);_scriptDir&&(I=_scriptDir);0!==I.indexOf("blob:")?I=I.substr(0,I.lastIndexOf("/")+1):I="";var J=a.printErr||console.warn.bind(console);for(H in G)G.hasOwnProperty(H)&&(a[H]=G[H]);G=null;
|
||||
a.quit&&(ja=a.quit);var K;a.wasmBinary&&(K=a.wasmBinary);var noExitRuntime;a.noExitRuntime&&(noExitRuntime=a.noExitRuntime);"object"!==typeof WebAssembly&&L("no native wasm support detected");
|
||||
function fa(b,c){c=c||"i8";"*"===c.charAt(c.length-1)&&(c="i32");switch(c){case "i1":return C[b>>0];case "i8":return C[b>>0];case "i16":return ka[b>>1];case "i32":return M[b>>2];case "i64":return M[b>>2];case "float":return E[b>>2];case "double":return la[b>>3];default:L("invalid type for getValue: "+c)}return null}var N,O=!1;function ma(b){var c=a["_"+b];c||L("Assertion failed: Cannot call unknown function "+(b+", make sure it is exported"));return c}
|
||||
function D(b,c,m,l){var q={string:function(d){var g=0;if(null!==d&&void 0!==d&&0!==d){var k=(d.length<<2)+1;g=P(k);var e=g,f=Q;if(0<k){k=e+k-1;for(var r=0;r<d.length;++r){var n=d.charCodeAt(r);if(55296<=n&&57343>=n){var h=d.charCodeAt(++r);n=65536+((n&1023)<<10)|h&1023}if(127>=n){if(e>=k)break;f[e++]=n}else{if(2047>=n){if(e+1>=k)break;f[e++]=192|n>>6}else{if(65535>=n){if(e+2>=k)break;f[e++]=224|n>>12}else{if(e+3>=k)break;f[e++]=240|n>>18;f[e++]=128|n>>12&63}f[e++]=128|n>>6&63}f[e++]=128|n&63}}f[e]=
|
||||
0}}return g},array:function(d){var g=P(d.length);C.set(d,g);return g}},u=ma(b),w=[];b=0;if(l)for(var v=0;v<l.length;v++){var A=q[m[v]];A?(0===b&&(b=na()),w[v]=A(l[v])):w[v]=l[v]}m=u.apply(null,w);m=function(d){if("string"===c)if(d){for(var g=Q,k=d+NaN,e=d;g[e]&&!(e>=k);)++e;if(16<e-d&&g.subarray&&oa)d=oa.decode(g.subarray(d,e));else{for(k="";d<e;){var f=g[d++];if(f&128){var r=g[d++]&63;if(192==(f&224))k+=String.fromCharCode((f&31)<<6|r);else{var n=g[d++]&63;f=224==(f&240)?(f&15)<<12|r<<6|n:(f&7)<<
|
||||
18|r<<12|n<<6|g[d++]&63;65536>f?k+=String.fromCharCode(f):(f-=65536,k+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else k+=String.fromCharCode(f)}d=k}}else d="";else d="boolean"===c?!!d:d;return d}(m);0!==b&&pa(b);return m}var oa="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0,R,C,Q,ka,M,ha,E,la;
|
||||
function qa(b){R=b;a.HEAP8=C=new Int8Array(b);a.HEAP16=ka=new Int16Array(b);a.HEAP32=M=new Int32Array(b);a.HEAPU8=Q=new Uint8Array(b);a.HEAPU16=new Uint16Array(b);a.HEAPU32=ha=new Uint32Array(b);a.HEAPF32=E=new Float32Array(b);a.HEAPF64=la=new Float64Array(b)}var ra=a.INITIAL_MEMORY||16777216;a.wasmMemory?N=a.wasmMemory:N=new WebAssembly.Memory({initial:ra/65536,maximum:32768});N&&(R=N.buffer);ra=R.byteLength;qa(R);var S,sa=[],ta=[],ua=[],va=[];function wa(){var b=a.preRun.shift();sa.unshift(b)}
|
||||
var T=0,U=null,V=null;a.preloadedImages={};a.preloadedAudios={};function L(b){if(a.onAbort)a.onAbort(b);J(b);O=!0;b=new WebAssembly.RuntimeError("abort("+b+"). Build with -s ASSERTIONS=1 for more info.");z(b);throw b;}function xa(){var b=W;return String.prototype.startsWith?b.startsWith("data:application/octet-stream;base64,"):0===b.indexOf("data:application/octet-stream;base64,")}var W="oggmented-wasm.wasm";if(!xa()){var ya=W;W=a.locateFile?a.locateFile(ya,I):I+ya}
|
||||
function za(){try{if(K)return new Uint8Array(K);throw"both async and sync fetching of the wasm failed";}catch(b){L(b)}}function Aa(){return K||"function"!==typeof fetch?Promise.resolve().then(za):fetch(W,{credentials:"same-origin"}).then(function(b){if(!b.ok)throw"failed to load wasm binary file at '"+W+"'";return b.arrayBuffer()}).catch(function(){return za()})}
|
||||
function X(b){for(;0<b.length;){var c=b.shift();if("function"==typeof c)c(a);else{var m=c.B;"number"===typeof m?void 0===c.u?S.get(m)():S.get(m)(c.u):m(void 0===c.u?null:c.u)}}}ta.push({B:function(){Ba()}});
|
||||
var Da={b:function(b,c,m){Q.copyWithin(b,c,c+m)},c:function(b){b>>>=0;var c=Q.length;if(2147483648<b)return!1;for(var m=1;4>=m;m*=2){var l=c*(1+.2/m);l=Math.min(l,b+100663296);l=Math.max(16777216,b,l);0<l%65536&&(l+=65536-l%65536);a:{try{N.grow(Math.min(2147483648,l)-R.byteLength+65535>>>16);qa(N.buffer);var q=1;break a}catch(u){}q=void 0}if(q)return!0}return!1},d:function(b){if(!noExitRuntime){if(a.onExit)a.onExit(b);O=!0}ja(b,new Ca(b))},a:N};
|
||||
(function(){function b(q){a.asm=q.exports;S=a.asm.e;T--;a.monitorRunDependencies&&a.monitorRunDependencies(T);0==T&&(null!==U&&(clearInterval(U),U=null),V&&(q=V,V=null,q()))}function c(q){b(q.instance)}function m(q){return Aa().then(function(u){return WebAssembly.instantiate(u,l)}).then(q,function(u){J("failed to asynchronously prepare wasm: "+u);L(u)})}var l={a:Da};T++;a.monitorRunDependencies&&a.monitorRunDependencies(T);if(a.instantiateWasm)try{return a.instantiateWasm(l,b)}catch(q){return J("Module.instantiateWasm callback failed with error: "+
|
||||
q),!1}(function(){return K||"function"!==typeof WebAssembly.instantiateStreaming||xa()||"function"!==typeof fetch?m(c):fetch(W,{credentials:"same-origin"}).then(function(q){return WebAssembly.instantiateStreaming(q,l).then(c,function(u){J("wasm streaming compile failed: "+u);J("falling back to ArrayBuffer instantiation");return m(c)})})})().catch(z);return{}})();var Ba=a.___wasm_call_ctors=function(){return(Ba=a.___wasm_call_ctors=a.asm.f).apply(null,arguments)};
|
||||
a._open_buffer=function(){return(a._open_buffer=a.asm.g).apply(null,arguments)};var ia=a._close_buffer=function(){return(ia=a._close_buffer=a.asm.h).apply(null,arguments)},ca=a._get_length=function(){return(ca=a._get_length=a.asm.i).apply(null,arguments)},ba=a._get_channels=function(){return(ba=a._get_channels=a.asm.j).apply(null,arguments)},da=a._get_rate=function(){return(da=a._get_rate=a.asm.k).apply(null,arguments)};a._get_time=function(){return(a._get_time=a.asm.l).apply(null,arguments)};
|
||||
a._get_streams=function(){return(a._get_streams=a.asm.m).apply(null,arguments)};
|
||||
var ea=a._read_float=function(){return(ea=a._read_float=a.asm.n).apply(null,arguments)},F=a._free=function(){return(F=a._free=a.asm.o).apply(null,arguments)},B=a._malloc=function(){return(B=a._malloc=a.asm.p).apply(null,arguments)},na=a.stackSave=function(){return(na=a.stackSave=a.asm.q).apply(null,arguments)},pa=a.stackRestore=function(){return(pa=a.stackRestore=a.asm.r).apply(null,arguments)},P=a.stackAlloc=function(){return(P=a.stackAlloc=a.asm.s).apply(null,arguments)};a.ccall=D;a.getValue=fa;
|
||||
var Y;function Ca(b){this.name="ExitStatus";this.message="Program terminated with exit("+b+")";this.status=b}V=function Ea(){Y||Z();Y||(V=Ea)};
|
||||
function Z(){function b(){if(!Y&&(Y=!0,a.calledRun=!0,!O)){X(ta);X(ua);x(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;){var c=a.postRun.shift();va.unshift(c)}X(va)}}if(!(0<T)){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)wa();X(sa);0<T||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);b()},1)):b())}}a.run=Z;
|
||||
if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();noExitRuntime=!0;Z();
|
||||
|
||||
|
||||
return Oggmented.ready
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = Oggmented;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return Oggmented; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["Oggmented"] = Oggmented;
|
||||
|
||||
BIN
public/src/js/lib/oggmented-wasm.wasm
Normal file
BIN
public/src/js/lib/oggmented-wasm.wasm
Normal file
Binary file not shown.
587
public/src/js/loader.js
Normal file
587
public/src/js/loader.js
Normal file
@@ -0,0 +1,587 @@
|
||||
class Loader{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(callback){
|
||||
this.callback = callback
|
||||
this.loadedAssets = 0
|
||||
this.assetsDiv = document.getElementById("assets")
|
||||
this.screen = document.getElementById("screen")
|
||||
this.startTime = Date.now()
|
||||
this.errorMessages = []
|
||||
this.songSearchGradient = "linear-gradient(to top, rgba(245, 246, 252, 0.08), #ff5963), "
|
||||
|
||||
var promises = []
|
||||
|
||||
promises.push(this.ajax("src/views/loader.html").then(page => {
|
||||
this.screen.innerHTML = page
|
||||
}))
|
||||
|
||||
promises.push(this.ajax("api/config").then(conf => {
|
||||
gameConfig = JSON.parse(conf)
|
||||
}))
|
||||
|
||||
Promise.all(promises).then(this.run.bind(this))
|
||||
}
|
||||
run(){
|
||||
this.promises = []
|
||||
this.loaderDiv = document.querySelector("#loader")
|
||||
this.loaderPercentage = document.querySelector("#loader .percentage")
|
||||
this.loaderProgress = document.querySelector("#loader .progress")
|
||||
|
||||
this.queryString = gameConfig._version.commit_short ? "?" + gameConfig._version.commit_short : ""
|
||||
|
||||
if(gameConfig.custom_js){
|
||||
this.addPromise(this.loadScript(gameConfig.custom_js), gameConfig.custom_js)
|
||||
}
|
||||
var oggSupport = new Audio().canPlayType("audio/ogg;codecs=vorbis")
|
||||
if(!oggSupport){
|
||||
assets.js.push("lib/oggmented-wasm.js")
|
||||
}
|
||||
assets.js.forEach(name => {
|
||||
this.addPromise(this.loadScript("src/js/" + name), "src/js/" + name)
|
||||
})
|
||||
|
||||
var pageVersion = versionLink.href
|
||||
var index = pageVersion.lastIndexOf("/")
|
||||
if(index !== -1){
|
||||
pageVersion = pageVersion.slice(index + 1)
|
||||
}
|
||||
this.addPromise(new Promise((resolve, reject) => {
|
||||
if(
|
||||
versionLink.href !== gameConfig._version.url &&
|
||||
gameConfig._version.commit &&
|
||||
versionLink.href.indexOf(gameConfig._version.commit) === -1
|
||||
){
|
||||
reject("Version on the page and config does not match\n(page: " + pageVersion + ",\nconfig: "+ gameConfig._version.commit + ")")
|
||||
}
|
||||
var cssCount = document.styleSheets.length + assets.css.length
|
||||
assets.css.forEach(name => {
|
||||
var stylesheet = document.createElement("link")
|
||||
stylesheet.rel = "stylesheet"
|
||||
stylesheet.href = "src/css/" + name + this.queryString
|
||||
document.head.appendChild(stylesheet)
|
||||
})
|
||||
var checkStyles = () => {
|
||||
if(document.styleSheets.length >= cssCount){
|
||||
resolve()
|
||||
clearInterval(interval)
|
||||
}
|
||||
}
|
||||
var interval = setInterval(checkStyles, 100)
|
||||
checkStyles()
|
||||
}))
|
||||
|
||||
for(var name in assets.fonts){
|
||||
var url = gameConfig.assets_baseurl + "fonts/" + assets.fonts[name]
|
||||
this.addPromise(new FontFace(name, "url('" + url + "')").load().then(font => {
|
||||
document.fonts.add(font)
|
||||
}), url)
|
||||
}
|
||||
|
||||
assets.img.forEach(name => {
|
||||
var id = this.getFilename(name)
|
||||
var image = document.createElement("img")
|
||||
image.crossOrigin = "anonymous"
|
||||
var url = gameConfig.assets_baseurl + "img/" + name
|
||||
this.addPromise(pageEvents.load(image), url)
|
||||
image.id = name
|
||||
image.src = url
|
||||
this.assetsDiv.appendChild(image)
|
||||
assets.image[id] = image
|
||||
})
|
||||
|
||||
var css = []
|
||||
for(let selector in assets.cssBackground){
|
||||
let name = assets.cssBackground[selector]
|
||||
var url = gameConfig.assets_baseurl + "img/" + name
|
||||
this.addPromise(loader.ajax(url, request => {
|
||||
request.responseType = "blob"
|
||||
}).then(blob => {
|
||||
var id = this.getFilename(name)
|
||||
var image = document.createElement("img")
|
||||
let blobUrl = URL.createObjectURL(blob)
|
||||
var promise = pageEvents.load(image).then(() => {
|
||||
var gradient = ""
|
||||
if(selector === ".pattern-bg"){
|
||||
loader.screen.style.backgroundImage = "url(\"" + blobUrl + "\")"
|
||||
}else if(selector === "#song-search"){
|
||||
gradient = this.songSearchGradient
|
||||
}
|
||||
css.push(this.cssRuleset({
|
||||
[selector]: {
|
||||
"background-image": gradient + "url(\"" + blobUrl + "\")"
|
||||
}
|
||||
}))
|
||||
})
|
||||
image.id = name
|
||||
image.src = blobUrl
|
||||
this.assetsDiv.appendChild(image)
|
||||
assets.image[id] = image
|
||||
return promise
|
||||
}), url)
|
||||
}
|
||||
|
||||
assets.views.forEach(name => {
|
||||
var id = this.getFilename(name)
|
||||
var url = "src/views/" + name + this.queryString
|
||||
this.addPromise(this.ajax(url).then(page => {
|
||||
assets.pages[id] = page
|
||||
}), url)
|
||||
})
|
||||
|
||||
this.addPromise(this.ajax("api/categories").then(cats => {
|
||||
assets.categories = JSON.parse(cats)
|
||||
assets.categories.forEach(cat => {
|
||||
if(cat.song_skin){
|
||||
cat.songSkin = cat.song_skin //rename the song_skin property and add category title to categories array
|
||||
delete cat.song_skin
|
||||
cat.songSkin.infoFill = cat.songSkin.info_fill
|
||||
delete cat.songSkin.info_fill
|
||||
}
|
||||
})
|
||||
|
||||
assets.categories.push({
|
||||
title: "default",
|
||||
songSkin: {
|
||||
background: "#ececec",
|
||||
border: ["#fbfbfb", "#8b8b8b"],
|
||||
outline: "#656565",
|
||||
infoFill: "#656565"
|
||||
}
|
||||
})
|
||||
}), "api/categories")
|
||||
|
||||
var url = gameConfig.assets_baseurl + "img/vectors.json" + this.queryString
|
||||
this.addPromise(this.ajax(url).then(response => {
|
||||
vectors = JSON.parse(response)
|
||||
}), url)
|
||||
|
||||
this.afterJSCount =
|
||||
[
|
||||
"api/songs",
|
||||
"blurPerformance",
|
||||
"categories"
|
||||
].length +
|
||||
assets.audioSfx.length +
|
||||
assets.audioMusic.length +
|
||||
assets.audioSfxLR.length +
|
||||
assets.audioSfxLoud.length +
|
||||
(gameConfig.accounts ? 1 : 0)
|
||||
|
||||
Promise.all(this.promises).then(() => {
|
||||
if(this.error){
|
||||
return
|
||||
}
|
||||
|
||||
var style = document.createElement("style")
|
||||
style.appendChild(document.createTextNode(css.join("\n")))
|
||||
document.head.appendChild(style)
|
||||
|
||||
this.addPromise(this.ajax("api/songs").then(songs => {
|
||||
songs = JSON.parse(songs)
|
||||
songs.forEach(song => {
|
||||
var directory = gameConfig.songs_baseurl + song.id + "/"
|
||||
var songExt = song.music_type ? song.music_type : "mp3"
|
||||
song.music = new RemoteFile(directory + "main." + songExt)
|
||||
if(song.type === "tja"){
|
||||
song.chart = new RemoteFile(directory + "main.tja")
|
||||
}else{
|
||||
song.chart = {separateDiff: true}
|
||||
for(var diff in song.courses){
|
||||
if(song.courses[diff]){
|
||||
song.chart[diff] = new RemoteFile(directory + diff + ".osu")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(song.lyrics){
|
||||
song.lyricsFile = new RemoteFile(directory + "main.vtt")
|
||||
}
|
||||
if(song.preview > 0){
|
||||
song.previewMusic = new RemoteFile(directory + "preview." + gameConfig.preview_type)
|
||||
}
|
||||
})
|
||||
assets.songsDefault = songs
|
||||
assets.songs = assets.songsDefault
|
||||
}), "api/songs")
|
||||
|
||||
var categoryPromises = []
|
||||
assets.categories //load category backgrounds to DOM
|
||||
.filter(cat => cat.songSkin && cat.songSkin.bg_img)
|
||||
.forEach(cat => {
|
||||
let name = cat.songSkin.bg_img
|
||||
var url = gameConfig.assets_baseurl + "img/" + name
|
||||
categoryPromises.push(loader.ajax(url, request => {
|
||||
request.responseType = "blob"
|
||||
}).then(blob => {
|
||||
var id = this.getFilename(name)
|
||||
var image = document.createElement("img")
|
||||
let blobUrl = URL.createObjectURL(blob)
|
||||
var promise = pageEvents.load(image)
|
||||
image.id = name
|
||||
image.src = blobUrl
|
||||
this.assetsDiv.appendChild(image)
|
||||
assets.image[id] = image
|
||||
return promise
|
||||
}).catch(response => {
|
||||
return this.errorMsg(response, url)
|
||||
}))
|
||||
})
|
||||
this.addPromise(Promise.all(categoryPromises))
|
||||
|
||||
snd.buffer = new SoundBuffer()
|
||||
if(!oggSupport){
|
||||
snd.buffer.oggDecoder = snd.buffer.fallbackDecoder
|
||||
}
|
||||
snd.musicGain = snd.buffer.createGain()
|
||||
snd.sfxGain = snd.buffer.createGain()
|
||||
snd.previewGain = snd.buffer.createGain()
|
||||
snd.sfxGainL = snd.buffer.createGain("left")
|
||||
snd.sfxGainR = snd.buffer.createGain("right")
|
||||
snd.sfxLoudGain = snd.buffer.createGain()
|
||||
snd.buffer.setCrossfade(
|
||||
[snd.musicGain, snd.previewGain],
|
||||
[snd.sfxGain, snd.sfxGainL, snd.sfxGainR],
|
||||
0.5
|
||||
)
|
||||
snd.sfxLoudGain.setVolume(1.2)
|
||||
snd.buffer.saveSettings()
|
||||
|
||||
this.afterJSCount = 0
|
||||
|
||||
assets.audioSfx.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.sfxGain), this.soundUrl(name))
|
||||
})
|
||||
assets.audioMusic.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.musicGain), this.soundUrl(name))
|
||||
})
|
||||
assets.audioSfxLR.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.sfxGain).then(sound => {
|
||||
var id = this.getFilename(name)
|
||||
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
|
||||
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
|
||||
}), this.soundUrl(name))
|
||||
})
|
||||
assets.audioSfxLoud.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.sfxLoudGain), this.soundUrl(name))
|
||||
})
|
||||
|
||||
this.canvasTest = new CanvasTest()
|
||||
this.addPromise(this.canvasTest.blurPerformance().then(result => {
|
||||
perf.blur = result
|
||||
if(result > 1000 / 50){
|
||||
// Less than 50 fps with blur enabled
|
||||
disableBlur = true
|
||||
}
|
||||
}), "blurPerformance")
|
||||
|
||||
if(gameConfig.accounts){
|
||||
this.addPromise(this.ajax("api/scores/get").then(response => {
|
||||
response = JSON.parse(response)
|
||||
if(response.status === "ok"){
|
||||
account.loggedIn = true
|
||||
account.username = response.username
|
||||
account.displayName = response.display_name
|
||||
account.don = response.don
|
||||
scoreStorage.load(response.scores)
|
||||
pageEvents.send("login", account.username)
|
||||
}
|
||||
}), "api/scores/get")
|
||||
}
|
||||
|
||||
settings = new Settings()
|
||||
pageEvents.setKbd()
|
||||
scoreStorage = new ScoreStorage()
|
||||
db = new IDB("taiko", "store")
|
||||
plugins = new Plugins()
|
||||
|
||||
if(localStorage.getItem("lastSearchQuery")){
|
||||
localStorage.removeItem("lastSearchQuery")
|
||||
}
|
||||
|
||||
Promise.all(this.promises).then(() => {
|
||||
if(this.error){
|
||||
return
|
||||
}
|
||||
if(!account.loggedIn){
|
||||
scoreStorage.load()
|
||||
}
|
||||
for(var i in assets.songsDefault){
|
||||
var song = assets.songsDefault[i]
|
||||
if(!song.hash){
|
||||
song.hash = song.title
|
||||
}
|
||||
scoreStorage.songTitles[song.title] = song.hash
|
||||
var score = scoreStorage.get(song.hash, false, true)
|
||||
if(score){
|
||||
score.title = song.title
|
||||
}
|
||||
}
|
||||
var promises = []
|
||||
|
||||
var readyEvent = "normal"
|
||||
var songId
|
||||
var hashLower = location.hash.toLowerCase()
|
||||
p2 = new P2Connection()
|
||||
if(hashLower.startsWith("#song=")){
|
||||
var number = parseInt(location.hash.slice(6))
|
||||
if(number > 0){
|
||||
songId = number
|
||||
readyEvent = "song-id"
|
||||
}
|
||||
}else if(location.hash.length === 6){
|
||||
p2.hashLock = true
|
||||
promises.push(new Promise(resolve => {
|
||||
p2.open()
|
||||
pageEvents.add(p2, "message", response => {
|
||||
if(response.type === "session"){
|
||||
pageEvents.send("session-start", "invited")
|
||||
readyEvent = "session-start"
|
||||
resolve()
|
||||
}else if(response.type === "gameend"){
|
||||
p2.hash("")
|
||||
p2.hashLock = false
|
||||
readyEvent = "session-expired"
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
p2.send("invite", {
|
||||
id: location.hash.slice(1).toLowerCase(),
|
||||
name: account.loggedIn ? account.displayName : null,
|
||||
don: account.loggedIn ? account.don : null
|
||||
})
|
||||
setTimeout(() => {
|
||||
if(p2.socket.readyState !== 1){
|
||||
p2.hash("")
|
||||
p2.hashLock = false
|
||||
resolve()
|
||||
}
|
||||
}, 10000)
|
||||
}).then(() => {
|
||||
pageEvents.remove(p2, "message")
|
||||
}))
|
||||
}else{
|
||||
p2.hash("")
|
||||
}
|
||||
|
||||
promises.push(this.canvasTest.drawAllImages().then(result => {
|
||||
perf.allImg = result
|
||||
}))
|
||||
|
||||
if(gameConfig.plugins){
|
||||
gameConfig.plugins.forEach(obj => {
|
||||
if(obj.url){
|
||||
var plugin = plugins.add(obj.url, {
|
||||
hide: obj.hide
|
||||
})
|
||||
if(plugin){
|
||||
plugin.loadErrors = true
|
||||
promises.push(plugin.load(true).then(() => {
|
||||
if(obj.start){
|
||||
return plugin.start(false, true)
|
||||
}
|
||||
}).catch(response => {
|
||||
return this.errorMsg(response, obj.url)
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
perf.load = Date.now() - this.startTime
|
||||
this.canvasTest.clean()
|
||||
this.clean()
|
||||
this.callback(songId)
|
||||
this.ready = true
|
||||
pageEvents.send("ready", readyEvent)
|
||||
}, e => this.errorMsg(e))
|
||||
}, e => this.errorMsg(e))
|
||||
})
|
||||
}
|
||||
addPromise(promise, url){
|
||||
this.promises.push(promise)
|
||||
promise.then(this.assetLoaded.bind(this), response => {
|
||||
return this.errorMsg(response, url)
|
||||
})
|
||||
}
|
||||
soundUrl(name){
|
||||
return gameConfig.assets_baseurl + "audio/" + name
|
||||
}
|
||||
loadSound(name, gain){
|
||||
var id = this.getFilename(name)
|
||||
return gain.load(new RemoteFile(this.soundUrl(name))).then(sound => {
|
||||
assets.sounds[id] = sound
|
||||
})
|
||||
}
|
||||
getFilename(name){
|
||||
return name.slice(0, name.lastIndexOf("."))
|
||||
}
|
||||
errorMsg(error, url){
|
||||
var rethrow
|
||||
if(url || error){
|
||||
if(typeof error === "object" && error.constructor === Error){
|
||||
rethrow = error
|
||||
error = error.stack || ""
|
||||
var index = error.indexOf("\n ")
|
||||
if(index !== -1){
|
||||
error = error.slice(0, index)
|
||||
}
|
||||
}else if(Array.isArray(error)){
|
||||
error = error[0]
|
||||
}
|
||||
if(url){
|
||||
error = (error ? error + ": " : "") + url
|
||||
}
|
||||
this.errorMessages.push(error)
|
||||
pageEvents.send("loader-error", url || error)
|
||||
}
|
||||
if(!this.error){
|
||||
this.error = true
|
||||
cancelTouch = false
|
||||
this.loaderDiv.classList.add("loaderError")
|
||||
if(typeof allStrings === "object"){
|
||||
var lang = localStorage.lang
|
||||
if(!lang){
|
||||
var userLang = navigator.languages.slice()
|
||||
userLang.unshift(navigator.language)
|
||||
for(var i in userLang){
|
||||
for(var j in allStrings){
|
||||
if(allStrings[j].regex.test(userLang[i])){
|
||||
lang = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!lang){
|
||||
lang = "en"
|
||||
}
|
||||
loader.screen.getElementsByClassName("view-content")[0].innerText = allStrings[lang] && allStrings[lang].errorOccured || allStrings.en.errorOccured
|
||||
}
|
||||
var loaderError = loader.screen.getElementsByClassName("loader-error-div")[0]
|
||||
loaderError.style.display = "flex"
|
||||
var diagTxt = loader.screen.getElementsByClassName("diag-txt")[0]
|
||||
var debugLink = loader.screen.getElementsByClassName("debug-link")[0]
|
||||
if(navigator.userAgent.indexOf("Android") >= 0){
|
||||
var iframe = document.createElement("iframe")
|
||||
diagTxt.appendChild(iframe)
|
||||
var body = iframe.contentWindow.document.body
|
||||
body.setAttribute("style", `
|
||||
font-family: monospace;
|
||||
margin: 2px 0 0 2px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
cursor: text;
|
||||
`)
|
||||
body.setAttribute("onblur", `
|
||||
getSelection().removeAllRanges()
|
||||
`)
|
||||
this.errorTxt = {
|
||||
element: body,
|
||||
method: "innerText"
|
||||
}
|
||||
}else{
|
||||
var textarea = document.createElement("textarea")
|
||||
textarea.readOnly = true
|
||||
diagTxt.appendChild(textarea)
|
||||
if(!this.touchEnabled){
|
||||
textarea.addEventListener("focus", () => {
|
||||
textarea.select()
|
||||
})
|
||||
textarea.addEventListener("blur", () => {
|
||||
getSelection().removeAllRanges()
|
||||
})
|
||||
}
|
||||
this.errorTxt = {
|
||||
element: textarea,
|
||||
method: "value"
|
||||
}
|
||||
}
|
||||
var show = () => {
|
||||
diagTxt.style.display = "block"
|
||||
debugLink.style.display = "none"
|
||||
}
|
||||
debugLink.addEventListener("click", show)
|
||||
debugLink.addEventListener("touchstart", show)
|
||||
this.clean(true)
|
||||
}
|
||||
var percentage = Math.floor(this.loadedAssets * 100 / (this.promises.length + this.afterJSCount))
|
||||
this.errorTxt.element[this.errorTxt.method] = "```\n" + this.errorMessages.join("\n") + "\nPercentage: " + percentage + "%\n```"
|
||||
if(rethrow || error){
|
||||
console.error(rethrow || error)
|
||||
}
|
||||
return Promise.reject()
|
||||
}
|
||||
assetLoaded(){
|
||||
if(!this.error){
|
||||
this.loadedAssets++
|
||||
var percentage = Math.floor(this.loadedAssets * 100 / (this.promises.length + this.afterJSCount))
|
||||
this.loaderProgress.style.width = percentage + "%"
|
||||
this.loaderPercentage.firstChild.data = percentage + "%"
|
||||
}
|
||||
}
|
||||
changePage(name, patternBg){
|
||||
this.screen.innerHTML = assets.pages[name]
|
||||
this.screen.classList[patternBg ? "add" : "remove"]("pattern-bg")
|
||||
}
|
||||
cssRuleset(rulesets){
|
||||
var css = []
|
||||
for(var selector in rulesets){
|
||||
var declarationsObj = rulesets[selector]
|
||||
var declarations = []
|
||||
for(var property in declarationsObj){
|
||||
var value = declarationsObj[property]
|
||||
declarations.push("\t" + property + ": " + value + ";")
|
||||
}
|
||||
css.push(selector + "{\n" + declarations.join("\n") + "\n}")
|
||||
}
|
||||
return css.join("\n")
|
||||
}
|
||||
ajax(url, customRequest, customResponse){
|
||||
var request = new XMLHttpRequest()
|
||||
request.open("GET", url)
|
||||
var promise = pageEvents.load(request)
|
||||
if(!customResponse){
|
||||
promise = promise.then(() => {
|
||||
if(request.status === 200){
|
||||
return request.response
|
||||
}else{
|
||||
return Promise.reject(`${url} (${request.status})`)
|
||||
}
|
||||
})
|
||||
}
|
||||
if(customRequest){
|
||||
customRequest(request)
|
||||
}
|
||||
request.send()
|
||||
return promise
|
||||
}
|
||||
loadScript(url){
|
||||
var script = document.createElement("script")
|
||||
var url = url + this.queryString
|
||||
var promise = pageEvents.load(script)
|
||||
script.src = url
|
||||
document.head.appendChild(script)
|
||||
return promise
|
||||
}
|
||||
getCsrfToken(){
|
||||
return this.ajax("api/csrftoken").then(response => {
|
||||
var json = JSON.parse(response)
|
||||
if(json.status === "ok"){
|
||||
return Promise.resolve(json.token)
|
||||
}else{
|
||||
return Promise.reject()
|
||||
}
|
||||
})
|
||||
}
|
||||
clean(error){
|
||||
delete this.loaderDiv
|
||||
delete this.loaderPercentage
|
||||
delete this.loaderProgress
|
||||
if(!error){
|
||||
delete this.promises
|
||||
delete this.errorText
|
||||
}
|
||||
pageEvents.remove(root, "touchstart")
|
||||
}
|
||||
}
|
||||
397
public/src/js/loadsong.js
Normal file
397
public/src/js/loadsong.js
Normal file
@@ -0,0 +1,397 @@
|
||||
class LoadSong{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(selectedSong, autoPlayEnabled, multiplayer, touchEnabled){
|
||||
this.selectedSong = selectedSong
|
||||
this.autoPlayEnabled = autoPlayEnabled
|
||||
this.multiplayer = multiplayer
|
||||
this.touchEnabled = touchEnabled
|
||||
var resolution = settings.getItem("resolution")
|
||||
this.imgScale = 1
|
||||
if(resolution === "medium"){
|
||||
this.imgScale = 0.75
|
||||
}else if(resolution === "low"){
|
||||
this.imgScale = 0.5
|
||||
}else if(resolution === "lowest"){
|
||||
this.imgScale = 0.25
|
||||
}
|
||||
|
||||
loader.changePage("loadsong", true)
|
||||
var loadingText = document.getElementById("loading-text")
|
||||
loadingText.appendChild(document.createTextNode(strings.loading))
|
||||
loadingText.setAttribute("alt", strings.loading)
|
||||
if(multiplayer){
|
||||
var cancel = document.getElementById("p2-cancel-button")
|
||||
cancel.appendChild(document.createTextNode(strings.cancel))
|
||||
cancel.setAttribute("alt", strings.cancel)
|
||||
}
|
||||
this.run()
|
||||
pageEvents.send("load-song", {
|
||||
selectedSong: selectedSong,
|
||||
autoPlayEnabled: autoPlayEnabled,
|
||||
multiplayer: multiplayer,
|
||||
touchEnabled: touchEnabled
|
||||
})
|
||||
}
|
||||
run(){
|
||||
var song = this.selectedSong
|
||||
var id = song.folder
|
||||
var songObj
|
||||
this.promises = []
|
||||
if(id !== "calibration"){
|
||||
assets.sounds["v_start"].play()
|
||||
assets.songs.forEach(song => {
|
||||
if(song.id === id){
|
||||
songObj = song
|
||||
}else{
|
||||
if(song.sound){
|
||||
song.sound.clean()
|
||||
delete song.sound
|
||||
}
|
||||
delete song.lyricsData
|
||||
}
|
||||
})
|
||||
}else{
|
||||
songObj = {
|
||||
music: "muted",
|
||||
custom: true
|
||||
}
|
||||
}
|
||||
this.songObj = songObj
|
||||
song.songBg = this.randInt(1, 5)
|
||||
song.songStage = this.randInt(1, 3)
|
||||
song.donBg = this.randInt(1, 6)
|
||||
if(this.songObj && this.songObj.category_id === 9){
|
||||
LoadSong.insertBackgroundVideo(this.songObj.id)
|
||||
}
|
||||
if(song.songSkin && song.songSkin.name){
|
||||
var imgLoad = []
|
||||
for(var type in song.songSkin){
|
||||
var value = song.songSkin[type]
|
||||
if(["song", "stage", "don"].indexOf(type) !== -1 && value && value !== "none"){
|
||||
var filename = "bg_" + type + "_" + song.songSkin.name
|
||||
if(value === "static"){
|
||||
imgLoad.push({
|
||||
filename: filename,
|
||||
type: type
|
||||
})
|
||||
}else{
|
||||
imgLoad.push({
|
||||
filename: filename + "_a",
|
||||
type: type
|
||||
})
|
||||
imgLoad.push({
|
||||
filename: filename + "_b",
|
||||
type: type
|
||||
})
|
||||
}
|
||||
if(type === "don"){
|
||||
song.donBg = null
|
||||
}else if(type === "song"){
|
||||
song.songBg = null
|
||||
}else if(type === "stage"){
|
||||
song.songStage = null
|
||||
}
|
||||
}
|
||||
}
|
||||
var skinBase = gameConfig.assets_baseurl + "song_skins/"
|
||||
for(var i = 0; i < imgLoad.length; i++){
|
||||
let filename = imgLoad[i].filename
|
||||
let prefix = song.songSkin.prefix || ""
|
||||
if((prefix + filename) in assets.image){
|
||||
continue
|
||||
}
|
||||
let img = document.createElement("img")
|
||||
let force = imgLoad[i].type === "song" && this.touchEnabled
|
||||
if(!songObj.custom){
|
||||
img.crossOrigin = "anonymous"
|
||||
}
|
||||
let promise = pageEvents.load(img)
|
||||
this.addPromise(promise.then(() => {
|
||||
return this.scaleImg(img, filename, prefix, force)
|
||||
}), songObj.custom ? filename + ".png" : skinBase + filename + ".png")
|
||||
if(songObj.custom){
|
||||
this.addPromise(song.songSkin[filename + ".png"].blob().then(blob => {
|
||||
img.src = URL.createObjectURL(blob)
|
||||
}), song.songSkin[filename + ".png"].url)
|
||||
}else{
|
||||
img.src = skinBase + filename + ".png"
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loadSongBg(id)
|
||||
|
||||
if(songObj.sound && songObj.sound.buffer){
|
||||
songObj.sound.gain = snd.musicGain
|
||||
}else if(songObj.music !== "muted"){
|
||||
this.addPromise(snd.musicGain.load(songObj.music).then(sound => {
|
||||
songObj.sound = sound
|
||||
}), songObj.music.url)
|
||||
}
|
||||
var chart = songObj.chart
|
||||
if(chart && chart.separateDiff){
|
||||
var chartDiff = this.selectedSong.difficulty
|
||||
chart = chart[chartDiff]
|
||||
}
|
||||
if(chart){
|
||||
this.addPromise(chart.read(song.type === "tja" ? "utf-8" : "").then(data => {
|
||||
this.songData = data.replace(/\0/g, "").split("\n")
|
||||
}), chart.url)
|
||||
}else{
|
||||
this.songData = ""
|
||||
}
|
||||
if(songObj.lyricsFile && !songObj.lyricsData && !this.multiplayer && (!this.touchEnabled || this.autoPlayEnabled) && settings.getItem("showLyrics")){
|
||||
this.addPromise(songObj.lyricsFile.read().then(data => {
|
||||
songObj.lyricsData = data
|
||||
}, () => {}), songObj.lyricsFile.url)
|
||||
}
|
||||
if(this.touchEnabled && !assets.image["touch_drum"]){
|
||||
let img = document.createElement("img")
|
||||
img.crossOrigin = "anonymous"
|
||||
var url = gameConfig.assets_baseurl + "img/touch_drum.png"
|
||||
this.addPromise(pageEvents.load(img).then(() => {
|
||||
return this.scaleImg(img, "touch_drum", "")
|
||||
}), url)
|
||||
img.src = url
|
||||
}
|
||||
var resultsImg = [
|
||||
"results_flowers",
|
||||
"results_mikoshi",
|
||||
"results_tetsuohana",
|
||||
"results_tetsuohana2"
|
||||
]
|
||||
resultsImg.forEach(id => {
|
||||
if(!assets.image[id]){
|
||||
var img = document.createElement("img")
|
||||
img.crossOrigin = "anonymous"
|
||||
var url = gameConfig.assets_baseurl + "img/" + id + ".png"
|
||||
this.addPromise(pageEvents.load(img).then(() => {
|
||||
return this.scaleImg(img, id, "")
|
||||
}), url)
|
||||
img.src = url
|
||||
}
|
||||
})
|
||||
if(songObj.volume && songObj.volume !== 1){
|
||||
this.promises.push(new Promise(resolve => setTimeout(resolve, 500)))
|
||||
}
|
||||
Promise.all(this.promises).then(() => {
|
||||
if(!this.error){
|
||||
this.setupMultiplayer()
|
||||
}
|
||||
})
|
||||
}
|
||||
addPromise(promise, url){
|
||||
this.promises.push(promise.catch(response => {
|
||||
this.errorMsg(response, url)
|
||||
return Promise.resolve()
|
||||
}))
|
||||
}
|
||||
errorMsg(error, url){
|
||||
if(!this.error){
|
||||
if(url){
|
||||
error = (Array.isArray(error) ? error[0] + ": " : (error ? error + ": " : "")) + url
|
||||
}
|
||||
pageEvents.send("load-song-error", error)
|
||||
errorMessage(new Error(error).stack)
|
||||
var title = this.selectedSong.title
|
||||
if(title !== this.selectedSong.originalTitle){
|
||||
title += " (" + this.selectedSong.originalTitle + ")"
|
||||
}
|
||||
assets.sounds["v_start"].stop()
|
||||
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)
|
||||
}
|
||||
}
|
||||
if(this.selectedSong.songStage !== null){
|
||||
filenames.push("bg_stage_" + this.selectedSong.songStage)
|
||||
}
|
||||
for(var i = 0; i < filenames.length; i++){
|
||||
var filename = filenames[i]
|
||||
var stage = filename.startsWith("bg_stage_")
|
||||
for(var letter = 0; letter < (stage ? 1 : 2); letter++){
|
||||
let filenameAb = filenames[i] + (stage ? "" : (letter === 0 ? "a" : "b"))
|
||||
if(!(filenameAb in assets.image)){
|
||||
let img = document.createElement("img")
|
||||
let force = filenameAb.startsWith("bg_song_") && this.touchEnabled
|
||||
img.crossOrigin = "anonymous"
|
||||
var url = gameConfig.assets_baseurl + "img/" + filenameAb + ".png"
|
||||
this.addPromise(pageEvents.load(img).then(() => {
|
||||
return this.scaleImg(img, filenameAb, "", force)
|
||||
}), url)
|
||||
img.src = url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scaleImg(img, filename, prefix, force){
|
||||
return new Promise((resolve, reject) => {
|
||||
var scale = this.imgScale
|
||||
if(force && scale > 0.5){
|
||||
scale = 0.5
|
||||
}
|
||||
var canvas = document.createElement("canvas")
|
||||
var w = Math.floor(img.width * scale)
|
||||
var h = Math.floor(img.height * scale)
|
||||
canvas.width = Math.max(1, w)
|
||||
canvas.height = Math.max(1, h)
|
||||
var ctx = canvas.getContext("2d")
|
||||
ctx.drawImage(img, 0, 0, w, h)
|
||||
var saveScaled = url => {
|
||||
let img2 = document.createElement("img")
|
||||
pageEvents.load(img2).then(() => {
|
||||
assets.image[prefix + filename] = img2
|
||||
loader.assetsDiv.appendChild(img2)
|
||||
resolve()
|
||||
}, reject)
|
||||
img2.id = prefix + filename
|
||||
img2.src = url
|
||||
}
|
||||
if("toBlob" in canvas){
|
||||
canvas.toBlob(blob => {
|
||||
saveScaled(URL.createObjectURL(blob))
|
||||
})
|
||||
}else{
|
||||
saveScaled(canvas.toDataURL())
|
||||
}
|
||||
})
|
||||
}
|
||||
randInt(min, max){
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
setupMultiplayer(){
|
||||
var song = this.selectedSong
|
||||
|
||||
if(this.multiplayer){
|
||||
var loadingText = document.getElementsByClassName("loading-text")[0]
|
||||
loadingText.firstChild.data = strings.waitingForP2
|
||||
loadingText.setAttribute("alt", strings.waitingForP2)
|
||||
|
||||
this.cancelButton = document.getElementById("p2-cancel-button")
|
||||
this.cancelButton.style.display = "inline-block"
|
||||
pageEvents.add(this.cancelButton, ["mousedown", "touchstart"], this.cancelLoad.bind(this))
|
||||
|
||||
this.song2Data = this.songData
|
||||
this.selectedSong2 = song
|
||||
pageEvents.add(p2, "message", event => {
|
||||
if(event.type === "gameload"){
|
||||
this.cancelButton.style.display = ""
|
||||
|
||||
if(event.value.diff === song.difficulty){
|
||||
this.startMultiplayer()
|
||||
}else{
|
||||
this.selectedSong2 = {}
|
||||
for(var i in this.selectedSong){
|
||||
this.selectedSong2[i] = this.selectedSong[i]
|
||||
}
|
||||
this.selectedSong2.difficulty = event.value.diff
|
||||
var chart = this.songObj.chart
|
||||
var chartDiff = this.selectedSong2.difficulty
|
||||
if(song.type === "tja" || !chart || !chart.separateDiff || !chart[chartDiff]){
|
||||
this.startMultiplayer()
|
||||
}else{
|
||||
chart[chartDiff].read(song.type === "tja" ? "utf-8" : "").then(data => {
|
||||
this.song2Data = data.replace(/\0/g, "").split("\n")
|
||||
}, () => {}).then(() => {
|
||||
this.startMultiplayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}else if(event.type === "gamestart"){
|
||||
this.clean()
|
||||
p2.clearMessage("songsel")
|
||||
var taikoGame1 = new Controller(song, this.songData, false, 1, this.touchEnabled)
|
||||
var taikoGame2 = new Controller(this.selectedSong2, this.song2Data, true, 2, this.touchEnabled)
|
||||
taikoGame1.run(taikoGame2)
|
||||
pageEvents.send("load-song-player2", this.selectedSong2)
|
||||
}else if(event.type === "left" || event.type === "gameend"){
|
||||
this.clean()
|
||||
new SongSelect(false, false, this.touchEnabled)
|
||||
}
|
||||
})
|
||||
p2.send("join", {
|
||||
id: song.folder,
|
||||
diff: song.difficulty,
|
||||
name: account.loggedIn ? account.displayName : null,
|
||||
don: account.loggedIn ? account.don : null
|
||||
})
|
||||
}else{
|
||||
this.clean()
|
||||
var taikoGame = new Controller(song, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
|
||||
taikoGame.run()
|
||||
}
|
||||
}
|
||||
startMultiplayer(repeat){
|
||||
if(document.hasFocus()){
|
||||
p2.send("gamestart")
|
||||
}else{
|
||||
if(!repeat){
|
||||
assets.sounds["v_sanka"].play()
|
||||
pageEvents.send("load-song-unfocused")
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.startMultiplayer(true)
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
cancelLoad(event){
|
||||
if(event.type === "mousedown"){
|
||||
if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}else{
|
||||
event.preventDefault()
|
||||
}
|
||||
p2.send("leave")
|
||||
assets.sounds["se_don"].play()
|
||||
this.cancelButton.style.pointerEvents = "none"
|
||||
pageEvents.send("load-song-cancel")
|
||||
}
|
||||
clean(){
|
||||
delete this.promises
|
||||
delete this.songObj
|
||||
delete this.videoElement
|
||||
pageEvents.remove(p2, "message")
|
||||
if(this.cancelButton){
|
||||
pageEvents.remove(this.cancelButton, ["mousedown", "touchstart"])
|
||||
delete this.cancelButton
|
||||
}
|
||||
}
|
||||
|
||||
static insertBackgroundVideo(songId) {
|
||||
const video = document.createElement("video");
|
||||
video.src = `songs/${songId}/main.mp4`;
|
||||
video.autoplay = true;
|
||||
video.muted = true; // 可选:静音
|
||||
video.style.objectFit = 'cover';
|
||||
video.style.position = 'fixed';
|
||||
video.style.top = "0";
|
||||
video.style.left = "0";
|
||||
video.style.zIndex = "0"; // 背景视频
|
||||
video.style.width = "100vw";
|
||||
video.style.height = "100vh";
|
||||
document.body.appendChild(video);
|
||||
window.videoElement = video;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
193
public/src/js/logo.js
Normal file
193
public/src/js/logo.js
Normal file
@@ -0,0 +1,193 @@
|
||||
class Logo{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.canvas = document.getElementById("logo")
|
||||
this.ctx = this.canvas.getContext("2d")
|
||||
this.pathSvg = failedTests.indexOf("Path2D SVG") === -1 && vectors.logo1
|
||||
this.symbolFont = "TnT, Meiryo, sans-serif"
|
||||
this.symbols = [{
|
||||
x: 315, y: 18, xAlt: 15, scale: true, text: "ブ",
|
||||
path: new Path2D(vectors.logo5)
|
||||
}, {
|
||||
x: 267, y: 50, yAlt: -34, scale: true, text: "ェ",
|
||||
path: new Path2D(vectors.logo4)
|
||||
}, {
|
||||
x: 197, y: 7, xAlt: 15, scale: true, text: "ウ",
|
||||
path: new Path2D(vectors.logo3)
|
||||
}, {
|
||||
x: 87, y: 7, xAlt: 15, text: "鼓",
|
||||
path: new Path2D(vectors.logo2),
|
||||
shadow: new Path2D(vectors.logo2Shadow)
|
||||
}, {
|
||||
x: 22, y: 16, xAlt: 10, scaleAlt: true, text: "太",
|
||||
path: new Path2D(vectors.logo1)
|
||||
}]
|
||||
pageEvents.add(window, "resize", this.update.bind(this))
|
||||
}
|
||||
updateSubtitle(){
|
||||
this.subtitleGradient = ["#df600d", "#d8446f", "#b2147b", "#428ac2", "#1f9099"]
|
||||
this.subtitle = []
|
||||
this.subtitleW = 0
|
||||
var index = 0
|
||||
var latinLowercase = /[a-z]/
|
||||
for(var i = 0; i < strings.taikoWeb.length; i++){
|
||||
var letter = strings.taikoWeb[i]
|
||||
var width = 57
|
||||
if(letter === "ェ"){
|
||||
width = 40
|
||||
}else if(letter === " "){
|
||||
width = 20
|
||||
}else if(letter === "i"){
|
||||
width = 22
|
||||
}else if(letter === "T"){
|
||||
width = 30
|
||||
}else if(latinLowercase.test(letter)){
|
||||
width = 38
|
||||
}
|
||||
this.subtitle.push({
|
||||
letter: letter,
|
||||
x: this.subtitleW + width / 2,
|
||||
index: letter === " " ? index : index++
|
||||
})
|
||||
this.subtitleW += width
|
||||
}
|
||||
this.update()
|
||||
}
|
||||
update(){
|
||||
var ctx = this.ctx
|
||||
ctx.save()
|
||||
|
||||
this.width = 1170
|
||||
this.height = 390
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
var winW = this.canvas.offsetWidth * pixelRatio
|
||||
var winH = this.canvas.offsetHeight * pixelRatio
|
||||
this.canvas.width = Math.max(1, winW)
|
||||
this.canvas.height = Math.max(1, winH)
|
||||
ctx.scale(winW / this.width, winH / this.height)
|
||||
|
||||
ctx.lineJoin = "round"
|
||||
ctx.miterLimit = 1
|
||||
ctx.textBaseline = "top"
|
||||
ctx.textAlign = "center"
|
||||
if(!this.pathSvg){
|
||||
ctx.font = "100px " + this.symbolFont
|
||||
}
|
||||
|
||||
for(var i = 0; i < this.symbols.length; i++){
|
||||
ctx.strokeStyle = "#3f0406"
|
||||
ctx.lineWidth = 13.5
|
||||
this.drawSymbol(this.symbols[i], "stroke", 4)
|
||||
}
|
||||
ctx.font = this.bold(strings.font) + "55px " + strings.font
|
||||
this.subtitleIterate((letter, x) => {
|
||||
ctx.lineWidth = strings.id === "en" ? 19 : 18.5
|
||||
ctx.strokeStyle = "#3f0406"
|
||||
ctx.strokeText(letter, x, 315)
|
||||
})
|
||||
if(this.pathSvg){
|
||||
ctx.fillStyle = "#3f0406"
|
||||
ctx.fillRect(400, 180, 30, 50)
|
||||
}else{
|
||||
ctx.font = "100px " + this.symbolFont
|
||||
}
|
||||
for(var i = 0; i < this.symbols.length; i++){
|
||||
var symbol = this.symbols[i]
|
||||
ctx.strokeStyle = "#7c361e"
|
||||
ctx.lineWidth = 13.5
|
||||
this.drawSymbol(symbol, "stroke")
|
||||
ctx.strokeStyle = "#fff"
|
||||
ctx.lineWidth = 7.5
|
||||
this.drawSymbol(symbol, "stroke")
|
||||
if(this.pathSvg){
|
||||
var grd = ctx.createLinearGradient(0, 55 - symbol.y, 0, 95 - symbol.y)
|
||||
grd.addColorStop(0, "#a41f1e")
|
||||
grd.addColorStop(1, "#a86a29")
|
||||
ctx.fillStyle = grd
|
||||
this.drawSymbol(symbol, "fill")
|
||||
ctx.save()
|
||||
ctx.scale(symbol.scale ? 2.8 : 3.2, 3.2)
|
||||
ctx.translate(symbol.x, symbol.y)
|
||||
ctx.clip(symbol.path)
|
||||
}
|
||||
grd = ctx.createLinearGradient(0, 55 - symbol.y, 0, 95 - symbol.y)
|
||||
grd.addColorStop(0, "#d80e11")
|
||||
grd.addColorStop(1, "#e08f19")
|
||||
ctx.fillStyle = grd
|
||||
if(this.pathSvg){
|
||||
ctx.translate(3, 2)
|
||||
ctx.fill(symbol.shadow || symbol.path)
|
||||
ctx.restore()
|
||||
}else{
|
||||
this.drawSymbol(symbol, "fill")
|
||||
}
|
||||
}
|
||||
if(this.pathSvg){
|
||||
ctx.fillStyle = "#fff"
|
||||
ctx.fillRect(382, 85, 30, 15)
|
||||
ctx.fillRect(402, 145, 15, 15)
|
||||
}else{
|
||||
ctx.font = this.bold(strings.font) + "55px " + strings.font
|
||||
}
|
||||
|
||||
this.subtitleIterate((letter, x) => {
|
||||
ctx.lineWidth = strings.id === "en" ? 19 : 18.5
|
||||
ctx.strokeStyle = "#7c361e"
|
||||
ctx.strokeText(letter, x, 305)
|
||||
})
|
||||
this.subtitleIterate((letter, x, i) => {
|
||||
ctx.lineWidth = strings.id === "en" ? 11 : 9.5
|
||||
ctx.strokeStyle = this.getSubtitleGradient(i)
|
||||
ctx.fillStyle = "#fff"
|
||||
ctx.strokeText(letter, x, 305)
|
||||
ctx.fillText(letter, x, 305)
|
||||
})
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
drawSymbol(symbol, action, y){
|
||||
var ctx = this.ctx
|
||||
ctx.save()
|
||||
ctx.scale((symbol.scale || !this.pathSvg && symbol.scaleAlt) ? 2.8 : 3.2, 3.2)
|
||||
ctx.translate(symbol.x, symbol.y + (y || 0))
|
||||
if(this.pathSvg){
|
||||
ctx[action](symbol.path)
|
||||
}else{
|
||||
ctx[action + "Text"](symbol.text, 30 + (symbol.xAlt || 0), -4 + (symbol.yAlt || 0))
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
subtitleIterate(func){
|
||||
for(var i = this.subtitle.length; i--;){
|
||||
var subtitleObj = this.subtitle[i]
|
||||
var x = (this.width - this.subtitleW) / 2 + subtitleObj.x
|
||||
func(subtitleObj.letter, x, subtitleObj.index)
|
||||
}
|
||||
}
|
||||
getSubtitleGradient(index){
|
||||
var sign = 1
|
||||
var position = 0
|
||||
var length = this.subtitleGradient.length - 1
|
||||
while(index >= 0){
|
||||
if(sign === 1){
|
||||
position = index % length
|
||||
}else{
|
||||
position = length - (index % length)
|
||||
}
|
||||
sign *= -1
|
||||
index -= length
|
||||
}
|
||||
return this.subtitleGradient[position]
|
||||
}
|
||||
bold(font){
|
||||
return font === "Microsoft YaHei, sans-serif" ? "bold " : ""
|
||||
}
|
||||
clean(){
|
||||
pageEvents.remove(window, "resize")
|
||||
delete this.symbols
|
||||
delete this.ctx
|
||||
delete this.canvas
|
||||
}
|
||||
}
|
||||
236
public/src/js/lyrics.js
Normal file
236
public/src/js/lyrics.js
Normal file
@@ -0,0 +1,236 @@
|
||||
class Lyrics{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(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.preferEn && lang === "en" || 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.branch && this.branch && currentLine.branch !== this.branch)){
|
||||
currentLine = this.lines[++this.current]
|
||||
}
|
||||
if(this.shown !== this.current){
|
||||
if(currentLine && ms >= currentLine.start){
|
||||
if(!currentLine.copy){
|
||||
this.setText(currentLine.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
|
||||
}
|
||||
}
|
||||
144
public/src/js/main.js
Normal file
144
public/src/js/main.js
Normal file
@@ -0,0 +1,144 @@
|
||||
addEventListener("error", function(err){
|
||||
var stack
|
||||
if("error" in err && err.error){
|
||||
stack = err.error.stack
|
||||
}else{
|
||||
stack = err.message + "\n at " + err.filename + ":" + err.lineno + ":" + err.colno
|
||||
}
|
||||
errorMessage(stack)
|
||||
})
|
||||
|
||||
function errorMessage(stack){
|
||||
localStorage["lastError"] = JSON.stringify({
|
||||
timestamp: Date.now(),
|
||||
stack: stack
|
||||
})
|
||||
}
|
||||
|
||||
function toggleFullscreen(){
|
||||
if("requestFullscreen" in root){
|
||||
if(document.fullscreenElement){
|
||||
document.exitFullscreen()
|
||||
}else{
|
||||
root.requestFullscreen()
|
||||
}
|
||||
}else if("webkitRequestFullscreen" in root){
|
||||
if(document.webkitFullscreenElement){
|
||||
document.webkitExitFullscreen()
|
||||
}else{
|
||||
root.webkitRequestFullscreen()
|
||||
}
|
||||
}else if("mozRequestFullScreen" in root){
|
||||
if(document.mozFullScreenElement){
|
||||
document.mozCancelFullScreen()
|
||||
}else{
|
||||
root.mozRequestFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resizeRoot(){
|
||||
if((noResizeRoot ? lastWidth !== innerWidth : true) && lastHeight !== innerHeight){
|
||||
lastWidth = innerWidth
|
||||
lastHeight = innerHeight
|
||||
root.style.height = innerHeight + "px"
|
||||
}
|
||||
}
|
||||
|
||||
function debug(){
|
||||
if(debugObj.state === "open"){
|
||||
debugObj.debug.clean()
|
||||
return "Debug closed"
|
||||
}else if(debugObj.state === "minimised"){
|
||||
debugObj.debug.restore()
|
||||
return "Debug restored"
|
||||
}else{
|
||||
debugObj.debug = new Debug()
|
||||
return "Debug opened"
|
||||
}
|
||||
}
|
||||
|
||||
var root = document.documentElement
|
||||
|
||||
if(/iPhone|iPad/.test(navigator.userAgent)){
|
||||
var fullScreenSupported = false
|
||||
}else{
|
||||
var fullScreenSupported = "requestFullscreen" in root || "webkitRequestFullscreen" in root || "mozRequestFullScreen" in root
|
||||
}
|
||||
|
||||
var pageEvents = new PageEvents()
|
||||
var snd = {}
|
||||
var p2
|
||||
var disableBlur = false
|
||||
var cancelTouch = true
|
||||
var lastWidth
|
||||
var lastHeight
|
||||
var debugObj = {
|
||||
state: "closed",
|
||||
debug: null
|
||||
}
|
||||
var perf = {
|
||||
blur: 0,
|
||||
allImg: 0,
|
||||
load: 0
|
||||
}
|
||||
var defaultDon = {
|
||||
body_fill: "#5fb7c1",
|
||||
face_fill: "#ff5724"
|
||||
}
|
||||
var strings
|
||||
var vectors
|
||||
var settings
|
||||
var scoreStorage
|
||||
var account = {}
|
||||
var gpicker
|
||||
var db
|
||||
var plugins
|
||||
var noResizeRoot = false
|
||||
var kanaPairs = [["っきゃ","ッキャ"],["っきゅ","ッキュ"],["っきょ","ッキョ"],["っしゃ","ッシャ"],["っしゅ","ッシュ"],["っしょ","ッショ"],["っちゃ","ッチャ"],["っちゅ","ッチュ"],["っちょ","ッチョ"],["っひゃ","ッヒャ"],["っひゅ","ッヒュ"],["っひょ","ッヒョ"],["っみゃ","ッミャ"],["っみゅ","ッミュ"],["っみょ","ッミョ"],["っりゃ","ッリャ"],["っりゅ","ッリュ"],["っりょ","ッリョ"],["っぎゃ","ッギャ"],["っぎゅ","ッギュ"],["っぎょ","ッギョ"],["っじゃ","ッジャ"],["っじゅ","ッジュ"],["っじょ","ッジョ"],["っびゃ","ッビャ"],["っびゅ","ッビュ"],["っびょ","ッビョ"],["っぴゃ","ッピャ"],["っぴゅ","ッピュ"],["っぴょ","ッピョ"],["っいぇ","ッイェ"],["っわぃ","ッウィ"],["っわぇ","ッウェ"],
|
||||
["っわぉ","ッウォ"],["っゔぁ","ッヴァ"],["っゔぃ","ッヴィ"],["っゔぇ","ッヴェ"],["っゔぉ","ッヴォ"],["っすぃ","ッスィ"],["っずぃ","ッズィ"],["っしぇ","ッシェ"],["っじぇ","ッジェ"],["っとぃ","ッティ"],["っとぅ","ットゥ"],["っでぅ","ッディ"],["っどぅ","ッドゥ"],["っつぁ","ッツァ"],["っつぃ","ッツィ"],["っつぇ","ッツェ"],["っつぉ","ッツォ"],["っふぁ","ッファ"],["っふぃ","ッフィ"],["っふぇ","ッフェ"],["っふぉ","ッフォ"],["っふゅ","ッフュ"],["っひぇ","ッヒェ"],["きゃ","キャ"],["きゅ","キュ"],["きょ","キョ"],["しゃ","シャ"],["しゅ","シュ"],["しょ","ショ"],["ちゃ","チャ"],["ちゅ","チュ"],["ちょ","チョ"],["にゃ","ニャ"],["にゅ","ニュ"],["にょ","ニョ"],["ひゃ","ヒャ"],
|
||||
["ひゅ","ヒュ"],["ひょ","ヒョ"],["みゃ","ミャ"],["みゅ","ミュ"],["みょ","ミョ"],["りゃ","リャ"],["りゅ","リュ"],["りょ","リョ"],["ぎゃ","ギャ"],["ぎゅ","ギュ"],["ぎょ","ギョ"],["じゃ","ジャ"],["じゅ","ジュ"],["じょ","ジョ"],["びゃ","ビャ"],["びゅ","ビュ"],["びょ","ビョ"],["ぴゃ","ピャ"],["ぴゅ","ピュ"],["ぴょ","ピョ"],["いぇ","イェ"],["わぃ","ウィ"],["わぇ","ウェ"],["わぉ","ウォ"],["ゔぁ","ヴァ"],["ゔぃ","ヴィ"],["ゔぇ","ヴェ"],["ゔぉ","ヴォ"],["すぃ","スィ"],["ずぃ","ズィ"],["しぇ","シェ"],["じぇ","ジェ"],["とぃ","ティ"],["とぅ","トゥ"],["でぅ","ディ"],["どぅ","ドゥ"],["つぁ","ツァ"],["つぃ","ツィ"],["つぇ","ツェ"],["つぉ","ツォ"],["ふぁ","ファ"],
|
||||
["ふぃ","フィ"],["ふぇ","フェ"],["ふぉ","フォ"],["ふゅ","フュ"],["ひぇ","ヒェ"],["っか","ッカ"],["っき","ッキ"],["っく","ック"],["っけ","ッケ"],["っこ","ッコ"],["っさ","ッサ"],["っし","ッシ"],["っす","ッス"],["っせ","ッセ"],["っそ","ッソ"],["った","ッタ"],["っち","ッチ"],["っつ","ッツ"],["って","ッテ"],["っと","ット"],["っは","ッハ"],["っひ","ッヒ"],["っふ","ッフ"],["っへ","ッヘ"],["っほ","ッホ"],["っま","ッマ"],["っみ","ッミ"],["っむ","ッム"],["っめ","ッメ"],["っも","ッモ"],["っや","ッヤ"],["っゆ","ッユ"],["っよ","ッヨ"],["っら","ッラ"],["っり","ッリ"],["っる","ッル"],["っれ","ッレ"],["っろ","ッロ"],["っわ","ッワ"],["っゐ","ッヰ"],
|
||||
["っゑ","ッヱ"],["っを","ッヲ"],["っが","ッガ"],["っぎ","ッギ"],["っぐ","ッグ"],["っげ","ッゲ"],["っご","ッゴ"],["っざ","ッザ"],["っじ","ッジ"],["っず","ッズ"],["っぜ","ッゼ"],["っぞ","ッゾ"],["っだ","ッダ"],["っぢ","ッヂ"],["っづ","ッヅ"],["っで","ッデ"],["っど","ッド"],["っば","ッバ"],["っび","ッビ"],["っぶ","ッブ"],["っべ","ッベ"],["っぼ","ッボ"],["っぱ","ッパ"],["っぴ","ッパ"],["っぷ","ップ"],["っぺ","ッペ"],["っぽ","ッポ"],["っゔ","ッヴ"],["あ","ア"],["い","イ"],["う","ウ"],["え","エ"],["お","オ"],["か","カ"],["き","キ"],["く","ク"],["け","ケ"],["こ","コ"],["さ","サ"],["し","シ"],["す","ス"],["せ","セ"],["そ","ソ"],["た","タ"],
|
||||
["ち","チ"],["つ","ツ"],["て","テ"],["と","ト"],["な","ナ"],["に","ニ"],["ぬ","ヌ"],["ね","ネ"],["の","ノ"],["は","ハ"],["ひ","ヒ"],["ふ","フ"],["へ","ヘ"],["ほ","ホ"],["ま","マ"],["み","ミ"],["む","ム"],["め","メ"],["も","モ"],["や","ヤ"],["ゆ","ユ"],["よ","ヨ"],["ら","ラ"],["り","リ"],["る","ル"],["れ","レ"],["ろ","ロ"],["わ","ワ"],["ゐ","ヰ"],["ゑ","ヱ"],["を","ヲ"],["ん","ン"],["が","ガ"],["ぎ","ギ"],["ぐ","グ"],["げ","ゲ"],["ご","ゴ"],["ざ","ザ"],["じ","ジ"],["ず","ズ"],["ぜ","ゼ"],["ぞ","ゾ"],["だ","ダ"],["ぢ","ヂ"],["づ","ヅ"],["で","デ"],["ど","ド"],
|
||||
["ば","バ"],["び","ビ"],["ぶ","ブ"],["べ","ベ"],["ぼ","ボ"],["ぱ","パ"],["ぴ","パ"],["ぷ","プ"],["ぺ","ペ"],["ぽ","ポ"],["ゔ","ヴ"]]
|
||||
|
||||
pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
|
||||
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT" && (event.target.tagName !== "INPUT" || event.target.type !== "file")){
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
var versionDiv = document.getElementById("version")
|
||||
var versionLink = document.getElementById("version-link")
|
||||
versionLink.tabIndex = -1
|
||||
pageEvents.add(versionDiv, ["click", "touchend"], event => {
|
||||
if(event.target === versionDiv){
|
||||
versionLink.click()
|
||||
pageEvents.send("version-link")
|
||||
}
|
||||
})
|
||||
resizeRoot()
|
||||
setInterval(resizeRoot, 100)
|
||||
pageEvents.keyAdd(debugObj, "all", "down", event => {
|
||||
if((event.keyCode === 186 || event.keyCode === 59) && event.ctrlKey && (event.shiftKey || event.altKey)){
|
||||
// Semicolon
|
||||
if(debugObj.state === "open"){
|
||||
debugObj.debug.minimise()
|
||||
}else if(debugObj.state === "minimised"){
|
||||
debugObj.debug.restore()
|
||||
}else{
|
||||
try{
|
||||
debugObj.debug = new Debug()
|
||||
}catch(e){}
|
||||
}
|
||||
}
|
||||
if(event.keyCode === 82 && debugObj.debug && debugObj.controller){
|
||||
// R
|
||||
debugObj.controller.restartSong()
|
||||
}
|
||||
})
|
||||
|
||||
var loader = new Loader(songId => {
|
||||
new Titlescreen(songId)
|
||||
})
|
||||
|
||||
119
public/src/js/mekadon.js
Normal file
119
public/src/js/mekadon.js
Normal file
@@ -0,0 +1,119 @@
|
||||
class Mekadon{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(controller, game){
|
||||
this.controller = controller
|
||||
this.game = game
|
||||
this.lr = false
|
||||
this.lastHit = -Infinity
|
||||
this.delay = controller.audioLatency
|
||||
}
|
||||
play(circle){
|
||||
var type = circle.type
|
||||
if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.endTime){
|
||||
if(circle.section && circle.timesHit === 0){
|
||||
this.game.resetSection()
|
||||
}
|
||||
circle.played(-1, false)
|
||||
this.game.updateCurrentCircle()
|
||||
}
|
||||
type = circle.type
|
||||
if(type === "balloon"){
|
||||
return this.playDrumrollAt(circle, 0, 30)
|
||||
}else if(type === "drumroll" || type === "daiDrumroll"){
|
||||
return this.playDrumrollAt(circle, 0, 60)
|
||||
}else{
|
||||
return this.playAt(circle, 0, 450)
|
||||
}
|
||||
}
|
||||
playAt(circle, ms, score, dai, reverse){
|
||||
var currentMs = circle.ms - this.getMS() + this.delay
|
||||
if(ms > currentMs - 10){
|
||||
return this.playNow(circle, score, dai, reverse)
|
||||
}
|
||||
}
|
||||
playDrumrollAt(circle, ms, pace, kaAmount){
|
||||
if(pace && this.getMS() >= this.lastHit + pace){
|
||||
var score = 1
|
||||
if(kaAmount > 0){
|
||||
score = Math.random() > kaAmount ? 1 : 2
|
||||
}
|
||||
return this.playAt(circle, ms, score)
|
||||
}
|
||||
}
|
||||
miss(circle){
|
||||
var currentMs = circle.ms - this.getMS()
|
||||
if(0 >= currentMs - 10){
|
||||
this.controller.displayScore(0, true)
|
||||
this.game.updateCurrentCircle()
|
||||
this.game.updateCombo(0)
|
||||
this.game.updateGlobalScore(0, 1, circle.gogoTime)
|
||||
this.game.sectionNotes.push(0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
playNow(circle, score, dai, reverse){
|
||||
var type = circle.type
|
||||
var keyDai = false
|
||||
var playDai = !dai || dai === 2
|
||||
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
|
||||
|
||||
if(drumrollNotes){
|
||||
var ms = this.getMS()
|
||||
}else{
|
||||
var ms = circle.ms
|
||||
}
|
||||
|
||||
if(reverse){
|
||||
if(type === "don" || type === "daiDon"){
|
||||
type = "ka"
|
||||
}else if(type === "ka" || type === "daiKa"){
|
||||
type = "don"
|
||||
}
|
||||
}
|
||||
if(type === "daiDon" && playDai){
|
||||
this.setKey("don_l", ms)
|
||||
this.setKey("don_r", ms)
|
||||
this.lr = false
|
||||
keyDai = true
|
||||
}else if(type === "don" || type === "daiDon" || drumrollNotes && score !== 2){
|
||||
this.setKey(this.lr ? "don_l" : "don_r", ms)
|
||||
this.lr = !this.lr
|
||||
}else if(type === "daiKa" && playDai){
|
||||
this.setKey("ka_l", ms)
|
||||
this.setKey("ka_r", ms)
|
||||
this.lr = false
|
||||
keyDai = true
|
||||
}else if(type === "ka" || type === "daiKa" || drumrollNotes){
|
||||
this.setKey(this.lr ? "ka_l" : "ka_r", ms)
|
||||
this.lr = !this.lr
|
||||
}
|
||||
if(type === "balloon"){
|
||||
if(circle.requiredHits === 1){
|
||||
assets.sounds["se_balloon"].play()
|
||||
}
|
||||
this.game.checkBalloon(circle)
|
||||
}else if(type === "drumroll" || type === "daiDrumroll"){
|
||||
this.game.checkDrumroll(circle, score === 2)
|
||||
}else{
|
||||
this.controller.displayScore(score, false, keyDai)
|
||||
this.game.updateCombo(score)
|
||||
this.game.updateGlobalScore(score, keyDai ? 2 : 1, circle.gogoTime)
|
||||
this.game.updateCurrentCircle()
|
||||
circle.played(score, keyDai)
|
||||
if(circle.section){
|
||||
this.game.resetSection()
|
||||
}
|
||||
this.game.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0))
|
||||
}
|
||||
this.lastHit = ms
|
||||
return true
|
||||
}
|
||||
getMS(){
|
||||
return this.controller.getElapsedTime()
|
||||
}
|
||||
setKey(name, ms){
|
||||
this.controller.setKey(true, name, ms)
|
||||
}
|
||||
}
|
||||
268
public/src/js/p2.js
Normal file
268
public/src/js/p2.js
Normal file
@@ -0,0 +1,268 @@
|
||||
class P2Connection{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.closed = true
|
||||
this.lastMessages = {}
|
||||
this.otherConnected = false
|
||||
this.name = null
|
||||
this.player = 1
|
||||
this.allEvents = new Map()
|
||||
this.addEventListener("message", this.message.bind(this))
|
||||
this.currentHash = ""
|
||||
this.disabled = 0
|
||||
pageEvents.add(window, "hashchange", this.onhashchange.bind(this))
|
||||
}
|
||||
addEventListener(type, callback){
|
||||
var addedType = this.allEvents.get(type)
|
||||
if(!addedType){
|
||||
addedType = new Set()
|
||||
this.allEvents.set(type, addedType)
|
||||
}
|
||||
return addedType.add(callback)
|
||||
}
|
||||
removeEventListener(type, callback){
|
||||
var addedType = this.allEvents.get(type)
|
||||
if(addedType){
|
||||
return addedType.delete(callback)
|
||||
}
|
||||
}
|
||||
open(){
|
||||
if(this.closed && !this.disabled){
|
||||
this.closed = false
|
||||
var wsProtocol = location.protocol == "https:" ? "wss:" : "ws:"
|
||||
this.socket = new WebSocket(gameConfig.multiplayer_url ? gameConfig.multiplayer_url : wsProtocol + "//" + location.host + location.pathname + "p2")
|
||||
pageEvents.race(this.socket, "open", "close").then(response => {
|
||||
if(response.type === "open"){
|
||||
return this.openEvent()
|
||||
}
|
||||
return this.closeEvent()
|
||||
})
|
||||
pageEvents.add(this.socket, "message", this.messageEvent.bind(this))
|
||||
}
|
||||
}
|
||||
openEvent(){
|
||||
var addedType = this.allEvents.get("open")
|
||||
if(addedType){
|
||||
addedType.forEach(callback => callback())
|
||||
}
|
||||
}
|
||||
close(){
|
||||
if(!this.closed){
|
||||
this.closed = true
|
||||
if(this.socket){
|
||||
this.socket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
closeEvent(){
|
||||
this.removeEventListener(onmessage)
|
||||
this.otherConnected = false
|
||||
this.session = false
|
||||
if(this.hashLock){
|
||||
this.hash("")
|
||||
this.hashLock = false
|
||||
}
|
||||
if(!this.closed){
|
||||
setTimeout(() => {
|
||||
if(this.socket.readyState !== this.socket.OPEN){
|
||||
this.open()
|
||||
}
|
||||
}, 500)
|
||||
pageEvents.send("p2-disconnected")
|
||||
}
|
||||
var addedType = this.allEvents.get("close")
|
||||
if(addedType){
|
||||
addedType.forEach(callback => callback())
|
||||
}
|
||||
}
|
||||
send(type, value){
|
||||
if(this.socket.readyState === this.socket.OPEN){
|
||||
if(typeof value === "undefined"){
|
||||
this.socket.send(JSON.stringify({type: type}))
|
||||
}else{
|
||||
this.socket.send(JSON.stringify({type: type, value: value}))
|
||||
}
|
||||
}else{
|
||||
pageEvents.once(this, "open").then(() => {
|
||||
this.send(type, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
messageEvent(event){
|
||||
try{
|
||||
var response = JSON.parse(event.data)
|
||||
}catch(e){
|
||||
var response = {}
|
||||
}
|
||||
this.lastMessages[response.type] = response
|
||||
var addedType = this.allEvents.get("message")
|
||||
if(addedType){
|
||||
addedType.forEach(callback => callback(response))
|
||||
}
|
||||
}
|
||||
getMessage(type){
|
||||
if(type in this.lastMessages){
|
||||
return this.lastMessages[type]
|
||||
}
|
||||
}
|
||||
clearMessage(type){
|
||||
if(type in this.lastMessages){
|
||||
this.lastMessages[type] = null
|
||||
}
|
||||
}
|
||||
message(response){
|
||||
switch(response.type){
|
||||
case "gameload":
|
||||
if("player" in response.value){
|
||||
this.player = response.value.player === 2 ? 2 : 1
|
||||
}
|
||||
case "gamestart":
|
||||
this.otherConnected = true
|
||||
this.notes = []
|
||||
this.drumrollPace = 45
|
||||
this.dai = 2
|
||||
this.kaAmount = 0
|
||||
this.results = false
|
||||
this.branch = "normal"
|
||||
scoreStorage.clearP2()
|
||||
break
|
||||
case "gameend":
|
||||
this.otherConnected = false
|
||||
if(this.session){
|
||||
pageEvents.send("session-end")
|
||||
}else if(!this.results){
|
||||
pageEvents.send("p2-game-end")
|
||||
}
|
||||
this.session = false
|
||||
if(this.hashLock){
|
||||
this.hash("")
|
||||
this.hashLock = false
|
||||
}
|
||||
this.name = null
|
||||
this.don = null
|
||||
scoreStorage.clearP2()
|
||||
break
|
||||
case "gameresults":
|
||||
this.results = {}
|
||||
for(var i in response.value){
|
||||
this.results[i] = response.value[i] === null ? null : response.value[i].toString()
|
||||
}
|
||||
break
|
||||
case "note":
|
||||
this.notes.push(response.value)
|
||||
if(response.value.dai){
|
||||
this.dai = response.value.dai
|
||||
}
|
||||
break
|
||||
case "drumroll":
|
||||
this.drumrollPace = response.value.pace
|
||||
if("kaAmount" in response.value){
|
||||
this.kaAmount = response.value.kaAmount
|
||||
}
|
||||
break
|
||||
case "branch":
|
||||
this.branch = response.value
|
||||
this.branchSet = false
|
||||
break
|
||||
case "session":
|
||||
this.clearMessage("users")
|
||||
this.otherConnected = 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
|
||||
}
|
||||
}
|
||||
onhashchange(){
|
||||
if(this.hashLock){
|
||||
this.hash(this.currentHash)
|
||||
}else{
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
hash(string){
|
||||
this.currentHash = string
|
||||
history.replaceState("", "", location.pathname + (string ? "#" + string : ""))
|
||||
}
|
||||
play(circle, mekadon){
|
||||
if(this.otherConnected || this.notes.length > 0){
|
||||
var type = circle.type
|
||||
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
|
||||
|
||||
if(drumrollNotes && mekadon.getMS() > circle.endTime + mekadon.delay){
|
||||
circle.played(-1, false)
|
||||
mekadon.game.updateCurrentCircle()
|
||||
}
|
||||
|
||||
if(drumrollNotes){
|
||||
mekadon.playDrumrollAt(circle, 0, this.drumrollPace, type === "drumroll" || type === "daiDrumroll" ? this.kaAmount : 0)
|
||||
}else if(this.notes.length === 0){
|
||||
mekadon.play(circle)
|
||||
}else{
|
||||
var note = this.notes[0]
|
||||
if(note.score >= 0){
|
||||
var dai = 1
|
||||
if(circle.type === "daiDon" || circle.type === "daiKa"){
|
||||
dai = this.dai
|
||||
}
|
||||
if(mekadon.playAt(circle, note.ms, note.score, dai, note.reverse)){
|
||||
this.notes.shift()
|
||||
}
|
||||
}else{
|
||||
if(mekadon.miss(circle)){
|
||||
this.notes.shift()
|
||||
}
|
||||
}
|
||||
}
|
||||
}else if(mekadon.miss(circle)){
|
||||
this.notes.shift()
|
||||
}
|
||||
}
|
||||
enable(){
|
||||
this.disabled = Math.max(0, this.disabled - 1)
|
||||
setTimeout(this.open.bind(this), 100)
|
||||
}
|
||||
disable(){
|
||||
this.disabled++
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
182
public/src/js/pageevents.js
Normal file
182
public/src/js/pageevents.js
Normal file
@@ -0,0 +1,182 @@
|
||||
class PageEvents{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.allEvents = new Map()
|
||||
this.keyListeners = new Map()
|
||||
this.mouseListeners = new Map()
|
||||
this.blurListeners = new Map()
|
||||
this.lastKeyEvent = -Infinity
|
||||
this.add(window, "keydown", this.keyEvent.bind(this))
|
||||
this.add(window, "keyup", this.keyEvent.bind(this))
|
||||
this.add(window, "mousemove", this.mouseEvent.bind(this))
|
||||
this.add(window, "blur", this.blurEvent.bind(this))
|
||||
this.kbd = []
|
||||
}
|
||||
add(target, type, callback, symbol){
|
||||
if(Array.isArray(type)){
|
||||
type.forEach(type => this.add(target, type, callback, symbol))
|
||||
return
|
||||
}
|
||||
this.remove(target, type)
|
||||
var addedEvent = this.allEvents.get(symbol || target)
|
||||
if(!addedEvent){
|
||||
addedEvent = new Map()
|
||||
this.allEvents.set(symbol || target, addedEvent)
|
||||
}
|
||||
addedEvent.set(type, callback)
|
||||
return target.addEventListener(type, callback)
|
||||
}
|
||||
remove(target, type, symbol){
|
||||
if(Array.isArray(type)){
|
||||
type.forEach(type => this.remove(target, type, symbol))
|
||||
return
|
||||
}
|
||||
var addedEvent = this.allEvents.get(symbol || target)
|
||||
if(addedEvent){
|
||||
var callback = addedEvent.get(type)
|
||||
if(callback){
|
||||
target.removeEventListener(type, callback)
|
||||
addedEvent.delete(type)
|
||||
if(addedEvent.size == 0){
|
||||
return this.allEvents.delete(symbol || target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
once(target, type, symbol){
|
||||
return new Promise(resolve => {
|
||||
this.add(target, type, event => {
|
||||
this.remove(target, type)
|
||||
return resolve(event)
|
||||
}, symbol)
|
||||
})
|
||||
}
|
||||
race(){
|
||||
var symbols = []
|
||||
var target = arguments[0]
|
||||
return new Promise(resolve => {
|
||||
for(var i = 1;i < arguments.length; i++){
|
||||
symbols[i] = Symbol()
|
||||
let type = arguments[i]
|
||||
this.add(target, type, event => {
|
||||
resolve({
|
||||
type: type,
|
||||
event: event
|
||||
})
|
||||
}, symbols[i])
|
||||
}
|
||||
}).then(response => {
|
||||
for(var i = 1;i < arguments.length; i++){
|
||||
this.remove(target, arguments[i], symbols[i])
|
||||
}
|
||||
return response
|
||||
})
|
||||
}
|
||||
load(target){
|
||||
return new Promise((resolve, reject) => {
|
||||
this.race(target, "load", "error", "abort").then(response => {
|
||||
switch(response.type){
|
||||
case "load":
|
||||
return resolve(response.event)
|
||||
case "error":
|
||||
return reject(["Loading error", target])
|
||||
case "abort":
|
||||
return reject("Loading aborted")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
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){
|
||||
this.lastKeyEvent = Date.now()
|
||||
if(event.target.tagName !== "INPUT"){
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
this.keyListeners.forEach(addedKeyCode => {
|
||||
this.checkListener(addedKeyCode.get("all"), event)
|
||||
this.checkListener(addedKeyCode.get(event.keyCode), event)
|
||||
})
|
||||
}
|
||||
checkListener(keyObj, event){
|
||||
if(keyObj && (
|
||||
keyObj.type === "both"
|
||||
|| keyObj.type === "down" && event.type === "keydown"
|
||||
|| keyObj.type === "up" && event.type === "up"
|
||||
)){
|
||||
keyObj.callback(event)
|
||||
}
|
||||
}
|
||||
keyAdd(target, keyCode, type, callback){
|
||||
// keyCode="all", type="both"
|
||||
var addedKeyCode = this.keyListeners.get(target)
|
||||
if(!addedKeyCode){
|
||||
addedKeyCode = new Map()
|
||||
this.keyListeners.set(target, addedKeyCode)
|
||||
}
|
||||
addedKeyCode.set(keyCode, {
|
||||
type: type,
|
||||
callback: callback
|
||||
})
|
||||
}
|
||||
keyRemove(target, keyCode){
|
||||
var addedKeyCode = this.keyListeners.get(target)
|
||||
if(addedKeyCode){
|
||||
var keyObj = addedKeyCode.get(keyCode)
|
||||
if(keyObj){
|
||||
addedKeyCode.delete(keyCode)
|
||||
if(addedKeyCode.size == 0){
|
||||
return this.keyListeners.delete(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keyOnce(target, keyCode, type){
|
||||
return new Promise(resolve => {
|
||||
this.keyAdd(target, keyCode, type, event => {
|
||||
this.keyRemove(target, keyCode)
|
||||
return resolve(event)
|
||||
})
|
||||
})
|
||||
}
|
||||
mouseEvent(event){
|
||||
this.lastMouse = event
|
||||
this.mouseListeners.forEach(callback => callback(event))
|
||||
}
|
||||
mouseAdd(target, callback){
|
||||
this.mouseListeners.set(target, callback)
|
||||
}
|
||||
mouseRemove(target){
|
||||
this.mouseListeners.delete(target)
|
||||
}
|
||||
blurEvent(event){
|
||||
this.blurListeners.forEach(callback => callback(event))
|
||||
}
|
||||
blurAdd(target, callback){
|
||||
this.blurListeners.set(target, callback)
|
||||
}
|
||||
blurRemove(target){
|
||||
this.blurListeners.delete(target)
|
||||
}
|
||||
getMouse(){
|
||||
return this.lastMouse
|
||||
}
|
||||
send(name, detail){
|
||||
dispatchEvent(new CustomEvent(name, {detail: detail}))
|
||||
}
|
||||
setKbd(){
|
||||
this.kbd = []
|
||||
var kbdSettings = settings.getItem("keyboardSettings")
|
||||
for(var name in kbdSettings){
|
||||
var keys = kbdSettings[name]
|
||||
for(var i in keys){
|
||||
this.kbd.push(keys[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
381
public/src/js/parseosu.js
Normal file
381
public/src/js/parseosu.js
Normal file
@@ -0,0 +1,381 @@
|
||||
class ParseOsu{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(fileContent, difficulty, stars, offset, metaOnly){
|
||||
this.osu = {
|
||||
OFFSET: 0,
|
||||
MSPERBEAT: 1,
|
||||
METER: 2,
|
||||
SAMPLESET: 3,
|
||||
SAMPLEINDEX: 4,
|
||||
VOLUME: 5,
|
||||
INHERITED: 6,
|
||||
KIAIMODE: 7,
|
||||
|
||||
X: 0,
|
||||
Y: 1,
|
||||
TIME: 2,
|
||||
TYPE: 3,
|
||||
HITSOUND: 4,
|
||||
EXTRAS: 5,
|
||||
ENDTIME: 5,
|
||||
|
||||
CIRCLE: 1,
|
||||
SLIDER: 2,
|
||||
NEWCOMBO: 4,
|
||||
SPINNER: 8,
|
||||
|
||||
NORMAL: 1,
|
||||
WHISTLE: 2,
|
||||
FINISH: 4,
|
||||
CLAP: 8,
|
||||
|
||||
CURVEPOINTS: 0,
|
||||
REPEAT: 1,
|
||||
PIXELLENGTH: 2,
|
||||
EDGEHITSOUNDS: 3,
|
||||
EDGEADDITIONS: 4
|
||||
}
|
||||
this.data = []
|
||||
for(let line of fileContent){
|
||||
line = line.replace(/\/\/.*/, "").trim()
|
||||
if(line !== ""){
|
||||
this.data.push(line)
|
||||
}
|
||||
}
|
||||
this.offset = (offset || 0) * -1000
|
||||
this.soundOffset = 0
|
||||
this.beatInfo = {
|
||||
beatInterval: 0,
|
||||
lastBeatInterval: 0,
|
||||
bpm: 0
|
||||
}
|
||||
this.events = []
|
||||
this.generalInfo = this.parseGeneralInfo()
|
||||
this.metadata = this.parseMetadata()
|
||||
this.editor = this.parseEditor()
|
||||
this.difficulty = this.parseDifficulty()
|
||||
this._difficulty = difficulty;
|
||||
this.stars = stars
|
||||
if(!metaOnly){
|
||||
this.timingPoints = this.parseTiming()
|
||||
this.circles = this.parseCircles()
|
||||
this.measures = this.parseMeasures()
|
||||
}
|
||||
}
|
||||
getStartEndIndexes(type){
|
||||
var indexes = {
|
||||
start: 0,
|
||||
end: 0
|
||||
}
|
||||
while(indexes.start < this.data.length){
|
||||
if(this.data[indexes.start] === "[" + type + "]"){
|
||||
break
|
||||
}
|
||||
indexes.start++
|
||||
}
|
||||
indexes.start++
|
||||
indexes.end = indexes.start
|
||||
while(indexes.end < this.data.length){
|
||||
if(this.data[indexes.end].match(/^\[\w+\]$/)){
|
||||
break
|
||||
}
|
||||
indexes.end++
|
||||
}
|
||||
indexes.end--
|
||||
return indexes
|
||||
}
|
||||
parseDifficulty(){
|
||||
var difficulty = {
|
||||
sliderMultiplier: 0,
|
||||
sliderTickRate: 0,
|
||||
approachRate: 0
|
||||
}
|
||||
var indexes = this.getStartEndIndexes("Difficulty")
|
||||
for(var i = indexes.start; i <= indexes.end; i++){
|
||||
var [item, key] = this.data[i].split(":")
|
||||
switch(item){
|
||||
case "SliderMultiplier":
|
||||
difficulty.sliderMultiplier = key
|
||||
difficulty.originalMultiplier = key
|
||||
break
|
||||
case "SliderTickRate":
|
||||
difficulty.sliderTickRate = key
|
||||
break
|
||||
case "ApproachRate":
|
||||
difficulty.approachRate = key
|
||||
break
|
||||
case "OverallDifficulty":
|
||||
difficulty.overallDifficulty = key
|
||||
break
|
||||
}
|
||||
}
|
||||
return difficulty
|
||||
}
|
||||
parseTiming(){
|
||||
var timingPoints = []
|
||||
var indexes = this.getStartEndIndexes("TimingPoints")
|
||||
var lastBeatInterval = parseInt(this.data[indexes.start].split(",")[1])
|
||||
for(var i = indexes.start; i <= indexes.end; i++){
|
||||
var values = this.data[i].split(",")
|
||||
var start = parseInt(values[this.osu.OFFSET])
|
||||
var msOrPercent = parseFloat(values[this.osu.MSPERBEAT])
|
||||
if(i == indexes.start){
|
||||
this.beatInfo.beatInterval = msOrPercent
|
||||
this.beatInfo.bpm = Math.floor(1000 / this.beatInfo.beatInterval * 60)
|
||||
}
|
||||
var beatReset = false
|
||||
if(msOrPercent < 0){
|
||||
var sliderMultiplier = this.difficulty.lastMultiplier / Math.abs(msOrPercent / 100)
|
||||
}else{
|
||||
var sliderMultiplier = 1000 / msOrPercent
|
||||
if(i == 0){
|
||||
this.difficulty.originalMultiplier = sliderMultiplier
|
||||
}
|
||||
this.difficulty.lastMultiplier = sliderMultiplier
|
||||
beatReset = true
|
||||
}
|
||||
timingPoints.push({
|
||||
start: start + this.offset,
|
||||
sliderMultiplier: sliderMultiplier,
|
||||
measure: parseInt(values[this.osu.METER]),
|
||||
gogoTime: parseInt(values[this.osu.KIAIMODE]),
|
||||
beatMS: 1000 / this.difficulty.lastMultiplier,
|
||||
beatReset: beatReset
|
||||
})
|
||||
}
|
||||
return timingPoints
|
||||
}
|
||||
parseMeasures(){
|
||||
var measures = []
|
||||
|
||||
for(var i = 0; i < this.timingPoints.length; i++){
|
||||
var currentTiming = this.timingPoints[i]
|
||||
var firstTiming = i === 0
|
||||
|
||||
var limit = this.circles[this.circles.length - 1].endTime + currentTiming.beatMS
|
||||
|
||||
for(var j = i + 1; j < this.timingPoints.length; j++){
|
||||
var nextTiming = this.timingPoints[j]
|
||||
var newLimit = nextTiming.start
|
||||
if(nextTiming.measure !== currentTiming.measure || nextTiming.beatReset){
|
||||
limit = newLimit - currentTiming.beatMS
|
||||
break
|
||||
}
|
||||
i = j
|
||||
}
|
||||
|
||||
var start = currentTiming.start
|
||||
var interval = currentTiming.beatMS * currentTiming.measure
|
||||
if(firstTiming){
|
||||
while(start >= interval){
|
||||
start -= interval
|
||||
}
|
||||
}
|
||||
for(var ms = start; ms <= limit; ms += interval){
|
||||
|
||||
var speed = currentTiming.sliderMultiplier
|
||||
for(var j = 0; j < this.timingPoints.length; j++){
|
||||
var timingPoint = this.timingPoints[j]
|
||||
if(j !== 0 && timingPoint.start - this.offset > ms){
|
||||
break
|
||||
}
|
||||
speed = timingPoint.sliderMultiplier
|
||||
}
|
||||
|
||||
measures.push({
|
||||
ms: ms,
|
||||
originalMS: ms,
|
||||
speed: speed,
|
||||
visible: true
|
||||
})
|
||||
}
|
||||
}
|
||||
return measures
|
||||
}
|
||||
parseGeneralInfo(){
|
||||
var generalInfo = {}
|
||||
var indexes = this.getStartEndIndexes("General")
|
||||
for(var i = indexes.start; i<= indexes.end; i++){
|
||||
var [item, key] = this.data[i].split(":")
|
||||
generalInfo[item] = key.trim()
|
||||
}
|
||||
return generalInfo
|
||||
}
|
||||
parseMetadata(){
|
||||
var metadata = {}
|
||||
var indexes = this.getStartEndIndexes("Metadata")
|
||||
for(var i = indexes.start; i <= indexes.end; i++){
|
||||
var [item, key] = this.data[i].split(":")
|
||||
metadata[item] = key.trim()
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
parseEditor(){
|
||||
var editor = {
|
||||
distanceSpacing: 0,
|
||||
beatDivisor: 0,
|
||||
gridSize: 0
|
||||
}
|
||||
var indexes = this.getStartEndIndexes("Editor")
|
||||
for(var i = indexes.start; i <= indexes.end; i++){
|
||||
var [item, key] = this.data[i].split(":")
|
||||
switch(item){
|
||||
case "DistanceSpacing":
|
||||
editor.distanceSpacing = parseFloat(key)
|
||||
break
|
||||
case "BeatDivisor":
|
||||
editor.beatDivisor = parseInt(key)
|
||||
break
|
||||
case "GridSize":
|
||||
editor.gridSize = parseInt(key)
|
||||
break
|
||||
}
|
||||
}
|
||||
return editor
|
||||
}
|
||||
difficultyRange(difficulty, min, mid, max){
|
||||
if(difficulty > 5){
|
||||
return mid + (max - mid) * (difficulty - 5) / 5
|
||||
}
|
||||
if(difficulty < 5){
|
||||
return mid - (mid - min) * (5 - difficulty) / 5
|
||||
}
|
||||
return mid
|
||||
}
|
||||
parseCircles(){
|
||||
var circles = []
|
||||
var circleID = 0
|
||||
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++){
|
||||
circleID++
|
||||
var values = this.data[i].split(",")
|
||||
var emptyValue = false
|
||||
var start = parseInt(values[this.osu.TIME])
|
||||
var speed = this.difficulty.originalMultiplier
|
||||
var gogoTime = false
|
||||
var osuType = parseInt(values[this.osu.TYPE])
|
||||
var hitSound = parseInt(values[this.osu.HITSOUND])
|
||||
var beatLength = speed
|
||||
var lastMultiplier = this.difficulty.lastMultiplier
|
||||
var beatMS = this.beatInfo.beatInterval
|
||||
if(circleID === 1 && start + this.offset < 0){
|
||||
var offset = start + this.offset
|
||||
this.soundOffset = offset
|
||||
this.offset -= offset
|
||||
}
|
||||
|
||||
for(var j = 0; j < this.timingPoints.length; j++){
|
||||
var timingPoint = this.timingPoints[j]
|
||||
if(j !== 0 && timingPoint.start - this.offset > start){
|
||||
break
|
||||
}
|
||||
speed = timingPoint.sliderMultiplier
|
||||
gogoTime = timingPoint.gogoTime
|
||||
beatMS = timingPoint.beatMS
|
||||
}
|
||||
|
||||
if(osuType & this.osu.SPINNER){
|
||||
|
||||
var endTime = parseInt(values[this.osu.ENDTIME])
|
||||
var hitMultiplier = this.difficultyRange(this.difficulty.overallDifficulty, 3, 5, 7.5) * 1.65
|
||||
var requiredHits = Math.floor(Math.max(1, (endTime - start) / 1000 * hitMultiplier))
|
||||
pushCircle(new Circle({
|
||||
id: circleID,
|
||||
start: start + this.offset,
|
||||
type: "balloon",
|
||||
txt: strings.note.balloon,
|
||||
speed: speed,
|
||||
endTime: endTime + this.offset,
|
||||
requiredHits: requiredHits,
|
||||
gogoTime: gogoTime,
|
||||
beatMS: beatMS
|
||||
}))
|
||||
|
||||
}else if(osuType & this.osu.SLIDER){
|
||||
|
||||
var extras = values.slice(this.osu.EXTRAS)
|
||||
|
||||
var distance = parseFloat(extras[this.osu.PIXELLENGTH]) * parseFloat(extras[this.osu.REPEAT])
|
||||
var velocity = this.difficulty.sliderMultiplier * speed / 10
|
||||
var endTime = start + distance / velocity
|
||||
|
||||
if(hitSound & this.osu.FINISH){
|
||||
type = "daiDrumroll"
|
||||
txt = strings.note.daiDrumroll
|
||||
}else{
|
||||
type = "drumroll"
|
||||
txt = strings.note.drumroll
|
||||
}
|
||||
pushCircle(new Circle({
|
||||
id: circleID,
|
||||
start: start + this.offset,
|
||||
type: type,
|
||||
txt: txt,
|
||||
speed: speed,
|
||||
endTime: endTime + this.offset,
|
||||
gogoTime: gogoTime,
|
||||
beatMS: beatMS
|
||||
}))
|
||||
|
||||
}else if(osuType & this.osu.CIRCLE){
|
||||
var type
|
||||
var txt
|
||||
|
||||
if(hitSound & this.osu.FINISH){
|
||||
if(hitSound & this.osu.WHISTLE || hitSound & this.osu.CLAP){
|
||||
type = "daiKa"
|
||||
txt = strings.note.daiKa
|
||||
}else if(hitSound & this.osu.NORMAL || hitSound === this.osu.FINISH){
|
||||
type = "daiDon"
|
||||
txt = strings.note.daiDon
|
||||
}else{
|
||||
emptyValue = true
|
||||
}
|
||||
}else if(hitSound & this.osu.WHISTLE || hitSound & this.osu.CLAP){
|
||||
type = "ka"
|
||||
txt = strings.note.ka
|
||||
}else if(hitSound & this.osu.NORMAL || hitSound === 0){
|
||||
type = "don"
|
||||
txt = strings.note.don
|
||||
}else{
|
||||
emptyValue = true
|
||||
}
|
||||
if(!emptyValue){
|
||||
pushCircle(new Circle({
|
||||
id: circleID,
|
||||
start: start + this.offset,
|
||||
type: type,
|
||||
txt: txt,
|
||||
speed: speed,
|
||||
gogoTime: gogoTime,
|
||||
beatMS: beatMS
|
||||
}))
|
||||
}
|
||||
}else{
|
||||
emptyValue = true
|
||||
}
|
||||
if(emptyValue){
|
||||
console.warn("Unknown note type found on line " + (i + 1) + ": " + this.data[i])
|
||||
}
|
||||
}
|
||||
this.scoremode = 2;
|
||||
var autoscore = new AutoScore(this._difficulty, this.stars, 2, circles);
|
||||
this.scoreinit = autoscore.ScoreInit;
|
||||
this.scorediff = autoscore.ScoreDiff;
|
||||
return circles
|
||||
}
|
||||
}
|
||||
702
public/src/js/parsetja.js
Normal file
702
public/src/js/parsetja.js
Normal file
@@ -0,0 +1,702 @@
|
||||
class ParseTja{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(file, difficulty, stars, offset, metaOnly){
|
||||
this.data = []
|
||||
for(let line of file){
|
||||
var indexComment = line.indexOf("//")
|
||||
if(indexComment !== -1 && !line.trim().toLowerCase().startsWith("maker:")){
|
||||
line = line.slice(0, indexComment).trim()
|
||||
}else{
|
||||
line = line.trim()
|
||||
}
|
||||
if(line !== ""){
|
||||
this.data.push(line)
|
||||
}
|
||||
}
|
||||
this.difficulty = difficulty
|
||||
this.stars = stars
|
||||
this.offset = (offset || 0) * -1000
|
||||
this.soundOffset = 0
|
||||
this.noteTypes = {
|
||||
"0": {name: false, txt: false},
|
||||
"1": {name: "don", txt: strings.note.don},
|
||||
"2": {name: "ka", txt: strings.note.ka},
|
||||
"3": {name: "daiDon", txt: strings.note.daiDon},
|
||||
"4": {name: "daiKa", txt: strings.note.daiKa},
|
||||
"5": {name: "drumroll", txt: strings.note.drumroll},
|
||||
"6": {name: "daiDrumroll", txt: strings.note.daiDrumroll},
|
||||
"7": {name: "balloon", txt: strings.note.balloon},
|
||||
"8": {name: false, txt: false},
|
||||
"9": {name: "balloon", txt: strings.note.balloon},
|
||||
"A": {name: "daiDon", txt: strings.note.daiDon},
|
||||
"B": {name: "daiKa", txt: strings.note.daiKa}
|
||||
}
|
||||
this.noteTypes_ex = strings.ex_note;
|
||||
this.courseTypes = {
|
||||
"0": "easy",
|
||||
"1": "normal",
|
||||
"2": "hard",
|
||||
"3": "oni",
|
||||
"4": "ura",
|
||||
"edit": "ura"
|
||||
}
|
||||
|
||||
this.metadata = this.parseMetadata()
|
||||
this.measures = []
|
||||
this.beatInfo = {}
|
||||
this.events = []
|
||||
if(!metaOnly){
|
||||
this.circles = this.parseCircles(difficulty)
|
||||
}
|
||||
}
|
||||
parseMetadata(){
|
||||
var metaNumbers = ["bpm", "offset", "demostart", "level", "scoremode", "scorediff"]
|
||||
var inSong = false
|
||||
var hasSong = false
|
||||
var courses = {}
|
||||
var currentCourse = {}
|
||||
var courseName = "oni"
|
||||
for(var lineNum = 0; lineNum < this.data.length; lineNum++){
|
||||
var line = this.data[lineNum]
|
||||
|
||||
if(line.slice(0, 1) === "#"){
|
||||
|
||||
var name = line.slice(1).toLowerCase()
|
||||
if((name === "start" || name === "start p1") && !inSong){
|
||||
|
||||
inSong = true
|
||||
if(!hasSong || name === "start" && courses[courseName] && courses[courseName].startName !== "start"){
|
||||
hasSong = false
|
||||
if(!(courseName in courses)){
|
||||
courses[courseName] = {}
|
||||
}
|
||||
courses[courseName].startName = name
|
||||
for(var opt in currentCourse){
|
||||
if(opt !== "branch"){
|
||||
courses[courseName][opt] = currentCourse[opt]
|
||||
}
|
||||
}
|
||||
courses[courseName].start = lineNum + 1
|
||||
courses[courseName].end = this.data.length
|
||||
}
|
||||
}else if(name === "end" && inSong){
|
||||
inSong = false
|
||||
if(!hasSong){
|
||||
hasSong = true
|
||||
courses[courseName].end = lineNum
|
||||
}
|
||||
}else if(name.startsWith("branchstart") && inSong){
|
||||
courses[courseName].branch = true
|
||||
}else if(name.startsWith("lyric") && inSong){
|
||||
courses[courseName].inlineLyrics = true
|
||||
}
|
||||
|
||||
}else if(!inSong){
|
||||
|
||||
if(line.indexOf(":") > 0){
|
||||
|
||||
var [name, value] = this.split(line, ":")
|
||||
name = name.toLowerCase().trim()
|
||||
value = value.trim()
|
||||
|
||||
if(name === "course"){
|
||||
value = value.toLowerCase()
|
||||
if(value in this.courseTypes){
|
||||
courseName = this.courseTypes[value]
|
||||
}else{
|
||||
courseName = value
|
||||
}
|
||||
hasSong = false
|
||||
}else if(name === "balloon"){
|
||||
value = value ? value.split(",").map(digit => parseInt(digit)) : []
|
||||
}else if(this.inArray(name, metaNumbers)){
|
||||
value = parseFloat(value)
|
||||
}
|
||||
else if (name === "scoreinit") {
|
||||
value = value ? parseFloat(value.split(",")[0]) : 0;
|
||||
}
|
||||
|
||||
currentCourse[name] = value
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return courses
|
||||
}
|
||||
inArray(string, array){
|
||||
return array.indexOf(string) >= 0
|
||||
}
|
||||
split(string, delimiter){
|
||||
var index = string.indexOf(delimiter)
|
||||
if(index < 0){
|
||||
return [string, ""]
|
||||
}
|
||||
return [string.slice(0, index), string.slice(index + delimiter.length)]
|
||||
}
|
||||
parseCircles(difficulty, lyricsOnly){
|
||||
var meta = this.metadata[difficulty] || {}
|
||||
var ms = (meta.offset || 0) * -1000 + this.offset
|
||||
var bpm = Math.abs(meta.bpm) || 120
|
||||
var scroll = 1
|
||||
var measure = 4
|
||||
if(!lyricsOnly){
|
||||
this.beatInfo.beatInterval = 60000 / bpm
|
||||
}
|
||||
var gogo = false
|
||||
var barLine = true
|
||||
|
||||
var balloonID = 0
|
||||
var balloons = meta.balloon || []
|
||||
|
||||
var lastDrumroll = false
|
||||
|
||||
var branches
|
||||
var branch = false
|
||||
var branchObj = {}
|
||||
var currentBranch = false
|
||||
var branchSettings = {}
|
||||
var branchFirstMeasure = false
|
||||
var sectionBegin = true
|
||||
var lastBpm = bpm
|
||||
var lastGogo = gogo
|
||||
var lyrics
|
||||
var lyricsIndex = null
|
||||
var lyricsLine = null
|
||||
var lyricsCopy = false
|
||||
|
||||
var measures = []
|
||||
var currentMeasure = []
|
||||
var firstNote = true
|
||||
var circles = []
|
||||
var circleID = 0
|
||||
var events = []
|
||||
var regexAZ = /[A-Z]/
|
||||
var regexSpace = /\s/
|
||||
var regexLinebreak = /\\n/g
|
||||
var isAllDon = (note_chain, start_pos) => {
|
||||
for (var i = start_pos; i < note_chain.length; ++i) {
|
||||
var note = note_chain[i];
|
||||
if (note && note.type !== "don" && note.type !== "daiDon") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
var checkChain = (note_chain, measure_length, is_last) => {
|
||||
//console.log(note_chain, measure_length, is_last);
|
||||
/*if (measure_length >= 24) {
|
||||
for (var note of note_chain) {
|
||||
note.text = this.noteTypes_ex[note.type][0];
|
||||
}
|
||||
} else { */
|
||||
var alldon_pos = null;
|
||||
for (var i = 0; i < note_chain.length - (is_last ? 1 : 0); ++i) {
|
||||
var note = note_chain[i];
|
||||
if (alldon_pos === null && is_last && isAllDon(note_chain, i)) {
|
||||
alldon_pos = i;
|
||||
}
|
||||
note.text = this.noteTypes_ex[note.type][alldon_pos != null ? (i - alldon_pos) % 2 : 0];
|
||||
}
|
||||
//}
|
||||
}
|
||||
var pushMeasure = () => {
|
||||
var note = currentMeasure[0]
|
||||
if(note){
|
||||
var speed = note.bpm * note.scroll / 60
|
||||
}else{
|
||||
var speed = bpm * scroll / 60
|
||||
}
|
||||
if(!lyricsOnly){
|
||||
measures.push({
|
||||
ms: ms,
|
||||
originalMS: ms,
|
||||
speed: speed,
|
||||
visible: barLine,
|
||||
branch: currentBranch,
|
||||
branchFirst: branchFirstMeasure
|
||||
})
|
||||
}
|
||||
branchFirstMeasure = false
|
||||
if(currentMeasure.length){
|
||||
for(var i = 0; i < currentMeasure.length; i++){
|
||||
var note = currentMeasure[i]
|
||||
if(firstNote && note.type && note.type !== "event"){
|
||||
firstNote = false
|
||||
if(ms < 0){
|
||||
this.soundOffset = ms
|
||||
ms = 0
|
||||
}
|
||||
}
|
||||
note.start = ms
|
||||
if(note.endDrumroll){
|
||||
note.endDrumroll.endTime = ms
|
||||
note.endDrumroll.originalEndTime = ms
|
||||
}
|
||||
var msPerMeasure = 60000 * measure / note.bpm
|
||||
ms += msPerMeasure / currentMeasure.length
|
||||
}
|
||||
var note_chain = [];
|
||||
for (var i = 0; i < currentMeasure.length; i++){
|
||||
var note = currentMeasure[i]
|
||||
if(!lyricsOnly){
|
||||
circleID++
|
||||
var circleObj = new Circle({
|
||||
id: circleID,
|
||||
start: note.start,
|
||||
type: note.type,
|
||||
txt: note.txt,
|
||||
speed: note.bpm * note.scroll / 60,
|
||||
gogoTime: note.gogo,
|
||||
endTime: note.endTime,
|
||||
requiredHits: note.requiredHits,
|
||||
beatMS: 60000 / note.bpm,
|
||||
branch: currentBranch,
|
||||
section: note.section
|
||||
})
|
||||
if(note.type){
|
||||
if(note.type === "don" || note.type === "ka" || note.type === "daiDon" || note.type === "daiKa"){
|
||||
note_chain.push(circleObj)
|
||||
}else{
|
||||
if(note_chain.length > 1 && currentMeasure.length >= 8){
|
||||
checkChain(note_chain, currentMeasure.length, false)
|
||||
}
|
||||
note_chain = []
|
||||
}
|
||||
if (lastDrumroll === note) {
|
||||
lastDrumroll = circleObj
|
||||
}
|
||||
|
||||
if(note.type !== "event"){
|
||||
circles.push(circleObj)
|
||||
}
|
||||
}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
|
||||
)
|
||||
){
|
||||
if(note_chain.length > 1 && currentMeasure.length >= 8){
|
||||
checkChain(note_chain, currentMeasure.length, true)
|
||||
}
|
||||
note_chain = []
|
||||
}
|
||||
if(note.event){
|
||||
events.push(circleObj)
|
||||
}
|
||||
}
|
||||
var lyricsObj = null
|
||||
if("lyricsLine" in note){
|
||||
lyricsObj = {
|
||||
start: note.start,
|
||||
text: note.lyricsLine
|
||||
}
|
||||
}else if(note.lyricsCopy){
|
||||
lyricsObj = {
|
||||
start: note.start,
|
||||
copy: true
|
||||
}
|
||||
}
|
||||
if(lyricsObj){
|
||||
if(currentBranch){
|
||||
lyricsObj.branch = currentBranch.name
|
||||
}
|
||||
insertLyrics(lyricsObj)
|
||||
}
|
||||
}
|
||||
if(!lyricsOnly && note_chain.length > 1 && currentMeasure.length >= 8){
|
||||
checkChain(note_chain, currentMeasure.length, false)
|
||||
}
|
||||
}else{
|
||||
var msPerMeasure = 60000 * measure / bpm
|
||||
ms += msPerMeasure
|
||||
}
|
||||
}
|
||||
var insertNote = circleObj => {
|
||||
if(circleObj){
|
||||
if(bpm !== lastBpm || gogo !== lastGogo){
|
||||
circleObj.event = true
|
||||
lastBpm = bpm
|
||||
lastGogo = gogo
|
||||
}
|
||||
if(lyricsLine !== null){
|
||||
circleObj.lyricsLine = lyricsLine
|
||||
lyricsLine = null
|
||||
}else if(lyricsCopy){
|
||||
circleObj.lyricsCopy = true
|
||||
}
|
||||
lyricsCopy = false
|
||||
currentMeasure.push(circleObj)
|
||||
}
|
||||
}
|
||||
var insertBlankNote = circleObj => {
|
||||
if(bpm !== lastBpm || gogo !== lastGogo){
|
||||
insertNote({
|
||||
type: "event",
|
||||
bpm: bpm,
|
||||
scroll: scroll,
|
||||
gogo: gogo
|
||||
})
|
||||
}else if(!circleObj){
|
||||
var circleObj2 = {
|
||||
bpm: bpm,
|
||||
scroll: scroll
|
||||
}
|
||||
if(lyricsLine !== null){
|
||||
circleObj2.lyricsLine = lyricsLine
|
||||
lyricsLine = null
|
||||
}else if(lyricsCopy){
|
||||
circleObj2.lyricsCopy = true
|
||||
}
|
||||
lyricsCopy = false
|
||||
currentMeasure.push(circleObj2)
|
||||
}
|
||||
if(circleObj){
|
||||
if(lyricsLine !== null){
|
||||
circleObj.lyricsLine = lyricsLine
|
||||
lyricsLine = null
|
||||
}else if(lyricsCopy){
|
||||
circleObj.lyricsCopy = true
|
||||
}
|
||||
lyricsCopy = false
|
||||
currentMeasure.push(circleObj)
|
||||
}
|
||||
}
|
||||
var insertLyrics = obj => {
|
||||
if(!lyrics){
|
||||
lyrics = []
|
||||
}else if(lyricsIndex !== null){
|
||||
lyrics[lyricsIndex].end = obj.start
|
||||
}
|
||||
lyricsIndex = lyrics.length
|
||||
lyrics.push(obj)
|
||||
}
|
||||
|
||||
for(var lineNum = meta.start; lineNum < meta.end; lineNum++){
|
||||
var line = this.data[lineNum]
|
||||
if(line.slice(0, 1) === "#"){
|
||||
|
||||
var line = line.slice(1)
|
||||
var [name, value] = this.split(line, " ")
|
||||
name = name.toLowerCase()
|
||||
|
||||
switch(name){
|
||||
case "gogostart":
|
||||
gogo = true
|
||||
break
|
||||
case "gogoend":
|
||||
gogo = false
|
||||
break
|
||||
case "bpmchange":
|
||||
bpm = parseFloat(value) || bpm
|
||||
break
|
||||
case "scroll":
|
||||
scroll = Math.abs(parseFloat(value)) || scroll
|
||||
break
|
||||
case "measure":
|
||||
var [numerator, denominator] = value.split("/")
|
||||
measure = numerator / denominator * 4 || measure
|
||||
break
|
||||
case "delay":
|
||||
ms += (parseFloat(value) || 0) * 1000
|
||||
break
|
||||
case "barlineon":
|
||||
barLine = true
|
||||
break
|
||||
case "barlineoff":
|
||||
barLine = false
|
||||
break
|
||||
case "branchstart":
|
||||
branch = true
|
||||
currentBranch = false
|
||||
branchFirstMeasure = true
|
||||
branchSettings = {
|
||||
ms: ms,
|
||||
gogo: gogo,
|
||||
bpm: bpm,
|
||||
scroll: scroll,
|
||||
sectionBegin: sectionBegin,
|
||||
lyricsCopy: !!lyrics
|
||||
}
|
||||
if(lyrics && lyricsIndex !== null){
|
||||
var line = lyrics[lyricsIndex]
|
||||
line.end = ms
|
||||
}
|
||||
lyricsIndex = null
|
||||
|
||||
value = value.split(",")
|
||||
if(!branches){
|
||||
branches = []
|
||||
}
|
||||
var req = {
|
||||
advanced: parseFloat(value[1]) || 0,
|
||||
master: parseFloat(value[2]) || 0
|
||||
}
|
||||
if(req.advanced > 0){
|
||||
var active = req.master > 0 ? "normal" : "master"
|
||||
}else{
|
||||
var active = req.master > 0 ? "advanced" : "master"
|
||||
}
|
||||
branchObj = {
|
||||
ms: ms,
|
||||
originalMS: ms,
|
||||
active: active,
|
||||
type: value[0].trim().toLowerCase() === "r" ? "drumroll" : "accuracy",
|
||||
requirement: req
|
||||
}
|
||||
branches.push(branchObj)
|
||||
if(measures.length === 1 && branchObj.type === "drumroll"){
|
||||
for(var i = circles.length; i--;){
|
||||
var circle = circles[i]
|
||||
if(circle.endTime && (circle.type === "drumroll" || circle.type === "daiDrumroll" || circle.type === "balloon")){
|
||||
measures.push({
|
||||
ms: circle.endTime,
|
||||
originalMS: circle.endTime,
|
||||
speed: circle.bpm * circle.scroll / 60,
|
||||
visible: false,
|
||||
branch: circle.branch
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if(measures.length !== 0){
|
||||
measures[measures.length - 1].nextBranch = branchObj
|
||||
}
|
||||
break
|
||||
case "branchend":
|
||||
branch = false
|
||||
currentBranch = false
|
||||
lyricsCopy = lyricsCopy || !!lyrics
|
||||
break
|
||||
case "section":
|
||||
sectionBegin = true
|
||||
if(branch && !currentBranch){
|
||||
branchSettings.sectionBegin = true
|
||||
}
|
||||
break
|
||||
case "n": case "e": case "m":
|
||||
if(!branch){
|
||||
break
|
||||
}
|
||||
if(lyrics){
|
||||
if(lyricsIndex !== null){
|
||||
var line = lyrics[lyricsIndex]
|
||||
line.end = ms
|
||||
}
|
||||
lyricsIndex = null
|
||||
}
|
||||
ms = branchSettings.ms
|
||||
gogo = branchSettings.gogo
|
||||
bpm = branchSettings.bpm
|
||||
scroll = branchSettings.scroll
|
||||
sectionBegin = branchSettings.sectionBegin
|
||||
lyricsCopy = branchSettings.lyricsCopy
|
||||
branchFirstMeasure = true
|
||||
var branchName = name === "m" ? "master" : (name === "e" ? "advanced" : "normal")
|
||||
currentBranch = {
|
||||
name: branchName,
|
||||
active: branchName === branchObj.active
|
||||
}
|
||||
branchObj[branchName] = currentBranch
|
||||
break
|
||||
case "lyric":
|
||||
lyricsLine = value.replace(regexLinebreak, "\n").trim()
|
||||
break
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
var string = line.toUpperCase().split("")
|
||||
|
||||
const abekobe = localStorage.getItem("abekobe") ?? "false";
|
||||
const detarame = parseFloat(localStorage.getItem("detarame") ?? "0", 10);
|
||||
|
||||
for(let symbol of string){
|
||||
|
||||
if (abekobe === "true") {
|
||||
if (symbol === "1") {
|
||||
symbol = "2";
|
||||
} else if (symbol === "2") {
|
||||
symbol = "1";
|
||||
} else if (symbol === "3") {
|
||||
symbol = "4";
|
||||
} else if (symbol === "4") {
|
||||
symbol = "3";
|
||||
} else if (symbol === "A") {
|
||||
symbol = "B";
|
||||
} else if (symbol === "B") {
|
||||
symbol - "A";
|
||||
}
|
||||
}
|
||||
|
||||
if (detarame > 0) {
|
||||
const randomValue = Math.random() * 100;
|
||||
if (randomValue < detarame) {
|
||||
|
||||
const first = ["1", "2"];
|
||||
const second = ["3", "4", "A", "B"];
|
||||
if (first.includes(symbol)) {
|
||||
const firstIndex = Math.floor(Math.random() * first.length);
|
||||
symbol = first[firstIndex];
|
||||
} else if (second.includes(symbol)) {
|
||||
const secondIndex = Math.floor(Math.random() * second.length);
|
||||
symbol = second[secondIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var error = false
|
||||
switch(symbol){
|
||||
|
||||
case "0":
|
||||
insertBlankNote()
|
||||
break
|
||||
case "1": case "2": case "3": case "4": case "A": case "B":
|
||||
var type = this.noteTypes[symbol]
|
||||
var circleObj = {
|
||||
type: type.name,
|
||||
txt: type.txt,
|
||||
gogo: gogo,
|
||||
bpm: bpm,
|
||||
scroll: scroll,
|
||||
section: sectionBegin
|
||||
}
|
||||
sectionBegin = false
|
||||
if(lastDrumroll){
|
||||
circleObj.endDrumroll = lastDrumroll
|
||||
lastDrumroll = false
|
||||
}
|
||||
insertNote(circleObj)
|
||||
break
|
||||
case "5": case "6": case "7": case "9":
|
||||
var type = this.noteTypes[symbol]
|
||||
var circleObj = {
|
||||
type: type.name,
|
||||
txt: type.txt,
|
||||
gogo: gogo,
|
||||
bpm: bpm,
|
||||
scroll: scroll,
|
||||
section: sectionBegin
|
||||
}
|
||||
sectionBegin = false
|
||||
if(lastDrumroll){
|
||||
if(symbol === "9"){
|
||||
insertNote({
|
||||
endDrumroll: lastDrumroll,
|
||||
gogo: gogo,
|
||||
bpm: bpm,
|
||||
scroll: scroll,
|
||||
section: sectionBegin
|
||||
})
|
||||
sectionBegin = false
|
||||
lastDrumroll = false
|
||||
}else{
|
||||
insertBlankNote()
|
||||
}
|
||||
break
|
||||
}
|
||||
if(symbol === "7" || symbol === "9"){
|
||||
var hits = balloons[balloonID]
|
||||
if(!hits || hits < 1){
|
||||
hits = 1
|
||||
}
|
||||
circleObj.requiredHits = hits
|
||||
balloonID++
|
||||
}
|
||||
lastDrumroll = circleObj
|
||||
insertNote(circleObj)
|
||||
break
|
||||
case "8":
|
||||
if(lastDrumroll){
|
||||
insertNote({
|
||||
endDrumroll: lastDrumroll,
|
||||
gogo: gogo,
|
||||
bpm: bpm,
|
||||
scroll: scroll,
|
||||
section: sectionBegin
|
||||
})
|
||||
sectionBegin = false
|
||||
lastDrumroll = false
|
||||
}else{
|
||||
insertBlankNote()
|
||||
}
|
||||
break
|
||||
case ",":
|
||||
if(currentMeasure.length === 0 && (bpm !== lastBpm || gogo !== lastGogo || lyricsLine !== null)){
|
||||
insertBlankNote()
|
||||
}
|
||||
pushMeasure()
|
||||
currentMeasure = []
|
||||
break
|
||||
default:
|
||||
if(regexAZ.test(symbol)){
|
||||
insertBlankNote()
|
||||
}else if(!regexSpace.test(symbol)){
|
||||
error = true
|
||||
}
|
||||
break
|
||||
|
||||
}
|
||||
if(error){
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if(lastDrumroll){
|
||||
lastDrumroll.endTime = ms
|
||||
lastDrumroll.originalEndTime = ms
|
||||
}
|
||||
if(lyricsLine !== null){
|
||||
insertLyrics({
|
||||
start: ms,
|
||||
text: lyricsLine
|
||||
})
|
||||
}
|
||||
pushMeasure()
|
||||
|
||||
if(!lyricsOnly){
|
||||
if(branches){
|
||||
circles.sort((a, b) => a.ms > b.ms ? 1 : -1)
|
||||
measures.sort((a, b) => a.ms > b.ms ? 1 : -1)
|
||||
circles.forEach((circle, i) => circle.id = i + 1)
|
||||
}
|
||||
this.measures = measures
|
||||
this.events = events
|
||||
this.branches = branches
|
||||
this.scoreinit = meta.scoreinit
|
||||
this.scorediff = meta.scorediff
|
||||
if(this.scoreinit && this.scorediff){
|
||||
this.scoremode = meta.scoremode || 1
|
||||
}else{
|
||||
this.scoremode = meta.scoremode || 2
|
||||
var autoscore = new AutoScore(difficulty, this.stars, this.scoremode, circles)
|
||||
this.scoreinit = autoscore.ScoreInit
|
||||
this.scorediff = autoscore.ScoreDiff
|
||||
}
|
||||
}
|
||||
if(lyrics && lyricsIndex !== null){
|
||||
var line = lyrics[lyricsIndex]
|
||||
line.end = Math.max(ms, line.start) + 5000
|
||||
}
|
||||
if(lyrics){
|
||||
this.lyrics = lyrics
|
||||
}else if(!lyricsOnly){
|
||||
for(var courseName in this.metadata){
|
||||
if(this.metadata[courseName].inlineLyrics){
|
||||
this.parseCircles(courseName, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return circles
|
||||
}
|
||||
}
|
||||
677
public/src/js/plugins.js
Normal file
677
public/src/js/plugins.js
Normal file
@@ -0,0 +1,677 @@
|
||||
class Plugins{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.allPlugins = []
|
||||
this.pluginMap = {}
|
||||
this.hashes = []
|
||||
this.startOrder = []
|
||||
}
|
||||
add(script, options){
|
||||
options = options || {}
|
||||
var hash = md5.base64(script.toString())
|
||||
var isUrl = typeof script === "string" && !options.raw
|
||||
if(isUrl){
|
||||
hash = "url " + hash
|
||||
}else if(typeof script !== "string"){
|
||||
hash = "class " + hash
|
||||
}
|
||||
var name = options.name
|
||||
if(!name && isUrl){
|
||||
name = script
|
||||
var index = name.lastIndexOf("?")
|
||||
if(index !== -1){
|
||||
name = name.slice(0, index)
|
||||
}
|
||||
var index = name.lastIndexOf("/")
|
||||
if(index !== -1){
|
||||
name = name.slice(index + 1)
|
||||
}
|
||||
if(name.endsWith(".taikoweb.js")){
|
||||
name = name.slice(0, -".taikoweb.js".length)
|
||||
}else if(name.endsWith(".js")){
|
||||
name = name.slice(0, -".js".length)
|
||||
}
|
||||
}
|
||||
name = name || "plugin"
|
||||
if(this.hashes.indexOf(hash) !== -1){
|
||||
console.warn("Skip adding an already addded plugin: " + name)
|
||||
return
|
||||
}
|
||||
var baseName = name
|
||||
for(var i = 2; name in this.pluginMap; i++){
|
||||
name = baseName + i.toString()
|
||||
}
|
||||
var plugin = new PluginLoader(script, name, hash, options.raw)
|
||||
plugin.hide = !!options.hide
|
||||
this.allPlugins.push({
|
||||
name: name,
|
||||
plugin: plugin
|
||||
})
|
||||
this.pluginMap[name] = plugin
|
||||
this.hashes.push(hash)
|
||||
return plugin
|
||||
}
|
||||
remove(name){
|
||||
if(name in this.pluginMap){
|
||||
var hash = this.pluginMap[name].hash
|
||||
if(hash){
|
||||
var index = this.hashes.indexOf(hash)
|
||||
if(index !== -1){
|
||||
this.hashes.splice(index, 1)
|
||||
}
|
||||
}
|
||||
this.unload(name)
|
||||
}
|
||||
var index = this.allPlugins.findIndex(obj => obj.name === name)
|
||||
if(index !== -1){
|
||||
this.allPlugins.splice(index, 1)
|
||||
}
|
||||
var index = this.startOrder.indexOf(name)
|
||||
if(index !== -1){
|
||||
this.startOrder.splice(index, 1)
|
||||
}
|
||||
delete this.pluginMap[name]
|
||||
}
|
||||
load(name){
|
||||
return this.pluginMap[name].load()
|
||||
}
|
||||
loadAll(){
|
||||
for(var i = 0; i < this.allPlugins.length; i++){
|
||||
this.allPlugins[i].plugin.load()
|
||||
}
|
||||
}
|
||||
start(name){
|
||||
return this.pluginMap[name].start()
|
||||
}
|
||||
startAll(){
|
||||
for(var i = 0; i < this.allPlugins.length; i++){
|
||||
this.allPlugins[i].plugin.start()
|
||||
}
|
||||
}
|
||||
stop(name){
|
||||
return this.pluginMap[name].stop()
|
||||
}
|
||||
stopAll(){
|
||||
for(var i = this.startOrder.length; i--;){
|
||||
this.pluginMap[this.startOrder[i]].stop()
|
||||
}
|
||||
}
|
||||
unload(name){
|
||||
return this.pluginMap[name].unload()
|
||||
}
|
||||
unloadAll(){
|
||||
for(var i = this.startOrder.length; i--;){
|
||||
this.pluginMap[this.startOrder[i]].unload()
|
||||
}
|
||||
for(var i = this.allPlugins.length; i--;){
|
||||
this.allPlugins[i].plugin.unload()
|
||||
}
|
||||
}
|
||||
unloadImported(){
|
||||
for(var i = this.startOrder.length; i--;){
|
||||
var plugin = this.pluginMap[this.startOrder[i]]
|
||||
if(plugin.imported){
|
||||
plugin.unload()
|
||||
}
|
||||
}
|
||||
for(var i = this.allPlugins.length; i--;){
|
||||
var obj = this.allPlugins[i]
|
||||
if(obj.plugin.imported){
|
||||
obj.plugin.unload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strFromFunc(func){
|
||||
var output = func.toString()
|
||||
return output.slice(output.indexOf("{") + 1, output.lastIndexOf("}"))
|
||||
}
|
||||
argsFromFunc(func){
|
||||
var output = func.toString()
|
||||
output = output.slice(0, output.indexOf("{"))
|
||||
output = output.slice(output.indexOf("(") + 1, output.lastIndexOf(")"))
|
||||
return output.split(",").map(str => str.trim()).filter(Boolean)
|
||||
}
|
||||
insertBefore(input, insertedText, searchString){
|
||||
var index = input.indexOf(searchString)
|
||||
if(index === -1){
|
||||
throw new Error("searchString not found: " + searchString)
|
||||
}
|
||||
return input.slice(0, index) + insertedText + input.slice(index)
|
||||
}
|
||||
insertAfter(input, searchString, insertedText){
|
||||
var index = input.indexOf(searchString)
|
||||
if(index === -1){
|
||||
throw new Error("searchString not found: " + searchString)
|
||||
}
|
||||
var length = searchString.length
|
||||
return input.slice(0, index + length) + insertedText + input.slice(index + length)
|
||||
}
|
||||
strReplace(input, searchString, insertedText, repeat=1){
|
||||
var position = 0
|
||||
for(var i = 0; i < repeat; i++){
|
||||
var index = input.indexOf(searchString, position)
|
||||
if(index === -1){
|
||||
if(repeat === Infinity){
|
||||
break
|
||||
}else{
|
||||
throw new Error("searchString not found: " + searchString)
|
||||
}
|
||||
}
|
||||
input = input.slice(0, index) + insertedText + input.slice(index + searchString.length)
|
||||
position = index + insertedText.length
|
||||
}
|
||||
return input
|
||||
}
|
||||
isObject(input){
|
||||
return input && typeof input === "object" && input.constructor === Object
|
||||
}
|
||||
deepMerge(target, ...sources){
|
||||
sources.forEach(source => {
|
||||
if(this.isObject(target) && this.isObject(source)){
|
||||
for(var i in source){
|
||||
if(this.isObject(source[i])){
|
||||
if(!target[i]){
|
||||
target[i] = {}
|
||||
}
|
||||
this.deepMerge(target[i], source[i])
|
||||
}else if(source[i]){
|
||||
target[i] = source[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return target
|
||||
}
|
||||
arrayDel(array, item){
|
||||
var index = array.indexOf(item)
|
||||
if(index !== -1){
|
||||
array.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
hasSettings(){
|
||||
for(var i = 0; i < this.allPlugins.length; i++){
|
||||
var plugin = this.allPlugins[i].plugin
|
||||
if(plugin.loaded && (!plugin.hide || plugin.settings())){
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
getSettings(){
|
||||
var items = []
|
||||
for(var i = 0; i < this.allPlugins.length; i++){
|
||||
var obj = this.allPlugins[i]
|
||||
let plugin = obj.plugin
|
||||
if(!plugin.loaded){
|
||||
continue
|
||||
}
|
||||
if(!plugin.hide){
|
||||
let description
|
||||
let description_lang
|
||||
var module = plugin.module
|
||||
if(module){
|
||||
description = [
|
||||
module.description,
|
||||
module.author ? strings.plugins.author.replace("%s", module.author) : null,
|
||||
module.version ? strings.plugins.version.replace("%s", module.version) : null
|
||||
].filter(Boolean).join("\n")
|
||||
description_lang = {}
|
||||
languageList.forEach(lang => {
|
||||
description_lang[lang] = [
|
||||
this.getLocalTitle(module.description, module.description_lang, lang),
|
||||
module.author ? allStrings[lang].plugins.author.replace("%s", module.author) : null,
|
||||
module.version ? allStrings[lang].plugins.version.replace("%s", module.version) : null
|
||||
].filter(Boolean).join("\n")
|
||||
})
|
||||
}
|
||||
var name = module && module.name || obj.name
|
||||
var name_lang = module && module.name_lang
|
||||
items.push({
|
||||
name: name,
|
||||
name_lang: name_lang,
|
||||
description: description,
|
||||
description_lang: description_lang,
|
||||
type: "toggle",
|
||||
default: true,
|
||||
getItem: () => plugin.started,
|
||||
setItem: value => {
|
||||
if(plugin.name in this.pluginMap){
|
||||
if(plugin.started && !value){
|
||||
this.stop(plugin.name)
|
||||
}else if(!plugin.started && value){
|
||||
this.start(plugin.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
var settings = plugin.settings()
|
||||
if(settings){
|
||||
settings.forEach(setting => {
|
||||
if(!setting.name){
|
||||
setting.name = name
|
||||
if(!setting.name_lang){
|
||||
setting.name_lang = name_lang
|
||||
}
|
||||
}
|
||||
if(typeof setting.getItem !== "function"){
|
||||
setting.getItem = () => {}
|
||||
}
|
||||
if(typeof setting.setItem !== "function"){
|
||||
setting.setItem = () => {}
|
||||
}
|
||||
if(!("indent" in setting) && !plugin.hide){
|
||||
setting.indent = 1
|
||||
}
|
||||
items.push(setting)
|
||||
})
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
getLocalTitle(title, titleLang, lang){
|
||||
if(titleLang){
|
||||
for(var id in titleLang){
|
||||
if(id === (lang || strings.id) && titleLang[id]){
|
||||
return titleLang[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
return title
|
||||
}
|
||||
}
|
||||
|
||||
class PluginLoader{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(script, name, hash, raw){
|
||||
this.name = name
|
||||
this.hash = hash
|
||||
if(typeof script === "string"){
|
||||
if(raw){
|
||||
this.url = URL.createObjectURL(new Blob([script], {
|
||||
type: "application/javascript"
|
||||
}))
|
||||
}else{
|
||||
this.url = script
|
||||
}
|
||||
}else{
|
||||
this.class = script
|
||||
}
|
||||
}
|
||||
load(loadErrors){
|
||||
if(this.loaded){
|
||||
return Promise.resolve()
|
||||
}else if(!this.url && !this.class){
|
||||
if(loadErrors){
|
||||
return Promise.reject()
|
||||
}else{
|
||||
return Promise.resolve()
|
||||
}
|
||||
}else{
|
||||
return (this.url ? import(this.url) : Promise.resolve({
|
||||
default: this.class
|
||||
})).then(module => {
|
||||
if(this.url){
|
||||
URL.revokeObjectURL(this.url)
|
||||
delete this.url
|
||||
}else{
|
||||
delete this.class
|
||||
}
|
||||
this.loaded = true
|
||||
try{
|
||||
this.module = new module.default()
|
||||
}catch(e){
|
||||
this.error()
|
||||
var error = new Error()
|
||||
error.stack = "Error initializing plugin: " + this.name + "\n" + e.stack
|
||||
if(loadErrors){
|
||||
return Promise.reject(error)
|
||||
}else{
|
||||
console.error(error)
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
var output
|
||||
try{
|
||||
if(this.module.beforeLoad){
|
||||
this.module.beforeLoad(this)
|
||||
}
|
||||
if(this.module.load){
|
||||
output = this.module.load(this)
|
||||
}
|
||||
}catch(e){
|
||||
this.error()
|
||||
var error = new Error()
|
||||
error.stack = "Error in plugin load: " + this.name + "\n" + e.stack
|
||||
if(loadErrors){
|
||||
return Promise.reject(error)
|
||||
}else{
|
||||
console.error(error)
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
if(typeof output === "object" && output.constructor === Promise){
|
||||
return output.catch(e => {
|
||||
this.error()
|
||||
var error = new Error()
|
||||
error.stack = "Error in plugin load promise: " + this.name + (e ? "\n" + e.stack : "")
|
||||
if(loadErrors){
|
||||
return Promise.reject(error)
|
||||
}else{
|
||||
console.error(error)
|
||||
return Promise.resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
}, e => {
|
||||
this.error()
|
||||
plugins.remove(this.name)
|
||||
if(e.name === "SyntaxError"){
|
||||
var error = new SyntaxError()
|
||||
error.stack = "Error in plugin syntax: " + this.name + "\n" + e.stack
|
||||
}else{
|
||||
var error = e
|
||||
}
|
||||
if(loadErrors){
|
||||
return Promise.reject(error)
|
||||
}else{
|
||||
console.error(error)
|
||||
return Promise.resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
start(orderChange, startErrors){
|
||||
if(!orderChange){
|
||||
plugins.startOrder.push(this.name)
|
||||
}
|
||||
return this.load().then(() => {
|
||||
if(!this.started && this.module){
|
||||
this.started = true
|
||||
try{
|
||||
if(this.module.beforeStart){
|
||||
this.module.beforeStart()
|
||||
}
|
||||
if(this.module.start){
|
||||
this.module.start()
|
||||
}
|
||||
}catch(e){
|
||||
this.error()
|
||||
var error = new Error()
|
||||
error.stack = "Error in plugin start: " + this.name + "\n" + e.stack
|
||||
if(startErrors){
|
||||
return Promise.reject(error)
|
||||
}else{
|
||||
console.error(error)
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop(orderChange, noError){
|
||||
if(this.loaded && this.started){
|
||||
if(!orderChange){
|
||||
var stopIndex = plugins.startOrder.indexOf(this.name)
|
||||
if(stopIndex !== -1){
|
||||
plugins.startOrder.splice(stopIndex, 1)
|
||||
for(var i = plugins.startOrder.length; i-- > stopIndex;){
|
||||
plugins.pluginMap[plugins.startOrder[i]].stop(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.started = false
|
||||
try{
|
||||
if(this.module.beforeStop){
|
||||
this.module.beforeStop()
|
||||
}
|
||||
if(this.module.stop){
|
||||
this.module.stop()
|
||||
}
|
||||
}catch(e){
|
||||
var error = new Error()
|
||||
error.stack = "Error in plugin stop: " + this.name + "\n" + e.stack
|
||||
console.error(error)
|
||||
if(!noError){
|
||||
this.error()
|
||||
}
|
||||
}
|
||||
|
||||
if(!orderChange && stopIndex !== -1){
|
||||
for(var i = stopIndex; i < plugins.startOrder.length; i++){
|
||||
plugins.pluginMap[plugins.startOrder[i]].start(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unload(error){
|
||||
if(this.loaded){
|
||||
if(this.started){
|
||||
this.stop(false, error)
|
||||
}
|
||||
this.loaded = false
|
||||
plugins.remove(this.name)
|
||||
if(this.module){
|
||||
try{
|
||||
if(this.module.beforeUnload){
|
||||
this.module.beforeUnload()
|
||||
}
|
||||
if(this.module.unload){
|
||||
this.module.unload()
|
||||
}
|
||||
}catch(e){
|
||||
var error = new Error()
|
||||
error.stack = "Error in plugin unload: " + this.name + "\n" + e.stack
|
||||
console.error(error)
|
||||
}
|
||||
delete this.module
|
||||
}
|
||||
}
|
||||
}
|
||||
error(){
|
||||
if(this.module && this.module.error){
|
||||
try{
|
||||
this.module.error()
|
||||
}catch(e){
|
||||
var error = new Error()
|
||||
error.stack = "Error in plugin error: " + this.name + "\n" + e.stack
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
this.unload(true)
|
||||
}
|
||||
settings(){
|
||||
if(this.module && this.module.settings){
|
||||
try{
|
||||
var settings = this.module.settings()
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
this.error()
|
||||
return
|
||||
}
|
||||
if(Array.isArray(settings)){
|
||||
return settings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EditValue{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(parent, name){
|
||||
if(name){
|
||||
if(!parent){
|
||||
throw new Error("Parent is not defined")
|
||||
}
|
||||
this.name = [parent, name]
|
||||
this.delete = !(name in parent)
|
||||
}else{
|
||||
this.original = parent
|
||||
}
|
||||
}
|
||||
load(callback){
|
||||
this.loadCallback = callback
|
||||
return this
|
||||
}
|
||||
start(){
|
||||
if(this.name){
|
||||
this.original = this.name[0][this.name[1]]
|
||||
}
|
||||
try{
|
||||
var output = this.loadCallback(this.original)
|
||||
}catch(e){
|
||||
console.error(this.loadCallback)
|
||||
var error = new Error()
|
||||
error.stack = "Error editing the value of " + this.getName() + "\n" + e.stack
|
||||
throw error
|
||||
}
|
||||
if(typeof output === "undefined"){
|
||||
console.error(this.loadCallback)
|
||||
throw new Error("Error editing the value of " + this.getName() + ": A value is expected to be returned")
|
||||
}
|
||||
if(this.name){
|
||||
this.name[0][this.name[1]] = output
|
||||
}
|
||||
return output
|
||||
}
|
||||
stop(){
|
||||
if(this.name){
|
||||
if(this.delete){
|
||||
delete this.name[0][this.name[1]]
|
||||
}else{
|
||||
this.name[0][this.name[1]] = this.original
|
||||
}
|
||||
}
|
||||
return this.original
|
||||
}
|
||||
getName(){
|
||||
var name = "unknown"
|
||||
try{
|
||||
if(this.name){
|
||||
var name = (
|
||||
typeof this.name[0] === "function" && this.name[0].name
|
||||
|| (
|
||||
typeof this.name[0] === "object" && typeof this.name[0].constructor === "function" && (
|
||||
this.name[0] instanceof this.name[0].constructor ? (() => {
|
||||
var consName = this.name[0].constructor.name || ""
|
||||
return consName.slice(0, 1).toLowerCase() + consName.slice(1)
|
||||
})() : this.name[0].constructor.name + ".prototype"
|
||||
)
|
||||
) || name
|
||||
) + (this.name[1] ? "." + this.name[1] : "")
|
||||
}
|
||||
}catch(e){
|
||||
name = "error"
|
||||
}
|
||||
return name
|
||||
}
|
||||
unload(){
|
||||
delete this.name
|
||||
delete this.original
|
||||
delete this.loadCallback
|
||||
}
|
||||
}
|
||||
|
||||
class EditFunction extends EditValue{
|
||||
start(){
|
||||
if(this.name){
|
||||
this.original = this.name[0][this.name[1]]
|
||||
}
|
||||
if(typeof this.original !== "function"){
|
||||
console.error(this.loadCallback)
|
||||
var error = new Error()
|
||||
error.stack = "Error editing the function value of " + this.getName() + ": Original value is not a function"
|
||||
throw error
|
||||
}
|
||||
var args = plugins.argsFromFunc(this.original)
|
||||
try{
|
||||
var output = this.loadCallback(plugins.strFromFunc(this.original), args)
|
||||
}catch(e){
|
||||
console.error(this.loadCallback)
|
||||
var error = new Error()
|
||||
error.stack = "Error editing the function value of " + this.getName() + "\n" + e.stack
|
||||
throw error
|
||||
}
|
||||
if(typeof output === "undefined"){
|
||||
console.error(this.loadCallback)
|
||||
throw new Error("Error editing the function value of " + this.getName() + ": A value is expected to be returned")
|
||||
}
|
||||
try{
|
||||
var output = Function(...args, output)
|
||||
}catch(e){
|
||||
console.error(this.loadCallback)
|
||||
var error = new SyntaxError()
|
||||
var blob = new Blob([output], {
|
||||
type: "application/javascript"
|
||||
})
|
||||
var url = URL.createObjectURL(blob)
|
||||
error.stack = "Error editing the function value of " + this.getName() + ": Could not evaluate string, check the full string for errors: " + url + "\n" + e.stack
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(url)
|
||||
}, 5 * 60 * 1000)
|
||||
throw error
|
||||
}
|
||||
if(this.name){
|
||||
this.name[0][this.name[1]] = output
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
class Patch{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.edits = []
|
||||
this.addedLanguages = []
|
||||
}
|
||||
addEdits(...args){
|
||||
args.forEach(arg => this.edits.push(arg))
|
||||
}
|
||||
addLanguage(lang, forceSet, fallback="en"){
|
||||
if(fallback){
|
||||
lang = plugins.deepMerge({}, allStrings[fallback], lang)
|
||||
}
|
||||
this.addedLanguages.push({
|
||||
lang: lang,
|
||||
forceSet: forceSet
|
||||
})
|
||||
}
|
||||
beforeStart(){
|
||||
this.edits.forEach(edit => edit.start())
|
||||
this.addedLanguages.forEach(obj => {
|
||||
settings.addLang(obj.lang, obj.forceSet)
|
||||
})
|
||||
}
|
||||
beforeStop(){
|
||||
for(var i = this.edits.length; i--;){
|
||||
this.edits[i].stop()
|
||||
}
|
||||
for(var i = this.addedLanguages.length; i--;){
|
||||
settings.removeLang(this.addedLanguages[i].lang)
|
||||
}
|
||||
}
|
||||
beforeUnload(){
|
||||
this.edits.forEach(edit => edit.unload())
|
||||
}
|
||||
log(...args){
|
||||
var name = this.name || "Plugin"
|
||||
console.log(
|
||||
"%c[" + name + "]",
|
||||
"font-weight: bold;",
|
||||
...args
|
||||
)
|
||||
}
|
||||
}
|
||||
980
public/src/js/scoresheet.js
Normal file
980
public/src/js/scoresheet.js
Normal file
@@ -0,0 +1,980 @@
|
||||
class Scoresheet{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(controller, results, multiplayer, touchEnabled){
|
||||
this.controller = controller
|
||||
this.resultsObj = 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){
|
||||
this.results[player0][i] = results[i] === null ? null : results[i].toString()
|
||||
}
|
||||
this.multiplayer = multiplayer
|
||||
this.touchEnabled = touchEnabled
|
||||
|
||||
this.canvas = document.getElementById("canvas")
|
||||
this.ctx = this.canvas.getContext("2d")
|
||||
var resolution = settings.getItem("resolution")
|
||||
var noSmoothing = resolution === "low" || resolution === "lowest"
|
||||
if(noSmoothing){
|
||||
this.ctx.imageSmoothingEnabled = false
|
||||
}
|
||||
if(resolution === "lowest"){
|
||||
this.canvas.style.imageRendering = "pixelated"
|
||||
}
|
||||
this.game = document.getElementById("game")
|
||||
|
||||
this.fadeScreen = document.createElement("div")
|
||||
this.fadeScreen.id = "fade-screen"
|
||||
this.game.appendChild(this.fadeScreen)
|
||||
|
||||
this.font = strings.font
|
||||
this.numbersFont = "TnT, Meiryo, sans-serif"
|
||||
this.state = {
|
||||
screen: "fadeIn",
|
||||
screenMS: this.getMS(),
|
||||
startDelay: 3300,
|
||||
hasPointer: 0,
|
||||
scoreNext: false
|
||||
}
|
||||
this.frame = 1000 / 60
|
||||
this.numbers = "001122334455667788900112233445".split("")
|
||||
|
||||
this.draw = new CanvasDraw(noSmoothing)
|
||||
this.canvasCache = new CanvasCache(noSmoothing)
|
||||
this.nameplateCache = new CanvasCache(noSmoothing)
|
||||
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["enter", "space", "esc", "don_l", "don_r"]
|
||||
}, this.keyDown.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
confirm: ["a", "b", "start", "ls", "rs"]
|
||||
}, this.keyDown.bind(this))
|
||||
|
||||
this.difficulty = {
|
||||
"easy": 0,
|
||||
"normal": 1,
|
||||
"hard": 2,
|
||||
"oni": 3,
|
||||
"ura": 4
|
||||
}
|
||||
|
||||
this.scoreSaved = false
|
||||
this.redrawRunning = true
|
||||
this.redrawBind = this.redraw.bind(this)
|
||||
this.redraw()
|
||||
|
||||
assets.sounds["v_results"].play()
|
||||
assets.sounds["bgm_result"].playLoop(3, false, 0, 0.847, 17.689)
|
||||
|
||||
this.session = p2.session
|
||||
if(this.session){
|
||||
if(p2.getMessage("songsel")){
|
||||
this.toSongsel(true)
|
||||
}
|
||||
pageEvents.add(p2, "message", response => {
|
||||
if(response.type === "songsel"){
|
||||
this.toSongsel(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
pageEvents.send("scoresheet", {
|
||||
selectedSong: controller.selectedSong,
|
||||
autoPlayEnabled: controller.autoPlayEnabled,
|
||||
multiplayer: multiplayer,
|
||||
touchEnabled: touchEnabled,
|
||||
results: this.results,
|
||||
p2results: multiplayer ? p2.results : null,
|
||||
keyboardEvents: controller.keyboard.keyboardEvents,
|
||||
gamepadEvents: controller.keyboard.gamepad.gamepadEvents,
|
||||
touchEvents: controller.view.touchEvents
|
||||
})
|
||||
}
|
||||
keyDown(pressed){
|
||||
if(pressed && this.redrawing){
|
||||
this.toNext()
|
||||
}
|
||||
}
|
||||
mouseDown(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
this.canvas.style.cursor = ""
|
||||
this.state.pointerLocked = true
|
||||
}else{
|
||||
this.state.pointerLocked = false
|
||||
if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
this.toNext()
|
||||
}
|
||||
toNext(){
|
||||
var elapsed = this.getMS() - this.state.screenMS
|
||||
if(this.state.screen === "fadeIn" && elapsed >= this.state.startDelay){
|
||||
this.toScoresShown()
|
||||
}else if(this.state.screen === "scoresShown" && elapsed >= 1000){
|
||||
this.toSongsel()
|
||||
}
|
||||
}
|
||||
toScoresShown(){
|
||||
if(!p2.session){
|
||||
this.state.screen = "scoresShown"
|
||||
this.state.screenMS = this.getMS()
|
||||
this.controller.playSound("neiro_1_don", 0, true)
|
||||
}
|
||||
}
|
||||
toSongsel(fromP2){
|
||||
if(!p2.session || fromP2){
|
||||
snd.musicGain.fadeOut(0.5)
|
||||
this.state.screen = "fadeOut"
|
||||
this.state.screenMS = this.getMS()
|
||||
if(!fromP2){
|
||||
this.controller.playSound("neiro_1_don", 0, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startRedraw(){
|
||||
this.redrawing = true
|
||||
requestAnimationFrame(this.redrawBind)
|
||||
this.winW = null
|
||||
this.winH = null
|
||||
|
||||
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this))
|
||||
|
||||
if(!this.multiplayer){
|
||||
this.tetsuoHana = document.createElement("div")
|
||||
this.tetsuoHana.id = "tetsuohana"
|
||||
var flowersBg = "url('" + assets.image["results_flowers"].src + "')"
|
||||
var mikoshiBg = "url('" + assets.image["results_mikoshi"].src + "')"
|
||||
var tetsuoHanaBg = "url('" + assets.image["results_tetsuohana" + (debugObj.state === "closed" ? "" : "2")].src + "')"
|
||||
var id = ["flowers1", "flowers2", "mikoshi", "tetsuo", "hana"]
|
||||
var bg = [flowersBg, flowersBg, mikoshiBg, tetsuoHanaBg, tetsuoHanaBg]
|
||||
for(var i = 0; i < id.length; i++){
|
||||
if(id[i] === "mikoshi"){
|
||||
var divOut = document.createElement("div")
|
||||
divOut.id = id[i] + "-out"
|
||||
this.tetsuoHana.appendChild(divOut)
|
||||
}else{
|
||||
var divOut = this.tetsuoHana
|
||||
}
|
||||
var div = document.createElement("div")
|
||||
div.id = id[i]
|
||||
var divIn = document.createElement("div")
|
||||
divIn.id = id[i] + "-in"
|
||||
divIn.style.backgroundImage = bg[i]
|
||||
div.appendChild(divIn)
|
||||
divOut.appendChild(div)
|
||||
}
|
||||
this.game.appendChild(this.tetsuoHana)
|
||||
}
|
||||
}
|
||||
|
||||
redraw(){
|
||||
if(!this.redrawRunning){
|
||||
return
|
||||
}
|
||||
if(this.redrawing){
|
||||
requestAnimationFrame(this.redrawBind)
|
||||
}
|
||||
var ms = this.getMS()
|
||||
|
||||
if(!this.redrawRunning){
|
||||
return
|
||||
}
|
||||
|
||||
var ctx = this.ctx
|
||||
ctx.save()
|
||||
|
||||
var winW = innerWidth
|
||||
var winH = lastHeight
|
||||
this.pixelRatio = window.devicePixelRatio || 1
|
||||
var resolution = settings.getItem("resolution")
|
||||
if(resolution === "medium"){
|
||||
this.pixelRatio *= 0.75
|
||||
}else if(resolution === "low"){
|
||||
this.pixelRatio *= 0.5
|
||||
}else if(resolution === "lowest"){
|
||||
this.pixelRatio *= 0.25
|
||||
}
|
||||
winW *= this.pixelRatio
|
||||
winH *= this.pixelRatio
|
||||
var ratioX = winW / 1280
|
||||
var ratioY = winH / 720
|
||||
var ratio = (ratioX < ratioY ? ratioX : ratioY)
|
||||
|
||||
if(this.redrawing){
|
||||
if(this.winW !== winW || this.winH !== winH){
|
||||
this.canvas.width = Math.max(1, winW)
|
||||
this.canvas.height = Math.max(1, winH)
|
||||
ctx.scale(ratio, ratio)
|
||||
this.canvas.style.width = (winW / this.pixelRatio) + "px"
|
||||
this.canvas.style.height = (winH / this.pixelRatio) + "px"
|
||||
|
||||
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
|
||||
this.nameplateCache.resize(274, 134, ratio + 0.2)
|
||||
|
||||
if(!this.multiplayer){
|
||||
this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio)
|
||||
if(this.tetsuoHanaClass === "dance"){
|
||||
this.tetsuoHana.classList.remove("dance", "dance2")
|
||||
setTimeout(()=>{
|
||||
this.tetsuoHana.classList.add("dance2")
|
||||
},50)
|
||||
}else if(this.tetsuoHanaClass === "failed"){
|
||||
this.tetsuoHana.classList.remove("failed")
|
||||
setTimeout(()=>{
|
||||
this.tetsuoHana.classList.add("failed")
|
||||
},50)
|
||||
}
|
||||
}
|
||||
}else if(!document.hasFocus() && this.state.screen === "scoresShown"){
|
||||
if(this.state["countup0"]){
|
||||
this.stopSound("se_results_countup", 0)
|
||||
}
|
||||
if(this.state["countup1"]){
|
||||
this.stopSound("se_results_countup", 1)
|
||||
}
|
||||
return
|
||||
}else{
|
||||
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
|
||||
}
|
||||
}else{
|
||||
ctx.scale(ratio, ratio)
|
||||
if(!this.canvasCache.canvas){
|
||||
this.canvasCache.resize(winW / ratio, 80 + 1, ratio)
|
||||
}
|
||||
if(!this.nameplateCache.canvas){
|
||||
this.nameplateCache.resize(274, 67, ratio + 0.2)
|
||||
}
|
||||
}
|
||||
this.winW = winW
|
||||
this.winH = winH
|
||||
this.ratio = ratio
|
||||
winW /= ratio
|
||||
winH /= ratio
|
||||
|
||||
var frameTop = winH / 2 - 720 / 2
|
||||
var frameLeft = winW / 2 - 1280 / 2
|
||||
|
||||
var players = this.multiplayer ? 2 : 1
|
||||
var p2Offset = 298
|
||||
|
||||
var bgOffset = 0
|
||||
var elapsed = ms - this.state.screenMS
|
||||
if(this.state.screen === "fadeIn" && elapsed < 1000){
|
||||
bgOffset = Math.min(1, this.draw.easeIn(1 - elapsed / 1000)) * (winH / 2)
|
||||
}
|
||||
if((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved){
|
||||
this.saveScore()
|
||||
}
|
||||
|
||||
if(bgOffset){
|
||||
ctx.save()
|
||||
ctx.translate(0, -bgOffset)
|
||||
}
|
||||
this.draw.pattern({
|
||||
ctx: ctx,
|
||||
img: assets.image["bg_score_p1"],
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: winW,
|
||||
h: winH / 2,
|
||||
dx: frameLeft - 35,
|
||||
dy: frameTop + 17
|
||||
})
|
||||
ctx.fillStyle = "rgba(127, 28, 12, 0.5)"
|
||||
ctx.fillRect(0, winH / 2 - 12, winW, 12)
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.25)"
|
||||
ctx.fillRect(0, winH / 2, winW, 20)
|
||||
if(bgOffset !== 0){
|
||||
ctx.fillStyle = "#000"
|
||||
ctx.fillRect(0, winH / 2 - 2, winW, 2)
|
||||
}
|
||||
ctx.fillStyle = "#fa4529"
|
||||
ctx.fillRect(0, 0, winW, frameTop + 64)
|
||||
ctx.fillStyle = "#bf2900"
|
||||
ctx.fillRect(0, frameTop + 64, winW, 8)
|
||||
|
||||
if(bgOffset){
|
||||
ctx.restore()
|
||||
ctx.save()
|
||||
ctx.translate(0, bgOffset)
|
||||
}
|
||||
|
||||
this.draw.pattern({
|
||||
ctx: ctx,
|
||||
img: assets.image[this.multiplayer ? "bg_score_p2" : "bg_score_p1"],
|
||||
x: 0,
|
||||
y: winH / 2,
|
||||
w: winW,
|
||||
h: winH / 2,
|
||||
dx: frameLeft - 35,
|
||||
dy: frameTop - 17
|
||||
})
|
||||
ctx.fillStyle = this.multiplayer ? "rgba(138, 245, 247, 0.5)" : "rgba(249, 163, 149, 0.5)"
|
||||
ctx.fillRect(0, winH / 2, winW, 12)
|
||||
ctx.fillStyle = "#000"
|
||||
if(bgOffset === 0){
|
||||
ctx.fillRect(0, winH / 2 - 2, winW, 4)
|
||||
}else{
|
||||
ctx.fillRect(0, winH / 2, winW, 2)
|
||||
}
|
||||
ctx.fillStyle = this.multiplayer ? "#6bbec0" : "#fa4529"
|
||||
ctx.fillRect(0, winH - frameTop - 64, winW, frameTop + 64)
|
||||
ctx.fillStyle = this.multiplayer ? "rgba(160, 228, 229, 0.8)" : "rgba(255, 144, 116, 0.8)"
|
||||
ctx.fillRect(0, winH - frameTop - 72, winW, 7)
|
||||
ctx.fillStyle = this.multiplayer ? "#a8e0e0" : "#ff9b7a"
|
||||
ctx.fillRect(0, winH - frameTop - 66, winW, 2)
|
||||
|
||||
if(bgOffset){
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
if(this.state.screen === "scoresShown" || this.state.screen === "fadeOut"){
|
||||
var elapsed = Infinity
|
||||
}else if(this.redrawing){
|
||||
var elapsed = ms - this.state.screenMS - this.state.startDelay
|
||||
}else{
|
||||
var elapsed = 0
|
||||
}
|
||||
|
||||
var rules = this.controller.game.rules
|
||||
var failedOffset = rules.clearReached(this.results[this.player[0]].gauge) ? 0 : -2000
|
||||
if(players === 2 && failedOffset !== 0){
|
||||
var p2results = this.results[this.player[1]]
|
||||
if(p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)){
|
||||
failedOffset = 0
|
||||
}
|
||||
}
|
||||
if(elapsed >= 3100 + failedOffset){
|
||||
for(var p = 0; p < players; p++){
|
||||
ctx.save()
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
var clear = this.rules[p].clearReached(results.gauge)
|
||||
if(p === 1 || !this.multiplayer && clear){
|
||||
ctx.translate(0, 290)
|
||||
}
|
||||
if(clear){
|
||||
ctx.globalCompositeOperation = "lighter"
|
||||
}
|
||||
ctx.globalAlpha = Math.min(1, Math.max(0, (elapsed - (3100 + failedOffset)) / 500)) * 0.5
|
||||
var grd = ctx.createLinearGradient(0, frameTop + 72, 0, frameTop + 368)
|
||||
grd.addColorStop(0, "#000")
|
||||
if(clear){
|
||||
grd.addColorStop(1, "#ffffba")
|
||||
}else{
|
||||
grd.addColorStop(1, "transparent")
|
||||
}
|
||||
ctx.fillStyle = grd
|
||||
ctx.fillRect(0, frameTop + 72, winW, 286)
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
|
||||
if(elapsed >= 0){
|
||||
if(this.state.hasPointer === 0){
|
||||
this.state.hasPointer = 1
|
||||
if(!this.state.pointerLocked){
|
||||
this.canvas.style.cursor = this.session ? "" : "pointer"
|
||||
}
|
||||
}
|
||||
ctx.save()
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
this.draw.alpha(Math.min(1, elapsed / 400), ctx, ctx => {
|
||||
ctx.scale(ratio, ratio)
|
||||
ctx.translate(frameLeft, frameTop)
|
||||
|
||||
this.canvasCache.get({
|
||||
ctx: ctx,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: winW,
|
||||
h: 80,
|
||||
id: "results"
|
||||
}, ctx => {
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: strings.results,
|
||||
fontSize: 48,
|
||||
fontFamily: this.font,
|
||||
x: 23,
|
||||
y: 15,
|
||||
letterSpacing: strings.id === "en" ? 0 : 3,
|
||||
forceShadow: true
|
||||
}, [
|
||||
{x: -2, y: -2, outline: "#000", letterBorder: 22},
|
||||
{},
|
||||
{x: 2, y: 2, shadow: [2, 2, 7]},
|
||||
{x: 2, y: 2, outline: "#ad1516", letterBorder: 10},
|
||||
{x: -2, y: -2, outline: "#ff797b"},
|
||||
{outline: "#f70808"},
|
||||
{fill: "#fff", shadow: [-1, 1, 3, 1.5]}
|
||||
])
|
||||
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: this.results[this.player[0]].title,
|
||||
fontSize: 40,
|
||||
fontFamily: this.font,
|
||||
x: 1257,
|
||||
y: 20,
|
||||
width: 600,
|
||||
align: "right",
|
||||
forceShadow: true
|
||||
}, [
|
||||
{outline: "#000", letterBorder: 10, shadow: [1, 1, 3]},
|
||||
{fill: "#fff"}
|
||||
])
|
||||
})
|
||||
|
||||
ctx.save()
|
||||
for(var p = 0; p < players; p++){
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
if(p === 1){
|
||||
ctx.translate(0, p2Offset)
|
||||
}
|
||||
|
||||
ctx.drawImage(assets.image["difficulty"],
|
||||
0, 144 * this.difficulty[results.difficulty],
|
||||
168, 143,
|
||||
300, 150, 189, 162
|
||||
)
|
||||
var diff = results.difficulty
|
||||
var text = strings[diff === "ura" ? "oni" : diff]
|
||||
ctx.font = this.draw.bold(this.font) + "28px " + this.font
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "bottom"
|
||||
ctx.strokeStyle = "#000"
|
||||
ctx.fillStyle = "#fff"
|
||||
ctx.lineWidth = 9
|
||||
ctx.miterLimit = 1
|
||||
ctx.strokeText(text, 395, 308)
|
||||
ctx.fillText(text, 395, 308)
|
||||
ctx.miterLimit = 10
|
||||
|
||||
var defaultName = p === 0 ? strings.defaultName : strings.default2PName
|
||||
if(p === this.player[0]){
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
if(this.controller.autoPlayEnabled){
|
||||
ctx.drawImage(assets.image["badge_auto"],
|
||||
431, 311, 34, 34
|
||||
)
|
||||
}
|
||||
|
||||
this.draw.roundedRect({
|
||||
ctx: ctx,
|
||||
x: 532,
|
||||
y: 98,
|
||||
w: 728,
|
||||
h: 232,
|
||||
radius: 30,
|
||||
})
|
||||
ctx.fillStyle = p === 1 ? "rgba(195, 228, 229, 0.8)" : "rgba(255, 224, 216, 0.8)"
|
||||
ctx.fill()
|
||||
this.draw.roundedRect({
|
||||
ctx: ctx,
|
||||
x: 556,
|
||||
y: 237,
|
||||
w: 254,
|
||||
h: 70,
|
||||
radius: 15,
|
||||
})
|
||||
ctx.fillStyle = "#000"
|
||||
ctx.fill()
|
||||
this.draw.roundedRect({
|
||||
ctx: ctx,
|
||||
x: 559,
|
||||
y: 240,
|
||||
w: 248,
|
||||
h: 64,
|
||||
radius: 14,
|
||||
})
|
||||
ctx.fillStyle = "#eec954"
|
||||
ctx.fill()
|
||||
this.draw.roundedRect({
|
||||
ctx: ctx,
|
||||
x: 567,
|
||||
y: 248,
|
||||
w: 232,
|
||||
h: 48,
|
||||
radius: 6,
|
||||
})
|
||||
ctx.fillStyle = "#000"
|
||||
ctx.fill()
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: strings.points,
|
||||
x: 792,
|
||||
y: strings.id === "ko" ? 260 : 253,
|
||||
fontSize: 36,
|
||||
fontFamily: this.font,
|
||||
align: "right",
|
||||
width: 36
|
||||
}, [
|
||||
{fill: "#fff"},
|
||||
{outline: "#000", letterBorder: 0.5}
|
||||
])
|
||||
|
||||
this.draw.score({
|
||||
ctx: ctx,
|
||||
score: "good",
|
||||
x: 823,
|
||||
y: 192,
|
||||
results: true
|
||||
})
|
||||
this.draw.score({
|
||||
ctx: ctx,
|
||||
score: "ok",
|
||||
x: 823,
|
||||
y: 233,
|
||||
results: true
|
||||
})
|
||||
this.draw.score({
|
||||
ctx: ctx,
|
||||
score: "bad",
|
||||
x: 823,
|
||||
y: 273,
|
||||
results: true
|
||||
})
|
||||
|
||||
ctx.textAlign = "right"
|
||||
var grd = ctx.createLinearGradient(0, 0, 0, 30)
|
||||
grd.addColorStop(0.2, "#ff4900")
|
||||
grd.addColorStop(0.9, "#f7fb00")
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: strings.maxCombo,
|
||||
x: 1149,
|
||||
y: 193,
|
||||
fontSize: 29,
|
||||
fontFamily: this.font,
|
||||
align: "right",
|
||||
width: 154,
|
||||
letterSpacing: strings.id === "ja" ? 1 : 0
|
||||
}, [
|
||||
{outline: "#000", letterBorder: 8},
|
||||
{fill: grd}
|
||||
])
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: strings.drumroll,
|
||||
x: 1150,
|
||||
y: 233,
|
||||
fontSize: 29,
|
||||
fontFamily: this.font,
|
||||
align: "right",
|
||||
width: 154,
|
||||
letterSpacing: strings.id === "ja" ? 4 : 0
|
||||
}, [
|
||||
{outline: "#000", letterBorder: 8},
|
||||
{fill: "#ffc700"}
|
||||
])
|
||||
}
|
||||
ctx.restore()
|
||||
})
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
if(!this.multiplayer){
|
||||
if(elapsed >= 400 && elapsed < 3100 + failedOffset){
|
||||
if(this.tetsuoHanaClass !== "fadein"){
|
||||
this.tetsuoHana.classList.add("fadein")
|
||||
this.tetsuoHanaClass = "fadein"
|
||||
}
|
||||
}else if(elapsed >= 3100 + failedOffset){
|
||||
if(this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed"){
|
||||
if(this.tetsuoHanaClass){
|
||||
this.tetsuoHana.classList.remove(this.tetsuoHanaClass)
|
||||
}
|
||||
this.tetsuoHanaClass = this.rules[this.player[0]].clearReached(this.results[this.player[0]].gauge) ? "dance" : "failed"
|
||||
this.tetsuoHana.classList.add(this.tetsuoHanaClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(elapsed >= 800){
|
||||
ctx.save()
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
this.draw.alpha(Math.min(1, (elapsed - 800) / 500), ctx, ctx => {
|
||||
ctx.scale(ratio, ratio)
|
||||
ctx.translate(frameLeft, frameTop)
|
||||
|
||||
for(var p = 0; p < players; p++){
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
if(p === 1){
|
||||
ctx.translate(0, p2Offset)
|
||||
}
|
||||
var w = 712
|
||||
this.draw.gauge({
|
||||
ctx: ctx,
|
||||
x: 558 + w,
|
||||
y: p === 1 ? 124 : 116,
|
||||
clear: this.rules[p].gaugeClear,
|
||||
percentage: this.rules[p].gaugePercent(results.gauge),
|
||||
font: this.font,
|
||||
scale: w / 788,
|
||||
scoresheet: true,
|
||||
blue: p === 1,
|
||||
multiplayer: p === 1
|
||||
})
|
||||
this.draw.soul({
|
||||
ctx: ctx,
|
||||
x: 1215,
|
||||
y: 144,
|
||||
scale: 36 / 42,
|
||||
cleared: this.rules[p].clearReached(results.gauge)
|
||||
})
|
||||
}
|
||||
})
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
if(elapsed >= 1200){
|
||||
ctx.save()
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
var noCrownResultWait = -2000;
|
||||
|
||||
for(var p = 0; p < players; p++){
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
var crownType = null
|
||||
if(this.rules[p].clearReached(results.gauge)){
|
||||
crownType = results.bad === "0" ? "gold" : "silver"
|
||||
}
|
||||
if(crownType !== null){
|
||||
noCrownResultWait = 0;
|
||||
var amount = Math.min(1, (elapsed - 1200) / 450)
|
||||
this.draw.alpha(this.draw.easeIn(amount), ctx, ctx => {
|
||||
ctx.save()
|
||||
ctx.scale(ratio, ratio)
|
||||
ctx.translate(frameLeft, frameTop)
|
||||
if(p === 1){
|
||||
ctx.translate(0, p2Offset)
|
||||
}
|
||||
|
||||
var crownScale = 1
|
||||
var shine = 0
|
||||
if(amount < 1){
|
||||
crownScale = 2.8 * (1 - amount) + 0.9
|
||||
}else if(elapsed < 1850){
|
||||
crownScale = 0.9 + (elapsed - 1650) / 2000
|
||||
}else if(elapsed < 2200){
|
||||
shine = (elapsed - 1850) / 175
|
||||
if(shine > 1){
|
||||
shine = 2 - shine
|
||||
}
|
||||
}
|
||||
if(this.state.screen === "fadeIn" && elapsed >= 1200 && !this.state["fullcomboPlayed" + p]){
|
||||
this.state["fullcomboPlayed" + p] = true
|
||||
if(crownType === "gold"){
|
||||
this.playSound("v_results_fullcombo" + (p === 1 ? "2" : ""), p)
|
||||
}
|
||||
}
|
||||
if(this.state.screen === "fadeIn" && elapsed >= 1650 && !this.state["crownPlayed" + p]){
|
||||
this.state["crownPlayed" + p] = true
|
||||
this.playSound("se_results_crown", p)
|
||||
}
|
||||
this.draw.crown({
|
||||
ctx: ctx,
|
||||
type: crownType,
|
||||
x: 395,
|
||||
y: 218,
|
||||
scale: crownScale,
|
||||
shine: shine,
|
||||
whiteOutline: true,
|
||||
ratio: ratio
|
||||
})
|
||||
|
||||
ctx.restore()
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
if(elapsed >= 2400 + noCrownResultWait){
|
||||
ctx.save()
|
||||
ctx.translate(frameLeft, frameTop)
|
||||
|
||||
var printNumbers = ["good", "ok", "bad", "maxCombo", "drumroll"]
|
||||
if(!this.state["countupTime0"]){
|
||||
var times = {}
|
||||
var lastTime = 0
|
||||
for(var p = 0; p < players; p++){
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame
|
||||
if(currentTime > lastTime){
|
||||
lastTime = currentTime
|
||||
}
|
||||
}
|
||||
for(var i in printNumbers){
|
||||
var largestTime = 0
|
||||
for(var p = 0; p < players; p++){
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
times[printNumbers[i]] = lastTime + 500
|
||||
var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
|
||||
if(currentTime > largestTime){
|
||||
largestTime = currentTime
|
||||
}
|
||||
}
|
||||
lastTime = largestTime
|
||||
}
|
||||
this.state.fadeInEnd = lastTime
|
||||
for(var p = 0; p < players; p++){
|
||||
this.state["countupTime" + p] = times
|
||||
}
|
||||
}
|
||||
|
||||
for(var p = 0; p < players; p++){
|
||||
var results = this.results[p]
|
||||
if(!results){
|
||||
continue
|
||||
}
|
||||
if(p === 1){
|
||||
ctx.translate(0, p2Offset)
|
||||
}
|
||||
ctx.save()
|
||||
|
||||
this.state.countupShown = false
|
||||
|
||||
var points = this.getNumber(results.points, 3100 + noCrownResultWait, elapsed)
|
||||
var scale = 1.3
|
||||
ctx.font = "35px " + this.numbersFont
|
||||
ctx.translate(760, 286)
|
||||
ctx.scale(1 / scale, 1 * 1.1)
|
||||
ctx.textAlign = "center"
|
||||
ctx.fillStyle = "#fff"
|
||||
ctx.strokeStyle = "#fff"
|
||||
ctx.lineWidth = 0.5
|
||||
for(var i = 0; i < points.length; i++){
|
||||
ctx.translate(-23.3 * scale, 0)
|
||||
ctx.fillText(points[points.length - i - 1], 0, 0)
|
||||
ctx.strokeText(points[points.length - i - 1], 0, 0)
|
||||
}
|
||||
ctx.restore()
|
||||
|
||||
if(!this.state["countupTime" + p]){
|
||||
var times = {}
|
||||
var lastTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame + 1000
|
||||
for(var i in printNumbers){
|
||||
times[printNumbers[i]] = lastTime + 500
|
||||
lastTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame
|
||||
}
|
||||
this.state["countupTime" + p] = times
|
||||
}
|
||||
|
||||
for(var i in printNumbers){
|
||||
var start = this.state["countupTime" + p][printNumbers[i]]
|
||||
this.draw.layeredText({
|
||||
ctx: ctx,
|
||||
text: this.getNumber(results[printNumbers[i]], start, elapsed),
|
||||
x: 971 + 270 * Math.floor(i / 3),
|
||||
y: 196 + (40 * (i % 3)),
|
||||
fontSize: 26,
|
||||
fontFamily: this.numbersFont,
|
||||
letterSpacing: 1,
|
||||
align: "right"
|
||||
}, [
|
||||
{outline: "#000", letterBorder: 9},
|
||||
{fill: "#fff"}
|
||||
])
|
||||
}
|
||||
|
||||
if(this.state.countupShown){
|
||||
if(!this.state["countup" + p]){
|
||||
this.state["countup" + p] = true
|
||||
this.loopSound("se_results_countup", p, [0.1, false, 0, 0, 0.07])
|
||||
}
|
||||
}else if(this.state["countup" + p]){
|
||||
this.state["countup" + p] = false
|
||||
this.stopSound("se_results_countup", p)
|
||||
if(this.state.screen === "fadeIn"){
|
||||
this.playSound("neiro_1_don", p)
|
||||
}
|
||||
}
|
||||
|
||||
if(this.state.screen === "fadeIn" && elapsed >= this.state.fadeInEnd){
|
||||
this.state.screen = "scoresShown"
|
||||
this.state.screenMS = this.getMS()
|
||||
}
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
if(this.session && !this.state.scoreNext && this.state.screen === "scoresShown" && ms - this.state.screenMS >= 10000){
|
||||
this.state.scoreNext = true
|
||||
if(p2.session){
|
||||
p2.send("songsel")
|
||||
}else{
|
||||
this.toSongsel(true)
|
||||
}
|
||||
}
|
||||
|
||||
if(this.state.screen === "fadeOut"){
|
||||
if(this.state.hasPointer === 1){
|
||||
this.state.hasPointer = 2
|
||||
this.canvas.style.cursor = ""
|
||||
}
|
||||
|
||||
if(!this.fadeScreenBlack){
|
||||
this.fadeScreenBlack = true
|
||||
this.fadeScreen.style.backgroundColor = "#000"
|
||||
}
|
||||
var elapsed = ms - this.state.screenMS
|
||||
|
||||
if(elapsed >= 1000){
|
||||
this.clean()
|
||||
this.controller.songSelection(true, this.showWarning)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
getNumber(score, start, elapsed){
|
||||
var numberPos = Math.floor((elapsed - start) / this.frame)
|
||||
if(numberPos < 0){
|
||||
return ""
|
||||
}
|
||||
var output = ""
|
||||
for(var i = 0; i < score.length; i++){
|
||||
if(numberPos < 30 * (i + 1)){
|
||||
this.state.countupShown = true
|
||||
return this.numbers[numberPos % 30] + output
|
||||
}else{
|
||||
output = score[score.length - i - 1] + output
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
getSound(id, p){
|
||||
return assets.sounds[id + (this.multiplayer ? "_p" + (p + 1) : "")]
|
||||
}
|
||||
playSound(id, p){
|
||||
this.getSound(id, p).play()
|
||||
}
|
||||
loopSound(id, p, args){
|
||||
this.getSound(id, p).playLoop(...args)
|
||||
}
|
||||
stopSound(id, p){
|
||||
this.getSound(id, p).stop()
|
||||
}
|
||||
|
||||
mod(length, index){
|
||||
return ((index % length) + length) % length
|
||||
}
|
||||
|
||||
getMS(){
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
saveScore(){
|
||||
if(this.controller.saveScore){
|
||||
if(this.resultsObj.points < 0){
|
||||
this.resultsObj.points = 0
|
||||
}
|
||||
var title = this.controller.selectedSong.originalTitle
|
||||
var hash = this.controller.selectedSong.hash
|
||||
var difficulty = this.resultsObj.difficulty
|
||||
var oldScore = scoreStorage.get(hash, difficulty, true)
|
||||
var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge)
|
||||
var crown = ""
|
||||
if(clearReached){
|
||||
crown = this.resultsObj.bad === 0 ? "gold" : "silver"
|
||||
}
|
||||
if(!oldScore || oldScore.points <= this.resultsObj.points){
|
||||
if(oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)){
|
||||
crown = oldScore.crown
|
||||
}
|
||||
this.resultsObj.crown = crown
|
||||
delete this.resultsObj.title
|
||||
delete this.resultsObj.difficulty
|
||||
delete this.resultsObj.gauge
|
||||
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => {
|
||||
this.showWarning = {name: "scoreSaveFailed"}
|
||||
})
|
||||
}else if(oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)){
|
||||
oldScore.crown = crown
|
||||
scoreStorage.add(hash, difficulty, oldScore, true, title).catch(() => {
|
||||
this.showWarning = {name: "scoreSaveFailed"}
|
||||
})
|
||||
}
|
||||
}
|
||||
this.scoreSaved = true
|
||||
}
|
||||
|
||||
clean(){
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
this.draw.clean()
|
||||
this.canvasCache.clean()
|
||||
assets.sounds["bgm_result"].stop()
|
||||
snd.buffer.loadSettings()
|
||||
this.redrawRunning = false
|
||||
pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
|
||||
if(this.touchEnabled){
|
||||
pageEvents.remove(document.getElementById("touch-full-btn"), "touchend")
|
||||
}
|
||||
if(this.session){
|
||||
pageEvents.remove(p2, "message")
|
||||
}
|
||||
if(!this.multiplayer){
|
||||
delete this.tetsuoHana
|
||||
}
|
||||
delete this.ctx
|
||||
delete this.canvas
|
||||
delete this.fadeScreen
|
||||
delete this.results
|
||||
delete this.rules
|
||||
}
|
||||
}
|
||||
329
public/src/js/scorestorage.js
Normal file
329
public/src/js/scorestorage.js
Normal file
@@ -0,0 +1,329 @@
|
||||
class ScoreStorage{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
this.scores = {}
|
||||
this.scoresP2 = {}
|
||||
this.requestP2 = new Set()
|
||||
this.requestedP2 = new Set()
|
||||
this.songTitles = {}
|
||||
this.difficulty = ["oni", "ura", "hard", "normal", "easy"]
|
||||
this.scoreKeys = ["points", "good", "ok", "bad", "maxCombo", "drumroll"]
|
||||
this.crownValue = ["", "silver", "gold"]
|
||||
}
|
||||
load(strings, loadFailed){
|
||||
var scores = {}
|
||||
var scoreStrings = {}
|
||||
if(loadFailed){
|
||||
try{
|
||||
var localScores = localStorage.getItem("saveFailed")
|
||||
if(localScores){
|
||||
scoreStrings = JSON.parse(localScores)
|
||||
}
|
||||
}catch(e){}
|
||||
}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
|
||||
if(typeof scoreString === "string" && scoreString){
|
||||
var diffArray = scoreString.split(";")
|
||||
for(var i in this.difficulty){
|
||||
if(diffArray[i]){
|
||||
var crown = parseInt(diffArray[i].slice(0, 1)) || 0
|
||||
var score = {
|
||||
crown: this.crownValue[crown] || ""
|
||||
}
|
||||
var scoreArray = diffArray[i].slice(1).split(",")
|
||||
for(var j in this.scoreKeys){
|
||||
var name = this.scoreKeys[j]
|
||||
var value = parseInt(scoreArray[j] || 0, 36) || 0
|
||||
if(value < 0){
|
||||
value = 0
|
||||
}
|
||||
score[name] = value
|
||||
}
|
||||
if(!songAdded){
|
||||
scores[hash] = {title: null}
|
||||
songAdded = true
|
||||
}
|
||||
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(){
|
||||
for(var hash in this.scores){
|
||||
this.writeString(hash)
|
||||
}
|
||||
this.write()
|
||||
return this.sendToServer({
|
||||
scores: this.prepareScores(this.scoreStrings),
|
||||
is_import: true
|
||||
})
|
||||
}
|
||||
write(){
|
||||
if(!account.loggedIn){
|
||||
try{
|
||||
localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings))
|
||||
}catch(e){}
|
||||
}
|
||||
}
|
||||
writeString(hash){
|
||||
var score = this.scores[hash]
|
||||
var diffArray = []
|
||||
var notEmpty = false
|
||||
for(var i = this.difficulty.length; i--;){
|
||||
var diff = this.difficulty[i]
|
||||
if(score[diff]){
|
||||
var scoreArray = []
|
||||
var crown = this.crownValue.indexOf(score[diff].crown).toString()
|
||||
for(var j in this.scoreKeys){
|
||||
var name = this.scoreKeys[j]
|
||||
var value = score[diff][name]
|
||||
value = Math.floor(value).toString(36)
|
||||
scoreArray.push(value)
|
||||
}
|
||||
diffArray.unshift(crown + scoreArray.join(","))
|
||||
notEmpty = true
|
||||
}else if(notEmpty){
|
||||
diffArray.unshift("")
|
||||
}
|
||||
}
|
||||
this.scoreStrings[hash] = diffArray.join(";")
|
||||
}
|
||||
titleHash(song){
|
||||
if(song in this.songTitles){
|
||||
return this.songTitles[song]
|
||||
}else{
|
||||
return song
|
||||
}
|
||||
}
|
||||
get(song, difficulty, isHash){
|
||||
if(!song){
|
||||
return this.scores
|
||||
}else{
|
||||
var hash = isHash ? song : this.titleHash(song)
|
||||
if(difficulty){
|
||||
if(hash in this.scores){
|
||||
return this.scores[hash][difficulty]
|
||||
}
|
||||
}else{
|
||||
return this.scores[hash]
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
if(!(hash in this.scores)){
|
||||
this.scores[hash] = {}
|
||||
}
|
||||
if(difficulty){
|
||||
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.writeString(hash)
|
||||
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(){
|
||||
var template = {crown: ""}
|
||||
for(var i in this.scoreKeys){
|
||||
var name = this.scoreKeys[i]
|
||||
template[name] = 0
|
||||
}
|
||||
return template
|
||||
}
|
||||
remove(song, difficulty, isHash){
|
||||
var hash = isHash ? song : this.titleHash(song)
|
||||
if(hash in this.scores){
|
||||
if(difficulty){
|
||||
if(difficulty in this.scores[hash]){
|
||||
delete this.scores[hash][difficulty]
|
||||
var noDiff = true
|
||||
for(var i in this.difficulty){
|
||||
if(this.scores[hash][this.difficulty[i]]){
|
||||
noDiff = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if(noDiff){
|
||||
delete this.scores[hash]
|
||||
delete this.scoreStrings[hash]
|
||||
}else{
|
||||
this.writeString(hash)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
delete this.scores[hash]
|
||||
delete this.scoreStrings[hash]
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
678
public/src/js/search.js
Normal file
678
public/src/js/search.js
Normal file
@@ -0,0 +1,678 @@
|
||||
class Search{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(songSelect){
|
||||
this.songSelect = songSelect
|
||||
this.opened = false
|
||||
this.enabled = true
|
||||
|
||||
this.style = document.createElement("style")
|
||||
var css = []
|
||||
for(var i in this.songSelect.songSkin){
|
||||
var skin = this.songSelect.songSkin[i]
|
||||
if("id" in skin || i === "default"){
|
||||
var id = "id" in skin ? ("cat" + skin.id) : i
|
||||
|
||||
css.push(loader.cssRuleset({
|
||||
[".song-search-" + id]: {
|
||||
"background-color": skin.background
|
||||
},
|
||||
[".song-search-" + id + "::before"]: {
|
||||
"border-color": skin.border[0],
|
||||
"border-bottom-color": skin.border[1],
|
||||
"border-right-color": skin.border[1]
|
||||
},
|
||||
[".song-search-" + id + " .song-search-result-title::before, .song-search-" + id + " .song-search-result-subtitle::before"]: {
|
||||
"-webkit-text-stroke-color": skin.outline
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
this.style.appendChild(document.createTextNode(css.join("\n")))
|
||||
loader.screen.appendChild(this.style)
|
||||
}
|
||||
|
||||
normalizeString(string){
|
||||
string = string
|
||||
.replace('’', '\'').replace('“', '"').replace('”', '"')
|
||||
.replace('。', '.').replace(',', ',').replace('、', ',')
|
||||
|
||||
kanaPairs.forEach(pair => {
|
||||
string = string.replace(pair[1], pair[0])
|
||||
})
|
||||
|
||||
return string.normalize("NFKD").replace(/[\u0300-\u036f]/g, "")
|
||||
}
|
||||
|
||||
perform(query){
|
||||
var results = []
|
||||
var filters = {}
|
||||
|
||||
var querySplit = query.split(" ").filter(word => {
|
||||
if(word.length > 0){
|
||||
var parts = word.toLowerCase().split(":")
|
||||
if(parts.length > 1){
|
||||
switch(parts[0]){
|
||||
case "easy":
|
||||
case "normal":
|
||||
case "hard":
|
||||
case "oni":
|
||||
case "ura":
|
||||
var range = this.parseRange(parts[1])
|
||||
if(range){
|
||||
filters[parts[0]] = range
|
||||
}
|
||||
break
|
||||
case "extreme":
|
||||
var range = this.parseRange(parts[1])
|
||||
if(range){
|
||||
filters.oni = this.parseRange(parts[1])
|
||||
}
|
||||
break
|
||||
case "clear":
|
||||
case "silver":
|
||||
case "gold":
|
||||
case "genre":
|
||||
case "lyrics":
|
||||
case "creative":
|
||||
case "played":
|
||||
case "maker":
|
||||
case "diverge":
|
||||
case "random":
|
||||
case "all":
|
||||
filters[parts[0]] = parts[1]
|
||||
break
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
query = this.normalizeString(querySplit.join(" ").trim())
|
||||
|
||||
var totalFilters = Object.keys(filters).length
|
||||
var random = false
|
||||
var allResults = false
|
||||
for(var i = 0; i < assets.songs.length; i++){
|
||||
var song = assets.songs[i]
|
||||
var passedFilters = 0
|
||||
|
||||
Object.keys(filters).forEach(filter => {
|
||||
var value = filters[filter]
|
||||
switch(filter){
|
||||
case "easy":
|
||||
case "normal":
|
||||
case "hard":
|
||||
case "oni":
|
||||
case "ura":
|
||||
if(song.courses[filter] && song.courses[filter].stars >= value.min && song.courses[filter].stars <= value.max){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "clear":
|
||||
case "silver":
|
||||
case "gold":
|
||||
if(value === "any"){
|
||||
var score = scoreStorage.scores[song.hash]
|
||||
scoreStorage.difficulty.forEach(difficulty => {
|
||||
if(score && score[difficulty] && score[difficulty].crown && (filter === "clear" || score[difficulty].crown === filter)){
|
||||
passedFilters++
|
||||
}
|
||||
})
|
||||
} else {
|
||||
var score = scoreStorage.scores[song.hash]
|
||||
if(score && score[value] && score[value].crown && (filter === "clear" || score[value].crown === filter)){
|
||||
passedFilters++
|
||||
}
|
||||
}
|
||||
break
|
||||
case "played":
|
||||
var score = scoreStorage.scores[song.hash]
|
||||
if((value === "yes" && score) || (value === "no" && !score)){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "lyrics":
|
||||
if((value === "yes" && song.lyrics) || (value === "no" && !song.lyrics)){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "creative":
|
||||
if((value === "yes" && song.maker) || (value === "no" && !song.maker)){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "maker":
|
||||
if(song.maker && song.maker.name.toLowerCase().includes(value.toLowerCase())){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "genre":
|
||||
var cat = assets.categories.find(cat => cat.id === song.category_id)
|
||||
var aliases = cat.aliases ? cat.aliases.concat([cat.title]) : [cat.title]
|
||||
|
||||
if(aliases.find(alias => alias.toLowerCase() === value.toLowerCase())){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "diverge":
|
||||
var branch = Object.values(song.courses).find(course => course && course.branch)
|
||||
if((value === "yes" && branch) || (value === "no" && !branch)){
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "random":
|
||||
if(value === "yes" || value === "no"){
|
||||
random = value === "yes"
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
case "all":
|
||||
if(value === "yes" || value === "no"){
|
||||
allResults = value === "yes"
|
||||
passedFilters++
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
if(passedFilters === totalFilters){
|
||||
results.push(song)
|
||||
}
|
||||
}
|
||||
|
||||
var maxResults = allResults ? Infinity : (totalFilters > 0 && !query ? 100 : 50)
|
||||
|
||||
if(query){
|
||||
results = fuzzysort.go(query, results, {
|
||||
keys: ["titlePrepared", "subtitlePrepared"],
|
||||
allowTypo: true,
|
||||
limit: maxResults,
|
||||
scoreFn: a => {
|
||||
if(a[0]){
|
||||
var score0 = a[0].score
|
||||
a[0].ranges = this.indexesToRanges(a[0].indexes)
|
||||
if(a[0].indexes.length > 1){
|
||||
var rangeAmount = a[0].ranges.length
|
||||
var lastIdx = -3
|
||||
a[0].ranges.forEach(range => {
|
||||
if(range[0] - lastIdx <= 2){
|
||||
rangeAmount--
|
||||
score0 -= 1000
|
||||
}
|
||||
lastIdx = range[1]
|
||||
})
|
||||
var index = a[0].target.toLowerCase().indexOf(query)
|
||||
if(index !== -1){
|
||||
a[0].ranges = [[index, index + query.length - 1]]
|
||||
}else if(rangeAmount > a[0].indexes.length / 2){
|
||||
score0 = -Infinity
|
||||
a[0].ranges = null
|
||||
}else if(rangeAmount !== 1){
|
||||
score0 -= 9000
|
||||
}
|
||||
}
|
||||
}
|
||||
if(a[1]){
|
||||
var score1 = a[1].score - 1000
|
||||
a[1].ranges = this.indexesToRanges(a[1].indexes)
|
||||
if(a[1].indexes.length > 1){
|
||||
var rangeAmount = a[1].ranges.length
|
||||
var lastIdx = -3
|
||||
a[1].ranges.forEach(range => {
|
||||
if(range[0] - lastIdx <= 2){
|
||||
rangeAmount--
|
||||
score1 -= 1000
|
||||
}
|
||||
lastIdx = range[1]
|
||||
})
|
||||
var index = a[1].target.indexOf(query)
|
||||
if(index !== -1){
|
||||
a[1].ranges = [[index, index + query.length - 1]]
|
||||
}else if(rangeAmount > a[1].indexes.length / 2){
|
||||
score1 = -Infinity
|
||||
a[1].ranges = null
|
||||
}else if(rangeAmount !== 1){
|
||||
score1 -= 9000
|
||||
}
|
||||
}
|
||||
}
|
||||
if(random){
|
||||
var rand = Math.random() * -9000
|
||||
if(score0 !== -Infinity){
|
||||
score0 = rand
|
||||
}
|
||||
if(score1 !== -Infinity){
|
||||
score1 = rand
|
||||
}
|
||||
}
|
||||
if(a[0]){
|
||||
return a[1] ? Math.max(score0, score1) : score0
|
||||
}else{
|
||||
return a[1] ? score1 : -Infinity
|
||||
}
|
||||
}
|
||||
})
|
||||
}else{
|
||||
if(random){
|
||||
for(var i = results.length - 1; i > 0; i--){
|
||||
var j = Math.floor(Math.random() * (i + 1))
|
||||
var temp = results[i]
|
||||
results[i] = results[j]
|
||||
results[j] = temp
|
||||
}
|
||||
}
|
||||
results = results.slice(0, maxResults).map(result => {
|
||||
return {obj: result}
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
createResult(result, resultWidth, fontSize){
|
||||
var song = result.obj
|
||||
var title = this.songSelect.getLocalTitle(song.title, song.title_lang)
|
||||
var subtitle = this.songSelect.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
|
||||
|
||||
var id = "default"
|
||||
if(song.category_id){
|
||||
var cat = assets.categories.find(cat => cat.id === song.category_id)
|
||||
if(cat && "id" in cat){
|
||||
id = "cat" + cat.id
|
||||
}
|
||||
}
|
||||
|
||||
var resultDiv = document.createElement("div")
|
||||
resultDiv.classList.add("song-search-result", "song-search-" + id)
|
||||
resultDiv.dataset.songId = song.id
|
||||
|
||||
var resultInfoDiv = document.createElement("div")
|
||||
resultInfoDiv.classList.add("song-search-result-info")
|
||||
var resultInfoTitle = document.createElement("span")
|
||||
resultInfoTitle.classList.add("song-search-result-title")
|
||||
|
||||
resultInfoTitle.appendChild(this.highlightResult(title, result[0]))
|
||||
resultInfoTitle.setAttribute("alt", title)
|
||||
|
||||
resultInfoDiv.appendChild(resultInfoTitle)
|
||||
|
||||
if(subtitle){
|
||||
resultInfoDiv.appendChild(document.createElement("br"))
|
||||
var resultInfoSubtitle = document.createElement("span")
|
||||
resultInfoSubtitle.classList.add("song-search-result-subtitle")
|
||||
|
||||
resultInfoSubtitle.appendChild(this.highlightResult(subtitle, result[1]))
|
||||
resultInfoSubtitle.setAttribute("alt", subtitle)
|
||||
|
||||
resultInfoDiv.appendChild(resultInfoSubtitle)
|
||||
}
|
||||
|
||||
resultDiv.appendChild(resultInfoDiv)
|
||||
|
||||
var courses = ["easy", "normal", "hard", "oni", "ura"]
|
||||
courses.forEach(course => {
|
||||
var courseDiv = document.createElement("div")
|
||||
courseDiv.classList.add("song-search-result-course", "song-search-result-" + course)
|
||||
if (song.courses[course]) {
|
||||
var crown = "noclear"
|
||||
if (scoreStorage.scores[song.hash]) {
|
||||
if (scoreStorage.scores[song.hash][course]) {
|
||||
crown = scoreStorage.scores[song.hash][course].crown || "noclear"
|
||||
}
|
||||
}
|
||||
var courseCrown = document.createElement("div")
|
||||
courseCrown.classList.add("song-search-result-crown", "song-search-result-" + crown)
|
||||
var courseStars = document.createElement("div")
|
||||
courseStars.classList.add("song-search-result-stars")
|
||||
courseStars.innerText = song.courses[course].stars + "\u2605"
|
||||
|
||||
courseDiv.appendChild(courseCrown)
|
||||
courseDiv.appendChild(courseStars)
|
||||
} else {
|
||||
courseDiv.classList.add("song-search-result-hidden")
|
||||
}
|
||||
|
||||
resultDiv.appendChild(courseDiv)
|
||||
})
|
||||
|
||||
this.songSelect.ctx.font = (1.2 * fontSize) + "px " + strings.font
|
||||
var titleWidth = this.songSelect.ctx.measureText(title).width
|
||||
var titleRatio = resultWidth / titleWidth
|
||||
if(titleRatio < 1){
|
||||
resultInfoTitle.style.transform = "scale(" + titleRatio + ", 1)"
|
||||
}
|
||||
if(subtitle){
|
||||
this.songSelect.ctx.font = (0.8 * 1.2 * fontSize) + "px " + strings.font
|
||||
var subtitleWidth = this.songSelect.ctx.measureText(subtitle).width
|
||||
var subtitleRatio = resultWidth / subtitleWidth
|
||||
if(subtitleRatio < 1){
|
||||
resultInfoSubtitle.style.transform = "scale(" + subtitleRatio + ", 1)"
|
||||
}
|
||||
}
|
||||
|
||||
return resultDiv
|
||||
}
|
||||
|
||||
highlightResult(text, result){
|
||||
var fragment = document.createDocumentFragment()
|
||||
var ranges = (result ? result.ranges : null) || []
|
||||
var lastIdx = 0
|
||||
ranges.forEach(range => {
|
||||
if(lastIdx !== range[0]){
|
||||
fragment.appendChild(document.createTextNode(text.slice(lastIdx, range[0])))
|
||||
}
|
||||
var span = document.createElement("span")
|
||||
span.classList.add("highlighted-text")
|
||||
span.innerText = text.slice(range[0], range[1] + 1)
|
||||
fragment.appendChild(span)
|
||||
lastIdx = range[1] + 1
|
||||
})
|
||||
if(text.length !== lastIdx){
|
||||
fragment.appendChild(document.createTextNode(text.slice(lastIdx)))
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
|
||||
setActive(idx){
|
||||
this.songSelect.playSound("se_ka")
|
||||
var active = this.div.querySelector(":scope .song-search-result-active")
|
||||
if(active){
|
||||
active.classList.remove("song-search-result-active")
|
||||
}
|
||||
|
||||
if(idx === null){
|
||||
this.active = null
|
||||
return
|
||||
}
|
||||
|
||||
var el = this.results[idx]
|
||||
this.input.blur()
|
||||
el.classList.add("song-search-result-active")
|
||||
this.scrollTo(el)
|
||||
|
||||
this.active = idx
|
||||
}
|
||||
|
||||
display(fromButton=false){
|
||||
if(!this.enabled){
|
||||
return
|
||||
}
|
||||
if(this.opened){
|
||||
return this.remove(true)
|
||||
}
|
||||
|
||||
this.opened = true
|
||||
this.results = []
|
||||
this.div = document.createElement("div")
|
||||
this.div.innerHTML = assets.pages["search"]
|
||||
|
||||
this.container = this.div.querySelector(":scope #song-search-container")
|
||||
if(this.touchEnabled){
|
||||
this.container.classList.add("touch-enabled")
|
||||
}
|
||||
pageEvents.add(this.container, ["mousedown", "touchstart"], this.onClick.bind(this))
|
||||
|
||||
this.input = this.div.querySelector(":scope #song-search-input")
|
||||
this.input.setAttribute("placeholder", strings.search.searchInput)
|
||||
pageEvents.add(this.input, ["input"], () => this.onInput())
|
||||
|
||||
this.songSelect.playSound("se_pause")
|
||||
loader.screen.appendChild(this.div)
|
||||
this.setTip()
|
||||
cancelTouch = false
|
||||
noResizeRoot = true
|
||||
if(this.songSelect.songs[this.songSelect.selectedSong].courses){
|
||||
snd.previewGain.setVolumeMul(0.5)
|
||||
}else if(this.songSelect.bgmEnabled){
|
||||
snd.musicGain.setVolumeMul(0.5)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.input.focus()
|
||||
this.input.setSelectionRange(0, this.input.value.length)
|
||||
}, 10)
|
||||
|
||||
var lastQuery = localStorage.getItem("lastSearchQuery")
|
||||
if(lastQuery){
|
||||
this.input.value = lastQuery
|
||||
this.input.dispatchEvent(new Event("input", {
|
||||
value: lastQuery
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
remove(byUser=false){
|
||||
if(this.opened){
|
||||
this.opened = false
|
||||
if(byUser){
|
||||
this.songSelect.playSound("se_cancel")
|
||||
}
|
||||
|
||||
pageEvents.remove(this.div.querySelector(":scope #song-search-container"),
|
||||
["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.input, ["input"])
|
||||
|
||||
this.div.remove()
|
||||
delete this.results
|
||||
delete this.div
|
||||
delete this.input
|
||||
delete this.tip
|
||||
delete this.active
|
||||
cancelTouch = true
|
||||
noResizeRoot = false
|
||||
if(this.songSelect.songs[this.songSelect.selectedSong].courses){
|
||||
snd.previewGain.setVolumeMul(1)
|
||||
}else if(this.songSelect.bgmEnabled){
|
||||
snd.musicGain.setVolumeMul(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTip(tip, error=false){
|
||||
if(this.tip){
|
||||
this.tip.remove()
|
||||
delete this.tip
|
||||
}
|
||||
|
||||
if(!tip){
|
||||
tip = strings.search.tip + " " + strings.search.tips[Math.floor(Math.random() * strings.search.tips.length)]
|
||||
}
|
||||
|
||||
var resultsDiv = this.div.querySelector(":scope #song-search-results")
|
||||
resultsDiv.innerHTML = ""
|
||||
this.results = []
|
||||
|
||||
this.tip = document.createElement("div")
|
||||
this.tip.id = "song-search-tip"
|
||||
this.tip.innerText = tip
|
||||
this.div.querySelector(":scope #song-search").appendChild(this.tip)
|
||||
|
||||
if(error){
|
||||
this.tip.classList.add("song-search-tip-error")
|
||||
}
|
||||
}
|
||||
|
||||
proceed(songId){
|
||||
if (/^-?\d+$/.test(songId)) {
|
||||
songId = parseInt(songId)
|
||||
}
|
||||
|
||||
var song = this.songSelect.songs.find(song => song.id === songId)
|
||||
this.remove()
|
||||
this.songSelect.playBgm(false)
|
||||
if(this.songSelect.previewing === "muted"){
|
||||
this.songSelect.previewing = null
|
||||
}
|
||||
|
||||
var songIndex = this.songSelect.songs.findIndex(song => song.id === songId)
|
||||
this.songSelect.setSelectedSong(songIndex)
|
||||
this.songSelect.toSelectDifficulty()
|
||||
}
|
||||
|
||||
scrollTo(element){
|
||||
var parentNode = element.parentNode
|
||||
var selected = element.getBoundingClientRect()
|
||||
var parent = parentNode.getBoundingClientRect()
|
||||
var scrollY = parentNode.scrollTop
|
||||
var selectedPosTop = selected.top - selected.height / 2
|
||||
if(Math.floor(selectedPosTop) < Math.floor(parent.top)){
|
||||
parentNode.scrollTop += selectedPosTop - parent.top
|
||||
}else{
|
||||
var selectedPosBottom = selected.top + selected.height * 1.5 - parent.top
|
||||
if(Math.floor(selectedPosBottom) > Math.floor(parent.height)){
|
||||
parentNode.scrollTop += selectedPosBottom - parent.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseRange(string){
|
||||
var range = string.split("-")
|
||||
if(range.length == 1){
|
||||
var min = parseInt(range[0]) || 0
|
||||
return min > 0 ? {min: min, max: min} : false
|
||||
} else if(range.length == 2){
|
||||
var min = parseInt(range[0]) || 0
|
||||
var max = parseInt(range[1]) || 0
|
||||
return min > 0 && max > 0 ? {min: min, max: max} : false
|
||||
}
|
||||
}
|
||||
|
||||
indexesToRanges(indexes){
|
||||
var ranges = []
|
||||
var range
|
||||
indexes.forEach(idx => {
|
||||
if(range && range[1] === idx - 1){
|
||||
range[1] = idx
|
||||
}else{
|
||||
range = [idx, idx]
|
||||
ranges.push(range)
|
||||
}
|
||||
})
|
||||
return ranges
|
||||
}
|
||||
|
||||
onInput(resize){
|
||||
var text = this.input.value
|
||||
localStorage.setItem("lastSearchQuery", text)
|
||||
text = text.toLowerCase()
|
||||
|
||||
if(text.length === 0){
|
||||
if(!resize){
|
||||
this.setTip()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var new_results = this.perform(text)
|
||||
|
||||
if(new_results.length === 0){
|
||||
this.setTip(strings.search.noResults, true)
|
||||
return
|
||||
}else if(this.tip){
|
||||
this.tip.remove()
|
||||
delete this.tip
|
||||
}
|
||||
|
||||
var resultsDiv = this.div.querySelector(":scope #song-search-results")
|
||||
resultsDiv.innerHTML = ""
|
||||
this.results = []
|
||||
|
||||
var fontSize = parseFloat(getComputedStyle(this.div.querySelector(":scope #song-search")).fontSize.slice(0, -2))
|
||||
var resultsWidth = parseFloat(getComputedStyle(resultsDiv).width.slice(0, -2))
|
||||
var vmin = Math.min(innerWidth, lastHeight) / 100
|
||||
var courseWidth = Math.min(3 * fontSize * 1.2, 7 * vmin)
|
||||
var resultWidth = resultsWidth - 1.8 * fontSize - 0.8 * fontSize - (courseWidth + 0.4 * fontSize * 1.2) * 5 - 0.6 * fontSize
|
||||
|
||||
this.songSelect.ctx.save()
|
||||
|
||||
var fragment = document.createDocumentFragment()
|
||||
new_results.forEach(result => {
|
||||
var result = this.createResult(result, resultWidth, fontSize)
|
||||
fragment.appendChild(result)
|
||||
this.results.push(result)
|
||||
})
|
||||
resultsDiv.appendChild(fragment)
|
||||
|
||||
this.songSelect.ctx.restore()
|
||||
}
|
||||
|
||||
onClick(e){
|
||||
if((e.target.id === "song-search-container" || e.target.id === "song-search-close") && e.which === 1){
|
||||
this.remove(true)
|
||||
}else if(e.which === 1){
|
||||
var songEl = e.target.closest(".song-search-result")
|
||||
if(songEl){
|
||||
var songId = songEl.dataset.songId
|
||||
this.proceed(songId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyPress(pressed, name, event, repeat, ctrl){
|
||||
if(name === "back" || (event && event.keyCode && event.keyCode === 70 && ctrl)) {
|
||||
this.remove(true)
|
||||
if(event){
|
||||
event.preventDefault()
|
||||
}
|
||||
}else if(name === "down" && this.results.length){
|
||||
if(this.input == document.activeElement && this.results){
|
||||
this.setActive(0)
|
||||
}else if(this.active === this.results.length - 1){
|
||||
this.setActive(null)
|
||||
this.input.focus()
|
||||
}else if(Number.isInteger(this.active)){
|
||||
this.setActive(this.active + 1)
|
||||
}else{
|
||||
this.setActive(0)
|
||||
}
|
||||
}else if(name === "up" && this.results.length){
|
||||
if(this.input == document.activeElement && this.results){
|
||||
this.setActive(this.results.length - 1)
|
||||
}else if(this.active === 0){
|
||||
this.setActive(null)
|
||||
this.input.focus()
|
||||
setTimeout(() => {
|
||||
this.input.setSelectionRange(this.input.value.length, this.input.value.length)
|
||||
}, 0)
|
||||
}else if(Number.isInteger(this.active)){
|
||||
this.setActive(this.active - 1)
|
||||
}else{
|
||||
this.setActive(this.results.length - 1)
|
||||
}
|
||||
}else if(name === "confirm"){
|
||||
if(Number.isInteger(this.active)){
|
||||
this.proceed(this.results[this.active].dataset.songId)
|
||||
}else{
|
||||
this.onInput()
|
||||
if(event.keyCode === 13 && this.songSelect.touchEnabled){
|
||||
this.input.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redraw(){
|
||||
if(this.opened && this.container){
|
||||
var vmin = Math.min(innerWidth, lastHeight) / 100
|
||||
if(this.vmin !== vmin){
|
||||
this.container.style.setProperty("--vmin", vmin + "px")
|
||||
this.vmin = vmin
|
||||
}
|
||||
}else{
|
||||
this.vmin = null
|
||||
}
|
||||
}
|
||||
|
||||
clean(){
|
||||
loader.screen.removeChild(this.style)
|
||||
fuzzysort.cleanup()
|
||||
delete this.container
|
||||
delete this.style
|
||||
delete this.songSelect
|
||||
}
|
||||
}
|
||||
92
public/src/js/session.js
Normal file
92
public/src/js/session.js
Normal file
@@ -0,0 +1,92 @@
|
||||
class Session{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(touchEnabled){
|
||||
this.touchEnabled = touchEnabled
|
||||
loader.changePage("session", true)
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
if(touchEnabled){
|
||||
this.getElement("view-outer").classList.add("touch-enabled")
|
||||
}
|
||||
this.sessionInvite = document.getElementById("session-invite")
|
||||
|
||||
var tutorialTitle = this.getElement("view-title")
|
||||
tutorialTitle.innerText = strings.session.multiplayerSession
|
||||
tutorialTitle.setAttribute("alt", strings.session.multiplayerSession)
|
||||
this.sessionInvite.parentNode.insertBefore(document.createTextNode(strings.session.linkTutorial), this.sessionInvite)
|
||||
this.endButton.innerText = strings.session.cancel
|
||||
this.endButton.setAttribute("alt", strings.session.cancel)
|
||||
|
||||
pageEvents.add(window, ["mousedown", "touchstart"], this.mouseDown.bind(this))
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["esc"]
|
||||
}, this.keyPress.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
confirm: ["start", "b", "ls", "rs"]
|
||||
}, this.keyPress.bind(this))
|
||||
|
||||
p2.hashLock = true
|
||||
pageEvents.add(p2, "message", response => {
|
||||
if(response.type === "invite"){
|
||||
this.sessionInvite.innerText = location.origin + location.pathname + "#" + response.value
|
||||
p2.hash(response.value)
|
||||
}else if(response.type === "songsel"){
|
||||
p2.clearMessage("users")
|
||||
this.onEnd(true)
|
||||
pageEvents.send("session-start", "host")
|
||||
}
|
||||
})
|
||||
p2.send("invite", {
|
||||
id: null,
|
||||
name: account.loggedIn ? account.displayName : null,
|
||||
don: account.loggedIn ? account.don : null
|
||||
})
|
||||
pageEvents.send("session")
|
||||
}
|
||||
getElement(name){
|
||||
return loader.screen.getElementsByClassName(name)[0]
|
||||
}
|
||||
mouseDown(event){
|
||||
if(event.type === "mousedown" && event.which !== 1){
|
||||
return
|
||||
}
|
||||
if(event.target === this.sessionInvite){
|
||||
this.sessionInvite.focus()
|
||||
}else{
|
||||
getSelection().removeAllRanges()
|
||||
this.sessionInvite.blur()
|
||||
}
|
||||
if(event.target === this.endButton){
|
||||
this.onEnd()
|
||||
}
|
||||
}
|
||||
keyPress(pressed){
|
||||
if(pressed){
|
||||
this.onEnd()
|
||||
}
|
||||
}
|
||||
onEnd(fromP2){
|
||||
if(!p2.session){
|
||||
p2.send("leave")
|
||||
p2.hash("")
|
||||
p2.hashLock = false
|
||||
pageEvents.send("session-cancel")
|
||||
}else if(!fromP2){
|
||||
return p2.send("songsel")
|
||||
}
|
||||
this.clean()
|
||||
assets.sounds["se_don"].play()
|
||||
setTimeout(() => {
|
||||
new SongSelect(false, false, this.touchEnabled)
|
||||
}, 500)
|
||||
}
|
||||
clean(){
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
pageEvents.remove(window, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(p2, "message")
|
||||
delete this.endButton
|
||||
delete this.sessionInvite
|
||||
}
|
||||
}
|
||||
1313
public/src/js/settings.js
Normal file
1313
public/src/js/settings.js
Normal file
File diff suppressed because it is too large
Load Diff
3277
public/src/js/songselect.js
Normal file
3277
public/src/js/songselect.js
Normal file
File diff suppressed because it is too large
Load Diff
246
public/src/js/soundbuffer.js
Normal file
246
public/src/js/soundbuffer.js
Normal file
@@ -0,0 +1,246 @@
|
||||
class SoundBuffer{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(){
|
||||
var AudioContext = window.AudioContext || window.webkitAudioContext
|
||||
this.context = new AudioContext()
|
||||
this.audioDecoder = this.context.decodeAudioData.bind(this.context)
|
||||
this.oggDecoder = this.audioDecoder
|
||||
pageEvents.add(window, ["click", "touchend", "keypress"], this.pageClicked.bind(this))
|
||||
this.gainList = []
|
||||
}
|
||||
load(file, gain){
|
||||
var decoder = file.name.endsWith(".ogg") ? this.oggDecoder : this.audioDecoder
|
||||
return file.arrayBuffer().then(response => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return decoder(response, resolve, reject)
|
||||
}).catch(error => Promise.reject([error, file.url]))
|
||||
}).then(buffer => {
|
||||
return new Sound(gain || {soundBuffer: this}, buffer)
|
||||
})
|
||||
}
|
||||
createGain(channel){
|
||||
var gain = new SoundGain(this, channel)
|
||||
this.gainList.push(gain)
|
||||
return gain
|
||||
}
|
||||
setCrossfade(gain1, gain2, median){
|
||||
if(!Array.isArray(gain1)){
|
||||
gain1 = [gain1]
|
||||
}
|
||||
if(!Array.isArray(gain2)){
|
||||
gain2 = [gain2]
|
||||
}
|
||||
gain1.forEach(gain => gain.setCrossfade(1 - median))
|
||||
gain2.forEach(gain => gain.setCrossfade(median))
|
||||
}
|
||||
getTime(){
|
||||
return this.context.currentTime
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
time = (time || 0)
|
||||
if(time < 0){
|
||||
time = 0
|
||||
}
|
||||
return time + (absolute ? 0 : this.getTime())
|
||||
}
|
||||
createSource(sound){
|
||||
var source = this.context.createBufferSource()
|
||||
source.buffer = sound.buffer
|
||||
source.connect(sound.gain.gainNode || this.context.destination)
|
||||
return source
|
||||
}
|
||||
pageClicked(){
|
||||
if(this.context.state === "suspended"){
|
||||
this.context.resume()
|
||||
}
|
||||
}
|
||||
saveSettings(){
|
||||
for(var i = 0; i < this.gainList.length; i++){
|
||||
var gain = this.gainList[i]
|
||||
gain.defaultVol = gain.volume
|
||||
}
|
||||
}
|
||||
loadSettings(){
|
||||
for(var i = 0; i < this.gainList.length; i++){
|
||||
var gain = this.gainList[i]
|
||||
gain.setVolume(gain.defaultVol)
|
||||
}
|
||||
}
|
||||
fallbackDecoder(buffer, resolve, reject){
|
||||
Oggmented().then(oggmented => oggmented.decodeOggData(buffer, resolve, reject), reject)
|
||||
}
|
||||
}
|
||||
class SoundGain{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(soundBuffer, channel){
|
||||
this.soundBuffer = soundBuffer
|
||||
this.gainNode = soundBuffer.context.createGain()
|
||||
if(channel){
|
||||
var index = channel === "left" ? 0 : 1
|
||||
this.merger = soundBuffer.context.createChannelMerger(2)
|
||||
this.merger.connect(soundBuffer.context.destination)
|
||||
this.gainNode.connect(this.merger, 0, index)
|
||||
}else{
|
||||
this.gainNode.connect(soundBuffer.context.destination)
|
||||
}
|
||||
this.setVolume(1)
|
||||
}
|
||||
load(url){
|
||||
return this.soundBuffer.load(url, this)
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
return this.soundBuffer.convertTime(time, absolute)
|
||||
}
|
||||
setVolume(amount){
|
||||
this.gainNode.gain.value = amount * amount
|
||||
this.volume = amount
|
||||
}
|
||||
setVolumeMul(amount){
|
||||
this.setVolume(amount * this.defaultVol)
|
||||
}
|
||||
setCrossfade(amount){
|
||||
this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount)))
|
||||
}
|
||||
fadeIn(duration, time, absolute){
|
||||
this.fadeVolume(0, this.volume * this.volume, duration, time, absolute)
|
||||
}
|
||||
fadeOut(duration, time, absolute){
|
||||
this.fadeVolume(this.volume * this.volume, 0, duration, time, absolute)
|
||||
}
|
||||
fadeVolume(vol1, vol2, duration, time, absolute){
|
||||
time = this.convertTime(time, absolute)
|
||||
this.gainNode.gain.linearRampToValueAtTime(vol1, time)
|
||||
this.gainNode.gain.linearRampToValueAtTime(vol2, time + (duration || 0))
|
||||
}
|
||||
mute(){
|
||||
this.gainNode.gain.value = 0
|
||||
}
|
||||
unmute(){
|
||||
this.setVolume(this.volume)
|
||||
}
|
||||
}
|
||||
class Sound{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(gain, buffer){
|
||||
this.gain = gain
|
||||
this.buffer = buffer
|
||||
this.soundBuffer = gain.soundBuffer
|
||||
this.duration = buffer.duration
|
||||
this.timeouts = new Set()
|
||||
this.sources = new Set()
|
||||
}
|
||||
copy(gain){
|
||||
return new Sound(gain || this.gain, this.buffer)
|
||||
}
|
||||
getTime(){
|
||||
return this.soundBuffer.getTime()
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
return this.soundBuffer.convertTime(time, absolute)
|
||||
}
|
||||
setTimeouts(time){
|
||||
return new Promise(resolve => {
|
||||
var relTime = time - this.getTime()
|
||||
if(relTime > 0){
|
||||
var timeout = setTimeout(() => {
|
||||
this.timeouts.delete(timeout)
|
||||
resolve()
|
||||
}, relTime * 1000)
|
||||
this.timeouts.add(timeout)
|
||||
}else{
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
clearTimeouts(){
|
||||
this.timeouts.forEach(timeout => {
|
||||
clearTimeout(timeout)
|
||||
this.timeouts.delete(timeout)
|
||||
})
|
||||
}
|
||||
playLoop(time, absolute, seek1, seek2, until){
|
||||
time = this.convertTime(time, absolute)
|
||||
seek1 = seek1 || 0
|
||||
if(typeof seek2 === "undefined"){
|
||||
seek2 = seek1
|
||||
}
|
||||
until = until || this.duration
|
||||
if(seek1 >= until || seek2 >= until){
|
||||
return
|
||||
}
|
||||
this.loop = {
|
||||
started: time + until - seek1,
|
||||
seek: seek2,
|
||||
until: until
|
||||
}
|
||||
this.play(time, true, seek1, until)
|
||||
this.addLoop()
|
||||
this.loop.interval = setInterval(() => {
|
||||
this.addLoop()
|
||||
}, 100)
|
||||
}
|
||||
addLoop(){
|
||||
while(this.getTime() > this.loop.started - 1){
|
||||
this.play(this.loop.started, true, this.loop.seek, this.loop.until)
|
||||
this.loop.started += this.loop.until - this.loop.seek
|
||||
}
|
||||
}
|
||||
play(time, absolute, seek, until){
|
||||
time = this.convertTime(time, absolute)
|
||||
var source = this.soundBuffer.createSource(this)
|
||||
seek = seek || 0
|
||||
until = until || this.duration
|
||||
this.setTimeouts(time).then(() => {
|
||||
this.cfg = {
|
||||
started: time,
|
||||
seek: seek,
|
||||
until: until
|
||||
}
|
||||
})
|
||||
source.start(time, Math.max(0, seek || 0), Math.max(0, until - seek))
|
||||
source.startTime = time
|
||||
this.sources.add(source)
|
||||
source.onended = () => {
|
||||
this.sources.delete(source)
|
||||
}
|
||||
}
|
||||
stop(time, absolute){
|
||||
time = this.convertTime(time, absolute)
|
||||
this.sources.forEach(source => {
|
||||
try{
|
||||
source.stop(Math.max(source.startTime, time))
|
||||
}catch(e){}
|
||||
})
|
||||
this.setTimeouts(time).then(() => {
|
||||
if(this.loop){
|
||||
clearInterval(this.loop.interval)
|
||||
}
|
||||
this.clearTimeouts()
|
||||
})
|
||||
}
|
||||
pause(time, absolute){
|
||||
if(this.cfg){
|
||||
time = this.convertTime(time, absolute)
|
||||
this.stop(time, true)
|
||||
this.cfg.pauseSeek = time - this.cfg.started + this.cfg.seek
|
||||
}
|
||||
}
|
||||
resume(time, absolute){
|
||||
if(this.cfg){
|
||||
if(this.loop){
|
||||
this.playLoop(time, absolute, this.cfg.pauseSeek, this.loop.seek, this.loop.until)
|
||||
}else{
|
||||
this.play(time, absolute, this.cfg.pauseSeek, this.cfg.until)
|
||||
}
|
||||
}
|
||||
}
|
||||
clean(){
|
||||
delete this.buffer
|
||||
}
|
||||
}
|
||||
1499
public/src/js/strings.js
Normal file
1499
public/src/js/strings.js
Normal file
File diff suppressed because it is too large
Load Diff
146
public/src/js/titlescreen.js
Normal file
146
public/src/js/titlescreen.js
Normal file
@@ -0,0 +1,146 @@
|
||||
class Titlescreen{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(songId){
|
||||
this.songId = songId
|
||||
db.getItem("customFolder").then(folder => this.customFolder = folder)
|
||||
|
||||
if(!songId){
|
||||
loader.changePage("titlescreen", false)
|
||||
loader.screen.style.backgroundImage = ""
|
||||
|
||||
this.titleScreen = document.getElementById("title-screen")
|
||||
this.proceed = document.getElementById("title-proceed")
|
||||
this.disclaimerText = document.getElementById("title-disclaimer-text")
|
||||
this.disclaimerCopyright = document.getElementById("title-disclaimer-copyright")
|
||||
this.logo = new Logo()
|
||||
}
|
||||
this.setLang(allStrings[settings.getItem("language")])
|
||||
|
||||
if(songId){
|
||||
if(localStorage.getItem("tutorial") === "true"){
|
||||
new SongSelect(false, false, this.touched, this.songId)
|
||||
}else{
|
||||
new SettingsView(false, true, this.songId)
|
||||
}
|
||||
}else{
|
||||
pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], event => {
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
this.touched = true
|
||||
}else if(event.type === "mousedown" && event.which !== 1){
|
||||
return
|
||||
}
|
||||
this.onPressed(true)
|
||||
})
|
||||
|
||||
assets.sounds["v_title"].play()
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["enter", "space", "don_l", "don_r"]
|
||||
}, this.onPressed.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
gamepadConfirm: ["a", "b", "x", "y", "start", "ls", "rs"]
|
||||
}, this.onPressed.bind(this))
|
||||
if(p2.session){
|
||||
pageEvents.add(p2, "message", response => {
|
||||
if(response.type === "songsel"){
|
||||
this.goNext(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
pageEvents.send("title-screen")
|
||||
}
|
||||
}
|
||||
|
||||
onPressed(pressed, name){
|
||||
if(pressed){
|
||||
if(name === "gamepadConfirm" && (snd.buffer.context.state === "suspended" || this.customFolder)){
|
||||
return
|
||||
}
|
||||
this.titleScreen.style.cursor = "auto"
|
||||
this.clean()
|
||||
if(!this.customFolder || assets.customSongs){
|
||||
assets.sounds["se_don"].play()
|
||||
}
|
||||
this.goNext()
|
||||
}
|
||||
}
|
||||
goNext(fromP2){
|
||||
if(p2.session && !fromP2){
|
||||
p2.send("songsel")
|
||||
}else{
|
||||
if(fromP2 || this.customFolder || localStorage.getItem("tutorial") === "true"){
|
||||
if(this.touched){
|
||||
localStorage.setItem("tutorial", "true")
|
||||
}
|
||||
pageEvents.remove(p2, "message")
|
||||
if(this.customFolder && !fromP2 && !assets.customSongs){
|
||||
var customSongs = new CustomSongs(this.touched, true, true)
|
||||
var soundPlayed = false
|
||||
var noError = true
|
||||
var promises = []
|
||||
var allFiles = []
|
||||
this.customFolder.forEach(file => {
|
||||
promises.push(customSongs.walkFilesystem(file, undefined, allFiles))
|
||||
})
|
||||
Promise.all(promises).then(() => {
|
||||
assets.sounds["se_don"].play()
|
||||
soundPlayed = true
|
||||
return customSongs.importLocal(allFiles)
|
||||
}).catch(() => {
|
||||
localStorage.removeItem("customSelected")
|
||||
db.removeItem("customFolder")
|
||||
if(!soundPlayed){
|
||||
assets.sounds["se_don"].play()
|
||||
}
|
||||
setTimeout(() => {
|
||||
new SongSelect(false, false, this.touched, this.songId)
|
||||
}, 500)
|
||||
noError = false
|
||||
}).then(() => {
|
||||
if(noError){
|
||||
setTimeout(() => {
|
||||
new SongSelect("customSongs", false, this.touched)
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
setTimeout(() => {
|
||||
new SongSelect(false, false, this.touched, this.songId)
|
||||
}, 500)
|
||||
}
|
||||
}else{
|
||||
setTimeout(() => {
|
||||
new SettingsView(this.touched, true, this.songId)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
setLang(lang, noEvent){
|
||||
settings.setLang(lang, true)
|
||||
if(this.songId){
|
||||
return
|
||||
}
|
||||
this.proceed.innerText = strings.titleProceed
|
||||
this.proceed.setAttribute("alt", strings.titleProceed)
|
||||
|
||||
this.disclaimerText.innerText = strings.titleDisclaimer
|
||||
this.disclaimerText.setAttribute("alt", strings.titleDisclaimer)
|
||||
this.disclaimerCopyright.innerText = strings.titleCopyright
|
||||
this.disclaimerCopyright.setAttribute("alt", strings.titleCopyright)
|
||||
|
||||
this.logo.updateSubtitle()
|
||||
}
|
||||
clean(){
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
this.logo.clean()
|
||||
assets.sounds["v_title"].stop()
|
||||
pageEvents.remove(this.titleScreen, ["mousedown", "touchstart"])
|
||||
delete this.titleScreen
|
||||
delete this.proceed
|
||||
delete this.titleDisclaimer
|
||||
delete this.titleCopyright
|
||||
}
|
||||
}
|
||||
184
public/src/js/tutorial.js
Normal file
184
public/src/js/tutorial.js
Normal file
@@ -0,0 +1,184 @@
|
||||
class Tutorial{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(fromSongSel, songId){
|
||||
this.fromSongSel = fromSongSel
|
||||
this.songId = songId
|
||||
loader.changePage("tutorial", true)
|
||||
assets.sounds["bgm_setsume"].playLoop(0.1, false, 0, 1.054, 16.054)
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
|
||||
this.tutorialTitle = this.getElement("view-title")
|
||||
this.tutorialDiv = document.createElement("div")
|
||||
this.getElement("view-content").appendChild(this.tutorialDiv)
|
||||
|
||||
this.items = []
|
||||
this.items.push(this.endButton)
|
||||
this.selected = this.items.length - 1
|
||||
|
||||
this.setStrings()
|
||||
|
||||
pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
|
||||
this.keyboard = new Keyboard({
|
||||
confirm: ["enter", "space", "don_l", "don_r"],
|
||||
previous: ["left", "up", "ka_l"],
|
||||
next: ["right", "down", "ka_r"],
|
||||
back: ["escape"]
|
||||
}, this.keyPressed.bind(this))
|
||||
this.gamepad = new Gamepad({
|
||||
"confirm": ["b", "ls", "rs"],
|
||||
"previous": ["u", "l", "lb", "lt", "lsu", "lsl"],
|
||||
"next": ["d", "r", "rb", "rt", "lsd", "lsr"],
|
||||
"back": ["start", "a"]
|
||||
}, this.keyPressed.bind(this))
|
||||
|
||||
pageEvents.send("tutorial")
|
||||
}
|
||||
getElement(name){
|
||||
return loader.screen.getElementsByClassName(name)[0]
|
||||
}
|
||||
keyPressed(pressed, name){
|
||||
if(!pressed){
|
||||
return
|
||||
}
|
||||
var selected = this.items[this.selected]
|
||||
if(name === "confirm"){
|
||||
if(selected === this.endButton){
|
||||
this.onEnd()
|
||||
}else{
|
||||
this.getLink(selected).click()
|
||||
assets.sounds["se_don"].play()
|
||||
}
|
||||
}else if(name === "previous" || name === "next"){
|
||||
if(this.items.length >= 2){
|
||||
selected.classList.remove("selected")
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
this.items[this.selected].classList.add("selected")
|
||||
assets.sounds["se_ka"].play()
|
||||
}
|
||||
}else if(name === "back"){
|
||||
this.onEnd()
|
||||
}
|
||||
}
|
||||
mod(length, index){
|
||||
return ((index % length) + length) % length
|
||||
}
|
||||
onEnd(event){
|
||||
var touched = false
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
touched = true
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
this.clean()
|
||||
assets.sounds["se_don"].play()
|
||||
try{
|
||||
localStorage.setItem("tutorial", "true")
|
||||
}catch(e){}
|
||||
setTimeout(() => {
|
||||
new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched, this.songId)
|
||||
}, 500)
|
||||
}
|
||||
getLink(target){
|
||||
return target.getElementsByTagName("a")[0]
|
||||
}
|
||||
linkButton(event){
|
||||
if(event.target === event.currentTarget && (event.type === "touchstart" || event.which === 1)){
|
||||
this.getLink(event.currentTarget).click()
|
||||
assets.sounds["se_don"].play()
|
||||
}
|
||||
}
|
||||
insertText(text, parent){
|
||||
parent.appendChild(document.createTextNode(text))
|
||||
}
|
||||
insertKey(key, parent){
|
||||
if(!Array.isArray(key)){
|
||||
key = [key]
|
||||
}
|
||||
var join = true
|
||||
for(var i = 0; i < key.length; i++){
|
||||
if(key[i] === false){
|
||||
join = false
|
||||
continue
|
||||
}
|
||||
if(i !== 0){
|
||||
if(join){
|
||||
var span = document.createElement("span")
|
||||
span.classList.add("key-join")
|
||||
span.innerText = strings.tutorial.key.join
|
||||
parent.appendChild(span)
|
||||
}else{
|
||||
parent.appendChild(document.createTextNode(strings.tutorial.key.or))
|
||||
}
|
||||
}
|
||||
var kbd = document.createElement("kbd")
|
||||
kbd.innerText = key[i]
|
||||
parent.appendChild(kbd)
|
||||
}
|
||||
}
|
||||
setStrings(){
|
||||
this.tutorialTitle.innerText = strings.howToPlay
|
||||
this.tutorialTitle.setAttribute("alt", strings.howToPlay)
|
||||
this.endButton.innerText = strings.tutorial.ok
|
||||
this.endButton.setAttribute("alt", strings.tutorial.ok)
|
||||
this.tutorialDiv.innerHTML = ""
|
||||
var kbdSettings = settings.getItem("keyboardSettings")
|
||||
var pauseKey = [strings.tutorial.key.esc]
|
||||
if(pageEvents.kbd.indexOf("q") === -1){
|
||||
pauseKey.push(false)
|
||||
pauseKey.push("Q")
|
||||
}
|
||||
var keys = [
|
||||
kbdSettings.don_l[0].toUpperCase(),
|
||||
kbdSettings.don_r[0].toUpperCase(),
|
||||
kbdSettings.ka_l[0].toUpperCase(),
|
||||
kbdSettings.ka_r[0].toUpperCase(),
|
||||
pauseKey,
|
||||
[strings.tutorial.key.shift, strings.tutorial.key.leftArrow],
|
||||
[strings.tutorial.key.shift, strings.tutorial.key.rightArrow],
|
||||
strings.tutorial.key.shift,
|
||||
strings.tutorial.key.ctrl
|
||||
]
|
||||
var keyIndex = 0
|
||||
strings.tutorial.basics.forEach(string => {
|
||||
var par = document.createElement("p")
|
||||
var stringKeys = string.split("%s")
|
||||
stringKeys.forEach((stringKey, i) => {
|
||||
if(i !== 0){
|
||||
this.insertKey(keys[keyIndex++], par)
|
||||
}
|
||||
this.insertText(stringKey, par)
|
||||
})
|
||||
this.tutorialDiv.appendChild(par)
|
||||
})
|
||||
var par = document.createElement("p")
|
||||
var span = document.createElement("span")
|
||||
span.style.fontWeight = "bold"
|
||||
span.innerText = strings.tutorial.otherControls
|
||||
par.appendChild(span)
|
||||
strings.tutorial.otherTutorial.forEach(string => {
|
||||
par.appendChild(document.createElement("br"))
|
||||
var stringKeys = string.split("%s")
|
||||
stringKeys.forEach((stringKey, i) => {
|
||||
if(i !== 0){
|
||||
this.insertKey(keys[keyIndex++], par)
|
||||
}
|
||||
this.insertText(stringKey, par)
|
||||
})
|
||||
})
|
||||
this.tutorialDiv.appendChild(par)
|
||||
}
|
||||
clean(){
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
|
||||
assets.sounds["bgm_setsume"].stop()
|
||||
delete this.tutorialTitle
|
||||
delete this.endButton
|
||||
delete this.tutorialDiv
|
||||
}
|
||||
}
|
||||
2269
public/src/js/view.js
Normal file
2269
public/src/js/view.js
Normal file
File diff suppressed because it is too large
Load Diff
190
public/src/js/viewassets.js
Normal file
190
public/src/js/viewassets.js
Normal file
@@ -0,0 +1,190 @@
|
||||
class ViewAssets{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(view){
|
||||
this.view = view
|
||||
this.controller = this.view.controller
|
||||
this.allAssets = []
|
||||
this.ctx = this.view.ctx
|
||||
|
||||
// Background
|
||||
this.don = this.createAsset("background", frame => {
|
||||
var imgw = 360
|
||||
var imgh = 184
|
||||
var w = imgw
|
||||
var h = imgh
|
||||
return {
|
||||
sx: Math.floor(frame / 10) * (imgw + 2),
|
||||
sy: (frame % 10) * (imgh + 2),
|
||||
sw: imgw,
|
||||
sh: imgh,
|
||||
x: view.portrait ? -60 : 0,
|
||||
y: view.portrait ? (view.player === 2 ? 560 : 35) : (view.player === 2 ? 360 : 0),
|
||||
w: w / h * (h + 0.5),
|
||||
h: h + 0.5
|
||||
}
|
||||
})
|
||||
this.don.addFrames("normal", [
|
||||
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
|
||||
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 ,
|
||||
0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,7 ,8 ,9 ,10,
|
||||
11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17
|
||||
], "don_anim_normal", this.controller.don)
|
||||
this.don.addFrames("10combo", 22, "don_anim_10combo", this.controller.don)
|
||||
this.don.addFrames("gogo", [
|
||||
42,43,43,44,45,46,47,48,49,50,51,52,53,54,
|
||||
55,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,11,12,13,
|
||||
14,14,15,16,17,18,19,20,21,22,23,24,25,26,
|
||||
27,28,29,30,31,32,33,34,35,36,37,38,39,40,41
|
||||
], "don_anim_gogo", this.controller.don)
|
||||
this.don.addFrames("gogostart", 27, "don_anim_gogostart", this.controller.don)
|
||||
this.don.normalAnimation = () => {
|
||||
if(this.view.gogoTime){
|
||||
var length = this.don.getAnimationLength("gogo")
|
||||
this.don.setUpdateSpeed(4 / length)
|
||||
this.don.setAnimation("gogo")
|
||||
}else{
|
||||
var score = this.controller.getGlobalScore()
|
||||
var cleared = this.controller.game.rules.clearReached(score.gauge)
|
||||
if(cleared){
|
||||
this.don.setAnimationStart(0)
|
||||
var length = this.don.getAnimationLength("clear")
|
||||
this.don.setUpdateSpeed(2 / length)
|
||||
this.don.setAnimation("clear")
|
||||
}else{
|
||||
this.don.setAnimationStart(0)
|
||||
var length = this.don.getAnimationLength("normal")
|
||||
this.don.setUpdateSpeed(4 / length)
|
||||
this.don.setAnimation("normal")
|
||||
}
|
||||
}
|
||||
}
|
||||
this.don.addFrames("clear", 30, "don_anim_clear", this.controller.don)
|
||||
this.don.normalAnimation()
|
||||
|
||||
// Bar
|
||||
this.fire = this.createAsset("bar", frame => {
|
||||
var imgw = 360
|
||||
var imgh = 370
|
||||
var scale = 130
|
||||
var ms = this.view.getMS()
|
||||
var elapsed = ms - this.view.gogoTimeStarted
|
||||
|
||||
var mul = this.view.slotPos.size / 106
|
||||
var barH = 130 * mul
|
||||
|
||||
if(this.view.gogoTime){
|
||||
var grow = 3 - Math.min(200, elapsed) / 100
|
||||
this.ctx.globalAlpha = Math.min(200, elapsed) / 200
|
||||
}else{
|
||||
var grow = 1 - Math.min(100, elapsed) / 100
|
||||
}
|
||||
var w = (barH * imgw) / scale * grow
|
||||
var h = (barH * imgh) / scale * grow
|
||||
this.ctx.globalCompositeOperation = "lighter"
|
||||
return {
|
||||
sx: frame * imgw,
|
||||
sy: 0,
|
||||
sw: imgw,
|
||||
sh: imgh,
|
||||
x: this.view.slotPos.x - w / 2,
|
||||
y: this.view.slotPos.y - h / 2,
|
||||
w: w,
|
||||
h: h
|
||||
}
|
||||
})
|
||||
this.fire.addFrames("normal", 7, "fire_anim")
|
||||
this.fire.setUpdateSpeed(1 / 8)
|
||||
|
||||
// Notes
|
||||
this.explosion = this.createAsset("notes", frame => {
|
||||
var w = 222
|
||||
var h = 222
|
||||
var mul = this.view.slotPos.size / 106
|
||||
this.ctx.globalCompositeOperation = "screen"
|
||||
var alpha = 1
|
||||
if(this.explosion.type < 2){
|
||||
if(frame < 2){
|
||||
mul *= 1 - (frame + 1) * 0.2
|
||||
}else if(frame > 9){
|
||||
alpha = Math.max(0, 1 - (frame - 10) / 4)
|
||||
}
|
||||
}else if(frame > 5){
|
||||
alpha = 0.5
|
||||
}
|
||||
if(alpha < 1 && !this.controller.touchEnabled){
|
||||
this.ctx.globalAlpha = alpha
|
||||
}
|
||||
return {
|
||||
sx: this.explosion.type * w,
|
||||
sy: Math.min(3, Math.floor(frame / 2)) * h,
|
||||
sw: w,
|
||||
sh: h,
|
||||
x: this.view.slotPos.x - w * mul / 2,
|
||||
y: this.view.slotPos.y - h * mul / 2,
|
||||
w: w * mul,
|
||||
h: h * mul
|
||||
}
|
||||
})
|
||||
this.explosion.type = null
|
||||
this.explosion.addFrames("normal", 14, "notes_explosion")
|
||||
this.explosion.setUpdateSpeed(1, true)
|
||||
|
||||
// Foreground
|
||||
this.fireworks = []
|
||||
for(let i = 0; i < 5 ; i++){
|
||||
var fireworksAsset = this.createAsset("foreground", frame => {
|
||||
var imgw = 230
|
||||
var imgh = 460
|
||||
var scale = 165
|
||||
var w = imgw
|
||||
var h = imgh
|
||||
var winW = this.view.winW / this.view.ratio
|
||||
var winH = this.view.winH / this.view.ratio
|
||||
return {
|
||||
sx: Math.floor(frame / 4) * imgw,
|
||||
sy: (frame % 4) * imgh,
|
||||
sw: imgw,
|
||||
sh: imgh,
|
||||
x: winW / 4 * i - w / 2 * (i / 2),
|
||||
y: winH - h,
|
||||
w: w,
|
||||
h: h
|
||||
}
|
||||
})
|
||||
fireworksAsset.addFrames("normal", 30, "fireworks_anim")
|
||||
fireworksAsset.setUpdateSpeed(1 / 16)
|
||||
this.fireworks.push(fireworksAsset)
|
||||
}
|
||||
|
||||
this.changeBeatInterval(this.view.beatInterval, true)
|
||||
}
|
||||
createAsset(layer, position){
|
||||
var asset = new CanvasAsset(this.view, layer, position)
|
||||
this.allAssets.push(asset)
|
||||
return asset
|
||||
}
|
||||
drawAssets(layer){
|
||||
this.allAssets.forEach(asset => {
|
||||
if(layer === asset.layer){
|
||||
asset.draw()
|
||||
}
|
||||
})
|
||||
}
|
||||
changeBeatInterval(beatMS, initial){
|
||||
this.allAssets.forEach(asset => {
|
||||
asset.changeBeatInterval(beatMS, initial)
|
||||
})
|
||||
}
|
||||
clean(){
|
||||
if(this.don){
|
||||
this.don.clean()
|
||||
}
|
||||
delete this.ctx
|
||||
delete this.don
|
||||
delete this.fire
|
||||
delete this.fireworks
|
||||
delete this.allAssets
|
||||
}
|
||||
}
|
||||
16
public/src/views/about.html
Normal file
16
public/src/views/about.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="view-outer">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="diag-txt"></div>
|
||||
<div class="left-buttons">
|
||||
<div id="link-issues" class="taibtn stroke-sub link-btn">
|
||||
<a target="_blank"></a>
|
||||
</div>
|
||||
<div id="link-email" class="taibtn stroke-sub link-btn">
|
||||
<a></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub selected"></div>
|
||||
</div>
|
||||
</div>
|
||||
53
public/src/views/account.html
Normal file
53
public/src/views/account.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<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 class="center-buttons">
|
||||
<div>
|
||||
<div class="privacy-btn taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
32
public/src/views/customsongs.html
Normal file
32
public/src/views/customsongs.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<div class="view-outer">
|
||||
<div class="view drag-bg">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="center-buttons">
|
||||
<div>
|
||||
<div id="link-localfolder" class="taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
<div id="group-gdrive">
|
||||
<div id="link-gdrivefolder" class="taibtn stroke-sub link-btn"></div>
|
||||
<div id="link-gdriveaccount" class="taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-buttons">
|
||||
<div id="link-privacy" class="taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub"></div>
|
||||
<div class="view-outer shadow-outer" id="dropzone">
|
||||
<div class="view">
|
||||
<div class="view-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-outer shadow-outer" id="customsongs-error">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="view-end-button taibtn stroke-sub selected"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form><input id="browse" type="file" webkitdirectory multiple></form>
|
||||
</div>
|
||||
39
public/src/views/debug.html
Normal file
39
public/src/views/debug.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="title stroke-sub" alt="Debug">Debug</div>
|
||||
<div class="minimise"></div>
|
||||
<div class="content">
|
||||
<div>Song offset:</div>
|
||||
<div class="offset input-slider">
|
||||
<span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span>
|
||||
</div>
|
||||
<div>Starting measure:</div>
|
||||
<div class="measure-num input-slider">
|
||||
<span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span>
|
||||
</div>
|
||||
<div class="branch-hide">
|
||||
<div>Branch:</div>
|
||||
<div class="branch-select select">
|
||||
<span class="reset">x</span><select>
|
||||
<option value="auto" selected style="background:#fff">Auto</option>
|
||||
<option value="normal" style="background:#ccc">Normal</option>
|
||||
<option value="advanced" style="background:#bdf">Professional</option>
|
||||
<option value="master" style="background:#ebf">Master</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>Music volume:</div>
|
||||
<div class="music-volume input-slider">
|
||||
<span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span>
|
||||
</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="autoplay-label"><input class="autoplay" type="checkbox">Auto play</label>
|
||||
<div class="bottom-btns">
|
||||
<div class="restart-btn">Restart song</div>
|
||||
<div class="exit-btn">Exit debug</div>
|
||||
</div>
|
||||
</div>
|
||||
15
public/src/views/game.html
Normal file
15
public/src/views/game.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div id="game">
|
||||
<div id="songbg">
|
||||
<div id="layer1"></div>
|
||||
<div id="layer2"></div>
|
||||
</div>
|
||||
<div id="song-stage"></div>
|
||||
<div id="touch-drum">
|
||||
<div id="touch-drum-img"></div>
|
||||
</div>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id="song-lyrics"></div>
|
||||
<div id="touch-buttons">
|
||||
<div id="touch-full-btn"></div><div id="touch-pause-btn"></div>
|
||||
</div>
|
||||
</div>
|
||||
11
public/src/views/loader.html
Normal file
11
public/src/views/loader.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div id="loader">
|
||||
<div class="progress"></div>
|
||||
<span class="percentage">0%</span>
|
||||
</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>
|
||||
7
public/src/views/loadsong.html
Normal file
7
public/src/views/loadsong.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div id="load-song">
|
||||
<div id="loading-song">
|
||||
<div id="loading-don"></div>
|
||||
<div class="loading-text stroke-sub" id="loading-text"></div>
|
||||
<div id="p2-cancel-button" class="taibtn stroke-sub"></div>
|
||||
</div>
|
||||
</div>
|
||||
26
public/src/views/login.html
Normal file
26
public/src/views/login.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<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 class="privacy-btn taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub selected"></div>
|
||||
</div>
|
||||
</div>
|
||||
9
public/src/views/search.html
Normal file
9
public/src/views/search.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div id="song-search-container">
|
||||
<div id="song-search">
|
||||
<div id="song-search-close" class="stroke-sub" alt="x">x</div>
|
||||
<div id="song-search-bar">
|
||||
<input type="search" id="song-search-input" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||
</div>
|
||||
<div id="song-search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
9
public/src/views/session.html
Normal file
9
public/src/views/session.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="view-outer">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content">
|
||||
<div id="session-invite"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub"></div>
|
||||
</div>
|
||||
</div>
|
||||
34
public/src/views/settings.html
Normal file
34
public/src/views/settings.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="view-outer settings-outer">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="left-buttons">
|
||||
<div id="settings-default" class="taibtn stroke-sub"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub"></div>
|
||||
<div class="view-outer shadow-outer" id="settings-gamepad">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content">
|
||||
<div class="setting-box">
|
||||
<div id="gamepad-bg">
|
||||
<div id="gamepad-value" class="stroke-sub"></div>
|
||||
<div id="gamepad-buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub selected"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-outer shadow-outer" id="settings-latency">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="left-buttons">
|
||||
<div id="latency-default" class="taibtn stroke-sub"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
5
public/src/views/songselect.html
Normal file
5
public/src/views/songselect.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<div id="song-select">
|
||||
<canvas id="song-sel-canvas"></canvas>
|
||||
<div id="song-sel-selectable" tabindex="1"></div>
|
||||
<div id="touch-full-btn"></div>
|
||||
</div>
|
||||
8
public/src/views/titlescreen.html
Normal file
8
public/src/views/titlescreen.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<div id="title-screen">
|
||||
<canvas id="logo"></canvas>
|
||||
<div class="click-to-continue stroke-sub" id="title-proceed"></div>
|
||||
<div id="title-disclaimer">
|
||||
<span class="stroke-sub" id="title-disclaimer-text"></span>
|
||||
<span class="stroke-sub" id="title-disclaimer-copyright"></span>
|
||||
</div>
|
||||
</div>
|
||||
7
public/src/views/tutorial.html
Normal file
7
public/src/views/tutorial.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="view-outer">
|
||||
<div class="view">
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="view-end-button taibtn stroke-sub selected"></div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user