2018-10-11 00:13:24 +02:00
|
|
|
class ParseOsu{
|
2019-11-04 15:20:44 +01:00
|
|
|
constructor(fileContent, difficulty, stars, offset, metaOnly){
|
2018-09-15 16:34:53 +02:00
|
|
|
this.osu = {
|
|
|
|
OFFSET: 0,
|
|
|
|
MSPERBEAT: 1,
|
|
|
|
METER: 2,
|
|
|
|
SAMPLESET: 3,
|
|
|
|
SAMPLEINDEX: 4,
|
|
|
|
VOLUME: 5,
|
|
|
|
INHERITED: 6,
|
|
|
|
KIAIMODE: 7,
|
|
|
|
|
|
|
|
X: 0,
|
|
|
|
Y: 1,
|
|
|
|
TIME: 2,
|
|
|
|
TYPE: 3,
|
|
|
|
HITSOUND: 4,
|
|
|
|
EXTRAS: 5,
|
|
|
|
ENDTIME: 5,
|
|
|
|
|
|
|
|
CIRCLE: 1,
|
|
|
|
SLIDER: 2,
|
|
|
|
NEWCOMBO: 4,
|
|
|
|
SPINNER: 8,
|
|
|
|
|
|
|
|
NORMAL: 1,
|
|
|
|
WHISTLE: 2,
|
|
|
|
FINISH: 4,
|
|
|
|
CLAP: 8,
|
|
|
|
|
|
|
|
CURVEPOINTS: 0,
|
|
|
|
REPEAT: 1,
|
|
|
|
PIXELLENGTH: 2,
|
|
|
|
EDGEHITSOUNDS: 3,
|
|
|
|
EDGEADDITIONS: 4
|
|
|
|
}
|
2018-09-27 20:39:25 +02:00
|
|
|
this.data = []
|
|
|
|
for(let line of fileContent){
|
2018-10-11 00:13:24 +02:00
|
|
|
line = line.replace(/\/\/.*/, "").trim()
|
2018-09-27 20:39:25 +02:00
|
|
|
if(line !== ""){
|
|
|
|
this.data.push(line)
|
|
|
|
}
|
|
|
|
}
|
2018-10-11 00:13:24 +02:00
|
|
|
this.offset = (offset || 0) * -1000
|
|
|
|
this.soundOffset = 0
|
2018-09-15 16:34:53 +02:00
|
|
|
this.beatInfo = {
|
|
|
|
beatInterval: 0,
|
2018-09-27 20:39:25 +02:00
|
|
|
lastBeatInterval: 0,
|
2018-09-15 16:34:53 +02:00
|
|
|
bpm: 0
|
|
|
|
}
|
2020-03-15 16:00:23 +01:00
|
|
|
this.events = []
|
2018-09-15 16:34:53 +02:00
|
|
|
this.generalInfo = this.parseGeneralInfo()
|
|
|
|
this.metadata = this.parseMetadata()
|
|
|
|
this.editor = this.parseEditor()
|
|
|
|
this.difficulty = this.parseDifficulty()
|
2019-10-31 17:10:53 +01:00
|
|
|
this._difficulty = difficulty;
|
2019-11-04 15:20:44 +01:00
|
|
|
this.stars = stars
|
2018-12-05 21:33:34 +01:00
|
|
|
if(!metaOnly){
|
|
|
|
this.timingPoints = this.parseTiming()
|
|
|
|
this.circles = this.parseCircles()
|
|
|
|
this.measures = this.parseMeasures()
|
|
|
|
}
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
getStartEndIndexes(type){
|
|
|
|
var indexes = {
|
|
|
|
start: 0,
|
|
|
|
end: 0
|
|
|
|
}
|
|
|
|
while(indexes.start < this.data.length){
|
2018-09-27 20:39:25 +02:00
|
|
|
if(this.data[indexes.start] === "[" + type + "]"){
|
2018-09-15 16:34:53 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
indexes.start++
|
|
|
|
}
|
|
|
|
indexes.start++
|
|
|
|
indexes.end = indexes.start
|
|
|
|
while(indexes.end < this.data.length){
|
2018-09-27 20:39:25 +02:00
|
|
|
if(this.data[indexes.end].match(/^\[\w+\]$/)){
|
2018-09-15 16:34:53 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
indexes.end++
|
|
|
|
}
|
|
|
|
indexes.end--
|
|
|
|
return indexes
|
|
|
|
}
|
|
|
|
parseDifficulty(){
|
|
|
|
var difficulty = {
|
|
|
|
sliderMultiplier: 0,
|
|
|
|
sliderTickRate: 0,
|
|
|
|
approachRate: 0
|
|
|
|
}
|
|
|
|
var indexes = this.getStartEndIndexes("Difficulty")
|
|
|
|
for(var i = indexes.start; i <= indexes.end; i++){
|
|
|
|
var [item, key] = this.data[i].split(":")
|
|
|
|
switch(item){
|
|
|
|
case "SliderMultiplier":
|
|
|
|
difficulty.sliderMultiplier = key
|
|
|
|
difficulty.originalMultiplier = key
|
|
|
|
break
|
|
|
|
case "SliderTickRate":
|
|
|
|
difficulty.sliderTickRate = key
|
|
|
|
break
|
|
|
|
case "ApproachRate":
|
|
|
|
difficulty.approachRate = key
|
|
|
|
break
|
|
|
|
case "OverallDifficulty":
|
|
|
|
difficulty.overallDifficulty = key
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return difficulty
|
|
|
|
}
|
|
|
|
parseTiming(){
|
|
|
|
var timingPoints = []
|
|
|
|
var indexes = this.getStartEndIndexes("TimingPoints")
|
|
|
|
var lastBeatInterval = parseInt(this.data[indexes.start].split(",")[1])
|
2018-10-27 20:35:04 +02:00
|
|
|
for(var i = indexes.start; i <= indexes.end; i++){
|
2018-09-15 16:34:53 +02:00
|
|
|
var values = this.data[i].split(",")
|
|
|
|
var start = parseInt(values[this.osu.OFFSET])
|
|
|
|
var msOrPercent = parseFloat(values[this.osu.MSPERBEAT])
|
|
|
|
if(i == indexes.start){
|
|
|
|
this.beatInfo.beatInterval = msOrPercent
|
|
|
|
this.beatInfo.bpm = Math.floor(1000 / this.beatInfo.beatInterval * 60)
|
|
|
|
}
|
2018-10-27 20:35:04 +02:00
|
|
|
var beatReset = false
|
2018-09-15 16:34:53 +02:00
|
|
|
if(msOrPercent < 0){
|
|
|
|
var sliderMultiplier = this.difficulty.lastMultiplier / Math.abs(msOrPercent / 100)
|
|
|
|
}else{
|
2018-09-18 15:59:40 +02:00
|
|
|
var sliderMultiplier = 1000 / msOrPercent
|
2018-09-15 16:34:53 +02:00
|
|
|
if(i == 0){
|
|
|
|
this.difficulty.originalMultiplier = sliderMultiplier
|
|
|
|
}
|
|
|
|
this.difficulty.lastMultiplier = sliderMultiplier
|
2018-10-27 20:35:04 +02:00
|
|
|
beatReset = true
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
timingPoints.push({
|
2018-10-11 00:13:24 +02:00
|
|
|
start: start + this.offset,
|
2018-09-15 16:34:53 +02:00
|
|
|
sliderMultiplier: sliderMultiplier,
|
2018-09-20 01:20:26 +02:00
|
|
|
measure: parseInt(values[this.osu.METER]),
|
2018-10-11 22:24:18 +02:00
|
|
|
gogoTime: parseInt(values[this.osu.KIAIMODE]),
|
2018-10-27 20:35:04 +02:00
|
|
|
beatMS: 1000 / this.difficulty.lastMultiplier,
|
|
|
|
beatReset: beatReset
|
2018-09-15 16:34:53 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return timingPoints
|
|
|
|
}
|
|
|
|
parseMeasures(){
|
|
|
|
var measures = []
|
2018-10-27 20:35:04 +02:00
|
|
|
|
|
|
|
for(var i = 0; i < this.timingPoints.length; i++){
|
|
|
|
var currentTiming = this.timingPoints[i]
|
|
|
|
var firstTiming = i === 0
|
|
|
|
|
|
|
|
var limit = this.circles[this.circles.length - 1].endTime + currentTiming.beatMS
|
|
|
|
|
|
|
|
for(var j = i + 1; j < this.timingPoints.length; j++){
|
|
|
|
var nextTiming = this.timingPoints[j]
|
|
|
|
var newLimit = nextTiming.start
|
|
|
|
if(nextTiming.measure !== currentTiming.measure || nextTiming.beatReset){
|
|
|
|
limit = newLimit - currentTiming.beatMS
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i = j
|
|
|
|
}
|
|
|
|
|
|
|
|
var start = currentTiming.start
|
|
|
|
var interval = currentTiming.beatMS * currentTiming.measure
|
|
|
|
if(firstTiming){
|
|
|
|
while(start >= interval){
|
|
|
|
start -= interval
|
|
|
|
}
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
2018-10-27 20:35:04 +02:00
|
|
|
for(var ms = start; ms <= limit; ms += interval){
|
|
|
|
|
|
|
|
var speed = currentTiming.sliderMultiplier
|
|
|
|
for(var j = 0; j < this.timingPoints.length; j++){
|
|
|
|
var timingPoint = this.timingPoints[j]
|
|
|
|
if(j !== 0 && timingPoint.start - this.offset > ms){
|
|
|
|
break
|
|
|
|
}
|
|
|
|
speed = timingPoint.sliderMultiplier
|
2015-07-17 10:22:46 +02:00
|
|
|
}
|
2018-10-27 20:35:04 +02:00
|
|
|
|
|
|
|
measures.push({
|
|
|
|
ms: ms,
|
|
|
|
originalMS: ms,
|
2019-02-24 13:04:14 +01:00
|
|
|
speed: speed,
|
|
|
|
visible: true
|
2018-10-27 20:35:04 +02:00
|
|
|
})
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return measures
|
|
|
|
}
|
|
|
|
parseGeneralInfo(){
|
2018-12-05 21:33:34 +01:00
|
|
|
var generalInfo = {}
|
2018-09-15 16:34:53 +02:00
|
|
|
var indexes = this.getStartEndIndexes("General")
|
|
|
|
for(var i = indexes.start; i<= indexes.end; i++){
|
|
|
|
var [item, key] = this.data[i].split(":")
|
2018-12-05 21:33:34 +01:00
|
|
|
generalInfo[item] = key.trim()
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
return generalInfo
|
|
|
|
}
|
|
|
|
parseMetadata(){
|
2018-12-05 21:33:34 +01:00
|
|
|
var metadata = {}
|
2018-09-15 16:34:53 +02:00
|
|
|
var indexes = this.getStartEndIndexes("Metadata")
|
|
|
|
for(var i = indexes.start; i <= indexes.end; i++){
|
|
|
|
var [item, key] = this.data[i].split(":")
|
2018-12-05 21:33:34 +01:00
|
|
|
metadata[item] = key.trim()
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
return metadata
|
|
|
|
}
|
|
|
|
parseEditor(){
|
|
|
|
var editor = {
|
|
|
|
distanceSpacing: 0,
|
|
|
|
beatDivisor: 0,
|
|
|
|
gridSize: 0
|
|
|
|
}
|
|
|
|
var indexes = this.getStartEndIndexes("Editor")
|
|
|
|
for(var i = indexes.start; i <= indexes.end; i++){
|
|
|
|
var [item, key] = this.data[i].split(":")
|
|
|
|
switch(item){
|
|
|
|
case "DistanceSpacing":
|
|
|
|
editor.distanceSpacing = parseFloat(key)
|
|
|
|
break
|
|
|
|
case "BeatDivisor":
|
|
|
|
editor.beatDivisor = parseInt(key)
|
|
|
|
break
|
|
|
|
case "GridSize":
|
|
|
|
editor.gridSize = parseInt(key)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return editor
|
|
|
|
}
|
|
|
|
difficultyRange(difficulty, min, mid, max){
|
|
|
|
if(difficulty > 5){
|
|
|
|
return mid + (max - mid) * (difficulty - 5) / 5
|
|
|
|
}
|
|
|
|
if(difficulty < 5){
|
|
|
|
return mid - (mid - min) * (5 - difficulty) / 5
|
|
|
|
}
|
|
|
|
return mid
|
|
|
|
}
|
|
|
|
parseCircles(){
|
|
|
|
var circles = []
|
|
|
|
var circleID = 0
|
|
|
|
var indexes = this.getStartEndIndexes("HitObjects")
|
2020-03-15 16:00:23 +01:00
|
|
|
var lastBeatMS = this.beatInfo.beatInterval
|
|
|
|
var lastGogo = false
|
|
|
|
|
|
|
|
var pushCircle = circle => {
|
|
|
|
circles.push(circle)
|
|
|
|
if(lastBeatMS !== circle.beatMS || lastGogo !== circle.gogoTime){
|
|
|
|
lastBeatMS = circle.beatMS
|
|
|
|
lastGogo = circle.gogoTime
|
|
|
|
this.events.push(circle)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-15 16:34:53 +02:00
|
|
|
for(var i = indexes.start; i <= indexes.end; i++){
|
|
|
|
circleID++
|
|
|
|
var values = this.data[i].split(",")
|
|
|
|
var emptyValue = false
|
|
|
|
var start = parseInt(values[this.osu.TIME])
|
|
|
|
var speed = this.difficulty.originalMultiplier
|
2018-09-20 01:20:26 +02:00
|
|
|
var gogoTime = false
|
2018-09-15 16:34:53 +02:00
|
|
|
var osuType = parseInt(values[this.osu.TYPE])
|
|
|
|
var hitSound = parseInt(values[this.osu.HITSOUND])
|
2018-09-27 17:31:57 +02:00
|
|
|
var beatLength = speed
|
2018-09-27 20:39:25 +02:00
|
|
|
var lastMultiplier = this.difficulty.lastMultiplier
|
2018-10-11 22:24:18 +02:00
|
|
|
var beatMS = this.beatInfo.beatInterval
|
2018-10-11 00:13:24 +02:00
|
|
|
if(circleID === 1 && start + this.offset < 0){
|
|
|
|
var offset = start + this.offset
|
|
|
|
this.soundOffset = offset
|
|
|
|
this.offset -= offset
|
|
|
|
}
|
2018-09-15 16:34:53 +02:00
|
|
|
|
|
|
|
for(var j = 0; j < this.timingPoints.length; j++){
|
2018-10-11 22:24:18 +02:00
|
|
|
var timingPoint = this.timingPoints[j]
|
2018-10-27 20:35:04 +02:00
|
|
|
if(j !== 0 && timingPoint.start - this.offset > start){
|
2018-09-15 16:34:53 +02:00
|
|
|
break
|
|
|
|
}
|
2018-10-11 22:24:18 +02:00
|
|
|
speed = timingPoint.sliderMultiplier
|
|
|
|
gogoTime = timingPoint.gogoTime
|
|
|
|
beatMS = timingPoint.beatMS
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if(osuType & this.osu.SPINNER){
|
|
|
|
|
|
|
|
var endTime = parseInt(values[this.osu.ENDTIME])
|
|
|
|
var hitMultiplier = this.difficultyRange(this.difficulty.overallDifficulty, 3, 5, 7.5) * 1.65
|
|
|
|
var requiredHits = Math.floor(Math.max(1, (endTime - start) / 1000 * hitMultiplier))
|
2020-03-15 16:00:23 +01:00
|
|
|
pushCircle(new Circle({
|
2018-09-20 01:20:26 +02:00
|
|
|
id: circleID,
|
2018-10-11 00:13:24 +02:00
|
|
|
start: start + this.offset,
|
2018-09-20 01:20:26 +02:00
|
|
|
type: "balloon",
|
2019-01-05 08:44:28 +01:00
|
|
|
txt: strings.note.balloon,
|
2018-09-20 01:20:26 +02:00
|
|
|
speed: speed,
|
2018-10-11 00:13:24 +02:00
|
|
|
endTime: endTime + this.offset,
|
2018-09-20 01:20:26 +02:00
|
|
|
requiredHits: requiredHits,
|
2018-10-11 22:24:18 +02:00
|
|
|
gogoTime: gogoTime,
|
|
|
|
beatMS: beatMS
|
2018-09-20 01:20:26 +02:00
|
|
|
}))
|
2018-09-15 16:34:53 +02:00
|
|
|
|
|
|
|
}else if(osuType & this.osu.SLIDER){
|
|
|
|
|
|
|
|
var extras = values.slice(this.osu.EXTRAS)
|
2018-09-27 17:31:57 +02:00
|
|
|
|
2019-01-16 13:33:42 +01:00
|
|
|
var distance = parseFloat(extras[this.osu.PIXELLENGTH]) * parseFloat(extras[this.osu.REPEAT])
|
2018-09-27 20:39:25 +02:00
|
|
|
var velocity = this.difficulty.sliderMultiplier * speed / 10
|
|
|
|
var endTime = start + distance / velocity
|
2018-09-27 17:31:57 +02:00
|
|
|
|
2018-09-15 16:34:53 +02:00
|
|
|
if(hitSound & this.osu.FINISH){
|
|
|
|
type = "daiDrumroll"
|
2019-01-05 08:44:28 +01:00
|
|
|
txt = strings.note.daiDrumroll
|
2018-09-15 16:34:53 +02:00
|
|
|
}else{
|
|
|
|
type = "drumroll"
|
2019-01-05 08:44:28 +01:00
|
|
|
txt = strings.note.drumroll
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
2020-03-15 16:00:23 +01:00
|
|
|
pushCircle(new Circle({
|
2018-09-20 01:20:26 +02:00
|
|
|
id: circleID,
|
2018-10-11 00:13:24 +02:00
|
|
|
start: start + this.offset,
|
2018-09-20 01:20:26 +02:00
|
|
|
type: type,
|
|
|
|
txt: txt,
|
|
|
|
speed: speed,
|
2018-10-11 00:13:24 +02:00
|
|
|
endTime: endTime + this.offset,
|
2018-10-11 22:24:18 +02:00
|
|
|
gogoTime: gogoTime,
|
|
|
|
beatMS: beatMS
|
2018-09-20 01:20:26 +02:00
|
|
|
}))
|
2018-09-15 16:34:53 +02:00
|
|
|
|
|
|
|
}else if(osuType & this.osu.CIRCLE){
|
|
|
|
var type
|
|
|
|
var txt
|
2018-09-18 15:59:40 +02:00
|
|
|
|
2018-09-15 16:34:53 +02:00
|
|
|
if(hitSound & this.osu.FINISH){
|
|
|
|
if(hitSound & this.osu.WHISTLE || hitSound & this.osu.CLAP){
|
|
|
|
type = "daiKa"
|
2019-01-21 16:47:22 +01:00
|
|
|
txt = strings.note.daiKa
|
2018-09-18 15:59:40 +02:00
|
|
|
}else if(hitSound & this.osu.NORMAL || hitSound === this.osu.FINISH){
|
2018-09-15 16:34:53 +02:00
|
|
|
type = "daiDon"
|
2019-01-21 16:47:22 +01:00
|
|
|
txt = strings.note.daiDon
|
2018-09-15 16:34:53 +02:00
|
|
|
}else{
|
|
|
|
emptyValue = true
|
|
|
|
}
|
|
|
|
}else if(hitSound & this.osu.WHISTLE || hitSound & this.osu.CLAP){
|
|
|
|
type = "ka"
|
2019-01-05 08:44:28 +01:00
|
|
|
txt = strings.note.ka
|
2018-09-18 15:59:40 +02:00
|
|
|
}else if(hitSound & this.osu.NORMAL || hitSound === 0){
|
2018-09-15 16:34:53 +02:00
|
|
|
type = "don"
|
2019-01-05 08:44:28 +01:00
|
|
|
txt = strings.note.don
|
2018-09-15 16:34:53 +02:00
|
|
|
}else{
|
|
|
|
emptyValue = true
|
|
|
|
}
|
|
|
|
if(!emptyValue){
|
2020-03-15 16:00:23 +01:00
|
|
|
pushCircle(new Circle({
|
2018-09-20 01:20:26 +02:00
|
|
|
id: circleID,
|
2018-10-11 00:13:24 +02:00
|
|
|
start: start + this.offset,
|
2018-09-20 01:20:26 +02:00
|
|
|
type: type,
|
|
|
|
txt: txt,
|
|
|
|
speed: speed,
|
2018-10-11 22:24:18 +02:00
|
|
|
gogoTime: gogoTime,
|
|
|
|
beatMS: beatMS
|
2018-09-20 01:20:26 +02:00
|
|
|
}))
|
2018-09-15 16:34:53 +02:00
|
|
|
}
|
|
|
|
}else{
|
|
|
|
emptyValue = true
|
|
|
|
}
|
|
|
|
if(emptyValue){
|
|
|
|
console.warn("Unknown note type found on line " + (i + 1) + ": " + this.data[i])
|
|
|
|
}
|
|
|
|
}
|
2019-10-31 17:10:53 +01:00
|
|
|
this.scoremode = 2;
|
2019-11-04 15:20:44 +01:00
|
|
|
var autoscore = new AutoScore(this._difficulty, this.stars, 2, circles);
|
2019-10-31 17:10:53 +01:00
|
|
|
this.scoreinit = autoscore.ScoreInit;
|
|
|
|
this.scorediff = autoscore.ScoreDiff;
|
2018-09-15 16:34:53 +02:00
|
|
|
return circles
|
|
|
|
}
|
|
|
|
}
|