191 lines
7.3 KiB
GDScript3
191 lines
7.3 KiB
GDScript3
|
extends Node
|
||
|
# RhythmGameText formats
|
||
|
# .rgts - simplified format cutting out redundant data, should be easy to write charts in
|
||
|
# .rgtx - a lossless representation of MM in-memory format
|
||
|
# .rgtm - a collection of rgts charts, with a [title] at the start of each one
|
||
|
enum Format{RGTS, RGTX, RGTM}
|
||
|
|
||
|
const EXTENSIONS = {
|
||
|
'rgts': Format.RGTS,
|
||
|
'rgtx': Format.RGTX,
|
||
|
'rgtm': Format.RGTM,
|
||
|
}
|
||
|
|
||
|
const NOTE_TYPES = {
|
||
|
't': Note.NOTE_TAP,
|
||
|
'h': Note.NOTE_HOLD,
|
||
|
's': Note.NOTE_STAR,
|
||
|
'e': Note.NOTE_SLIDE,
|
||
|
'b': Note.NOTE_TAP, # Break
|
||
|
'x': Note.NOTE_STAR # Break star
|
||
|
}
|
||
|
|
||
|
const SLIDE_TYPES = {
|
||
|
'0': null, # Seems to be used for stars without slides attached
|
||
|
'1': Note.SlideType.CHORD,
|
||
|
'2': Note.SlideType.ARC_ACW,
|
||
|
'3': Note.SlideType.ARC_CW,
|
||
|
'4': Note.SlideType.COMPLEX, # Orbit around center ACW on the way
|
||
|
'5': Note.SlideType.COMPLEX, # CW of above
|
||
|
'6': Note.SlideType.COMPLEX, # S zigzag through center
|
||
|
'7': Note.SlideType.COMPLEX, # Z zigzag through center
|
||
|
'8': Note.SlideType.COMPLEX, # V into center
|
||
|
'9': Note.SlideType.COMPLEX, # Go to center then orbit off to the side ACW
|
||
|
'a': Note.SlideType.COMPLEX, # CW of above
|
||
|
'b': Note.SlideType.COMPLEX, # V into column 2 places ACW
|
||
|
'c': Note.SlideType.COMPLEX, # V into column 2 places CW
|
||
|
'd': Note.SlideType.CHORD_TRIPLE, # Triple cone. Spreads out to the adjacent receptors of the target.
|
||
|
'e': Note.SlideType.CHORD, # Not used in any of our charts
|
||
|
'f': Note.SlideType.CHORD, # Not used in any of our charts
|
||
|
}
|
||
|
const SLIDE_IN_R := sin(PI/8) # Circle radius circumscribed by chords 0-3, 1-4, 2-5 etc.
|
||
|
|
||
|
|
||
|
static func load_file(filename: String):
|
||
|
var extension = filename.rsplit('.', false, 1)[1]
|
||
|
if not EXTENSIONS.has(extension):
|
||
|
return -1
|
||
|
var format = EXTENSIONS[extension]
|
||
|
var file := File.new()
|
||
|
var err := file.open(filename, File.READ)
|
||
|
if err != OK:
|
||
|
print(err)
|
||
|
return err
|
||
|
var length = file.get_len()
|
||
|
var chart_ids = []
|
||
|
var lines = [[]]
|
||
|
# This loop will segment the lines as if the file were RGTM
|
||
|
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
||
|
var line : String = file.get_line()
|
||
|
if line.begins_with('['): # Split to a new list for each chart definition
|
||
|
chart_ids.append(line.lstrip('[').rstrip(']'))
|
||
|
lines.append([])
|
||
|
elif !line.empty():
|
||
|
lines[-1].push_back(line)
|
||
|
file.close()
|
||
|
print('Parsing chart: ', filename)
|
||
|
|
||
|
match format:
|
||
|
Format.RGTS:
|
||
|
var metadata_and_notes = parse_rgts(lines[0])
|
||
|
return metadata_and_notes
|
||
|
Format.RGTX:
|
||
|
var metadata_and_notes = parse_rgtx(lines[0])
|
||
|
return metadata_and_notes
|
||
|
Format.RGTM:
|
||
|
lines.pop_front() # Anything before the first [header] is meaningless
|
||
|
var charts = {}
|
||
|
for i in len(lines):
|
||
|
charts[chart_ids[i]] = parse_rgts(lines[i])
|
||
|
return charts
|
||
|
return format
|
||
|
|
||
|
|
||
|
static func parse_rgtx(lines: PoolStringArray):
|
||
|
return [] # To be implemented later
|
||
|
|
||
|
|
||
|
const beats_per_measure = 4.0 # TODO: Bit of an ugly hack, need to revisit this later
|
||
|
static func parse_rgts(lines: PoolStringArray):
|
||
|
var metadata := {}
|
||
|
var num_taps := 0
|
||
|
var num_holds := 0
|
||
|
var num_slides := 0
|
||
|
var notes := []
|
||
|
var slide_ids := {}
|
||
|
var slide_stars := {} # Multiple stars might link to one star. We only care about linking for the spin speed.
|
||
|
var last_star := []
|
||
|
for i in Rules.COLS:
|
||
|
last_star.append(null)
|
||
|
|
||
|
for line in lines:
|
||
|
if len(line) < 4: # shortest legal line would be like '1:1t'
|
||
|
continue
|
||
|
var s = line.split(':')
|
||
|
var time := float(s[0]) * beats_per_measure
|
||
|
var note_hits := []
|
||
|
var note_nonhits := []
|
||
|
for i in range(1, len(s)):
|
||
|
var n = s[i]
|
||
|
var column := int(n[0])
|
||
|
var ntype = n[1]
|
||
|
n = n.substr(2)
|
||
|
|
||
|
match ntype:
|
||
|
't', 'b': # tap
|
||
|
note_hits.append(Note.NoteTap.new(time, column, ntype=='b'))
|
||
|
num_taps += 1
|
||
|
'h': # hold
|
||
|
var duration = float(n) * beats_per_measure
|
||
|
note_hits.append(Note.NoteHold.new(time, column, duration))
|
||
|
num_holds += 1
|
||
|
's', 'x': # slide star
|
||
|
var star = Note.NoteStar.new(time, column, ntype=='z')
|
||
|
note_hits.append(star)
|
||
|
num_slides += 1
|
||
|
last_star[column] = star
|
||
|
if len(n) > 1: # Not all stars have proper slide info
|
||
|
var slide_type = n[0] # hex digit
|
||
|
var slide_id = int(n.substr(1))
|
||
|
if slide_id > 0:
|
||
|
slide_stars[slide_id] = star
|
||
|
var slide = Note.NoteSlide.new(time, column)
|
||
|
slide_ids[slide_id] = slide
|
||
|
note_nonhits.append(slide)
|
||
|
'e': # slide end
|
||
|
var slide_type = n[0] # numeric digit, left as str just in case
|
||
|
var slide_id = int(n.substr(1))
|
||
|
if slide_id in slide_ids: # Classic slide end
|
||
|
slide_ids[slide_id].time_release = time
|
||
|
if slide_id in slide_stars:
|
||
|
slide_stars[slide_id].duration = slide_ids[slide_id].duration # Should probably recalc in case start time is different but w/e
|
||
|
slide_ids[slide_id].column_release = column
|
||
|
slide_ids[slide_id].slide_type = SLIDE_TYPES[slide_type]
|
||
|
slide_ids[slide_id].update_slide_variables()
|
||
|
if SLIDE_TYPES[slide_type] == Note.SlideType.COMPLEX:
|
||
|
var col_hit = slide_ids[slide_id].column
|
||
|
var RUV = GameTheme.RADIAL_UNIT_VECTORS
|
||
|
var RCA = GameTheme.RADIAL_COL_ANGLES
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[col_hit]) # Start col
|
||
|
match slide_type:
|
||
|
'4': # Orbit ACW around center. Size of loop is roughly inscribed in chords of 0-3, 1-4, 2-5... NB: doesn't loop if directly opposite col
|
||
|
Note.curve2d_make_orbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], true)
|
||
|
'5': # CW of above
|
||
|
Note.curve2d_make_orbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], false)
|
||
|
'6': # S zigzag through center
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)] * SLIDE_IN_R)
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)] * SLIDE_IN_R)
|
||
|
'7': # Z zigzag through center
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)] * SLIDE_IN_R)
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)] * SLIDE_IN_R)
|
||
|
'8': # V into center
|
||
|
slide_ids[slide_id].values.curve2d.add_point(Vector2.ZERO)
|
||
|
'9': # Orbit off-center ACW
|
||
|
Note.curve2d_make_sideorbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], true)
|
||
|
'a': # CW of above
|
||
|
Note.curve2d_make_sideorbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], false)
|
||
|
'b': # V into column 2 places ACW
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)])
|
||
|
'c': # V into column 2 places CW
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)])
|
||
|
slide_ids[slide_id].values.curve2d.add_point(RUV[column]) # End col
|
||
|
else: # Naked slide start
|
||
|
if last_star[column] != null:
|
||
|
slide_stars[slide_id] = last_star[column]
|
||
|
else:
|
||
|
print_debug('Naked slide with no prior star in column!')
|
||
|
var note = Note.NoteSlide.new(time, column)
|
||
|
slide_ids[slide_id] = note
|
||
|
note_nonhits.append(note)
|
||
|
'_':
|
||
|
print_debug('Unknown note type: ', ntype)
|
||
|
|
||
|
if len(note_hits) > 1:
|
||
|
for note in note_hits: # Set multihit on each one
|
||
|
note.double_hit = true
|
||
|
notes += note_hits + note_nonhits
|
||
|
metadata['num_taps'] = num_taps
|
||
|
metadata['num_holds'] = num_holds
|
||
|
metadata['num_slides'] = num_slides
|
||
|
return [metadata, notes]
|