Commit f51574c8 authored by nanahira's avatar nanahira

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

parents 7dbd2122 6c206752
......@@ -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
......
......@@ -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);
}
.song-stage-2{
background-image: url("bg_stage_2.png");
background-size: calc(100vh / 720 * 254);
.settings-outer{
background-image: url("bg_settings.png");
}
.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");
}
......@@ -66,8 +66,5 @@
"",
"logo5":
"",
"globe":
""
}
......@@ -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;
}
......@@ -94,3 +94,6 @@
z-index: 2;
transition: 1s background-color linear;
}
.fix-animations *{
animation: none !important;
}
......@@ -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);
}
......
......@@ -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{
......
......@@ -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{
......
......@@ -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;
......
.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;
}
......@@ -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, "<br>\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
......
......@@ -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": [],
......
......@@ -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
......
......@@ -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
......
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
......@@ -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
......
......@@ -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
......
This diff is collapsed.
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")
}
}
}
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
}
}
......@@ -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)
......
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]
}
}
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)
}
})
this.btn = {}
this.update()
pageEvents.keyAdd(this, "all", "both", this.keyEvent.bind(this))
pageEvents.blurAdd(this, this.blurEvent.bind(this))
}
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
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
}
}
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)
}
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
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[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")
if(key in this.kbd){
this.callback(pressed, this.kbd[key], event)
}else if(this.wildcard){
this.callback(pressed, this.kbd["wildcard"], event)
}
}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]
}
}
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
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)
}
}
}
}
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
}
}
......@@ -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
......
......@@ -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){
snd.musicGain.load(songObj.music, true).then(sound => {
}else if(!songObj.music){
snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.mp3").then(sound => {
songObj.sound = sound
resolve()
}, reject)
}else{
snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.ogg").then(sound => {
}else if(songObj.music !== "muted"){
snd.musicGain.load(songObj.music, true).then(sound => {
songObj.sound = sound
resolve()
}, reject)
}else{
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")
......
......@@ -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: "",
......
......@@ -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)
})
......@@ -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)
}
}
......@@ -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)){
......
......@@ -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])
}
}
}
}
......@@ -181,7 +181,8 @@ class ParseOsu{
measures.push({
ms: ms,
originalMS: ms,
speed: speed
speed: speed,
visible: true
})
}
}
......
This diff is collapsed.
......@@ -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")
......
......@@ -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
......
This diff is collapsed.
This diff is collapsed.
......@@ -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)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<div id="tutorial-outer">
<div id="tutorial">
<div id="tutorial-title" class="stroke-sub"></div>
<div id="tutorial-content">
<div class="view-outer">
<div class="view">
<div class="view-title stroke-sub"></div>
<div class="view-content">
<div id="session-invite"></div>
</div>
<div id="tutorial-end-button" class="taibtn stroke-sub"></div>
<div class="view-end-button taibtn stroke-sub"></div>
</div>
</div>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment