diff --git a/app.py b/app.py
index 5eb6969..7161c58 100644
--- a/app.py
+++ b/app.py
@@ -40,10 +40,10 @@ def get_config():
try:
config = json.load(open('config.json', 'r'))
except ValueError:
- print 'WARNING: Invalid config.json, using default values'
+ print('WARNING: Invalid config.json, using default values')
config = {}
else:
- print 'WARNING: No config.json found, using default values'
+ print('WARNING: No config.json found, using default values')
config = {}
if not config.get('songs_baseurl'):
@@ -55,77 +55,6 @@ def get_config():
return config
-def parse_osu(osu):
- osu_lines = open(osu, 'r').read().replace('\x00', '').split('\n')
- sections = {}
- current_section = (None, [])
-
- for line in osu_lines:
- line = line.strip()
- secm = re.match('^\[(\w+)\]$', line)
- if secm:
- if current_section:
- sections[current_section[0]] = current_section[1]
- current_section = (secm.group(1), [])
- else:
- if current_section:
- current_section[1].append(line)
- else:
- current_section = ('Default', [line])
-
- if current_section:
- sections[current_section[0]] = current_section[1]
-
- return sections
-
-
-def get_osu_key(osu, section, key, default=None):
- sec = osu[section]
- for line in sec:
- ok = line.split(':', 1)[0].strip()
- ov = line.split(':', 1)[1].strip()
-
- if ok.lower() == key.lower():
- return ov
-
- return default
-
-
-def get_preview(song_id, song_type):
- preview = 0
-
- if song_type == "tja":
- if os.path.isfile('public/songs/%s/main.tja' % song_id):
- preview = get_tja_preview('public/songs/%s/main.tja' % song_id)
- else:
- osus = [osu for osu in os.listdir('public/songs/%s' % song_id) if osu in ['easy.osu', 'normal.osu', 'hard.osu', 'oni.osu']]
- if osus:
- osud = parse_osu('public/songs/%s/%s' % (song_id, osus[0]))
- preview = int(get_osu_key(osud, 'General', 'PreviewTime', 0))
-
- return preview
-
-
-def get_tja_preview(tja):
- tja_lines = open(tja, 'r').read().replace('\x00', '').split('\n')
-
- for line in tja_lines:
- line = line.strip()
- if ':' in line:
- name, value = line.split(':', 1)
- if name.lower() == 'demostart':
- value = value.strip()
- try:
- value = float(value)
- except ValueError:
- pass
- else:
- return int(value * 1000)
- elif line.lower() == '#start':
- break
- return 0
-
-
def get_version():
version = {'commit': None, 'commit_short': '', 'version': None, 'url': DEFAULT_URL}
if os.path.isfile('version.json'):
@@ -168,7 +97,7 @@ def route_api_preview():
abort(400)
song_type = song_row[0][12]
- prev_path = make_preview(song_id, song_type)
+ prev_path = make_preview(song_id, song_type, song_row[0][15])
if not prev_path:
return redirect(get_config()['songs_baseurl'] + '%s/main.ogg' % song_id)
@@ -182,7 +111,6 @@ def route_api_songs():
raw_categories = query_db('select * from categories')
categories = {}
- def_category = {'title': None, 'title_en': None}
for cat in raw_categories:
categories[cat[0]] = cat[1]
@@ -195,9 +123,9 @@ def route_api_songs():
for song in songs:
song_id = song[0]
song_type = song[12]
- preview = get_preview(song_id, song_type)
+ preview = song[15]
- category_out = categories[song[11]] if song[11] in categories else def_category
+ category_out = categories[song[11]] if song[11] in categories else ""
song_skin_out = song_skins[song[14]] if song[14] in song_skins else None
songs_out.append({
@@ -213,7 +141,8 @@ def route_api_songs():
'category': category_out,
'type': song_type,
'offset': song[13],
- 'song_skin': song_skin_out
+ 'song_skin': song_skin_out,
+ 'volume': song[16]
})
return jsonify(songs_out)
@@ -226,13 +155,12 @@ def route_api_config():
return jsonify(config)
-def make_preview(song_id, song_type):
+def make_preview(song_id, song_type, preview):
song_path = 'public/songs/%s/main.ogg' % song_id
prev_path = 'public/songs/%s/preview.mp3' % song_id
if os.path.isfile(song_path) and not os.path.isfile(prev_path):
- preview = get_preview(song_id, song_type) / 1000
- if not preview or preview <= 0.1:
+ if not preview or preview <= 0:
print('Skipping #%s due to no preview' % song_id)
return False
diff --git a/public/assets/audio/bgm_settings.mp3 b/public/assets/audio/bgm_settings.mp3
new file mode 100644
index 0000000..e09d492
Binary files /dev/null and b/public/assets/audio/bgm_settings.mp3 differ
diff --git a/public/assets/img/img.css b/public/assets/img/img.css
index 7a9d682..2a79238 100644
--- a/public/assets/img/img.css
+++ b/public/assets/img/img.css
@@ -10,24 +10,16 @@
#loading-don{
background-image: url("dancing-don.gif");
}
-#touch-drum-img{
- background-image: url("touch_drum.png");
-}
#touch-full-btn{
background-image: url("touch_fullscreen.png");
}
#touch-pause-btn{
background-image: url("touch_pause.png");
}
-.song-stage-1{
- background-image: url("bg_stage_1.png");
- background-size: calc(100vh / 720 * 66);
+.settings-outer{
+ background-image: url("bg_settings.png");
}
-.song-stage-2{
- background-image: url("bg_stage_2.png");
- background-size: calc(100vh / 720 * 254);
-}
-.song-stage-3{
- background-image: url("bg_stage_3.png");
- background-size: calc(100vh / 720 * 458);
+#gamepad-bg,
+#gamepad-buttons{
+ background-image: url("settings_gamepad.png");
}
diff --git a/public/assets/img/settings_gamepad.png b/public/assets/img/settings_gamepad.png
new file mode 100644
index 0000000..c287f06
Binary files /dev/null and b/public/assets/img/settings_gamepad.png differ
diff --git a/public/assets/img/vectors.json b/public/assets/img/vectors.json
index 0e55a2a..536fb30 100644
--- a/public/assets/img/vectors.json
+++ b/public/assets/img/vectors.json
@@ -66,8 +66,5 @@
"",
"logo5":
-"",
-
-"globe":
""
}
diff --git a/public/src/css/debug.css b/public/src/css/debug.css
index 7c838f0..f9e1478 100644
--- a/public/src/css/debug.css
+++ b/public/src/css/debug.css
@@ -44,7 +44,8 @@
box-sizing: border-box;
}
-#debug .input-slider{
+#debug .input-slider,
+#debug .select{
display: flex;
width: 100%;
height: 30px;
@@ -59,7 +60,8 @@
padding: 2px 4px;
text-align: center;
}
-#debug .input-slider>span{
+#debug .input-slider>span,
+#debug .select>span{
display: block;
width: 10%;
height: 100%;
@@ -70,10 +72,19 @@
line-height: 2em;
cursor: pointer;
}
-#debug .input-slider>span:hover{
+#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;
@@ -111,6 +122,7 @@
margin-left: 3px;
}
-#debug .autoplay-label{
+#debug .autoplay-label,
+#debug .branch-hide{
display: none;
}
diff --git a/public/src/css/game.css b/public/src/css/game.css
index 2944e82..f8b7214 100644
--- a/public/src/css/game.css
+++ b/public/src/css/game.css
@@ -94,3 +94,6 @@
z-index: 2;
transition: 1s background-color linear;
}
+.fix-animations *{
+ animation: none !important;
+}
diff --git a/public/src/css/loader.css b/public/src/css/loader.css
index 2899ce0..9e97ec1 100644
--- a/public/src/css/loader.css
+++ b/public/src/css/loader.css
@@ -15,7 +15,7 @@ body{
margin: 0;
padding: 0;
background-color: #000;
- background-position: top center;
+ background-position: center;
background-size: 30vh;
}
#screen.pattern-bg{
@@ -29,8 +29,8 @@ body{
width:90%;
height:10%;
border:1px solid black;
- position: fixed;
- top:50%;
+ position: absolute;
+ top:45%;
left:5%;
background: rgba(0,0,0,0.65);
}
diff --git a/public/src/css/main.css b/public/src/css/main.css
index 2ec0cf7..c6e1f80 100644
--- a/public/src/css/main.css
+++ b/public/src/css/main.css
@@ -23,115 +23,6 @@
left: 0;
z-index: -1;
}
-#tutorial-outer{
- display: flex;
- justify-content: center;
- align-items: center;
- overflow: hidden;
- position: absolute;
- width: 100%;
- height: 100%;
-}
-#tutorial{
- background: rgb(246, 234, 212);
- color: black;
- border: 0.25em black solid;
- border-radius: 0.5em;
- width: 800px;
- padding: 1em;
- margin: 1em;
- font-size: 21px;
- position: relative;
-}
-.touch-enabled #tutorial{
- font-size: 3vmin;
-}
-#tutorial-title{
- z-index: 1;
- position: absolute;
- color: white;
- top: -0.7em;
- font-size: 1.65em;
-}
-#tutorial-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;
-}
-.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;
-}
-#tutorial-end-button{
- float: right;
- padding: 0.4em 1.5em;
- font-weight: bold;
- border-color: #000;
- color: #000;
-}
-.taibtn:hover,
-#tutorial-end-button:hover{
- position: relative;
- z-index: 1;
- color: #fff;
- background: #ffb547;
- border-color: #fff;
-}
-.taibtn::before{
- padding-left: inherit;
-}
-#about-link-btns{
- float: left;
- display: flex;
-}
-#about-link-btns .taibtn{
- margin-right: 0.4em;
-}
-#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;
-}
-.text-warn{
- color: #d00;
-}
-.link-btn a{
- color: inherit;
- text-decoration: none;
- pointer-events: none;
-}
-.nowrap{
- white-space: nowrap;
-}
#session-invite{
width: 100%;
height: 1.9em;
@@ -149,10 +40,10 @@ kbd{
}
@keyframes bgscroll{
from{
- background-position: 0 top;
+ background-position: 50% top;
}
to{
- background-position: calc(-100vh / 720 * 512) top;
+ background-position: calc(50% - 100vh / 720 * 512) top;
}
}
#song-select{
diff --git a/public/src/css/songbg.css b/public/src/css/songbg.css
index 9374f93..5f6f2ef 100644
--- a/public/src/css/songbg.css
+++ b/public/src/css/songbg.css
@@ -26,6 +26,7 @@
height: calc(44 / 720 * 100vh);
background-position: center bottom;
background-repeat-y: no-repeat;
+ background-size: auto 100%;
bottom: 0;
}
.portrait #songbg{
diff --git a/public/src/css/titlescreen.css b/public/src/css/titlescreen.css
index 6a280a3..3ca80fc 100644
--- a/public/src/css/titlescreen.css
+++ b/public/src/css/titlescreen.css
@@ -38,53 +38,6 @@
-webkit-text-stroke: 0.25em #f00;
filter: blur(0.3vmin);
}
-#lang{
- font-size: 3vmin;
- position: absolute;
- bottom: 0;
- left: 0;
- width: 7em;
- height: 4em;
- color: #000;
- z-index: 5;
-}
-#lang:focus-within{
- outline: #4d90fe auto 5px;
-}
-#lang-dropdown{
- font-size: 1.7em;
- font-family: Microsoft YaHei, sans-serif;
- opacity: 0;
- width: 100%;
- height: 100%;
- color: #000;
- cursor: pointer;
-}
-#lang-id{
- position: absolute;
- top: 0;
- bottom: 0;
- left: 2.6em;
- font-family: TnT, Meiryo, sans-serif;
- font-size: 1.5em;
- font-weight: normal;
- color: #fff;
- line-height: 2.75em;
- z-index: 0;
-}
-#lang-icon{
- position: absolute;
- width: 2.8em;
- height: 2.8em;
- padding: 0.6em;
- fill: currentColor;
-}
-#lang:hover #lang-icon{
- color: #f00;
-}
-#lang:hover #lang-id::before {
- -webkit-text-stroke: 0.25em #f00;
-}
#title-disclaimer {
text-align: center;
position: absolute;
diff --git a/public/src/css/view.css b/public/src/css/view.css
new file mode 100644
index 0000000..0340d97
--- /dev/null
+++ b/public/src/css/view.css
@@ -0,0 +1,261 @@
+.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;
+}
+.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;
+}
+#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;
+}
+.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;
+}
+.settings-outer .view-content:not(:hover) .setting-box.selected,
+.view-outer:not(.settings-outer) .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,
+.view-outer:not(.settings-outer) .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-value{
+ display: flex;
+ background: #fff;
+ width: 50%;
+ border-radius: 0.2em;
+ padding: 0.5em;
+ box-sizing: border-box;
+}
+.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{
+ display: none;
+}
+#settings-gamepad .view{
+ position: absolute;
+ margin: auto;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: 574px;
+ height: 428px;
+ max-height: calc(100vh - 14em + 88px);
+}
+#settings-gamepad .setting-box{
+ height: auto;
+}
+#gamepad-bg{
+ position: relative;
+ width: 550px;
+ height: 317px;
+ max-height: none;
+ background-repeat: no-repeat;
+ text-align: center;
+ font-size: 1.4em;
+ cursor: pointer;
+}
+#gamepad-buttons{
+ position: absolute;
+ left: 141px;
+ top: 120px;
+ width: 282px;
+ height: 131px;
+ background-position: 0 -318px;
+ background-repeat: no-repeat;
+ pointer-events: none;
+}
+#gamepad-value{
+ position: relative;
+ margin-top: 1em;
+}
+#gamepad-value::before{
+ left: auto;
+}
diff --git a/public/src/js/about.js b/public/src/js/about.js
index 235a8de..104d8a1 100644
--- a/public/src/js/about.js
+++ b/public/src/js/about.js
@@ -4,20 +4,20 @@
loader.changePage("about", true)
cancelTouch = false
- this.endButton = document.getElementById("tutorial-end-button")
+ this.endButton = this.getElement("view-end-button")
this.diagTxt = document.getElementById("diag-txt")
this.version = document.getElementById("version-link").href
- this.tutorialOuter = document.getElementById("tutorial-outer")
+ 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 = document.getElementById("tutorial-title")
+ var tutorialTitle = this.getElement("view-title")
tutorialTitle.innerText = strings.aboutSimulator
tutorialTitle.setAttribute("alt", strings.aboutSimulator)
- var tutorialContent = document.getElementById("tutorial-content")
+ var tutorialContent = this.getElement("view-content")
strings.about.bugReporting.forEach(string => {
tutorialContent.appendChild(document.createTextNode(string))
tutorialContent.appendChild(document.createElement("br"))
@@ -34,20 +34,62 @@
pageEvents.add(this.linkIssues, ["click", "touchend"], this.linkButton.bind(this))
pageEvents.add(this.linkEmail, ["click", "touchend"], this.linkButton.bind(this))
- pageEvents.once(this.endButton, ["mousedown", "touchstart"]).then(this.onEnd.bind(this))
- pageEvents.keyOnce(this, 13, "down").then(this.onEnd.bind(this))
+ pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
+ this.items = [this.linkIssues, this.linkEmail, this.endButton]
+ this.selected = 2
+ 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": ["start", "b", "ls", "rs"]
- }, this.onEnd.bind(this))
+ "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))
- this.addDiag()
+ 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 && event.type === "touchstart"){
- event.preventDefault()
- touched = true
+ if(event){
+ if(event.type === "touchstart"){
+ event.preventDefault()
+ touched = true
+ }else if(event.which !== 1){
+ return
+ }
}
this.clean()
assets.sounds["se_don"].play()
@@ -143,17 +185,24 @@
}
}
- var issueBody = strings.issueTemplate + "\n\n\n\n" + diag
+ var issueBody = strings.about.issueTemplate + "\n\n\n\n" + diag
this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "
\r\n"))
+
+ return diag
}
getLink(target){
return target.getElementsByTagName("a")[0]
}
linkButton(event){
- this.getLink(event.currentTarget).click()
+ 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"])
pageEvents.remove(this.linkEmail, ["click", "touchend"])
@@ -161,7 +210,7 @@
if(this.textarea){
pageEvents.remove(this.textarea, ["focus", "blur"])
}
- pageEvents.keyRemove(this, 13)
+ pageEvents.keyRemove(this, "all")
delete this.endButton
delete this.diagTxt
delete this.version
diff --git a/public/src/js/assets.js b/public/src/js/assets.js
index 4f938a1..e363842 100644
--- a/public/src/js/assets.js
+++ b/public/src/js/assets.js
@@ -7,6 +7,7 @@ var assets = {
"scoresheet.js",
"songselect.js",
"keyboard.js",
+ "gameinput.js",
"game.js",
"controller.js",
"circle.js",
@@ -27,7 +28,8 @@ var assets = {
"debug.js",
"session.js",
"importsongs.js",
- "logo.js"
+ "logo.js",
+ "settings.js"
],
"css": [
"main.css",
@@ -35,7 +37,8 @@ var assets = {
"loadsong.css",
"game.css",
"debug.css",
- "songbg.css"
+ "songbg.css",
+ "view.css"
],
"assetsCss": [
"fonts/fonts.css",
@@ -71,18 +74,15 @@ var assets = {
"bg_score_p2.png",
"bg_settings.png",
"bg_pause.png",
- "bg_stage_1.png",
- "bg_stage_2.png",
- "bg_stage_3.png",
"badge_auto.png",
- "touch_drum.png",
"touch_pause.png",
"touch_fullscreen.png",
"mimizu.png",
"results_flowers.png",
"results_mikoshi.png",
"results_tetsuohana.png",
- "results_tetsuohana2.png"
+ "results_tetsuohana2.png",
+ "settings_gamepad.png"
],
"audioSfx": [
"se_cancel.wav",
@@ -154,7 +154,8 @@ var assets = {
"audioMusic": [
"bgm_songsel.mp3",
"bgm_result.mp3",
- "bgm_setsume.mp3"
+ "bgm_setsume.mp3",
+ "bgm_settings.mp3"
],
"fonts": [
"Kozuka",
@@ -168,7 +169,8 @@ var assets = {
"tutorial.html",
"about.html",
"debug.html",
- "session.html"
+ "session.html",
+ "settings.html"
],
"songs": [],
diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js
index 56a1f7d..a731bcb 100644
--- a/public/src/js/canvascache.js
+++ b/public/src/js/canvascache.js
@@ -29,33 +29,58 @@ class CanvasCache{
return
}
var saved = false
+ var time = Date.now()
if(!img){
var w = config.w
var h = config.h
- this.x += this.lastW + 1
+ this.x += this.lastW + (this.lastW ? 1 : 0)
if(this.x + w > this.w){
this.x = 0
this.y += this.lastH + 1
}
- this.lastW = w
- this.lastH = Math.max(this.lastH, h)
- img = {
- x: this.x,
- y: this.y,
- w: w,
- h: h
+ if(this.y + h > this.h){
+ var clear = true
+ var oldest = {time: time}
+ this.map.forEach((oldImg, id) => {
+ if(oldImg.time < oldest.time){
+ oldest.id = id
+ oldest.time = oldImg.time
+ }
+ })
+ 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
+ }
}
- this.map.set(config.id, img)
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.time = time
if(setOnly){
this.ctx.restore()
return
@@ -81,6 +106,10 @@ class CanvasCache{
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
diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js
index 472730c..0e77fbb 100644
--- a/public/src/js/canvasdraw.js
+++ b/public/src/js/canvasdraw.js
@@ -48,9 +48,9 @@
this.regex = {
comma: /[,.]/,
ideographicComma: /[、。]/,
- apostrophe: /['']/,
+ apostrophe: /[''’]/,
degree: /[゚°]/,
- brackets: /[\((\))\[\]「」『』【】]/,
+ brackets: /[\((\))\[\]「」『』【】::;;]/,
tilde: /[\--~~〜_]/,
tall: /[bbddffgghhj-lj-ltt♪]/,
i: /[ii]/,
@@ -268,13 +268,16 @@
easeOut(pos){
return Math.sin(Math.PI / 2 * pos)
}
+ easeOutBack(pos){
+ return Math.sin(Math.PI / 1.74 * pos) * 1.03
+ }
easeInOut(pos){
return (Math.cos(Math.PI * pos) - 1) / -2
}
verticalText(config){
var ctx = config.ctx
- var inputText = config.text
+ var inputText = config.text.toString()
var mul = config.fontSize / 40
var ura = false
var r = this.regex
@@ -572,7 +575,7 @@
}
if(symbol.ura){
ctx.font = (30 * mul) + "px Meiryo, sans-serif"
- ctx.textBaseline = "center"
+ ctx.textBaseline = "middle"
ctx.beginPath()
ctx.arc(currentX, currentY + (17 * mul), (18 * mul), 0, Math.PI * 2)
if(action === "stroke"){
@@ -581,7 +584,7 @@
}else if(action === "fill"){
ctx.strokeStyle = config.fill
ctx.lineWidth = 2.5 * mul
- ctx.fillText(symbol.text, currentX, currentY)
+ ctx.fillText(symbol.text, currentX, currentY + (17 * mul))
}
ctx.stroke()
}else{
@@ -598,7 +601,7 @@
layeredText(config, layers){
var ctx = config.ctx
- var inputText = config.text
+ var inputText = config.text.toString()
var mul = config.fontSize / 40
var ura = false
var r = this.regex
@@ -622,8 +625,6 @@
drawn.push({text: symbol, x: -2, y: 0, w: 20, scale: [0.6, 0.5]})
}else if(symbol === " "){
drawn.push({text: symbol, x: 0, y: 0, w: 10})
- }else if(symbol === "'"){
- drawn.push({text: ",", x: 0, y: -15, w: 7, scale: [1, 0.7]})
}else if(symbol === '"'){
drawn.push({text: symbol, x: 2, y: 0, w: 10})
}else if(symbol === "∀"){
@@ -634,6 +635,8 @@
}
}else if(symbol === "."){
drawn.push({text: symbol, x: -9, y: 0, w: 37})
+ }else if(r.apostrophe.test(symbol)){
+ drawn.push({text: ",", x: 0, y: -15, w: 7, scale: [1, 0.7]})
}else if(r.comma.test(symbol)){
// Comma, full stop
if(bold){
@@ -788,7 +791,7 @@
}
if(symbol.ura){
ctx.font = (30 * mul) + "px Meiryo, sans-serif"
- ctx.textBaseline = "center"
+ ctx.textBaseline = "middle"
ctx.beginPath()
ctx.arc(currentX, currentY + (17 * mul), (18 * mul), 0, Math.PI * 2)
if(action === "strokeText"){
@@ -797,7 +800,7 @@
}else if(action === "fillText"){
ctx.strokeStyle = layer.fill
ctx.lineWidth = 2.5 * mul
- ctx.fillText(symbol.text, currentX, currentY)
+ ctx.fillText(symbol.text, currentX, currentY + (17 * mul))
}
ctx.stroke()
}else{
@@ -1167,6 +1170,7 @@
var firstTop = config.multiplayer ? 0 : 30
var secondTop = config.multiplayer ? 0 : 8
+ config.percentage = Math.max(0, Math.min(1, config.percentage))
var cleared = config.percentage - 1 / 50 >= config.clear
var gaugeW = 14 * 50
diff --git a/public/src/js/circle.js b/public/src/js/circle.js
index 6d3c650..261629e 100644
--- a/public/src/js/circle.js
+++ b/public/src/js/circle.js
@@ -1,6 +1,5 @@
class Circle{
constructor(config){
- // id, ms, type, text, speed, endTime, requiredHits
this.id = config.id
this.ms = config.start
this.originalMS = this.ms
@@ -23,38 +22,13 @@ class Circle{
this.gogoChecked = false
this.beatMS = config.beatMS
this.fixedPos = config.fixedPos
- }
- getMS(){
- return this.ms
- }
- getEndTime(){
- return this.endTime
- }
- getType(){
- return this.type
- }
- getLastFrame(){
- return this.lastFrame
+ this.branch = config.branch
+ this.section = config.section
}
animate(ms){
this.animating = true
this.animT = ms
}
- isAnimated(){
- return this.animating
- }
- getAnimT(){
- return this.animT
- }
- getPlayed(){
- return this.isPlayed
- }
- isAnimationFinished(){
- return this.animationEnded
- }
- endAnimation(){
- this.animationEnded = true
- }
played(score, big){
this.score = score
this.isPlayed = score <= 0 ? score - 1 : (big ? 2 : 1)
@@ -65,16 +39,4 @@ class Circle{
this.timesKa++
}
}
- getScore(){
- return this.score
- }
- getID(){
- return this.id
- }
- getText(){
- return this.text
- }
- getSpeed(){
- return this.speed
- }
}
\ No newline at end of file
diff --git a/public/src/js/controller.js b/public/src/js/controller.js
index ef7dca3..cf253b4 100644
--- a/public/src/js/controller.js
+++ b/public/src/js/controller.js
@@ -21,13 +21,14 @@ class Controller{
assets.songs.forEach(song => {
if(song.id == this.selectedSong.folder){
this.mainAsset = song.sound
+ this.volume = song.volume || 1
}
})
this.game = new Game(this, this.selectedSong, this.parsedSongData)
this.view = new View(this)
this.mekadon = new Mekadon(this, this.game)
- this.keyboard = new Keyboard(this)
+ this.keyboard = new GameInput(this)
this.playedSounds = {}
}
@@ -35,6 +36,9 @@ class Controller{
if(syncWith){
this.syncWith = syncWith
}
+ if(this.multiplayer !== 2){
+ snd.musicGain.setVolumeMul(this.volume)
+ }
this.game.run()
this.view.run()
if(this.multiplayer === 1){
@@ -58,11 +62,19 @@ class Controller{
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
- this.mainAsset.stop()
+ if(this.mainAsset){
+ this.mainAsset.stop()
+ }
if(this.multiplayer !== 2){
clearInterval(this.gameInterval)
}
@@ -153,8 +165,26 @@ class Controller{
if(this.multiplayer){
new LoadSong(this.selectedSong, false, true, this.touchEnabled)
}else{
- var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
- taikoGame.run()
+ new Promise(resolve => {
+ var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
+ if(songObj.chart){
+ var reader = new FileReader()
+ var promise = pageEvents.load(reader).then(event => {
+ this.songData = event.target.result.replace(/\0/g, "").split("\n")
+ resolve()
+ })
+ if(this.selectedSong.type === "tja"){
+ reader.readAsText(songObj.chart, "sjis")
+ }else{
+ reader.readAsText(songObj.chart)
+ }
+ }else{
+ resolve()
+ }
+ }).then(() => {
+ var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
+ taikoGame.run()
+ })
}
}
playSound(id, time){
@@ -180,11 +210,8 @@ class Controller{
getKeys(){
return this.keyboard.getKeys()
}
- setKey(keyCode, down, ms){
- return this.keyboard.setKey(keyCode, down, ms)
- }
- getBindings(){
- return this.keyboard.getBindings()
+ setKey(pressed, name, ms){
+ return this.keyboard.setKey(pressed, name, ms)
}
getElapsedTime(){
return this.game.elapsedTime
@@ -214,7 +241,7 @@ class Controller{
if(this.multiplayer){
p2.play(circle, this.mekadon)
}else{
- this.mekadon.play(circle)
+ return this.mekadon.play(circle)
}
}
clean(){
@@ -224,6 +251,7 @@ class Controller{
this.stopMainLoop()
this.keyboard.clean()
this.view.clean()
+ snd.buffer.loadSettings()
if(!this.multiplayer){
debugObj.controller = null
diff --git a/public/src/js/debug.js b/public/src/js/debug.js
index ca2cf51..a6fa9a7 100644
--- a/public/src/js/debug.js
+++ b/public/src/js/debug.js
@@ -8,15 +8,20 @@ class Debug{
this.debugDiv.innerHTML = assets.pages["debug"]
document.body.appendChild(this.debugDiv)
- this.titleDiv = this.debugDiv.getElementsByClassName("title")[0]
- this.minimiseDiv = this.debugDiv.getElementsByClassName("minimise")[0]
- this.offsetDiv = this.debugDiv.getElementsByClassName("offset")[0]
- this.measureNumDiv = this.debugDiv.getElementsByClassName("measure-num")[0]
- this.restartCheckbox = this.debugDiv.getElementsByClassName("change-restart")[0]
- this.autoplayLabel = this.debugDiv.getElementsByClassName("autoplay-label")[0]
- this.autoplayCheckbox = this.debugDiv.getElementsByClassName("autoplay")[0]
- this.restartBtn = this.debugDiv.getElementsByClassName("restart-btn")[0]
- this.exitBtn = this.debugDiv.getElementsByClassName("exit-btn")[0]
+ 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.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
pageEvents.add(window, ["mousedown", "mouseup", "blur"], this.stopMove.bind(this))
@@ -26,6 +31,8 @@ class Debug{
pageEvents.add(this.restartBtn, "click", this.restartSong.bind(this))
pageEvents.add(this.exitBtn, "click", this.clean.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", this.branchReset.bind(this))
this.offsetSlider = new InputSlider(this.offsetDiv, -60, 60, 3)
this.offsetSlider.onchange(this.offsetChange.bind(this))
@@ -34,9 +41,17 @@ class Debug{
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.moveTo(100, 100)
this.restore()
this.updateStatus()
+ pageEvents.send("debug")
+ }
+ byClass(name){
+ return this.debugDiv.getElementsByClassName(name)[0]
}
startMove(event){
if(event.which === 1){
@@ -87,20 +102,30 @@ class Debug{
}
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"
+ }
- this.controller = debugObj.controller
var selectedSong = this.controller.selectedSong
this.defaultOffset = selectedSong.offset || 0
if(this.songFolder === selectedSong.folder){
this.offsetChange(this.offsetSlider.get(), true)
+ this.branchChange(null, true)
+ this.volumeChange(this.volumeSlider.get(), true)
}else{
this.songFolder = selectedSong.folder
this.offsetSlider.set(this.defaultOffset)
+ this.branchReset(null, true)
+ this.volumeSlider.set(this.controller.volume)
}
- var measures = this.controller.parsedSongData.measures
+ 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 && measures.length > this.measureNum){
var measureMS = measures[this.measureNum].ms
@@ -116,6 +141,7 @@ class Debug{
if(circles[i].endTime >= measureMS){
break
}
+ game.skipNote(circles[i])
}
if(game.mainMusicPlaying){
game.mainMusicPlaying = false
@@ -127,6 +153,7 @@ class Debug{
if(this.controller && !debugObj.controller){
this.restartBtn.style.display = ""
this.autoplayLabel.style.display = ""
+ this.branchHideDiv.style.display = ""
this.controller = null
}
}
@@ -141,6 +168,11 @@ class Debug{
songData.measures.forEach(measure => {
measure.ms = measure.originalMS + offset
})
+ if(songData.branches){
+ songData.branches.forEach(branch => {
+ branch.ms = branch.originalMS + offset
+ })
+ }
if(this.restartCheckbox.checked && !noRestart){
this.restartSong()
}
@@ -152,6 +184,14 @@ class Debug{
this.restartSong()
}
}
+ volumeChange(value, noRestart){
+ if(this.controller){
+ snd.musicGain.setVolumeMul(value)
+ }
+ if(this.restartCheckbox.checked && !noRestart){
+ this.restartSong()
+ }
+ }
restartSong(){
if(this.controller){
this.controller.restartSong()
@@ -162,29 +202,57 @@ class Debug{
this.controller.autoPlayEnabled = this.autoplayCheckbox.checked
if(!this.controller.autoPlayEnabled){
var keyboard = debugObj.controller.keyboard
- var kbd = keyboard.getBindings()
- keyboard.setKey(kbd.don_l, false)
- keyboard.setKey(kbd.don_r, false)
- keyboard.setKey(kbd.ka_l, false)
- keyboard.setKey(kbd.ka_r, false)
+ 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)
+ }
clean(){
this.offsetSlider.clean()
+ this.measureNumSlider.clean()
pageEvents.remove(window, ["mousedown", "mouseup", "blur"])
pageEvents.mouseRemove(this)
+ pageEvents.remove(this.titleDiv, "mousedown")
pageEvents.remove(this.title, "mousedown")
pageEvents.remove(this.minimiseDiv, "click")
pageEvents.remove(this.restartBtn, "click")
pageEvents.remove(this.exitBtn, "click")
pageEvents.remove(this.autoplayCheckbox, "change")
+ pageEvents.remove(this.branchSelect, "change")
+ pageEvents.remove(this.branchResetBtn, "click")
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.restartCheckbox
delete this.autoplayLabel
delete this.autoplayCheckbox
diff --git a/public/src/js/game.js b/public/src/js/game.js
index 2df56d0..63a92b7 100644
--- a/public/src/js/game.js
+++ b/public/src/js/game.js
@@ -19,8 +19,8 @@ class Game{
difficulty: this.rules.difficulty
}
this.HPGain = 100 / this.songData.circles.filter(circle => {
- var type = circle.getType()
- return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa"
+ var type = circle.type
+ return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active)
}).length
this.paused = false
this.started = false
@@ -28,6 +28,9 @@ class Game{
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){
@@ -69,17 +72,19 @@ class Game{
}
updateCirclesStatus(){
var nextSet = false
+ var ms = this.elapsedTime
var circles = this.songData.circles
var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1
- for(var i = startIndex; i < circles.length && i < this.currentCircle + 2; i++){
+ var index = 0
+
+ for(var i = startIndex; i < circles.length; i++){
var circle = circles[i]
- if(!circle.getPlayed()){
- var ms = this.elapsedTime
- var type = circle.getType()
+ if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){
+ var type = circle.type
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
- var endTime = circle.getEndTime() + (drumrollNotes ? 0 : this.rules.bad)
+ var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad)
- if(ms >= circle.getMS()){
+ if(ms >= circle.ms){
if(drumrollNotes && !circle.rendaPlayed && ms < endTime){
circle.rendaPlayed = true
if(this.rules.difficulty === "easy"){
@@ -97,11 +102,14 @@ class Game{
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.getMS()) / circle.timesHit
+ pace: (ms - circle.ms) / circle.timesHit
}
if(type === "drumroll" || type === "daiDrumroll"){
value.kaAmount = circle.timesKa / circle.timesHit
@@ -109,58 +117,178 @@ class Game{
p2.send("drumroll", value)
}
}else{
- var currentScore = 0
- circle.played(-1, type === "daiDon" || type === "daiKa")
- this.controller.displayScore(currentScore, true)
+ this.skipNote(circle)
this.updateCurrentCircle()
- this.updateCombo(currentScore)
- this.updateGlobalScore(currentScore, 1)
- if(this.controller.multiplayer === 1){
- p2.send("note", {
- score: -1
- })
- }
}
}
}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")
+ }
+ }
+ }
+ }
+ }
+ 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
+ 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(circle && this.controller.autoPlayEnabled){
- return this.controller.autoPlay(circle)
+ if(this.controller.autoPlayEnabled){
+ while(circle && this.controller.autoPlay(circle)){
+ circle = circles[this.currentCircle]
+ }
+ return
}
var keys = this.controller.getKeys()
- var kbd = this.controller.getBindings()
- var don_l = keys[kbd["don_l"]] && !this.controller.isWaiting(kbd["don_l"], "score")
- var don_r = keys[kbd["don_r"]] && !this.controller.isWaiting(kbd["don_r"], "score")
- var ka_l = keys[kbd["ka_l"]] && !this.controller.isWaiting(kbd["ka_l"], "score")
- var ka_r = keys[kbd["ka_r"]] && !this.controller.isWaiting(kbd["ka_r"], "score")
+ 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")
- if(don_l && don_r){
- this.checkKey([kbd["don_l"], kbd["don_r"]], circle, "daiDon")
- }else if(don_l){
- this.checkKey([kbd["don_l"]], circle, "don")
- }else if(don_r){
- this.checkKey([kbd["don_r"]], circle, "don")
+ 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")
+ }
}
- if(ka_l && ka_r){
- this.checkKey([kbd["ka_l"], kbd["ka_r"]], circle, "daiKa")
- }else if(ka_l){
- this.checkKey([kbd["ka_l"]], circle, "ka")
- }else if(ka_r){
- this.checkKey([kbd["ka_r"]], circle, "ka")
+ 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.getPlayed()){
+ if(circle && !circle.isPlayed){
if(!this.checkScore(circle, check)){
return
}
@@ -171,7 +299,7 @@ class Game{
}
checkScore(circle, check){
var ms = this.elapsedTime
- var type = circle.getType()
+ var type = circle.type
var keysDon = check === "don" || check === "daiDon"
var keysKa = check === "ka" || check === "daiKa"
@@ -182,7 +310,14 @@ class Game{
var keyTime = this.controller.getKeyTime()
var currentTime = keysDon ? keyTime["don"] : keyTime["ka"]
- var relative = currentTime - circle.getMS()
+ var relative = currentTime - circle.ms
+
+ 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){
@@ -213,17 +348,26 @@ class Game{
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(this.controller.multiplayer == 1){
+ 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.getMS() - currentTime,
- dai: typeDai ? keyDai ? 2 : 1 : 0
+ ms: circle.ms - currentTime,
+ dai: typeDai ? (keyDai ? 2 : 1) : 0
}
if((!keysDon || !typeDon) && (!keysKa || !typeKa)){
value.reverse = true
@@ -231,12 +375,12 @@ class Game{
p2.send("note", value)
}
}else{
- if(circle.getMS() > currentTime || currentTime > circle.getEndTime()){
+ if(circle.ms > currentTime || currentTime > circle.endTime){
return true
}
if(keysDon && type === "balloon"){
this.checkBalloon(circle)
- if(check === "daiDon" && !circle.getPlayed()){
+ if(check === "daiDon" && !circle.isPlayed){
this.checkBalloon(circle)
}
}else if((keysDon || keysKa) && (type === "drumroll" || type === "daiDrumroll")){
@@ -256,24 +400,28 @@ class Game{
circle.played(score)
if(this.controller.multiplayer == 1){
p2.send("drumroll", {
- pace: (this.elapsedTime - circle.getMS()) / circle.timesHit
+ pace: (this.elapsedTime - circle.ms) / circle.timesHit
})
}
}else{
var score = 300
circle.hit()
}
- this.globalScore.drumroll ++
+ this.globalScore.drumroll++
+ this.sectionDrumroll++
this.globalScore.points += score
this.view.setDarkBg(false)
}
checkDrumroll(circle, keysKa){
var ms = this.elapsedTime
- var dai = circle.getType() === "daiDrumroll"
+ 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.getType() === "drumroll"){
+ if(circle.type === "drumroll"){
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
}else{
var sound = keyTime["don"] > keyTime["ka"] ? "daiDon" : "daiKa"
@@ -291,6 +439,7 @@ class Game{
circleAnim.animate(ms)
this.view.drumroll.push(circleAnim)
this.globalScore.drumroll++
+ this.sectionDrumroll++
this.globalScore.points += score * (dai ? 2 : 1)
this.view.setDarkBg(false)
}
@@ -298,11 +447,11 @@ class Game{
var ms = this.elapsedTime
if(!this.lastCircle){
var circles = this.songData.circles
- this.lastCircle = circles[circles.length - 1].getEndTime()
+ this.lastCircle = circles[circles.length - 1].endTime
if(this.controller.multiplayer){
var syncWith = this.controller.syncWith
var syncCircles = syncWith.game.songData.circles
- var syncLastCircle = syncCircles[syncCircles.length - 1].getEndTime()
+ var syncLastCircle = syncCircles[syncCircles.length - 1].endTime
if(syncLastCircle > this.lastCircle){
this.lastCircle = syncLastCircle
}
@@ -319,7 +468,8 @@ class Game{
var started = this.fadeOutStarted
if(started){
var ms = this.elapsedTime
- var musicDuration = this.controller.mainAsset.duration * 1000 - this.controller.offset
+ var duration = this.mainAsset ? this.mainAsset.duration : 0
+ var musicDuration = duration * 1000 - this.controller.offset
if(this.musicFadeOut === 0){
if(this.controller.multiplayer === 1){
p2.send("gameresults", this.getGlobalScore())
@@ -345,7 +495,7 @@ class Game{
playMainMusic(){
var ms = this.elapsedTime + this.controller.offset
if(!this.mainMusicPlaying && (!this.fadeOutStarted || ms < this.fadeOutStarted + 1600)){
- if(this.controller.multiplayer !== 2){
+ if(this.controller.multiplayer !== 2 && this.mainAsset){
this.mainAsset.play((ms < 0 ? -ms : 0) / 1000, false, Math.max(0, ms / 1000))
}
this.mainMusicPlaying = true
@@ -356,12 +506,15 @@ class Game{
assets.sounds["se_pause"].play()
this.paused = true
this.latestDate = Date.now()
- this.mainAsset.stop()
+ if(this.mainAsset){
+ this.mainAsset.stop()
+ }
this.mainMusicPlaying = false
this.view.pauseMove(0, true)
this.view.gameDiv.classList.add("game-paused")
this.view.lastMousemove = this.view.getMS()
this.view.cursorHidden = false
+ pageEvents.send("pause")
}else{
assets.sounds["se_cancel"].play()
this.paused = false
@@ -370,6 +523,7 @@ class Game{
this.sndTime = currentDate - snd.buffer.getTime() * 1000
this.view.gameDiv.classList.remove("game-paused")
this.view.pointer()
+ pageEvents.send("unpause", currentDate - this.latestDate)
}
}
isPaused(){
@@ -385,12 +539,13 @@ class Game{
this.sndTime = this.startDate - snd.buffer.getTime() * 1000
}else if(ms < 0 || ms >= 0 && this.started){
var currentDate = Date.now()
- if(!this.controller.touchEnabled){
+ 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
@@ -407,7 +562,10 @@ class Game{
return this.songData.circles
}
updateCurrentCircle(){
- this.currentCircle++
+ var circles = this.songData.circles
+ do{
+ var circle = circles[++this.currentCircle]
+ }while(circle && circle.branch && !circle.branch.active)
}
getCurrentCircle(){
return this.currentCircle
@@ -461,4 +619,59 @@ class Game{
}
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 >= ms
+ })
+ if(closestCircle !== -1){
+ this.currentCircle = closestCircle
+ }
+ }
+ this.HPGain = 100 / this.songData.circles.filter(circle => {
+ var type = circle.type
+ return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active)
+ }).length
+ if(this.controller.multiplayer === 1){
+ p2.send("branch", activeName)
+ }
+ }
+ resetSection(){
+ this.sectionNotes = []
+ this.sectionDrumroll = 0
+ }
}
diff --git a/public/src/js/gameinput.js b/public/src/js/gameinput.js
new file mode 100644
index 0000000..4707652
--- /dev/null
+++ b/public/src/js/gameinput.js
@@ -0,0 +1,247 @@
+class GameInput{
+ constructor(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){
+ 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()){
+ assets.sounds["se_ka"].play()
+ 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
+ if(name == "don_l" || name == "don_r"){
+ this.checkKeySound(name, "don")
+ this.keyboardEvents++
+ }else if(name == "ka_l" || name == "ka_r"){
+ this.checkKeySound(name, "ka")
+ this.keyboardEvents++
+ }
+ }else{
+ this.keys[name] = false
+ this.waitKeyupScore[name] = false
+ this.waitKeyupSound[name] = false
+ this.waitKeyupMenu[name] = false
+ }
+ }
+ 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")
+ }
+ }
+}
diff --git a/public/src/js/gamepad.js b/public/src/js/gamepad.js
index b6255c0..a4b3327 100644
--- a/public/src/js/gamepad.js
+++ b/public/src/js/gamepad.js
@@ -1,6 +1,7 @@
class Gamepad{
constructor(bindings, callback){
this.bindings = bindings
+ this.callback = !!callback
this.b = {
"a": 0,
"b": 1,
@@ -25,6 +26,7 @@ class Gamepad{
"lsl": "lsl"
}
this.btn = {}
+ this.gamepadEvents = 0
if(callback){
this.interval = setInterval(() => {
this.play(callback)
@@ -86,6 +88,9 @@ class Gamepad{
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
@@ -123,6 +128,7 @@ class Gamepad{
if(pressed){
callback(true, keyCode)
+ this.gamepadEvents++
}else if(!button){
if(released){
this.toRelease[keyCode + "released"] = true
@@ -134,6 +140,11 @@ class Gamepad{
}
}
clean(){
- clearInterval(this.interval)
+ if(this.callback){
+ clearInterval(this.interval)
+ }
+ delete this.bindings
+ delete this.b
+ delete this.btn
}
}
diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js
index bf669ea..112b675 100644
--- a/public/src/js/importsongs.js
+++ b/public/src/js/importsongs.js
@@ -29,6 +29,8 @@
this.otherFiles = {}
this.songs = []
this.stylesheet = []
+ this.songTitle = {}
+ this.uraRegex = /\s*[\((]裏[\))]$/
this.courseTypes = {
"easy": 0,
"normal": 1,
@@ -82,7 +84,7 @@
file: file,
index: i
})
- }else if(name === "genre.ini" || name === "box.def"){
+ }else if(name === "genre.ini" || name === "box.def" || name === "songtitle.txt"){
var level = (file.webkitRelativePath.match(/\//g) || []).length
metaFiles.push({
file: file,
@@ -154,6 +156,20 @@
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.webkitRelativePath.toLowerCase().slice(0, file.name.length * -1)
@@ -168,7 +184,11 @@
this.osuFiles.forEach(filesLoop)
}
}).catch(() => {})
- reader.readAsText(file, "sjis")
+ if(name === "songtitle.txt"){
+ reader.readAsText(file)
+ }else{
+ reader.readAsText(file, "sjis")
+ }
return promise
}
@@ -183,26 +203,27 @@
var songObj = {
id: index + 1,
type: "tja",
- chart: data,
- stars: []
+ chart: file,
+ stars: [],
+ music: "muted"
}
+ var titleLang = {}
+ var subtitleLang = {}
var dir = file.webkitRelativePath.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
var hasCategory = false
for(var diff in tja.metadata){
var meta = tja.metadata[diff]
- songObj.title = songObj.title_en = meta.title || file.name.slice(0, file.name.lastIndexOf("."))
+ songObj.title = meta.title || file.name.slice(0, file.name.lastIndexOf("."))
var subtitle = meta.subtitle || ""
- if(subtitle.startsWith("--")){
- subtitle = subtitle.slice(2)
- }
- songObj.subtitle = songObj.subtitle_en = subtitle
- songObj.preview = meta.demostart ? Math.floor(meta.demostart * 1000) : 0
- if(meta.level){
- songObj.stars[this.courseTypes[diff]] = meta.level
+ if(subtitle.startsWith("--") || subtitle.startsWith("++")){
+ subtitle = subtitle.slice(2).trim()
}
+ songObj.subtitle = subtitle
+ songObj.preview = meta.demostart || 0
+ songObj.stars[this.courseTypes[diff]] = (meta.level || "0") + (meta.branch ? " B" : "")
if(meta.wave){
- songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()]
+ songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music
}
if(meta.genre){
songObj.category = this.categories[meta.genre.toLowerCase()] || meta.genre
@@ -210,11 +231,44 @@
if(meta.taikowebskin){
songObj.song_skin = this.getSkin(dir, meta.taikowebskin)
}
+ 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]
+ }else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){
+ titleLang[id] = this.songTitle[songTitle][id] + ura
+ }
+ if(meta["subtitle" + id]){
+ subtitleLang[id] = meta["subtitle" + id]
+ }
+ }
+ }
+ var titleLangArray = []
+ for(var id in titleLang){
+ titleLangArray.push(id + " " + titleLang[id])
+ }
+ if(titleLangArray.length !== 0){
+ songObj.title_lang = titleLangArray.join("\n")
+ }
+ var subtitleLangArray = []
+ for(var id in subtitleLang){
+ subtitleLangArray.push(id + " " + subtitleLang[id])
+ }
+ if(subtitleLangArray.length !== 0){
+ songObj.subtitle_lang = subtitleLangArray.join("\n")
}
if(!songObj.category){
songObj.category = category || this.getCategory(file)
}
- if(songObj.music && songObj.stars.filter(star => star).length !== 0){
+ if(songObj.stars.length !== 0){
this.songs[index] = songObj
}
}).catch(() => {})
@@ -235,12 +289,12 @@
var songObj = {
id: index + 1,
type: "osu",
- chart: data,
+ chart: file,
subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist,
- subtitle_en: osu.metadata.Artist || osu.metadata.ArtistUnicode,
- preview: osu.generalInfo.PreviewTime,
+ subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode,
+ preview: osu.generalInfo.PreviewTime / 1000,
stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1],
- music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()]
+ music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted"
}
var filename = file.name.slice(0, file.name.lastIndexOf("."))
var title = osu.metadata.TitleUnicode || osu.metadata.Title
@@ -251,13 +305,11 @@
suffix = " " + matches[0]
}
songObj.title = title + suffix
- songObj.title_en = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix
+ songObj.title_lang = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix
}else{
songObj.title = filename
}
- if(songObj.music){
- this.songs[index] = songObj
- }
+ this.songs[index] = songObj
songObj.category = category || this.getCategory(file)
}).catch(() => {})
reader.readAsText(file)
@@ -386,6 +438,7 @@
document.head.appendChild(style)
}
if(this.songs.length){
+ var length = this.songs.length
assets.songs = this.songs
assets.customSongs = true
assets.customSelected = 0
@@ -395,6 +448,7 @@
loader.screen.removeChild(this.loaderDiv)
this.clean()
new SongSelect("browse", false, this.songSelect.touchEnabled)
+ pageEvents.send("import-songs", length)
}, 500)
}else{
loader.screen.removeChild(this.loaderDiv)
diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js
index 6d938ac..949673b 100644
--- a/public/src/js/keyboard.js
+++ b/public/src/js/keyboard.js
@@ -1,247 +1,105 @@
class Keyboard{
- constructor(controller){
- this.controller = controller
- this.game = this.controller.game
-
- this.kbd = {
- "don_l": 70, // F
- "don_r": 74, // J
- "ka_l": 68, // D
- "ka_r": 75, // K
- "pause": 81, // Q
- "back": 8, // Backspace
- "previous": 37, // Left
- "next": 39, // Right
- "confirm": 13 // Enter
+ constructor(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.kbdAlias = {
- "pause": [27], // Esc
- "previous": [38], // Up
- "next": [40], // Down
- "confirm": [32] // Space
- }
- this.keys = {}
- this.waitKeyupScore = {}
- this.waitKeyupSound = {}
- this.waitKeyupMenu = {}
- this.keyTime = {
- "don": -Infinity,
- "ka": -Infinity
- }
-
- var gameBtn = {}
- gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"]
- gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y", "rs"]
- gameBtn[this.kbd["ka_l"]] = ["lb", "lt"]
- gameBtn[this.kbd["ka_r"]] = ["rb", "rt"]
- this.gamepad = new Gamepad(gameBtn)
- this.gamepadInterval = setInterval(this.gamepadKeys.bind(this), 1000 / 60 / 2)
-
- var menuBtn = {
- "cancel": ["a"],
- }
- menuBtn[this.kbd["confirm"]] = ["b", "ls", "rs"]
- menuBtn[this.kbd["previous"]] = ["u", "l", "lb", "lt", "lsu", "lsl"],
- menuBtn[this.kbd["next"]] = ["d", "r", "rb", "rt", "lsd", "lsr"]
- menuBtn[this.kbd["pause"]] = ["start"]
- this.gamepadMenu = new Gamepad(menuBtn)
-
- this.kbdSearch = {}
- for(var name in this.kbdAlias){
- var list = this.kbdAlias[name]
- for(var i in list){
- this.kbdSearch[list[i]] = this.kbd[name]
+ this.btn = {}
+ this.update()
+ pageEvents.keyAdd(this, "all", "both", this.keyEvent.bind(this))
+ pageEvents.blurAdd(this, this.blurEvent.bind(this))
+ }
+ update(){
+ var kbdSettings = settings.getItem("keyboardSettings")
+ var drumKeys = {}
+ for(var name in kbdSettings){
+ var keys = kbdSettings[name]
+ for(var i in keys){
+ drumKeys[keys[i]] = name
}
}
- for(var name in this.kbd){
- this.kbdSearch[this.kbd[name]] = this.kbd[name]
- }
-
- pageEvents.keyAdd(this, "all", "both", event => {
- if(event.keyCode === 8){
- // Disable back navigation when pressing backspace
- event.preventDefault()
- }
- var key = this.kbdSearch[event.keyCode]
- if(key && !event.repeat && this.buttonEnabled(key)){
- var ms = this.game.getAccurateTime()
- this.setKey(key, event.type === "keydown", ms)
- }
- })
- }
- getBindings(){
- return this.kbd
- }
- buttonEnabled(keyCode){
- if(this.controller.autoPlayEnabled){
- switch(keyCode){
- case this.kbd["don_l"]:
- case this.kbd["don_r"]:
- case this.kbd["ka_l"]:
- case this.kbd["ka_r"]:
- return false
- }
- }
- return true
- }
- checkGameKeys(){
- if(this.controller.autoPlayEnabled){
- this.checkKeySound(this.kbd["don_l"], "don")
- this.checkKeySound(this.kbd["don_r"], "don")
- this.checkKeySound(this.kbd["ka_l"], "ka")
- this.checkKeySound(this.kbd["ka_r"], "ka")
- }
- }
- gamepadKeys(){
- if(!this.game.isPaused() && !this.controller.autoPlayEnabled){
- this.gamepad.play((pressed, keyCode) => {
- if(pressed){
- if(this.keys[keyCode]){
- this.setKey(keyCode, false)
- }
- this.setKey(keyCode, true, this.game.getAccurateTime())
- }else{
- this.setKey(keyCode, false)
+ 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
}
- })
- }
- }
- checkMenuKeys(){
- if(!this.controller.multiplayer && !this.locked){
- var moveMenu = 0
- var ms = this.game.getAccurateTime()
- this.gamepadMenu.play((pressed, keyCode) => {
- if(pressed){
- if(this.game.isPaused()){
- if(keyCode === "cancel"){
- this.locked = true
- return setTimeout(() => {
- this.controller.togglePause()
- this.locked = false
- }, 200)
+ 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
}
}
- if(this.keys[keyCode]){
- this.setKey(keyCode, false)
- }
- this.setKey(keyCode, true, ms)
}else{
- this.setKey(keyCode, false)
- }
- })
- this.checkKey(this.kbd["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)
+ if(key in this.substitute){
+ key = this.substitute[key]
+ }
+ if(!(key in this.kbd)){
+ if(key === "wildcard"){
+ this.wildcard = true
+ }
+ this.kbd[key] = name
+ }
}
}
- this.checkKey(this.kbd["previous"], "menu", moveMenuMinus)
- this.checkKey(this.kbd["ka_l"], "menu", moveMenuMinus)
- this.checkKey(this.kbd["next"], "menu", moveMenuPlus)
- this.checkKey(this.kbd["ka_r"], "menu", moveMenuPlus)
- this.checkKey(this.kbd["confirm"], "menu", moveMenuConfirm)
- this.checkKey(this.kbd["don_l"], "menu", moveMenuConfirm)
- this.checkKey(this.kbd["don_r"], "menu", moveMenuConfirm)
- if(moveMenu && this.game.isPaused()){
- assets.sounds["se_ka"].play()
- this.controller.view.pauseMove(moveMenu)
- }
- }
- if(this.controller.multiplayer !== 2){
- this.checkKey(this.kbd["back"], "menu", () => {
- if(this.controller.multiplayer === 1){
- p2.send("gameend")
- }
- this.controller.togglePause()
- this.controller.songSelection()
- })
}
}
- checkKey(keyCode, type, callback){
- if(this.keys[keyCode] && !this.isWaiting(keyCode, type)){
- this.waitForKeyup(keyCode, type)
- callback()
+ keyEvent(event){
+ var key = event.key.toLowerCase()
+ if(key === "escape" || key === "backspace" || key === "tab"){
+ event.preventDefault()
}
- }
- checkKeySound(keyCode, sound){
- this.checkKey(keyCode, "sound", () => {
- var circles = this.controller.getCircles()
- var circle = circles[this.controller.getCurrentCircle()]
- if(
- sound === "don"
- && circle
- && !circle.getPlayed()
- && circle.getType() === "balloon"
- && circle.requiredHits - circle.timesHit <= 1
- ){
- this.controller.playSound("se_balloon")
+ if(!event.repeat){
+ var pressed = event.type === "keydown"
+ if(pressed){
+ this.btn[key] = true
}else{
- this.controller.playSound("neiro_1_" + sound)
+ delete this.btn[key]
+ if(key in this.kbd){
+ for(var i in this.btn){
+ if(this.kbd[i] === this.kbd[key]){
+ return
+ }
+ }
+ }
}
- this.keyTime[sound] = this.keyTime[keyCode]
- })
- }
- getKeys(){
- return this.keys
- }
- setKey(keyCode, down, ms){
- if(down){
- this.keys[keyCode] = true
- if(this.game.isPaused()){
- return
+ if(key in this.kbd){
+ this.callback(pressed, this.kbd[key], event)
+ }else if(this.wildcard){
+ this.callback(pressed, this.kbd["wildcard"], event)
}
- this.keyTime[keyCode] = ms
- if(keyCode == this.kbd.don_l || keyCode == this.kbd.don_r){
- this.checkKeySound(keyCode, "don")
- }else if(keyCode == this.kbd.ka_l || keyCode == this.kbd.ka_r){
- this.checkKeySound(keyCode, "ka")
- }
- }else{
- this.keys[keyCode] = false
- this.waitKeyupScore[keyCode] = false
- this.waitKeyupSound[keyCode] = false
- this.waitKeyupMenu[keyCode] = false
}
}
- isWaiting(keyCode, type){
- if(type === "score"){
- return this.waitKeyupScore[keyCode]
- }else if(type === "sound"){
- return this.waitKeyupSound[keyCode]
- }else if(type === "menu"){
- return this.waitKeyupMenu[keyCode]
+ 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)
+ }
+ }
}
}
- waitForKeyup(keyCode, type){
- if(type === "score"){
- this.waitKeyupScore[keyCode] = true
- }else if(type === "sound"){
- this.waitKeyupSound[keyCode] = true
- }else if(type === "menu"){
- this.waitKeyupMenu[keyCode] = true
- }
- }
- getKeyTime(){
- return this.keyTime
- }
clean(){
pageEvents.keyRemove(this, "all")
- clearInterval(this.gamepadInterval)
+ pageEvents.blurRemove(this)
+ delete this.bindings
+ delete this.callback
+ delete this.kbd
+ delete this.btn
}
}
diff --git a/public/src/js/loader.js b/public/src/js/loader.js
index a2ce20d..a3ba450 100644
--- a/public/src/js/loader.js
+++ b/public/src/js/loader.js
@@ -25,6 +25,12 @@ class Loader{
var queryString = gameConfig._version.commit_short ? "?" + gameConfig._version.commit_short : ""
+ if(gameConfig.custom_js){
+ var script = document.createElement("script")
+ this.addPromise(pageEvents.load(script))
+ script.src = gameConfig.custom_js + queryString
+ document.head.appendChild(script)
+ }
assets.js.forEach(name => {
var script = document.createElement("script")
this.addPromise(pageEvents.load(script))
@@ -32,7 +38,15 @@ class Loader{
document.head.appendChild(script)
})
- this.addPromise(new Promise(resolve => {
+ this.addPromise(new Promise((resolve, reject) => {
+ if(
+ versionLink.href !== gameConfig._version.url &&
+ gameConfig._version.commit &&
+ versionLink.href.indexOf(gameConfig._version.commit) === -1
+ ){
+ // Version in the config does not match version on the page
+ reject()
+ }
var cssCount = document.styleSheets.length + assets.css.length
assets.css.forEach(name => {
var stylesheet = document.createElement("link")
@@ -112,6 +126,7 @@ class Loader{
0.5
)
snd.sfxLoudGain.setVolume(1.2)
+ snd.buffer.saveSettings()
this.afterJSCount = 0
@@ -147,17 +162,29 @@ class Loader{
}
}))
+ var readyEvent = "normal"
+ var songId
+ var hashLower = location.hash.toLowerCase()
p2 = new P2Connection()
- if(location.hash.length === 6){
+ 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
this.addPromise(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()
}
})
@@ -176,13 +203,17 @@ class Loader{
p2.hash("")
}
+ settings = new Settings()
+ pageEvents.setKbd()
+
Promise.all(this.promises).then(() => {
this.canvasTest.drawAllImages().then(result => {
perf.allImg = result
perf.load = Date.now() - this.startTime
this.canvasTest.clean()
this.clean()
- this.callback()
+ this.callback(songId)
+ pageEvents.send("ready", readyEvent)
})
}, this.errorMsg.bind(this))
@@ -192,7 +223,7 @@ class Loader{
}
addPromise(promise){
this.promises.push(promise)
- promise.then(this.assetLoaded.bind(this))
+ promise.then(this.assetLoaded.bind(this), this.errorMsg.bind(this))
}
loadSound(name, gain){
var id = this.getFilename(name)
@@ -205,6 +236,7 @@ class Loader{
}
errorMsg(error){
console.error(error)
+ pageEvents.send("loader-error", error)
this.error = true
this.loaderPercentage.appendChild(document.createElement("br"))
this.loaderPercentage.appendChild(document.createTextNode("An error occurred, please refresh"))
@@ -237,7 +269,9 @@ class Loader{
}
clean(){
var fontDetectDiv = document.getElementById("fontdetectHelper")
- fontDetectDiv.parentNode.removeChild(fontDetectDiv)
+ if(fontDetectDiv){
+ fontDetectDiv.parentNode.removeChild(fontDetectDiv)
+ }
delete this.loaderPercentage
delete this.loaderProgress
delete this.promises
diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js
index 273217f..d70b799 100644
--- a/public/src/js/loadsong.js
+++ b/public/src/js/loadsong.js
@@ -4,6 +4,15 @@ class LoadSong{
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")
@@ -15,6 +24,12 @@ class LoadSong{
cancel.setAttribute("alt", strings.cancel)
}
this.run()
+ pageEvents.send("load-song", {
+ selectedSong: selectedSong,
+ autoPlayEnabled: autoPlayEnabled,
+ multiplayer: multiplayer,
+ touchEnabled: touchEnabled
+ })
}
run(){
var song = this.selectedSong
@@ -51,9 +66,10 @@ class LoadSong{
}
if(type === "don"){
song.donBg = null
- }
- if(type === "song"){
+ }else if(type === "song"){
song.songBg = null
+ }else if(type === "stage"){
+ song.songStage = null
}
}
}
@@ -65,19 +81,14 @@ class LoadSong{
continue
}
let img = document.createElement("img")
- if(!songObj.music && this.touchEnabled && imgLoad[i].type === "song"){
+ let force = imgLoad[i].type === "song" && this.touchEnabled
+ if(!songObj.music && (this.imgScale !== 1 || force)){
img.crossOrigin = "Anonymous"
}
let promise = pageEvents.load(img)
- if(imgLoad[i].type === "song"){
- promises.push(promise.then(() => {
- return this.scaleImg(img, filename, prefix)
- }))
- }else{
- promises.push(promise.then(() => {
- assets.image[prefix + filename] = img
- }))
- }
+ promises.push(promise.then(() => {
+ return this.scaleImg(img, filename, prefix, force)
+ }))
if(songObj.music){
img.src = URL.createObjectURL(song.songSkin[filename + ".png"])
}else{
@@ -91,32 +102,53 @@ class LoadSong{
if(songObj.sound){
songObj.sound.gain = snd.musicGain
resolve()
- }else if(songObj.music){
+ }else if(!songObj.music){
+ snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.mp3").then(sound => {
+ songObj.sound = sound
+ resolve()
+ }, reject)
+ }else if(songObj.music !== "muted"){
snd.musicGain.load(songObj.music, true).then(sound => {
songObj.sound = sound
resolve()
}, reject)
}else{
- snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.ogg").then(sound => {
- songObj.sound = sound
- resolve()
- }, reject)
+ resolve()
}
}))
if(songObj.chart){
- this.songData = songObj.chart
+ var reader = new FileReader()
+ promises.push(pageEvents.load(reader).then(event => {
+ this.songData = event.target.result.replace(/\0/g, "").split("\n")
+ }))
+ if(song.type === "tja"){
+ reader.readAsText(songObj.chart, "sjis")
+ }else{
+ reader.readAsText(songObj.chart)
+ }
}else{
promises.push(loader.ajax(this.getSongPath(song)).then(data => {
this.songData = data.replace(/\0/g, "").split("\n")
}))
}
+ if(this.touchEnabled && !assets.image["touch_drum"]){
+ let img = document.createElement("img")
+ if(this.imgScale !== 1){
+ img.crossOrigin = "Anonymous"
+ }
+ promises.push(pageEvents.load(img).then(() => {
+ return this.scaleImg(img, "touch_drum", "")
+ }))
+ img.src = gameConfig.assets_baseurl + "img/touch_drum.png"
+ }
Promise.all(promises).then(() => {
this.setupMultiplayer()
}, error => {
- console.error(error)
if(Array.isArray(error) && error[1] instanceof HTMLElement){
error = error[0] + ": " + error[1].outerHTML
}
+ console.error(error)
+ pageEvents.send("load-song-error", error)
errorMessage(new Error(error).stack)
alert("An error occurred, please refresh")
})
@@ -134,23 +166,23 @@ class LoadSong{
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++){
- for(var letter = 0; letter < 2; letter++){
- let filenameAb = filenames[i] + (letter === 0 ? "a" : "b")
+ 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")
- if(filenameAb.startsWith("bg_song_")){
- if(this.touchEnabled){
- img.crossOrigin = "Anonymous"
- }
- promises.push(pageEvents.load(img).then(() => {
- return this.scaleImg(img, filenameAb, "")
- }))
- }else{
- promises.push(pageEvents.load(img).then(() => {
- assets.image[filenameAb] = img
- }))
+ let force = filenameAb.startsWith("bg_song_") && this.touchEnabled
+ if(this.imgScale !== 1 || force){
+ img.crossOrigin = "Anonymous"
}
+ promises.push(pageEvents.load(img).then(() => {
+ return this.scaleImg(img, filenameAb, "", force)
+ }))
img.src = gameConfig.assets_baseurl + "img/" + filenameAb + ".png"
}
}
@@ -158,12 +190,16 @@ class LoadSong{
Promise.all(promises).then(resolve, reject)
})
}
- scaleImg(img, filename, prefix){
+ scaleImg(img, filename, prefix, force){
return new Promise((resolve, reject) => {
- if(this.touchEnabled){
+ var scale = this.imgScale
+ if(force && scale > 0.5){
+ scale = 0.5
+ }
+ if(scale !== 1){
var canvas = document.createElement("canvas")
- var w = Math.floor(img.width / 2)
- var h = Math.floor(img.height / 2)
+ var w = Math.floor(img.width * scale)
+ var h = Math.floor(img.height * scale)
canvas.width = w
canvas.height = h
var ctx = canvas.getContext("2d")
@@ -243,6 +279,7 @@ class LoadSong{
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)
@@ -264,6 +301,7 @@ class LoadSong{
}else{
if(!repeat){
assets.sounds["v_sanka"].play()
+ pageEvents.send("load-song-unfocused")
}
setTimeout(() => {
this.startMultiplayer(true)
@@ -281,6 +319,7 @@ class LoadSong{
p2.send("leave")
assets.sounds["se_don"].play()
this.cancelButton.style.pointerEvents = "none"
+ pageEvents.send("load-song-cancel")
}
clean(){
pageEvents.remove(p2, "message")
diff --git a/public/src/js/logo.js b/public/src/js/logo.js
index 44f98d7..e77f992 100644
--- a/public/src/js/logo.js
+++ b/public/src/js/logo.js
@@ -2,7 +2,7 @@
constructor(){
this.canvas = document.getElementById("logo")
this.ctx = this.canvas.getContext("2d")
- this.pathSvg = failedTests.indexOf("Path2D SVG") === -1
+ 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: "ブ",
diff --git a/public/src/js/main.js b/public/src/js/main.js
index 1ba7a91..e9067f1 100644
--- a/public/src/js/main.js
+++ b/public/src/js/main.js
@@ -82,6 +82,7 @@ var perf = {
}
var strings
var vectors
+var settings
pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){
@@ -90,8 +91,12 @@ pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
})
var versionDiv = document.getElementById("version")
var versionLink = document.getElementById("version-link")
-pageEvents.add(versionDiv, ["click", "touchend"], () => {
- versionLink.click()
+versionLink.tabIndex = -1
+pageEvents.add(versionDiv, ["click", "touchend"], event => {
+ if(event.target === versionDiv){
+ versionLink.click()
+ pageEvents.send("version-link")
+ }
})
resizeRoot()
setInterval(resizeRoot, 100)
@@ -103,7 +108,9 @@ pageEvents.keyAdd(debugObj, "all", "down", event => {
}else if(debugObj.state === "minimised"){
debugObj.debug.restore()
}else{
- debugObj.debug = new Debug()
+ try{
+ debugObj.debug = new Debug()
+ }catch(e){}
}
}
if(event.keyCode === 82 && debugObj.debug && debugObj.controller){
@@ -112,7 +119,7 @@ pageEvents.keyAdd(debugObj, "all", "down", event => {
}
})
-var loader = new Loader(() => {
- new Titlescreen()
+var loader = new Loader(songId => {
+ new Titlescreen(songId)
})
diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js
index cca80e2..a58247e 100644
--- a/public/src/js/mekadon.js
+++ b/public/src/js/mekadon.js
@@ -6,22 +6,25 @@ class Mekadon{
this.lastHit = -Infinity
}
play(circle){
- var type = circle.getType()
- if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.getEndTime()){
+ 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.getType()
+ type = circle.type
if(type === "balloon"){
- this.playDrumrollAt(circle, 0, 30)
+ return this.playDrumrollAt(circle, 0, 30)
}else if(type === "drumroll" || type === "daiDrumroll"){
- this.playDrumrollAt(circle, 0, 60)
+ return this.playDrumrollAt(circle, 0, 60)
}else{
- this.playAt(circle, 0, 450)
+ return this.playAt(circle, 0, 450)
}
}
playAt(circle, ms, score, dai, reverse){
- var currentMs = circle.getMS() - this.getMS()
+ var currentMs = circle.ms - this.getMS()
if(ms > currentMs - 10){
return this.playNow(circle, score, dai, reverse)
}
@@ -32,22 +35,22 @@ class Mekadon{
if(kaAmount > 0){
score = Math.random() > kaAmount ? 1 : 2
}
- this.playAt(circle, ms, score)
+ return this.playAt(circle, ms, score)
}
}
miss(circle){
- var currentMs = circle.getMS() - this.getMS()
+ 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 kbd = this.controller.getBindings()
- var type = circle.getType()
+ var type = circle.type
var keyDai = false
var playDai = !dai || dai === 2
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
@@ -55,7 +58,7 @@ class Mekadon{
if(drumrollNotes){
var ms = this.getMS()
}else{
- var ms = circle.getMS()
+ var ms = circle.ms
}
if(reverse){
@@ -65,25 +68,25 @@ class Mekadon{
type = "don"
}
}
- if(type == "daiDon" && playDai){
- this.setKey(kbd["don_l"], ms)
- this.setKey(kbd["don_r"], ms)
+ 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 ? kbd["don_l"] : kbd["don_r"], ms)
+ }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(kbd["ka_l"], ms)
- this.setKey(kbd["ka_r"], ms)
+ }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 ? kbd["ka_l"] : kbd["ka_r"], ms)
+ }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){
+ if(circle.requiredHits === 1){
assets.sounds["se_balloon"].play()
}
this.game.checkBalloon(circle)
@@ -95,6 +98,10 @@ class Mekadon{
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
@@ -102,8 +109,7 @@ class Mekadon{
getMS(){
return this.controller.getElapsedTime()
}
- setKey(keyCode, ms){
- this.controller.setKey(keyCode, false)
- this.controller.setKey(keyCode, true, ms)
+ setKey(name, ms){
+ this.controller.setKey(true, name, ms)
}
}
diff --git a/public/src/js/p2.js b/public/src/js/p2.js
index b635cb1..a0c266d 100644
--- a/public/src/js/p2.js
+++ b/public/src/js/p2.js
@@ -58,6 +58,7 @@ class P2Connection{
this.open()
}
}, 500)
+ pageEvents.send("p2-disconnected")
}
var addedType = this.allEvents.get("close")
if(addedType){
@@ -108,9 +109,15 @@ class P2Connection{
this.dai = 2
this.kaAmount = 0
this.results = false
+ this.branch = "normal"
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("")
@@ -135,6 +142,10 @@ class P2Connection{
this.kaAmount = response.value.kaAmount
}
break
+ case "branch":
+ this.branch = response.value
+ this.branchSet = false
+ break
case "session":
this.clearMessage("users")
this.otherConnected = true
@@ -155,10 +166,10 @@ class P2Connection{
}
play(circle, mekadon){
if(this.otherConnected || this.notes.length > 0){
- var type = circle.getType()
+ var type = circle.type
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
- if(drumrollNotes && mekadon.getMS() > circle.getEndTime()){
+ if(drumrollNotes && mekadon.getMS() > circle.endTime){
circle.played(-1, false)
mekadon.game.updateCurrentCircle()
}
@@ -171,7 +182,7 @@ class P2Connection{
var note = this.notes[0]
if(note.score >= 0){
var dai = 1
- if(circle.getType() === "daiDon" || circle.getType() === "daiKa"){
+ if(circle.type === "daiDon" || circle.type === "daiKa"){
dai = this.dai
}
if(mekadon.playAt(circle, note.ms, note.score, dai, note.reverse)){
diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js
index 789275e..c8c0af7 100644
--- a/public/src/js/pageevents.js
+++ b/public/src/js/pageevents.js
@@ -3,10 +3,13 @@ class PageEvents{
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){
if(Array.isArray(type)){
@@ -81,8 +84,9 @@ class PageEvents{
})
}
keyEvent(event){
- if ([68, 70, 74, 75].indexOf(event.keyCode) > -1) { // D, F, J, K
+ if(this.kbd.indexOf(event.key.toLowerCase()) !== -1){
this.lastKeyEvent = Date.now()
+ event.preventDefault()
}
this.keyListeners.forEach(addedKeyCode => {
this.checkListener(addedKeyCode.get("all"), event)
@@ -140,7 +144,29 @@ class PageEvents{
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])
+ }
+ }
+ }
}
diff --git a/public/src/js/parseosu.js b/public/src/js/parseosu.js
index 336bc70..b8d94e2 100644
--- a/public/src/js/parseosu.js
+++ b/public/src/js/parseosu.js
@@ -181,7 +181,8 @@ class ParseOsu{
measures.push({
ms: ms,
originalMS: ms,
- speed: speed
+ speed: speed,
+ visible: true
})
}
}
diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js
index 0055b99..2b10f9b 100644
--- a/public/src/js/parsetja.js
+++ b/public/src/js/parsetja.js
@@ -10,18 +10,20 @@
this.difficulty = difficulty
this.offset = (offset || 0) * -1000
this.soundOffset = 0
- this.noteTypes = [
- {name: false, txt: false},
- {name: "don", txt: strings.note.don},
- {name: "ka", txt: strings.note.ka},
- {name: "daiDon", txt: strings.note.daiDon},
- {name: "daiKa", txt: strings.note.daiKa},
- {name: "drumroll", txt: strings.note.drumroll},
- {name: "daiDrumroll", txt: strings.note.daiDrumroll},
- {name: "balloon", txt: strings.note.balloon},
- {name: false, txt: false},
- {name: "balloon", txt: strings.note.balloon}
- ]
+ 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.courseTypes = {
"0": "easy",
"1": "normal",
@@ -44,7 +46,7 @@
var hasSong = false
var courses = {}
var currentCourse = {}
- var courseName = this.difficulty
+ var courseName = "oni"
for(var lineNum = 0; lineNum < this.data.length; lineNum++){
var line = this.data[lineNum]
@@ -55,11 +57,13 @@
inSong = true
if(!hasSong){
+ if(!(courseName in courses)){
+ courses[courseName] = {}
+ }
for(var name in currentCourse){
- if(!(courseName in courses)){
- courses[courseName] = {}
+ if(name !== "branch"){
+ courses[courseName][name] = currentCourse[name]
}
- courses[courseName][name] = currentCourse[name]
}
courses[courseName].start = lineNum + 1
courses[courseName].end = this.data.length
@@ -70,6 +74,8 @@
hasSong = true
courses[courseName].end = lineNum
}
+ }else if(name.startsWith("branchstart") && inSong){
+ courses[courseName].branch = true
}
}else if(!inSong){
@@ -114,10 +120,7 @@
parseCircles(){
var meta = this.metadata[this.difficulty]
var ms = (meta.offset || 0) * -1000 + this.offset
- var bpm = meta.bpm || 0
- if(bpm <= 0){
- bpm = 1
- }
+ var bpm = Math.abs(meta.bpm) || 120
var scroll = 1
var measure = 4
this.beatInfo.beatInterval = 60000 / bpm
@@ -128,29 +131,36 @@
var balloons = meta.balloon || []
var lastDrumroll = false
+
var branch = false
- var branchType
- var branchPreference = "m"
+ var branchObj = {}
+ var currentBranch = false
+ var branchSettings = {}
+ var branchFirstMeasure = false
+ var sectionBegin = true
var currentMeasure = []
var firstNote = true
var circles = []
var circleID = 0
+ var regexAZ = /[A-Z]/
var pushMeasure = () => {
- if(barLine){
- var note = currentMeasure[0]
- if(note){
- var speed = note.bpm * note.scroll / 60
- }else{
- var speed = bpm * scroll / 60
- }
- this.measures.push({
- ms: ms,
- originalMS: ms,
- speed: speed
- })
+ var note = currentMeasure[0]
+ if(note){
+ var speed = note.bpm * note.scroll / 60
+ }else{
+ var speed = bpm * scroll / 60
}
+ this.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]
@@ -182,7 +192,9 @@
gogoTime: note.gogo,
endTime: note.endTime,
requiredHits: note.requiredHits,
- beatMS: 60000 / note.bpm
+ beatMS: 60000 / note.bpm,
+ branch: currentBranch,
+ section: note.section
})
if(lastDrumroll === note){
lastDrumroll = circleObj
@@ -204,61 +216,115 @@
var line = line.slice(1).toLowerCase()
var [name, value] = this.split(line, " ")
- if(!branch || branch && branchType === branchPreference){
- switch(name){
- case "gogostart":
- gogo = true
- break
- case "gogoend":
- gogo = false
- break
- case "bpmchange":
- bpm = parseFloat(value)
- break
- case "scroll":
- scroll = parseFloat(value)
- break
- case "measure":
- var [numerator, denominator] = value.split("/")
- measure = numerator / denominator * 4
- break
- case "delay":
- ms += (parseFloat(value) || 0) * 1000
- break
- case "barlineon":
- barLine = true
- break
- case "barlineoff":
- barLine = false
- break
- }
- }
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
- branchType = ""
+ currentBranch = false
+ branchFirstMeasure = true
+ branchSettings = {
+ ms: ms,
+ gogo: gogo,
+ bpm: bpm,
+ scroll: scroll,
+ sectionBegin: sectionBegin
+ }
value = value.split(",")
- var forkType = value[0].toLowerCase()
- if(forkType === "r" || parseFloat(value[2]) <= 100){
- branchPreference = "m"
- }else if(parseFloat(value[1]) <= 100){
- branchPreference = "e"
+ if(!this.branches){
+ this.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{
- branchPreference = "n"
+ var active = req.master > 0 ? "advanced" : "master"
+ }
+ branchObj = {
+ ms: ms,
+ originalMS: ms,
+ active: active,
+ type: value[0].trim().toLowerCase() === "r" ? "drumroll" : "accuracy",
+ requirement: req
+ }
+ this.branches.push(branchObj)
+ if(this.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"){
+ this.measures.push({
+ ms: circle.endTime,
+ originalMS: circle.endTime,
+ speed: circle.bpm * circle.scroll / 60,
+ visible: false,
+ branch: circle.branch
+ })
+ break
+ }
+ }
+ }
+ if(this.measures.length !== 0){
+ this.measures[this.measures.length - 1].nextBranch = branchObj
}
break
case "branchend":
- case "section":
branch = false
+ currentBranch = false
+ break
+ case "section":
+ sectionBegin = true
+ if(branch && !currentBranch){
+ branchSettings.sectionBegin = true
+ }
break
case "n": case "e": case "m":
- branchType = name
+ if(!branch){
+ break
+ }
+ ms = branchSettings.ms
+ gogo = branchSettings.gogo
+ bpm = branchSettings.bpm
+ scroll = branchSettings.scroll
+ sectionBegin = branchSettings.sectionBegin
+ branchFirstMeasure = true
+ var branchName = name === "m" ? "master" : (name === "e" ? "advanced" : "normal")
+ currentBranch = {
+ name: branchName,
+ active: branchName === branchObj.active
+ }
+ branchObj[branchName] = currentBranch
break
}
- }else if(!branch || branch && branchType === branchPreference){
+ }else{
- var string = line.split("")
+ var string = line.toUpperCase().split("")
for(let symbol of string){
@@ -271,15 +337,17 @@
scroll: scroll
})
break
- case "1": case "2": case "3": case "4":
+ 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
+ scroll: scroll,
+ section: sectionBegin
}
+ sectionBegin = false
if(lastDrumroll){
circleObj.endDrumroll = lastDrumroll
lastDrumroll = false
@@ -293,15 +361,19 @@
txt: type.txt,
gogo: gogo,
bpm: bpm,
- scroll: scroll
+ scroll: scroll,
+ section: sectionBegin
}
+ sectionBegin = false
if(lastDrumroll){
if(symbol === "9"){
currentMeasure.push({
endDrumroll: lastDrumroll,
bpm: bpm,
- scroll: scroll
+ scroll: scroll,
+ section: sectionBegin
})
+ sectionBegin = false
lastDrumroll = false
}else{
currentMeasure.push({
@@ -327,8 +399,10 @@
currentMeasure.push({
endDrumroll: lastDrumroll,
bpm: bpm,
- scroll: scroll
+ scroll: scroll,
+ section: sectionBegin
})
+ sectionBegin = false
lastDrumroll = false
}else{
currentMeasure.push({
@@ -342,7 +416,14 @@
currentMeasure = []
break
default:
- error = true
+ if(regexAZ.test(symbol)){
+ currentMeasure.push({
+ bpm: bpm,
+ scroll: scroll
+ })
+ }else{
+ error = true
+ }
break
}
@@ -359,6 +440,11 @@
lastDrumroll.originalEndTime = ms
}
+ if(this.branches){
+ circles.sort((a, b) => a.ms > b.ms ? 1 : -1)
+ this.measures.sort((a, b) => a.ms > b.ms ? 1 : -1)
+ circles.forEach((circle, i) => circle.id = i + 1)
+ }
return circles
}
}
diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js
index 35f93c1..24fde5d 100644
--- a/public/src/js/scoresheet.js
+++ b/public/src/js/scoresheet.js
@@ -31,9 +31,12 @@ class Scoresheet{
this.draw = new CanvasDraw()
this.canvasCache = new CanvasCache()
+ this.keyboard = new Keyboard({
+ confirm: ["enter", "space", "esc", "don_l", "don_r"]
+ }, this.keyDown.bind(this))
this.gamepad = new Gamepad({
- "13": ["a", "b", "start", "ls", "rs"]
- })
+ confirm: ["a", "b", "start", "ls", "rs"]
+ }, this.keyDown.bind(this))
this.difficulty = {
"easy": 0,
@@ -60,24 +63,20 @@ class Scoresheet{
}
})
}
+ 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(event, code){
- if(!code){
- if(event.repeat){
- return
- }
- code = event.keyCode
- }
- var key = {
- confirm: code == 13 || code == 32 || code == 70 || code == 74,
- // Enter, Space, F, J
- cancel: code == 27 || code == 8
- // Esc, Backspace
- }
- if(key.cancel && event){
- event.preventDefault()
- }
- if(key.confirm || key.cancel){
+ keyDown(pressed){
+ if(pressed && this.redrawing){
this.toNext()
}
}
@@ -126,7 +125,6 @@ class Scoresheet{
this.winW = null
this.winH = null
- pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this))
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this))
if(!this.multiplayer){
@@ -166,12 +164,6 @@ class Scoresheet{
}
var ms = this.getMS()
- this.gamepad.play((pressed, keyCode) => {
- if(pressed){
- this.keyDown(false, keyCode)
- }
- })
-
if(!this.redrawRunning){
return
}
@@ -182,6 +174,14 @@ class Scoresheet{
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
@@ -842,12 +842,13 @@ class Scoresheet{
}
clean(){
+ this.keyboard.clean()
+ this.gamepad.clean()
this.draw.clean()
this.canvasCache.clean()
assets.sounds["bgm_result"].stop()
- snd.musicGain.fadeIn()
+ snd.buffer.loadSettings()
this.redrawRunning = false
- pageEvents.keyRemove(this, "all")
pageEvents.remove(this.canvas, ["mousedown", "touchstart"])
if(this.multiplayer !== 2 && this.touchEnabled){
pageEvents.remove(document.getElementById("touch-full-btn"), "touchend")
diff --git a/public/src/js/session.js b/public/src/js/session.js
index fb967c9..0f08c67 100644
--- a/public/src/js/session.js
+++ b/public/src/js/session.js
@@ -2,13 +2,13 @@ class Session{
constructor(touchEnabled){
this.touchEnabled = touchEnabled
loader.changePage("session", true)
- this.endButton = document.getElementById("tutorial-end-button")
+ this.endButton = this.getElement("view-end-button")
if(touchEnabled){
- document.getElementById("tutorial-outer").classList.add("touch-enabled")
+ this.getElement("view-outer").classList.add("touch-enabled")
}
this.sessionInvite = document.getElementById("session-invite")
- var tutorialTitle = document.getElementById("tutorial-title")
+ 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)
@@ -16,11 +16,12 @@ class Session{
this.endButton.setAttribute("alt", strings.session.cancel)
pageEvents.add(window, ["mousedown", "touchstart"], this.mouseDown.bind(this))
- pageEvents.keyOnce(this, 27, "down").then(this.onEnd.bind(this))
-
+ this.keyboard = new Keyboard({
+ confirm: ["esc"]
+ }, this.keyPress.bind(this))
this.gamepad = new Gamepad({
- "confirm": ["start", "b", "ls", "rs"]
- }, this.onEnd.bind(this))
+ confirm: ["start", "b", "ls", "rs"]
+ }, this.keyPress.bind(this))
p2.hashLock = true
pageEvents.add(p2, "message", response => {
@@ -29,12 +30,20 @@ class Session{
p2.hash(response.value)
}else if(response.type === "songsel"){
p2.clearMessage("users")
- this.onEnd(false, true)
+ this.onEnd(true)
+ pageEvents.send("session-start", "host")
}
})
p2.send("invite")
+ 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{
@@ -45,17 +54,20 @@ class Session{
this.onEnd()
}
}
- onEnd(event, fromP2){
+ 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")
}
- if(event && event.type === "keydown"){
- event.preventDefault()
- }
this.clean()
assets.sounds["se_don"].play()
setTimeout(() => {
@@ -63,9 +75,9 @@ class Session{
}, 500)
}
clean(){
+ this.keyboard.clean()
this.gamepad.clean()
pageEvents.remove(window, ["mousedown", "touchstart"])
- pageEvents.keyRemove(this, 27)
pageEvents.remove(p2, "message")
delete this.endButton
delete this.sessionInvite
diff --git a/public/src/js/settings.js b/public/src/js/settings.js
new file mode 100644
index 0000000..3c78e6f
--- /dev/null
+++ b/public/src/js/settings.js
@@ -0,0 +1,528 @@
+class Settings{
+ constructor(){
+ var ios = /iPhone|iPad/.test(navigator.userAgent)
+ var phone = /Android|iPhone|iPad/.test(navigator.userAgent)
+
+ this.items = {
+ language: {
+ type: "language",
+ options: ["ja", "en", "cn", "tw", "ko"],
+ default: this.getLang()
+ },
+ resolution: {
+ type: "select",
+ options: ["high", "medium", "low", "lowest"],
+ default: phone ? "medium" : "high"
+ },
+ touchAnimation: {
+ type: "toggle",
+ default: !ios,
+ touch: true
+ },
+ keyboardSettings: {
+ type: "keyboard",
+ default: {
+ ka_l: ["d"],
+ don_l: ["f"],
+ don_r: ["j"],
+ ka_r: ["k"]
+ },
+ touch: false
+ },
+ gamepadLayout: {
+ type: "gamepad",
+ options: ["a", "b", "c"],
+ default: "a",
+ gamepad: true
+ }
+ }
+
+ this.storage = {}
+ try{
+ var storage = JSON.parse(localStorage.getItem("settings") || "{}")
+ for(var i in this.items){
+ var current = this.items[i]
+ if(current.type === "language"){
+ this.storage[i] = localStorage.getItem("lang")
+ if(current.options.indexOf(this.storage[i]) === -1){
+ this.storage[i] = null
+ }
+ }else if(i in storage){
+ if((current.type === "select" || current.type === "gamepad") && current.options.indexOf(storage[i]) === -1){
+ this.storage[i] = null
+ }else if(current.type === "keyboard"){
+ var obj = {}
+ for(var j in current.default){
+ if(storage[i] && storage[i][j] && storage[i][j][0]){
+ obj[j] = storage[i][j]
+ }else{
+ obj = null
+ break
+ }
+ }
+ this.storage[i] = obj
+ }else{
+ this.storage[i] = storage[i]
+ }
+ }else{
+ this.storage[i] = null
+ }
+ }
+ }catch(e){
+ for(var i in this.items){
+ this.storage[i] = null
+ }
+ }
+ }
+ getItem(name){
+ var value = this.storage[name]
+ return value === null ? this.items[name].default : value
+ }
+ setItem(name, value){
+ this.storage[name] = value
+ try{
+ if(name === "language"){
+ if(value){
+ localStorage.setItem("lang", value)
+ }else{
+ localStorage.removeItem("lang")
+ }
+ }else{
+ var language = this.storage.language
+ delete this.storage.language
+ localStorage.setItem("settings", JSON.stringify(this.storage))
+ this.storage.language = language
+ }
+ }catch(e){}
+ }
+ getLang(){
+ if("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])){
+ return j
+ }
+ }
+ }
+ }
+ return "ja"
+ }
+ setLang(lang, noEvent){
+ strings = lang
+ var boldFonts = strings.font === "Microsoft YaHei, sans-serif"
+ loader.screen.style.fontFamily = strings.font
+ loader.screen.style.fontWeight = boldFonts ? "bold" : ""
+ loader.screen.classList[boldFonts ? "add" : "remove"]("bold-fonts")
+ if(!noEvent){
+ pageEvents.send("language-change", lang.id)
+ }
+ }
+}
+
+class SettingsView{
+ constructor(touchEnabled, tutorial, songId){
+ this.touchEnabled = touchEnabled
+ this.tutorial = tutorial
+ this.songId = songId
+
+ loader.changePage("settings", tutorial)
+ assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992)
+ this.defaultButton = document.getElementById("settings-default")
+ if(touchEnabled){
+ this.getElement("view-outer").classList.add("touch-enabled")
+ }
+ var gamepadEnabled = false
+ if("getGamepads" in navigator){
+ var gamepads = navigator.getGamepads()
+ for(var i = 0; i < gamepads.length; i++){
+ if(gamepads[i]){
+ gamepadEnabled = true
+ break
+ }
+ }
+ }
+ this.mode = "settings"
+
+ this.keyboard = new Keyboard({
+ "confirm": ["enter", "space", "don_l", "don_r"],
+ "up": ["up"],
+ "previous": ["left", "ka_l"],
+ "next": ["right", "down", "ka_r"],
+ "back": ["esc"],
+ "other": ["wildcard"]
+ }, this.keyPressed.bind(this))
+ this.gamepad = new Gamepad({
+ "confirm": ["b", "ls", "rs"],
+ "up": ["u", "lsu"],
+ "previous": ["l", "lb", "lt", "lsl"],
+ "next": ["d", "r", "rb", "rt", "lsd", "lsr"],
+ "back": ["start", "a"]
+ }, this.keyPressed.bind(this))
+
+ this.viewTitle = this.getElement("view-title")
+ this.endButton = this.getElement("view-end-button")
+ this.resolution = settings.getItem("resolution")
+
+ var content = this.getElement("view-content")
+ this.items = []
+ this.selected = 0
+ for(let i in settings.items){
+ var current = settings.items[i]
+ if(
+ !touchEnabled && current.touch === true ||
+ touchEnabled && current.touch === false ||
+ !gamepadEnabled && current.gamepad === true
+ ){
+ continue
+ }
+ var settingBox = document.createElement("div")
+ settingBox.classList.add("setting-box")
+ var nameDiv = document.createElement("div")
+ nameDiv.classList.add("setting-name", "stroke-sub")
+ var name = strings.settings[i].name
+ nameDiv.innerText = name
+ nameDiv.setAttribute("alt", name)
+ settingBox.appendChild(nameDiv)
+ var valueDiv = document.createElement("div")
+ valueDiv.classList.add("setting-value")
+ this.getValue(i, valueDiv)
+ settingBox.appendChild(valueDiv)
+ content.appendChild(settingBox)
+ if(this.items.length === this.selected){
+ settingBox.classList.add("selected")
+ }
+ this.addTouch(settingBox, event => this.setValue(i))
+ this.items.push({
+ id: i,
+ settingBox: settingBox,
+ nameDiv: nameDiv,
+ valueDiv: valueDiv
+ })
+ }
+ this.items.push({
+ id: "default",
+ settingBox: this.defaultButton
+ })
+ this.addTouch(this.defaultButton, this.defaultSettings.bind(this))
+ this.items.push({
+ id: "back",
+ settingBox: this.endButton
+ })
+ this.addTouch(this.endButton, this.onEnd.bind(this))
+
+ this.gamepadSettings = document.getElementById("settings-gamepad")
+ this.addTouch(this.gamepadSettings, event => {
+ if(event.target === event.currentTarget){
+ this.gamepadBack()
+ }
+ })
+ this.gamepadTitle = this.gamepadSettings.getElementsByClassName("view-title")[0]
+ this.gamepadEndButton = this.gamepadSettings.getElementsByClassName("view-end-button")[0]
+ this.addTouch(this.gamepadEndButton, event => this.gamepadBack(true))
+ this.gamepadBox = this.gamepadSettings.getElementsByClassName("setting-box")[0]
+ this.addTouch(this.gamepadBox, event => this.gamepadSet(1))
+ this.gamepadButtons = document.getElementById("gamepad-buttons")
+ this.gamepadValue = document.getElementById("gamepad-value")
+
+ this.setStrings()
+
+ pageEvents.send("settings")
+ }
+ getElement(name){
+ return loader.screen.getElementsByClassName(name)[0]
+ }
+ addTouch(element, callback){
+ pageEvents.add(element, ["mousedown", "touchstart"], event => {
+ if(event.type === "touchstart"){
+ event.preventDefault()
+ this.touched = true
+ }else if(event.which !== 1){
+ return
+ }else{
+ this.touched = false
+ }
+ callback(event)
+ })
+ }
+ removeTouch(element){
+ pageEvents.remove(element, ["mousedown", "touchstart"])
+ }
+ getValue(name, valueDiv){
+ var current = settings.items[name]
+ var value = settings.getItem(name)
+ if(current.type === "language"){
+ value = allStrings[value].name + " (" + value + ")"
+ }else if(current.type === "select" || current.type === "gamepad"){
+ value = strings.settings[name][value]
+ }else if(current.type === "toggle"){
+ value = value ? strings.settings.on : strings.settings.off
+ }else if(current.type === "keyboard"){
+ valueDiv.innerHTML = ""
+ for(var i in value){
+ var keyDiv = document.createElement("div")
+ keyDiv.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10"
+ var key = value[i][0]
+ for(var j in this.keyboard.substitute){
+ if(this.keyboard.substitute[j] === key){
+ key = j
+ break
+ }
+ }
+ keyDiv.innerText = key.toUpperCase()
+ valueDiv.appendChild(keyDiv)
+ }
+ return
+ }
+ valueDiv.innerText = value
+ }
+ setValue(name){
+ var current = settings.items[name]
+ var value = settings.getItem(name)
+ var selectedIndex = this.items.findIndex(item => item.id === name)
+ var selected = this.items[selectedIndex]
+ if(this.mode !== "settings"){
+ if(this.selected === selectedIndex){
+ this.keyboardBack(selected)
+ }
+ return
+ }
+ if(this.selected !== selectedIndex){
+ this.items[this.selected].settingBox.classList.remove("selected")
+ this.selected = selectedIndex
+ selected.settingBox.classList.add("selected")
+ }
+ if(current.type === "language" || current.type === "select"){
+ value = current.options[this.mod(current.options.length, current.options.indexOf(value) + 1)]
+ }else if(current.type === "toggle"){
+ value = !value
+ }else if(current.type === "keyboard"){
+ this.mode = "keyboard"
+ selected.settingBox.style.animation = "none"
+ selected.valueDiv.classList.add("selected")
+ this.keyboardKeys = {}
+ this.keyboardSet()
+ assets.sounds["se_don"].play()
+ return
+ }else if(current.type === "gamepad"){
+ this.mode = "gamepad"
+ this.gamepadSelected = current.options.indexOf(value)
+ this.gamepadSet()
+ assets.sounds["se_don"].play()
+ return
+ }
+ settings.setItem(name, value)
+ this.getValue(name, this.items[this.selected].valueDiv)
+ assets.sounds["se_ka"].play()
+ if(current.type === "language"){
+ this.setLang(allStrings[value])
+ }
+ }
+ keyPressed(pressed, name, event){
+ if(!pressed){
+ return
+ }
+ this.touched = false
+ var selected = this.items[this.selected]
+ if(this.mode === "settings"){
+ if(name === "confirm"){
+ if(selected.id === "back"){
+ this.onEnd()
+ }else if(selected.id === "default"){
+ this.defaultSettings()
+ }else{
+ this.setValue(selected.id)
+ }
+ }else if(name === "up" || name === "previous" || name === "next"){
+ selected.settingBox.classList.remove("selected")
+ do{
+ this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
+ }while(this.items[this.selected].id === "default" && name !== "previous")
+ selected = this.items[this.selected]
+ selected.settingBox.classList.add("selected")
+ selected.settingBox.scrollIntoView()
+ assets.sounds["se_ka"].play()
+ }else if(name === "back"){
+ this.onEnd()
+ }
+ }else if(this.mode === "gamepad"){
+ if(name === "confirm"){
+ this.gamepadBack(true)
+ }else if(name === "up" || name === "previous" || name === "next"){
+ this.gamepadSet(name === "next" ? 1 : -1)
+ }else if(name === "back"){
+ this.gamepadBack()
+ }
+ }else if(this.mode === "keyboard"){
+ if(name === "back"){
+ this.keyboardBack(selected)
+ assets.sounds["se_cancel"].play()
+ }else{
+ event.preventDefault()
+ var currentKey = event.key.toLowerCase()
+ for(var i in this.keyboardKeys){
+ if(this.keyboardKeys[i][0] === currentKey || !currentKey){
+ return
+ }
+ }
+ var current = this.keyboardCurrent
+ assets.sounds[current === "ka_l" || current === "ka_r" ? "se_ka" : "se_don"].play()
+ this.keyboardKeys[current] = [currentKey]
+ this.keyboardSet()
+ }
+ }
+ }
+ keyboardSet(){
+ var selected = this.items[this.selected]
+ var current = settings.items[selected.id]
+ selected.valueDiv.innerHTML = ""
+ for(var i in current.default){
+ var keyDiv = document.createElement("div")
+ keyDiv.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10"
+ if(this.keyboardKeys[i]){
+ var key = this.keyboardKeys[i][0]
+ for(var j in this.keyboard.substitute){
+ if(this.keyboard.substitute[j] === key){
+ key = j
+ break
+ }
+ }
+ keyDiv.innerText = key.toUpperCase()
+ selected.valueDiv.appendChild(keyDiv)
+ }else{
+ keyDiv.innerText = "[" + strings.settings[selected.id][i] + "]"
+ selected.valueDiv.appendChild(keyDiv)
+ this.keyboardCurrent = i
+ return
+ }
+ }
+ settings.setItem(selected.id, this.keyboardKeys)
+ this.keyboardBack(selected)
+ this.keyboard.update()
+ pageEvents.setKbd()
+ }
+ keyboardBack(selected){
+ this.mode = "settings"
+ selected.settingBox.style.animation = ""
+ selected.valueDiv.classList.remove("selected")
+ this.getValue(selected.id, selected.valueDiv)
+ }
+ gamepadSet(diff){
+ if(this.mode !== "gamepad"){
+ return
+ }
+ var selected = this.items[this.selected]
+ var current = settings.items[selected.id]
+ if(diff){
+ this.gamepadSelected = this.mod(current.options.length, this.gamepadSelected + diff)
+ assets.sounds["se_ka"].play()
+ }
+ var opt = current.options[this.gamepadSelected]
+ var value = strings.settings[selected.id][opt]
+ this.gamepadValue.innerText = value
+ this.gamepadValue.setAttribute("alt", value)
+ this.gamepadButtons.style.backgroundPosition = "0 " + (-318 - 132 * this.gamepadSelected) + "px"
+ this.gamepadSettings.style.display = "block"
+ }
+ gamepadBack(confirm){
+ if(this.mode !== "gamepad"){
+ return
+ }
+ var selected = this.items[this.selected]
+ var current = settings.items[selected.id]
+ settings.setItem(selected.id, current.options[this.gamepadSelected])
+ this.getValue(selected.id, selected.valueDiv)
+ assets.sounds[confirm ? "se_don" : "se_cancel"].play()
+ this.gamepadSettings.style.display = ""
+ this.mode = "settings"
+ }
+ defaultSettings(){
+ if(this.mode === "keyboard"){
+ this.keyboardBack(this.items[this.selected])
+ }
+ for(var i in settings.items){
+ settings.setItem(i, null)
+ }
+ this.setLang(allStrings[settings.getItem("language")])
+ this.keyboard.update()
+ pageEvents.setKbd()
+ assets.sounds["se_don"].play()
+ }
+ onEnd(){
+ this.clean()
+ assets.sounds["se_don"].play()
+ setTimeout(() => {
+ if(this.tutorial && !this.touched){
+ new Tutorial(false, this.songId)
+ }else{
+ try{
+ localStorage.setItem("tutorial", "true")
+ }catch(e){}
+ new SongSelect(this.tutorial ? false : "settings", false, this.touched, this.songId)
+ }
+ }, 500)
+ }
+ setLang(lang){
+ settings.setLang(lang)
+ if(failedTests.length !== 0){
+ showUnsupported(strings)
+ }
+ for(var i in this.items){
+ var item = this.items[i]
+ if(item.valueDiv){
+ var name = strings.settings[item.id].name
+ item.nameDiv.innerText = name
+ item.nameDiv.setAttribute("alt", name)
+ this.getValue(item.id, item.valueDiv)
+ }
+ }
+ this.setStrings()
+ }
+ setStrings(){
+ this.viewTitle.innerText = strings.gameSettings
+ this.viewTitle.setAttribute("alt", strings.gameSettings)
+ this.endButton.innerText = strings.settings.ok
+ this.endButton.setAttribute("alt", strings.settings.ok)
+ this.gamepadTitle.innerText = strings.settings.gamepadLayout.name
+ this.gamepadTitle.setAttribute("alt", strings.settings.gamepadLayout.name)
+ this.gamepadEndButton.innerText = strings.settings.ok
+ this.gamepadEndButton.setAttribute("alt", strings.settings.ok)
+ this.defaultButton.innerText = strings.settings.default
+ this.defaultButton.setAttribute("alt", strings.settings.default)
+ }
+ mod(length, index){
+ return ((index % length) + length) % length
+ }
+ clean(){
+ this.keyboard.clean()
+ this.gamepad.clean()
+ assets.sounds["bgm_settings"].stop()
+ for(var i in this.items){
+ this.removeTouch(this.items[i].settingBox)
+ }
+ if(this.defaultButton){
+ delete this.defaultButton
+ }
+ this.removeTouch(this.gamepadSettings)
+ this.removeTouch(this.gamepadEndButton)
+ this.removeTouch(this.gamepadBox)
+ delete this.tutorialTitle
+ delete this.endButton
+ delete this.items
+ delete this.gamepadSettings
+ delete this.gamepadTitle
+ delete this.gamepadEndButton
+ delete this.gamepadBox
+ delete this.gamepadButtons
+ delete this.gamepadValue
+ if(this.resolution !== settings.getItem("resolution")){
+ for(var i in assets.image){
+ if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){
+ URL.revokeObjectURL(assets.image[i].src)
+ delete assets.image[i]
+ }
+ }
+ }
+ }
+}
diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js
index 6147122..fd8a399 100644
--- a/public/src/js/songselect.js
+++ b/public/src/js/songselect.js
@@ -1,5 +1,5 @@
class SongSelect{
- constructor(fromTutorial, fadeIn, touchEnabled){
+ constructor(fromTutorial, fadeIn, touchEnabled, songId){
this.touchEnabled = touchEnabled
loader.changePage("songselect", false)
@@ -25,21 +25,27 @@ class SongSelect{
},
"tutorial": {
sort: 7,
- background: "#9afbe1",
- border: ["#d6ffff", "#6bae9c"],
- outline: "#31ae94"
+ background: "#29e8aa",
+ border: ["#86ffbd", "#009a8c"],
+ outline: "#08a28c"
},
"about": {
sort: 7,
- background: "#91cfff",
- border: ["#dff0ff", "#6890b2"],
- outline: "#217abb"
+ background: "#a2d0e7",
+ border: ["#c6dfff", "#4485d9"],
+ outline: "#2390d9"
+ },
+ "settings": {
+ sort: 7,
+ background: "#ce93fa",
+ border: ["#dec4fd", "#a543ef"],
+ outline: "#a741ef"
},
"browse": {
sort: 7,
- background: "#9791ff",
- border: ["#e2dfff", "#6d68b2"],
- outline: "#5350ba"
+ background: "#fab5d3",
+ border: ["#ffe7ef", "#d36aa2"],
+ outline: "#d36aa2"
},
"J-POP": {
sort: 0,
@@ -107,7 +113,8 @@ class SongSelect{
type: song.type,
offset: song.offset,
songSkin: song.song_skin || {},
- music: song.music
+ music: song.music,
+ volume: song.volume
})
}
this.songs.sort((a, b) => {
@@ -148,6 +155,12 @@ class SongSelect{
action: "about",
category: strings.random
})
+ this.songs.push({
+ title: strings.gameSettings,
+ skin: this.songSkin.settings,
+ action: "settings",
+ category: strings.random
+ })
if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
this.browse = document.getElementById("browse")
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
@@ -213,23 +226,32 @@ class SongSelect{
this.selectedDiff = 0
assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506)
- if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage)){
+ if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage) && !songId){
fromTutorial = touchEnabled ? "about" : "tutorial"
}
if(p2.session){
fromTutorial = false
}
+ var songIdIndex = -1
if(fromTutorial){
this.selectedSong = this.songs.findIndex(song => song.action === fromTutorial)
this.playBgm(true)
}else{
- if(assets.customSongs){
+ if(songId){
+ songIdIndex = this.songs.findIndex(song => song.id === songId)
+ if(songIdIndex === -1){
+ this.clearHash()
+ }
+ }
+ if(songIdIndex !== -1){
+ this.selectedSong = songIdIndex
+ }else if(assets.customSongs){
this.selectedSong = assets.customSelected
}else if((!p2.session || fadeIn) && "selectedSong" in localStorage){
this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1)
}
- assets.sounds["v_songsel"].play()
+ assets.sounds[songIdIndex !== -1 ? "v_diffsel" : "v_songsel"].play()
snd.musicGain.fadeOut()
this.playBgm(false)
}
@@ -247,7 +269,7 @@ class SongSelect{
var skipStart = fromTutorial || p2.session
this.state = {
- screen: fadeIn ? "titleFadeIn" : (skipStart ? "song" : "title"),
+ screen: songIdIndex !== -1 ? "difficulty" : (fadeIn ? "titleFadeIn" : (skipStart ? "song" : "title")),
screenMS: this.getMS(),
move: 0,
moveMS: 0,
@@ -267,23 +289,33 @@ class SongSelect{
this.startPreview(true)
this.pressedKeys = {}
+ this.keyboard = new Keyboard({
+ confirm: ["enter", "space", "don_l", "don_r"],
+ back: ["escape"],
+ left: ["left", "ka_l"],
+ right: ["right", "ka_r"],
+ up: ["up"],
+ down: ["down"],
+ session: ["backspace"],
+ ctrl: ["ctrl"],
+ shift: ["shift"]
+ }, this.keyPress.bind(this))
this.gamepad = new Gamepad({
- "13": ["b", "start", "ls", "rs"],
- "27": ["a"],
- "37": ["l", "lb", "lt", "lsl"],
- "39": ["r", "rb", "rt", "lsr"],
- "38": ["u", "lsu"],
- "40": ["d", "lsd"],
- "8": ["back"],
- "ctrl": ["y"],
- "shift": ["x"]
- })
+ confirm: ["b", "start", "ls", "rs"],
+ back: ["a"],
+ left: ["l", "lb", "lt", "lsl"],
+ right: ["r", "rb", "rt", "lsr"],
+ up: ["u", "lsu"],
+ down: ["d", "lsd"],
+ session: ["back"],
+ ctrl: ["y"],
+ shift: ["x"]
+ }, this.keyPress.bind(this))
if(!assets.customSongs){
this.startP2()
}
- pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this))
pageEvents.add(loader.screen, "mousemove", this.mouseMove.bind(this))
pageEvents.add(loader.screen, "mouseleave", () => {
this.state.moveHover = null
@@ -302,73 +334,54 @@ class SongSelect{
this.redrawRunning = true
this.redrawBind = this.redraw.bind(this)
this.redraw()
+ pageEvents.send("song-select")
+ pageEvents.send("song-select-move", this.songs[this.selectedSong])
+ if(songIdIndex !== -1){
+ pageEvents.send("song-select-difficulty", this.songs[this.selectedSong])
+ }
}
- keyDown(event, code){
- if(code){
- var modifiers = {
- shift: this.pressedKeys["shift"],
- ctrl: this.pressedKeys["ctrl"]
+ keyPress(pressed, name, event){
+ if(pressed){
+ if(!this.pressedKeys[name]){
+ this.pressedKeys[name] = this.getMS() + 300
}
}else{
- code = event.keyCode
- var modifiers = {
- shift: event.shiftKey,
- ctrl: event.ctrlKey
- }
- }
- if(code === "ctrl" || code === "shift" || !this.redrawRunning){
+ this.pressedKeys[name] = 0
return
}
-
- var key = {
- confirm: code == 13 || code == 32 || code == 70 || code == 74,
- // Enter, Space, F, J
- cancel: code == 27,
- // Esc
- session: code == 8,
- // Backspace
- left: code == 37 || code == 68,
- // Left, D
- right: code == 39 || code == 75,
- // Right, K
- up: code == 38,
- // Up
- down: code == 40
- // Down
- }
- if(key.cancel && event){
- event.preventDefault()
+ if(name === "ctrl" || name === "shift" || !this.redrawRunning){
+ return
}
if(this.state.screen === "song"){
- if(key.confirm){
+ if(name === "confirm"){
this.toSelectDifficulty()
- }else if(key.cancel){
+ }else if(name === "back"){
this.toTitleScreen()
- }else if(key.session){
+ }else if(name === "session"){
this.toSession()
- }else if(key.left){
+ }else if(name === "left"){
this.moveToSong(-1)
- }else if(key.right){
+ }else if(name === "right"){
this.moveToSong(1)
}
}else if(this.state.screen === "difficulty"){
- if(key.confirm){
+ if(name === "confirm"){
if(this.selectedDiff === 0){
this.toSongSelect()
}else if(this.selectedDiff === 1){
this.toOptions(1)
}else{
- this.toLoadSong(this.selectedDiff - this.diffOptions.length, modifiers.shift, modifiers.ctrl)
+ this.toLoadSong(this.selectedDiff - this.diffOptions.length, this.pressedKeys["shift"], this.pressedKeys["ctrl"])
}
- }else if(key.cancel || key.session){
+ }else if(name === "back" || name === "session"){
this.toSongSelect()
- }else if(key.left){
+ }else if(name === "left"){
this.moveToDiff(-1)
- }else if(key.right){
+ }else if(name === "right"){
this.moveToDiff(1)
- }else if(this.selectedDiff === 1 && (key.up || key.down)){
- this.toOptions(key.up ? -1 : 1)
+ }else if(this.selectedDiff === 1 && (name === "up" || name === "down")){
+ this.toOptions(name === "up" ? -1 : 1)
}
}
}
@@ -508,7 +521,7 @@ class SongSelect{
}else if(550 < x && x < 1050 && 95 < y && y < 524){
var moveBy = Math.floor((x - 550) / ((1050 - 550) / 5)) + this.diffOptions.length
var currentSong = this.songs[this.selectedSong]
- if(this.state.ura && moveBy === this.diffOptions + 3 || currentSong.stars[moveBy - this.diffOptions.length]){
+ if(this.state.ura && moveBy === this.diffOptions.length + 3 || currentSong.stars[moveBy - this.diffOptions.length]){
return moveBy
}
}
@@ -590,6 +603,7 @@ class SongSelect{
assets.sounds["se_don"].play()
assets.sounds["v_songsel"].stop()
assets.sounds["v_diffsel"].play(0.3)
+ pageEvents.send("song-select-difficulty", currentSong)
}else if(currentSong.action === "back"){
this.clean()
this.toTitleScreen()
@@ -603,10 +617,13 @@ class SongSelect{
setTimeout(() => {
this.moveToSong(moveBy)
}, 200)
+ pageEvents.send("song-select-random")
}else if(currentSong.action === "tutorial"){
this.toTutorial()
}else if(currentSong.action === "about"){
this.toAbout()
+ }else if(currentSong.action === "settings"){
+ this.toSettings()
}else if(currentSong.action === "browse"){
this.toBrowse()
}
@@ -630,6 +647,8 @@ class SongSelect{
assets.sounds["v_diffsel"].stop()
assets.sounds["se_cancel"].play()
}
+ this.clearHash()
+ pageEvents.send("song-select-back")
}
toLoadSong(difficulty, shift, ctrl, touch){
this.clean()
@@ -703,6 +722,13 @@ class SongSelect{
new About(this.touchEnabled)
}, 500)
}
+ toSettings(){
+ assets.sounds["se_don"].play()
+ this.clean()
+ setTimeout(() => {
+ new SettingsView(this.touchEnabled)
+ }, 500)
+ }
toSession(){
if(p2.socket.readyState !== 1 || assets.customSongs){
return
@@ -728,6 +754,7 @@ class SongSelect{
setTimeout(() => {
new SongSelect("browse", false, this.touchEnabled)
}, 500)
+ pageEvents.send("import-songs-default")
}else{
this.browse.click()
}
@@ -740,20 +767,10 @@ class SongSelect{
requestAnimationFrame(this.redrawBind)
var ms = this.getMS()
- this.gamepad.play((pressed, keyCode) => {
- if(pressed){
- if(!this.pressedKeys[keyCode]){
- this.pressedKeys[keyCode] = ms + 300
- this.keyDown(false, keyCode)
- }
- }else{
- this.pressedKeys[keyCode] = 0
- }
- })
for(var key in this.pressedKeys){
if(this.pressedKeys[key]){
if(ms >= this.pressedKeys[key] + 50){
- this.keyDown(false, key)
+ this.keyPress(true, key)
this.pressedKeys[key] = ms
}
}
@@ -770,6 +787,14 @@ class SongSelect{
winW = winH / 9 * 32
}
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
@@ -783,9 +808,11 @@ class SongSelect{
this.canvas.style.height = (winH / this.pixelRatio) + "px"
var borders = (this.songAsset.border + this.songAsset.innerBorder) * 2
+ var songsLength = Math.ceil(winW / ratio / (this.songAsset.width + this.songAsset.marginLeft)) + 1
+
this.songTitleCache.resize(
- (this.songAsset.width - borders + 1) * Math.ceil(this.songs.length / 3),
- (this.songAsset.height - borders + 1) * 3,
+ (this.songAsset.width - borders + 1) * songsLength,
+ this.songAsset.height - borders + 1,
ratio + 0.2
)
@@ -807,7 +834,7 @@ class SongSelect{
categories++
}
})
- this.categoryCache.resize(280, (this.songAsset.marginTop + 1) * categories , ratio + 0.5)
+ this.categoryCache.resize(280, this.songAsset.marginTop + 1 , ratio + 0.5)
this.difficultyCache.resize((44 + 56 + 2) * 5, 135 + 10, ratio + 0.5)
@@ -952,7 +979,11 @@ class SongSelect{
var elapsed = ms - this.state.moveMS
if(this.state.move && ms > this.state.moveMS + resize2 - scrollDelay){
assets.sounds["se_ka"].play()
+ var previousSelectedSong = this.selectedSong
this.selectedSong = this.mod(this.songs.length, this.selectedSong + this.state.move)
+ if(previousSelectedSong !== this.selectedSong){
+ pageEvents.send("song-select-move", this.songs[this.selectedSong])
+ }
this.state.move = 0
this.state.locked = 2
@@ -1299,27 +1330,47 @@ class SongSelect{
outlineSize: currentUra ? this.songAsset.letterBorder : 0
})
})
- var songStars = currentUra ? currentSong.stars[4] : currentSong.stars[i]
- for(var j = 0; j < 10; j++){
- if(songSel){
- var yPos = _y + 113 + j * 17
- }else{
- var yPos = _y + 178 + j * 19.5
- }
- if(10 - j > songStars){
- ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7")
- ctx.beginPath()
- ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2)
- ctx.fill()
- }else{
- this.draw.diffStar({
- ctx: ctx,
- songSel: songSel,
- ura: currentUra,
- x: _x,
- y: yPos,
- ratio: ratio
- })
+ var songStarsArray = (currentUra ? currentSong.stars[4] : currentSong.stars[i]).toString().split(" ")
+ var songStars = songStarsArray[0]
+ var songBranch = songStarsArray[1] === "B"
+ var elapsedMS = this.state.screenMS > this.state.moveMS || !songSel ? this.state.screenMS : this.state.moveMS
+ var fade = ((ms - elapsedMS) % 2000) / 2000
+ if(songBranch && fade > 0.25 && fade < 0.75){
+ this.draw.verticalText({
+ ctx: ctx,
+ text: strings.songBranch,
+ x: _x,
+ y: _y + (songSel ? 110 : 185),
+ width: songSel ? 44 : 56,
+ height: songSel ? 160 : 170,
+ fill: songSel && !currentUra ? "#c85200" : "#fff",
+ fontSize: songSel ? 25 : 27,
+ fontFamily: songSel ? "Meiryo, Microsoft YaHei, sans-serif" : this.font,
+ outline: songSel ? false : "#f22666",
+ outlineSize: songSel ? 0 : this.songAsset.letterBorder
+ })
+ }else{
+ for(var j = 0; j < 10; j++){
+ if(songSel){
+ var yPos = _y + 113 + j * 17
+ }else{
+ var yPos = _y + 178 + j * 19.5
+ }
+ if(10 - j > songStars){
+ ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7")
+ ctx.beginPath()
+ ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2)
+ ctx.fill()
+ }else{
+ this.draw.diffStar({
+ ctx: ctx,
+ songSel: songSel,
+ ura: currentUra,
+ x: _x,
+ y: yPos,
+ ratio: ratio
+ })
+ }
}
}
var currentDiff = this.selectedDiff - this.diffOptions.length
@@ -1687,55 +1738,45 @@ class SongSelect{
if("id" in currentSong){
var startLoad = this.getMS()
if(loadOnly){
- var resolveLoading
- this.previewLoading = currentSong.id
+ var currentId = null
}else{
+ var currentId = this.previewId
this.previewing = this.selectedSong
- if(this.previewLoading === currentSong.id){
- this.previewLoading = null
- return
- }
}
- var currentId = this.previewId
var songObj = this.previewList.find(song => song && song.id === id)
if(songObj){
if(!loadOnly){
this.preview = songObj.preview_sound
this.preview.gain = snd.previewGain
- this.previewLoaded(startLoad, songObj.preview_time)
+ this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
}
}else{
songObj = {id: id}
- var previewFilename = prvTime > 0.1 ? "/preview.mp3" : "/main.ogg"
+ var previewFilename = prvTime > 0 ? "/preview.mp3" : "/main.ogg"
var loadPreview = previewFilename => {
return snd.previewGain.load(gameConfig.songs_baseurl + id + previewFilename)
}
new Promise((resolve, reject) => {
- if(currentSong.music){
- songObj.preview_time = prvTime
- snd.previewGain.load(currentSong.music, true).then(resolve, reject)
- }else{
+ if(!currentSong.music){
songObj.preview_time = 0
loadPreview(previewFilename).catch(() => {
songObj.preview_time = prvTime
return loadPreview("/main.ogg")
}).then(resolve, reject)
+ }else if(currentSong.music !== "muted"){
+ songObj.preview_time = prvTime
+ snd.previewGain.load(currentSong.music, true).then(resolve, reject)
}
}).then(sound => {
if(currentId === this.previewId){
songObj.preview_sound = sound
- if(!loadOnly || !this.previewLoading){
- this.previewing = this.selectedSong
- this.preview = sound
- this.previewLoaded(startLoad, songObj.preview_time)
- }
- if(loadOnly){
- this.previewLoading = null
- }
+ this.preview = sound
+ this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
+
var oldPreview = this.previewList.shift()
if(oldPreview){
oldPreview.preview_sound.clean()
@@ -1748,12 +1789,13 @@ class SongSelect{
}
}
}
- previewLoaded(startLoad, prvTime){
+ previewLoaded(startLoad, prvTime, volume){
var endLoad = this.getMS()
var difference = endLoad - startLoad
var minDelay = 300
var delay = minDelay - Math.min(minDelay, difference)
- this.preview.playLoop(delay / 1000, false, prvTime / 1000)
+ snd.previewGain.setVolumeMul(volume || 1)
+ this.preview.playLoop(delay / 1000, false, prvTime)
}
endPreview(){
this.previewId++
@@ -1880,22 +1922,32 @@ class SongSelect{
return title
}
+ clearHash(){
+ if(location.hash.toLowerCase().startsWith("#song=")){
+ p2.hash("")
+ }
+ }
+
getMS(){
return Date.now()
}
clean(){
+ this.keyboard.clean()
+ this.gamepad.clean()
+ this.clearHash()
this.draw.clean()
this.songTitleCache.clean()
this.selectTextCache.clean()
this.categoryCache.clean()
this.difficultyCache.clean()
this.sessionCache.clean()
+ this.currentSongCache.clean()
assets.sounds["bgm_songsel"].stop()
if(!this.bgmEnabled){
snd.musicGain.fadeIn()
setTimeout(() => {
- snd.musicGain.fadeIn()
+ snd.buffer.loadSettings()
}, 500)
}
this.redrawRunning = false
@@ -1905,7 +1957,6 @@ class SongSelect{
song.preview_sound.clean()
}
})
- pageEvents.keyRemove(this, "all")
pageEvents.remove(loader.screen, ["mousemove", "mouseleave", "mousedown", "touchstart"])
pageEvents.remove(this.canvas, "touchend")
pageEvents.remove(p2, "message")
diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js
index 0262a6b..3e6f45e 100644
--- a/public/src/js/soundbuffer.js
+++ b/public/src/js/soundbuffer.js
@@ -3,6 +3,7 @@
var AudioContext = window.AudioContext || window.webkitAudioContext
this.context = new AudioContext()
pageEvents.add(window, ["click", "touchend"], this.pageClicked.bind(this))
+ this.gainList = []
}
load(url, local, gain){
if(local){
@@ -27,7 +28,9 @@
})
}
createGain(channel){
- return new SoundGain(this, channel)
+ var gain = new SoundGain(this, channel)
+ this.gainList.push(gain)
+ return gain
}
setCrossfade(gain1, gain2, median){
if(!Array.isArray(gain1)){
@@ -60,6 +63,18 @@
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)
+ }
+ }
}
class SoundGain{
constructor(soundBuffer, channel){
@@ -85,8 +100,11 @@ class SoundGain{
this.gainNode.gain.value = amount * amount
this.volume = amount
}
+ setVolumeMul(amount){
+ this.setVolume(amount * this.defaultVol)
+ }
setCrossfade(amount){
- this.setVolume(Math.pow(Math.sin(Math.PI / 2 * amount), 1 / 4))
+ this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount)))
}
fadeIn(duration, time, absolute){
this.fadeVolume(0, this.volume * this.volume, duration, time, absolute)
diff --git a/public/src/js/strings.js b/public/src/js/strings.js
index 510a36c..eb77015 100644
--- a/public/src/js/strings.js
+++ b/public/src/js/strings.js
@@ -1,7 +1,7 @@
function StringsJa(){
this.id = "ja"
this.name = "日本語"
- this.regex = /^ja$/
+ this.regex = /^ja$|^ja-/
this.font = "TnT, Meiryo, sans-serif"
this.taikoWeb = "たいこウェブ"
@@ -24,6 +24,7 @@
this.randomSong = "ランダムに曲をえらぶ"
this.howToPlay = "あそびかた説明"
this.aboutSimulator = "このシミュレータについて"
+ this.gameSettings = "ゲーム設定"
this.browse = "参照する…"
this.defaultSongList = "デフォルト曲リスト"
this.songOptions = "演奏オプション"
@@ -34,6 +35,7 @@
this.normal = "ふつう"
this.hard = "むずかしい"
this.oni = "おに"
+ this.songBranch = "譜面分岐あり"
this.sessionStart = "オンラインセッションを開始する!"
this.sessionEnd = "オンラインセッションを終了する"
this.loading = "ロード中..."
@@ -53,6 +55,11 @@
this.good = "良"
this.ok = "可"
this.bad = "不可"
+ this.branch = {
+ "normal": "普通譜面",
+ "advanced": "玄人譜面",
+ "master": "達人譜面"
+ }
this.pauseOptions = [
"演奏をつづける",
"はじめからやりなおす",
@@ -65,16 +72,16 @@
this.tutorial = {
basics: [
- "Hit the drum when the notes reach the taiko!",
- "For red notes, hit the face of the drum (%s or %s)...",
- "...and for blue notes, hit the rim! (%s or %s)",
- "USB controllers are also supported!"
+ "流れてくる音符がワクに重なったらバチで太鼓をたたこう!",
+ "赤い音符は面をたたこう(%sまたは%s)",
+ "青い音符はフチをたたこう(%sまたは%s)",
+ "USBコントローラがサポートされています!"
],
- otherControls: "Other controls",
+ otherControls: "他のコントロール",
otherTutorial: [
- "%s \u2014 pause game",
- "%s while selecting difficulty \u2014 enable autoplay mode",
- "%s while selecting difficulty \u2014 enable 2P mode"
+ "%sはゲームを一時停止します",
+ "むずかしさをえらぶしながら%sキーを押しながらオートモードを有効",
+ "むずかしさをえらぶしながら%sキーを押しながらネットプレイモードを有効"
],
ok: "OK"
}
@@ -92,6 +99,38 @@
linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.",
cancel: "キャンセル"
}
+ this.settings = {
+ language: {
+ name: "言語"
+ },
+ resolution: {
+ name: "ゲームの解像度",
+ high: "高",
+ medium: "中",
+ low: "低",
+ lowest: "最低"
+ },
+ touchAnimation: {
+ name: "タッチアニメーション"
+ },
+ keyboardSettings: {
+ name: "キーボード設定",
+ ka_l: "ふち(左)",
+ don_l: "面(左)",
+ don_r: "面(右)",
+ ka_r: "ふち(右)"
+ },
+ gamepadLayout: {
+ name: "そうさタイプ設定",
+ a: "タイプA",
+ b: "タイプB",
+ c: "タイプC"
+ },
+ on: "オン",
+ off: "オフ",
+ default: "既定値にリセット",
+ ok: "OK"
+ }
this.browserSupport = {
browserWarning: "サポートされていないブラウザを実行しています (%s)",
details: "詳しく",
@@ -125,6 +164,7 @@ function StringsEn(){
this.randomSong = "Random Song"
this.howToPlay = "How to Play"
this.aboutSimulator = "About Simulator"
+ this.gameSettings = "Game Settings"
this.browse = "Browse…"
this.defaultSongList = "Default Song List"
this.songOptions = "Song Options"
@@ -135,6 +175,7 @@ function StringsEn(){
this.normal = "Normal"
this.hard = "Hard"
this.oni = "Extreme"
+ this.songBranch = "Diverge Notes"
this.sessionStart = "Begin an Online Session!"
this.sessionEnd = "End Online Session"
this.loading = "Loading..."
@@ -154,6 +195,11 @@ function StringsEn(){
this.good = "GOOD"
this.ok = "OK"
this.bad = "BAD"
+ this.branch = {
+ "normal": "Normal",
+ "advanced": "Professional",
+ "master": "Master"
+ }
this.pauseOptions = [
"Continue",
"Retry",
@@ -166,8 +212,8 @@ function StringsEn(){
this.tutorial = {
basics: [
- "Hit the drum when the notes reach the taiko!",
- "For red notes, hit the face of the drum (%s or %s)...",
+ "When a note overlaps the frame, that is your cue to hit the drum!",
+ "For red notes, hit the surface of the drum (%s or %s)...",
"...and for blue notes, hit the rim! (%s or %s)",
"USB controllers are also supported!"
],
@@ -193,6 +239,38 @@ function StringsEn(){
linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.",
cancel: "Cancel"
}
+ this.settings = {
+ language: {
+ name: "Language"
+ },
+ resolution: {
+ name: "Game Resolution",
+ high: "High",
+ medium: "Medium",
+ low: "Low",
+ lowest: "Lowest"
+ },
+ touchAnimation: {
+ name: "Touch Animation"
+ },
+ keyboardSettings: {
+ name: "Keyboard Settings",
+ ka_l: "Left Rim",
+ don_l: "Left Surface",
+ don_r: "Right Surface",
+ ka_r: "Right Rim"
+ },
+ gamepadLayout: {
+ name: "Gamepad Layout",
+ a: "Type A",
+ b: "Type B",
+ c: "Type C"
+ },
+ on: "On",
+ off: "Off",
+ default: "Reset to Defaults",
+ ok: "OK"
+ }
this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
@@ -226,6 +304,7 @@ function StringsCn(){
this.randomSong = "随机选曲"
this.howToPlay = "操作说明"
this.aboutSimulator = "关于模拟器"
+ this.gameSettings = "游戏设定"
this.browse = "浏览…"
this.defaultSongList = "默认歌曲列表"
this.songOptions = "选项"
@@ -236,6 +315,7 @@ function StringsCn(){
this.normal = "普通"
this.hard = "困难"
this.oni = "魔王"
+ this.songBranch = "有谱面分歧"
this.sessionStart = "开始在线会话!"
this.sessionEnd = "结束在线会话"
this.loading = "加载中..."
@@ -255,6 +335,11 @@ function StringsCn(){
this.good = "良"
this.ok = "可"
this.bad = "不可"
+ this.branch = {
+ "normal": "一般谱面",
+ "advanced": "进阶谱面",
+ "master": "达人谱面"
+ }
this.pauseOptions = [
"继续演奏",
"从头开始",
@@ -267,16 +352,16 @@ function StringsCn(){
this.tutorial = {
basics: [
- "Hit the drum when the notes reach the taiko!",
- "For red notes, hit the face of the drum (%s or %s)...",
- "...and for blue notes, hit the rim! (%s or %s)",
- "USB controllers are also supported!"
+ "当流动的音符将与框框重叠时就用鼓棒敲打太鼓吧",
+ "遇到红色音符要敲打鼓面(%s或%s)",
+ "遇到蓝色音符则敲打鼓边(%s或%s)",
+ "USB控制器也支持!"
],
- otherControls: "Other controls",
+ otherControls: "其他控制",
otherTutorial: [
- "%s \u2014 pause game",
- "%s while selecting difficulty \u2014 enable autoplay mode",
- "%s while selecting difficulty \u2014 enable 2P mode"
+ "%s暂停游戏",
+ "选择难度时按住%s以启用自动模式",
+ "选择难度时按住%s以启用网络对战模式"
],
ok: "确定"
}
@@ -294,6 +379,38 @@ function StringsCn(){
linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.",
cancel: "取消"
}
+ this.settings = {
+ language: {
+ name: "语言"
+ },
+ resolution: {
+ name: "游戏分辨率",
+ high: "高",
+ medium: "中",
+ low: "低",
+ lowest: "最低"
+ },
+ touchAnimation: {
+ name: "触摸动画"
+ },
+ keyboardSettings: {
+ name: "键盘设置",
+ ka_l: "边缘(左)",
+ don_l: "表面(左)",
+ don_r: "表面(右)",
+ ka_r: "边缘(右)"
+ },
+ gamepadLayout: {
+ name: "操作类型设定",
+ a: "类型A",
+ b: "类型B",
+ c: "类型C"
+ },
+ on: "开",
+ off: "关",
+ default: "重置为默认值",
+ ok: "确定"
+ }
this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
@@ -327,6 +444,7 @@ function StringsTw(){
this.randomSong = "隨機選曲"
this.howToPlay = "操作說明"
this.aboutSimulator = "關於模擬器"
+ this.gameSettings = "遊戲設定"
this.browse = "開啟檔案…"
this.defaultSongList = "默認歌曲列表"
this.songOptions = "選項"
@@ -337,6 +455,7 @@ function StringsTw(){
this.normal = "普通"
this.hard = "困難"
this.oni = "魔王"
+ this.songBranch = "有譜面分歧"
this.sessionStart = "開始多人模式!"
this.sessionEnd = "結束多人模式"
this.loading = "讀取中..."
@@ -356,6 +475,11 @@ function StringsTw(){
this.good = "良"
this.ok = "可"
this.bad = "不可"
+ this.branch = {
+ "normal": "一般譜面",
+ "advanced": "進階譜面",
+ "master": "達人譜面"
+ }
this.pauseOptions = [
"繼續演奏",
"從頭開始",
@@ -368,16 +492,16 @@ function StringsTw(){
this.tutorial = {
basics: [
- "Hit the drum when the notes reach the taiko!",
- "For red notes, hit the face of the drum (%s or %s)...",
- "...and for blue notes, hit the rim! (%s or %s)",
- "USB controllers are also supported!"
+ "當流動的音符將與框框重疊時就用鼓棒敲打太鼓吧",
+ "遇到紅色音符要敲打鼓面(%s或%s)",
+ "遇到藍色音符則敲打鼓邊(%s或%s)",
+ "USB控制器也支持!"
],
- otherControls: "Other controls",
+ otherControls: "其他控制",
otherTutorial: [
- "%s \u2014 pause game",
- "%s while selecting difficulty \u2014 enable autoplay mode",
- "%s while selecting difficulty \u2014 enable 2P mode"
+ "%s暫停遊戲",
+ "選擇難度時按住%s以啟用自動模式",
+ "選擇難度時按住%s以啟用網上對打模式"
],
ok: "確定"
}
@@ -395,6 +519,38 @@ function StringsTw(){
linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.",
cancel: "取消"
}
+ this.settings = {
+ language: {
+ name: "語系"
+ },
+ resolution: {
+ name: "遊戲分辨率",
+ high: "高",
+ medium: "中",
+ low: "低",
+ lowest: "最低"
+ },
+ touchAnimation: {
+ name: "觸摸動畫"
+ },
+ keyboardSettings: {
+ name: "鍵盤設置",
+ ka_l: "邊緣(左)",
+ don_l: "表面(左)",
+ don_r: "表面(右)",
+ ka_r: "邊緣(右)"
+ },
+ gamepadLayout: {
+ name: "操作類型設定",
+ a: "類型A",
+ b: "類型B",
+ c: "類型C"
+ },
+ on: "開",
+ off: "關",
+ default: "重置為默認值",
+ ok: "確定"
+ }
this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
@@ -428,6 +584,7 @@ function StringsKo(){
this.randomSong = "랜덤"
this.howToPlay = "지도 시간"
this.aboutSimulator = "게임 정보"
+ this.gameSettings = "게임 설정"
this.browse = "찾아보기…"
this.defaultSongList = "기본 노래 목록"
this.songOptions = "옵션"
@@ -438,6 +595,7 @@ function StringsKo(){
this.normal = "보통"
this.hard = "어려움"
this.oni = "귀신"
+ this.songBranch = "악보 분기 있습니다"
this.sessionStart = "온라인 세션 시작!"
this.sessionEnd = "온라인 세션 끝내기"
this.loading = "로딩 중..."
@@ -457,6 +615,11 @@ function StringsKo(){
this.good = "얼쑤"
this.ok = "좋다"
this.bad = "에구"
+ this.branch = {
+ "normal": "보통 악보",
+ "advanced": "현인 악보",
+ "master": "달인 악보"
+ }
this.pauseOptions = [
"연주 계속하기",
"처음부터 다시",
@@ -469,16 +632,16 @@ function StringsKo(){
this.tutorial = {
basics: [
- "Hit the drum when the notes reach the taiko!",
- "For red notes, hit the face of the drum (%s or %s)...",
- "...and for blue notes, hit the rim! (%s or %s)",
- "USB controllers are also supported!"
+ "이동하는 음표가 테두리와 겹쳐졌을 때 북채로 태고를 두드리자!",
+ "빨간 음표는 면을 두드리자 (%s 또는 %s)",
+ "파란 음표는 테를 두드리자 (%s 또는 %s)",
+ "USB 컨트롤러도 지원됩니다!"
],
- otherControls: "Other controls",
+ otherControls: "기타 컨트롤",
otherTutorial: [
- "%s \u2014 pause game",
- "%s while selecting difficulty \u2014 enable autoplay mode",
- "%s while selecting difficulty \u2014 enable 2P mode"
+ "%s \u2014 게임을 일시 중지합니다",
+ "난이도 선택 동안 %s 홀드 \u2014 오토 모드 활성화",
+ "난이도 선택 동안 %s 홀드 \u2014 넷 플레이 모드 활성화"
],
ok: "확인"
}
@@ -496,6 +659,38 @@ function StringsKo(){
linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.",
cancel: "취소"
}
+ this.settings = {
+ language: {
+ name: "언어"
+ },
+ resolution: {
+ name: "게임 해상도",
+ high: "높은",
+ medium: "중간",
+ low: "저",
+ lowest: "최저"
+ },
+ touchAnimation: {
+ name: "터치 애니메이션"
+ },
+ keyboardSettings: {
+ name: "키보드 설정",
+ ka_l: "가장자리 (왼쪽)",
+ don_l: "표면 (왼쪽)",
+ don_r: "표면 (오른쪽)",
+ ka_r: "가장자리 (오른쪽)"
+ },
+ gamepadLayout: {
+ name: "조작 타입 설정",
+ a: "타입 A",
+ b: "타입 B",
+ c: "타입 C"
+ },
+ on: "온",
+ off: "오프",
+ default: "기본값으로 재설정",
+ ok: "확인"
+ }
this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js
index 9f7c4b5..6068a93 100644
--- a/public/src/js/titlescreen.js
+++ b/public/src/js/titlescreen.js
@@ -1,146 +1,102 @@
class Titlescreen{
- constructor(){
- loader.changePage("titlescreen", false)
+ constructor(songId){
+ this.songId = songId
- this.titleScreen = document.getElementById("title-screen")
- this.proceed = document.getElementById("title-proceed")
- this.langDropdown = document.getElementById("lang-dropdown")
- this.langId = document.getElementById("lang-id")
- this.disclaimerText = document.getElementById("title-disclaimer-text")
- this.disclaimerCopyright = document.getElementById("title-disclaimer-copyright")
- document.getElementById("globe-path").setAttribute("d", vectors.globe)
-
- this.logo = new Logo()
- this.lang = this.getLang()
- this.setLang(allStrings[this.lang])
- this.addLangs()
+ if(!songId){
+ loader.changePage("titlescreen", false)
+
+ 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")])
- pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this))
- pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], this.onPressed.bind(this))
- pageEvents.add(this.langDropdown, "change", this.langChange.bind(this))
-
- assets.sounds["v_title"].play()
- this.gamepad = new Gamepad({
- "13": ["a", "b", "x", "y", "start", "ls", "rs"]
- }, pressed => {
- if(pressed){
- this.onPressed()
+ if(songId){
+ if(localStorage.getItem("tutorial") === "true"){
+ new SongSelect(false, false, this.touched, this.songId)
+ }else{
+ new SettingsView(false, true, this.songId)
}
- })
- if(p2.session){
- pageEvents.add(p2, "message", response => {
- if(response.type === "songsel"){
- this.goNext(true)
+ }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({
+ confirm: ["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")
}
}
- keyDown(event, code){
- if(event && event.target === this.langDropdown){
- return
+ onPressed(pressed, name){
+ if(pressed){
+ this.titleScreen.style.cursor = "auto"
+ this.clean()
+ assets.sounds["se_don"].play()
+ this.goNext()
}
- if(!code){
- code = event.keyCode
- }
- if(code == 13 || code == 32 || code == 70 || code == 74){
- // Enter, Space, F, J
- this.onPressed()
- }
- }
- onPressed(event){
- if(event){
- if(event.type === "touchstart"){
- event.preventDefault()
- this.touched = true
- }else if(event.type === "mousedown" && event.which !== 1){
- return
- }
- }
- this.titleScreen.style.cursor = "auto"
- this.clean()
- assets.sounds["se_don"].play()
- this.goNext()
}
goNext(fromP2){
if(p2.session && !fromP2){
p2.send("songsel")
- }else if(fromP2 || this.touched || localStorage.getItem("tutorial") === "true"){
+ }else if(fromP2 || localStorage.getItem("tutorial") === "true"){
+ if(this.touched){
+ localStorage.setItem("tutorial", "true")
+ }
pageEvents.remove(p2, "message")
setTimeout(() => {
- new SongSelect(false, false, this.touched)
+ new SongSelect(false, false, this.touched, this.songId)
}, 500)
}else{
setTimeout(() => {
- new Tutorial()
+ new SettingsView(this.touched, true, this.songId)
}, 500)
}
}
-
- getLang(){
- if(localStorage.lang && localStorage.lang in allStrings){
- return localStorage.lang
+ setLang(lang, noEvent){
+ settings.setLang(lang, true)
+ if(this.songId){
+ return
}
- if("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])){
- return j
- }
- }
- }
- }
- return "ja"
- }
- setLang(lang){
- strings = lang
this.proceed.innerText = strings.titleProceed
this.proceed.setAttribute("alt", strings.titleProceed)
- this.langId.innerText = strings.id.toUpperCase()
- this.langId.setAttribute("alt", strings.id.toUpperCase())
this.disclaimerText.innerText = strings.titleDisclaimer
this.disclaimerText.setAttribute("alt", strings.titleDisclaimer)
this.disclaimerCopyright.innerText = strings.titleCopyright
this.disclaimerCopyright.setAttribute("alt", strings.titleCopyright)
- loader.screen.style.fontFamily = strings.font
- loader.screen.style.fontWeight = strings.font === "Microsoft YaHei, sans-serif" ? "bold" : ""
- if(failedTests.length !== 0){
- showUnsupported(strings)
- }
this.logo.updateSubtitle()
}
- addLangs(){
- for(var i in allStrings){
- var option = document.createElement("option")
- option.value = i
- if(i === this.lang){
- option.selected = true
- }
- option.appendChild(document.createTextNode(allStrings[i].name))
- this.langDropdown.appendChild(option)
- }
- }
- langChange(){
- this.lang = this.langDropdown.value
- localStorage.lang = this.lang
- this.setLang(allStrings[this.lang])
- }
-
clean(){
+ this.keyboard.clean()
this.gamepad.clean()
this.logo.clean()
assets.sounds["v_title"].stop()
- pageEvents.keyRemove(this, "all")
pageEvents.remove(this.titleScreen, ["mousedown", "touchstart"])
- pageEvents.remove(this.langDropdown, "change")
delete this.titleScreen
delete this.proceed
delete this.titleDisclaimer
delete this.titleCopyright
- delete this.langDropdown
}
}
diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js
index 7de7e31..2d1302c 100644
--- a/public/src/js/tutorial.js
+++ b/public/src/js/tutorial.js
@@ -1,15 +1,72 @@
class Tutorial{
- constructor(fromSongSel){
+ constructor(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 = document.getElementById("tutorial-end-button")
+ this.endButton = this.getElement("view-end-button")
- var tutorialTitle = document.getElementById("tutorial-title")
- tutorialTitle.innerText = strings.howToPlay
- tutorialTitle.setAttribute("alt", strings.howToPlay)
- var tutorialContent = document.getElementById("tutorial-content")
- var keys = ["F", "J", "D", "K", "Q", "SHIFT", "CTRL"]
+ this.tutorialTitle = this.getElement("view-title")
+ this.tutorialDiv = document.createElement("div")
+ this.getElement("view-content").appendChild(this.tutorialDiv)
+ this.setStrings()
+
+ pageEvents.add(this.endButton, ["mousedown", "touchstart"], event => {
+ if(event.type === "touchstart"){
+ event.preventDefault()
+ this.touched = true
+ }else if(event.type === "mousedown" && event.which !== 1){
+ return
+ }
+ this.onEnd(true)
+ })
+ this.keyboard = new Keyboard({
+ confirm: ["enter", "space", "esc", "don_l", "don_r"]
+ }, this.onEnd.bind(this))
+ this.gamepad = new Gamepad({
+ confirm: ["start", "b", "ls", "rs"]
+ }, this.onEnd.bind(this))
+
+ pageEvents.send("tutorial")
+ }
+ getElement(name){
+ return loader.screen.getElementsByClassName(name)[0]
+ }
+ insertText(text, parent){
+ parent.appendChild(document.createTextNode(text))
+ }
+ insertKey(key, parent){
+ var kbd = document.createElement("kbd")
+ kbd.innerText = key
+ parent.appendChild(kbd)
+ }
+ onEnd(pressed, name){
+ if(pressed){
+ this.clean()
+ assets.sounds["se_don"].play()
+ try{
+ localStorage.setItem("tutorial", "true")
+ }catch(e){}
+ setTimeout(() => {
+ new SongSelect(this.fromSongSel ? "tutorial" : false, false, this.touched, this.songId)
+ }, 500)
+ }
+ }
+ 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 = pageEvents.kbd.indexOf("q") === -1 ? "Q" : "ESC"
+ var keys = [
+ kbdSettings.don_l[0].toUpperCase(),
+ kbdSettings.don_r[0].toUpperCase(),
+ kbdSettings.ka_l[0].toUpperCase(),
+ kbdSettings.ka_r[0].toUpperCase(),
+ pauseKey, "SHIFT", "CTRL"
+ ]
var keyIndex = 0
strings.tutorial.basics.forEach(string => {
var par = document.createElement("p")
@@ -20,7 +77,7 @@ class Tutorial{
}
this.insertText(stringKey, par)
})
- tutorialContent.appendChild(par)
+ this.tutorialDiv.appendChild(par)
})
var par = document.createElement("p")
var span = document.createElement("span")
@@ -37,43 +94,15 @@ class Tutorial{
this.insertText(stringKey, par)
})
})
- tutorialContent.appendChild(par)
- this.endButton.innerText = strings.tutorial.ok
- this.endButton.setAttribute("alt", strings.tutorial.ok)
-
- pageEvents.once(this.endButton, ["mousedown", "touchstart"]).then(this.onEnd.bind(this))
- pageEvents.keyOnce(this, 13, "down").then(this.onEnd.bind(this))
-
- this.gamepad = new Gamepad({
- "confirm": ["start", "b", "ls", "rs"]
- }, this.onEnd.bind(this))
- }
- insertText(text, parent){
- parent.appendChild(document.createTextNode(text))
- }
- insertKey(key, parent){
- var kbd = document.createElement("kbd")
- kbd.innerText = key
- parent.appendChild(kbd)
- }
- onEnd(event){
- var touched = false
- if(event && event.type === "touchstart"){
- event.preventDefault()
- touched = true
- }
- this.clean()
- assets.sounds["se_don"].play()
- localStorage.setItem("tutorial", "true")
- setTimeout(() => {
- new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched)
- }, 500)
+ this.tutorialDiv.appendChild(par)
}
clean(){
+ this.keyboard.clean()
this.gamepad.clean()
- assets.sounds["bgm_setsume"].stop()
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
- pageEvents.keyRemove(this, 13)
+ assets.sounds["bgm_setsume"].stop()
+ delete this.tutorialTitle
delete this.endButton
+ delete this.tutorialDiv
}
}
diff --git a/public/src/js/view.js b/public/src/js/view.js
index 61513d0..06c30c7 100644
--- a/public/src/js/view.js
+++ b/public/src/js/view.js
@@ -74,6 +74,34 @@
this.nextBeat = 0
this.gogoTime = 0
this.drumroll = []
+ this.touchEvents = 0
+ if(this.controller.parsedSongData.branches){
+ this.branch = "normal"
+ this.branchAnimate = {
+ ms: -Infinity,
+ fromBranch: "normal"
+ }
+ this.branchMap = {
+ "normal": {
+ "bg": "rgba(0, 0, 0, 0)",
+ "text": "#d3d3d3",
+ "stroke": "#393939",
+ "shadow": "#000"
+ },
+ "advanced": {
+ "bg": "rgba(29, 129, 189, 0.4)",
+ "text": "#94d7e7",
+ "stroke": "#315973",
+ "shadow": "#082031"
+ },
+ "master": {
+ "bg": "rgba(230, 29, 189, 0.4)",
+ "text": "#f796ef",
+ "stroke": "#7e2e6e",
+ "shadow": "#3e0836"
+ }
+ }
+ }
this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval
this.font = strings.font
@@ -84,11 +112,13 @@
this.titleCache = new CanvasCache()
this.comboCache = new CanvasCache()
this.pauseCache = new CanvasCache()
+ this.branchCache = new CanvasCache()
this.multiplayer = this.controller.multiplayer
this.touchEnabled = this.controller.touchEnabled
this.touch = -Infinity
+ this.touchAnimation = settings.getItem("touchAnimation")
if(this.multiplayer !== 2){
@@ -96,6 +126,8 @@
this.touchDrumDiv = document.getElementById("touch-drum")
this.touchDrumImg = document.getElementById("touch-drum-img")
+ this.setBgImage(this.touchDrumImg, assets.image["touch_drum"].src)
+
if(this.controller.autoPlayEnabled){
this.touchDrumDiv.style.display = "none"
}
@@ -131,7 +163,8 @@
}
this.setDonBg()
- this.lastMousemove = this.controller.game.getAccurateTime()
+ this.startTime = this.controller.game.getAccurateTime()
+ this.lastMousemove = this.startTime
pageEvents.mouseAdd(this, this.onmousemove.bind(this))
this.refresh()
@@ -150,6 +183,14 @@
var touchMultiplayer = this.touchEnabled && this.multiplayer && !this.portrait
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
if(this.portrait){
@@ -161,6 +202,7 @@
}
var ratio = (ratioX < ratioY ? ratioX : ratioY)
+ var resized = false
if(this.winW !== winW || this.winH !== winH){
this.winW = winW
this.winH = winH
@@ -180,6 +222,7 @@
}
this.fillComboCache()
this.setDonBgHeight()
+ resized = true
}else if(this.controller.game.paused && !document.hasFocus()){
return
}else if(this.multiplayer !== 2){
@@ -668,7 +711,7 @@
}
ctx.restore()
- // Bar pressed keys
+ // Branch background
var keyTime = this.controller.getKeyTime()
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
var padding = this.slotPos.paddingLeft
@@ -676,12 +719,78 @@
var barY = this.slotPos.y - 65 * mul
var barH = 130 * mul
+ if(this.branchAnimate && ms <= this.branchAnimate.ms + 300){
+ var alpha = Math.max(0, (ms - this.branchAnimate.ms) / 300)
+ ctx.globalAlpha = 1 - alpha
+ ctx.fillStyle = this.branchMap[this.branchAnimate.fromBranch].bg
+ ctx.fillRect(padding, barY, winW - padding, barH)
+ ctx.globalAlpha = alpha
+ }
+ if(this.branch){
+ ctx.fillStyle = this.branchMap[this.branch].bg
+ ctx.fillRect(padding, barY, winW - padding, barH)
+ ctx.globalAlpha = 1
+ }
+
+ // Current branch text
+ if(this.branch){
+ if(resized){
+ this.fillBranchCache()
+ }
+ var textW = Math.floor(260 * mul)
+ var textH = Math.floor(barH)
+ var textX = winW - textW
+ var oldOffset = 0
+ var newOffset = 0
+ var elapsed = ms - this.startTime
+ if(elapsed < 250){
+ textX = winW
+ }else if(elapsed < 500){
+ textX += (1 - this.draw.easeOutBack((elapsed - 250) / 250)) * textW
+ }
+ if(this.branchAnimate && ms - this.branchAnimate.ms < 310 && ms >= this.branchAnimate.ms){
+ var fromBranch = this.branchAnimate.fromBranch
+ var elapsed = ms - this.branchAnimate.ms
+ var reverse = fromBranch === "master" || fromBranch === "advanced" && this.branch === "normal" ? -1 : 1
+ if(elapsed < 65){
+ oldOffset = elapsed / 65 * 12 * mul * reverse
+ ctx.globalAlpha = 1
+ var newAlpha = 0
+ }else if(elapsed < 215){
+ var animPoint = (elapsed - 65) / 150
+ oldOffset = (12 - animPoint * 48) * mul * reverse
+ newOffset = (36 - animPoint * 48) * mul * reverse
+ ctx.globalAlpha = this.draw.easeIn(1 - animPoint)
+ var newAlpha = this.draw.easeIn(animPoint)
+ }else{
+ newOffset = (1 - (elapsed - 215) / 95) * -12 * mul * reverse
+ ctx.globalAlpha = 0
+ var newAlpha = 1
+ }
+ this.branchCache.get({
+ ctx: ctx,
+ x: textX, y: barY + oldOffset,
+ w: textW, h: textH,
+ id: fromBranch
+ })
+ ctx.globalAlpha = newAlpha
+ }
+ this.branchCache.get({
+ ctx: ctx,
+ x: textX, y: barY + newOffset,
+ w: textW, h: textH,
+ id: this.branch
+ })
+ ctx.globalAlpha = 1
+ }
+
+ // Go go time background
if(this.gogoTime || ms <= this.gogoTimeStarted + 100){
var grd = ctx.createLinearGradient(padding, 0, winW, 0)
- grd.addColorStop(0, "#512a2c")
- grd.addColorStop(0.46, "#6f2a2d")
- grd.addColorStop(0.76, "#8a4763")
- grd.addColorStop(1, "#2c2a2c")
+ grd.addColorStop(0, "rgba(255, 0, 0, 0.16)")
+ grd.addColorStop(0.45, "rgba(255, 0, 0, 0.28)")
+ grd.addColorStop(0.77, "rgba(255, 83, 157, 0.4)")
+ grd.addColorStop(1, "rgba(255, 83, 157, 0)")
ctx.fillStyle = grd
if(!this.touchEnabled){
var alpha = Math.min(100, ms - this.gogoTimeStarted) / 100
@@ -692,6 +801,8 @@
}
ctx.fillRect(padding, barY, winW - padding, barH)
}
+
+ // Bar pressed keys
if(keyTime[sound] > ms - 130){
var gradients = {
"don": "255, 0, 0",
@@ -719,11 +830,10 @@
)
// Taiko pressed keys
- var kbd = this.controller.getBindings()
var keys = ["ka_l", "ka_r", "don_l", "don_r"]
for(var i = 0; i < keys.length; i++){
- var keyMS = ms - keyTime[kbd[keys[i]]]
+ var keyMS = ms - keyTime[keys[i]]
if(keyMS < 130){
if(keyMS > 70 && !this.touchEnabled){
ctx.globalAlpha = this.draw.easeOut(1 - (keyMS - 70) / 60)
@@ -750,7 +860,7 @@
comboScale = this.draw.fade(scoreMS / 100)
}
var glyphW = 51
- var glyphH = 64
+ var glyphH = 65
var letterSpacing = (comboText.length >= 4 ? 38 : 42) * mul
var orange = comboCount >= 100 ? "1" : "0"
@@ -789,6 +899,7 @@
ctx.lineWidth = 7 * mul
ctx.textAlign = "center"
ctx.miterLimit = 1
+ ctx.strokeStyle = "#000"
ctx.strokeText(strings.combo, comboX, comboTextY)
ctx.miterLimit = 10
ctx.fillText(strings.combo, comboX, comboTextY)
@@ -999,7 +1110,7 @@
var songSkinName = selectedSong.songSkin.name
var supportsBlend = "mixBlendMode" in this.songBg.style
var songLayers = [document.getElementById("layer1"), document.getElementById("layer2")]
- var prefix = selectedSong.songSkin.prefix || ""
+ var prefix = ""
if(selectedSong.category in this.categories){
var catId = this.categories[selectedSong.category].sort
@@ -1010,8 +1121,9 @@
if(!selectedSong.songSkin.song){
var id = selectedSong.songBg
this.songBg.classList.add("songbg-" + id)
- this.setLayers(songLayers, prefix + "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend)
+ this.setLayers(songLayers, "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend)
}else if(selectedSong.songSkin.song !== "none"){
+ var prefix = selectedSong.songSkin.prefix || ""
var notStatic = selectedSong.songSkin.song !== "static"
if(notStatic){
this.songBg.classList.add("songbg-" + selectedSong.songSkin.song)
@@ -1021,7 +1133,9 @@
if(!selectedSong.songSkin.stage){
this.songStage.classList.add("song-stage-" + selectedSong.songStage)
+ this.setBgImage(this.songStage, assets.image["bg_stage_" + selectedSong.songStage].src)
}else if(selectedSong.songSkin.stage !== "none"){
+ var prefix = selectedSong.songSkin.prefix || ""
this.setBgImage(this.songStage, assets.image[prefix + "bg_stage_" + songSkinName].src)
}
}
@@ -1030,7 +1144,7 @@
var songSkinName = selectedSong.songSkin.name
var donLayers = []
var filename = !selectedSong.songSkin.don && this.multiplayer === 2 ? "bg_don2_" : "bg_don_"
- var prefix = selectedSong.songSkin.prefix || ""
+ var prefix = ""
this.donBg = document.createElement("div")
this.donBg.classList.add("donbg")
@@ -1049,10 +1163,11 @@
var asset1, asset2
if(!selectedSong.songSkin.don){
this.donBg.classList.add("donbg-" + selectedSong.donBg)
- this.setLayers(donLayers, prefix + filename + selectedSong.donBg, true)
+ this.setLayers(donLayers, filename + selectedSong.donBg, true)
asset1 = filename + selectedSong.donBg + "a"
asset2 = filename + selectedSong.donBg + "b"
}else if(selectedSong.songSkin.don !== "none"){
+ var prefix = selectedSong.songSkin.prefix || ""
var notStatic = selectedSong.songSkin.don !== "static"
if(notStatic){
this.donBg.classList.add("donbg-" + selectedSong.songSkin.don)
@@ -1076,6 +1191,11 @@
}
setDonBgHeight(){
this.donBg.style.setProperty("--h", getComputedStyle(this.donBg).height)
+ var gameDiv = this.gameDiv
+ gameDiv.classList.add("fix-animations")
+ setTimeout(()=>{
+ gameDiv.classList.remove("fix-animations")
+ }, 50)
}
setLayers(elements, file, ab){
if(ab){
@@ -1101,15 +1221,23 @@
var timeForDistance = this.posToMs(distanceForCircle, measure.speed)
var startingTime = measure.ms - timeForDistance
var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed)
- if(ms >= startingTime && ms <= finishTime){
+ if(measure.visible && (!measure.branch || measure.branch.active) && ms >= startingTime && ms <= finishTime){
var measureX = this.slotPos.x + this.msToPos(measure.ms - ms, measure.speed)
- this.ctx.strokeStyle = "#bdbdbd"
+ this.ctx.strokeStyle = measure.branchFirst ? "#ff0" : "#bdbdbd"
this.ctx.lineWidth = 3
this.ctx.beginPath()
this.ctx.moveTo(measureX, measureY)
this.ctx.lineTo(measureX, measureY + measureH)
this.ctx.stroke()
}
+ if(this.multiplayer !== 2 && ms >= measure.ms && measure.nextBranch && !measure.viewChecked && measure.gameChecked){
+ measure.viewChecked = true
+ if(measure.nextBranch.active !== this.branch){
+ this.branchAnimate.ms = ms
+ this.branchAnimate.fromBranch = this.branch
+ }
+ this.branch = measure.nextBranch.active
+ }
})
}
updateNoteFaces(){
@@ -1136,21 +1264,21 @@
for(var i = circles.length; i--;){
var circle = circles[i]
- var speed = circle.getSpeed()
+ var speed = circle.speed
var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed)
- var startingTime = circle.getMS() - timeForDistance
- var finishTime = circle.getEndTime() + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed)
+ var startingTime = circle.ms - timeForDistance
+ var finishTime = circle.endTime + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed)
- if(circle.getPlayed() <= 0 || circle.getScore() === 0){
- if(ms >= startingTime && ms <= finishTime && circle.getPlayed() !== -1){
+ if(circle.isPlayed <= 0 || circle.score === 0){
+ if((!circle.branch || circle.branch.active) && ms >= startingTime && ms <= finishTime && circle.isPlayed !== -1){
this.drawCircle(circle)
}
- }else if(!circle.isAnimated()){
+ }else if(!circle.animating){
// Start animation to gauge
circle.animate(ms)
}
- if(ms >= circle.ms && !circle.gogoChecked){
+ if(ms >= circle.ms && !circle.gogoChecked && (!circle.branch || circle.branch.active)){
if(this.gogoTime != circle.gogoTime){
this.toggleGogoTime(circle)
}
@@ -1164,9 +1292,9 @@
for(var i = 0; i < circles.length; i++){
var circle = circles[i]
- if(circle.isAnimated()){
+ if(circle.animating){
- var animT = circle.getAnimT()
+ var animT = circle.animT
if(ms < animT + 490){
if(circle.fixedPos){
@@ -1182,7 +1310,7 @@
var pos = this.animateBezier[3]
this.drawCircle(circle, pos, (ms - animT - 490) / 160)
}else{
- circle.endAnimation()
+ circle.animationEnded = true
}
}
}
@@ -1210,13 +1338,13 @@
var lyricsSize = 20 * mul
var fill, size, faceID
- var type = circle.getType()
+ var type = circle.type
var ms = this.getMS()
- var circleMs = circle.getMS()
- var endTime = circle.getEndTime()
- var animated = circle.isAnimated()
- var speed = circle.getSpeed()
- var played = circle.getPlayed()
+ var circleMs = circle.ms
+ var endTime = circle.endTime
+ var animated = circle.animating
+ var speed = circle.speed
+ var played = circle.isPlayed
var drumroll = 0
var endX = 0
@@ -1322,9 +1450,9 @@
ctx.fill()
ctx.globalAlpha = 1
}
- if(!circle.isAnimated()){
+ if(!circle.animating && circle.text){
// Text
- var text = circle.getText()
+ var text = circle.text
var textX = circlePos.x
var textY = circlePos.y + 83 * mul
ctx.font = lyricsSize + "px Kozuka, Microsoft YaHei, sans-serif"
@@ -1374,8 +1502,8 @@
fillComboCache(){
var fontSize = 58
var letterSpacing = fontSize * 0.67
- var glyphW = 50
- var glyphH = 64
+ var glyphW = 51
+ var glyphH = 65
var textX = 5
var textY = 5
var letterBorder = fontSize * 0.15
@@ -1435,6 +1563,37 @@
})
this.globalAlpha = 1
}
+ fillBranchCache(){
+ var mul = this.slotPos.size / 106
+ var textW = Math.floor(260 * mul)
+ var barH = Math.floor(130 * mul)
+ var branchNames = this.controller.game.branchNames
+ var textX = textW - 33 * mul
+ var textY = 63 * mul
+ var fontSize = (strings.id === "en" ? 33 : (strings.id === "ko" ? 38 : 43)) * mul
+ this.branchCache.resize((textW + 1), (barH + 1) * 3, this.ratio)
+ for(var i in branchNames){
+ this.branchCache.set({
+ w: textW,
+ h: barH,
+ id: branchNames[i]
+ }, ctx => {
+ var currentMap = this.branchMap[branchNames[i]]
+ ctx.font = this.draw.bold(this.font) + fontSize + "px " + this.font
+ ctx.lineJoin = "round"
+ ctx.miterLimit = 1
+ ctx.textAlign = "right"
+ ctx.textBaseline = "middle"
+ ctx.lineWidth = 8 * mul
+ ctx.strokeStyle = currentMap.shadow
+ ctx.strokeText(strings.branch[branchNames[i]], textX, textY + 4 * mul)
+ ctx.strokeStyle = currentMap.stroke
+ ctx.strokeText(strings.branch[branchNames[i]], textX, textY)
+ ctx.fillStyle = currentMap.text
+ ctx.fillText(strings.branch[branchNames[i]], textX, textY)
+ })
+ }
+ }
toggleGogoTime(circle){
this.gogoTime = circle.gogoTime
this.gogoTimeStarted = circle.ms
@@ -1465,7 +1624,7 @@
if(this.gogoTime){
var circles = this.controller.parsedSongData.circles
var lastCircle = circles[circles.length - 1]
- var endTime = lastCircle.getEndTime() + 3000
+ var endTime = lastCircle.endTime + 3000
if(ms >= endTime){
this.toggleGogoTime({
gogoTime: 0,
@@ -1553,14 +1712,16 @@
this.touchDrumDiv.style.width = drumWidth + "px"
this.touchDrumDiv.style.height = drumHeight + "px"
}
- if(this.touch > ms - 100){
- if(!this.drumPadding){
- this.drumPadding = true
- this.touchDrumImg.style.backgroundPositionY = "7px"
+ if(this.touchAnimation){
+ if(this.touch > ms - 100){
+ if(!this.drumPadding){
+ this.drumPadding = true
+ this.touchDrumImg.style.backgroundPositionY = "7px"
+ }
+ }else if(this.drumPadding){
+ this.drumPadding = false
+ this.touchDrumImg.style.backgroundPositionY = ""
}
- }else if(this.drumPadding){
- this.drumPadding = false
- this.touchDrumImg.style.backgroundPositionY = ""
}
}
}
@@ -1601,16 +1762,16 @@
this.touchNote("ka_r")
}
}
+ this.touchEvents++
}
}
}
touchNote(note){
var keyboard = this.controller.keyboard
- var kbd = keyboard.getBindings()
var ms = this.controller.game.getAccurateTime()
this.touch = ms
- keyboard.setKey(kbd[note], false)
- keyboard.setKey(kbd[note], true, ms)
+ keyboard.setKey(false, note)
+ keyboard.setKey(true, note, ms)
}
mod(length, index){
return ((index % length) + length) % length
@@ -1631,12 +1792,17 @@
switch(pos){
case 1:
assets.sounds["se_don"].play()
- return this.controller.restartSong()
+ this.controller.restartSong()
+ pageEvents.send("pause-restart")
+ break
case 2:
assets.sounds["se_don"].play()
- return this.controller.songSelection()
+ this.controller.songSelection()
+ pageEvents.send("pause-song-select")
+ break
default:
- return this.controller.togglePause()
+ this.controller.togglePause()
+ break
}
}
onmousedown(event){
@@ -1721,6 +1887,8 @@
this.assets.clean()
this.titleCache.clean()
this.comboCache.clean()
+ this.pauseCache.clean()
+ this.branchCache.clean()
if(this.multiplayer !== 2){
if(this.touchEnabled){
diff --git a/public/src/views/about.html b/public/src/views/about.html
index 7ec8436..3506eb3 100644
--- a/public/src/views/about.html
+++ b/public/src/views/about.html
@@ -1,9 +1,9 @@
-