Refactoring in preparation for Step stuff
This commit is contained in:
parent
eba538dfc1
commit
ab848312e2
|
@ -0,0 +1,190 @@
|
||||||
|
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]
|
|
@ -0,0 +1,167 @@
|
||||||
|
extends Node
|
||||||
|
# Stepmania simfile
|
||||||
|
|
||||||
|
|
||||||
|
const NOTE_VALUES = {
|
||||||
|
'0': 'None',
|
||||||
|
'1': 'Tap',
|
||||||
|
'2': 'HoldStart',
|
||||||
|
'3': 'HoldRollEnd',
|
||||||
|
'4': 'RollStart',
|
||||||
|
'M': 'Mine',
|
||||||
|
# These three are less likely to show up anywhere, no need to implement
|
||||||
|
'K': 'Keysound',
|
||||||
|
'L': 'Lift',
|
||||||
|
'F': 'Fake',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const CHART_DIFFICULTIES = {
|
||||||
|
'Beginner': 0,
|
||||||
|
'Easy': 1,
|
||||||
|
'Medium': 2,
|
||||||
|
'Hard': 3,
|
||||||
|
'Challenge': 4,
|
||||||
|
'Edit': 5,
|
||||||
|
# Some will just write whatever for special difficulties, but we should at least color-code these standard ones
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const TAG_TRANSLATIONS = {
|
||||||
|
'#TITLE': 'title',
|
||||||
|
'#SUBTITLE': 'subtitle',
|
||||||
|
'#ARTIST': 'artist',
|
||||||
|
'#TITLETRANSLIT': 'title_transliteration',
|
||||||
|
'#SUBTITLETRANSLIT': 'subtitle_transliteration',
|
||||||
|
'#ARTISTTRANSLIT': 'artist_transliteration',
|
||||||
|
'#GENRE': 'genre',
|
||||||
|
'#CREDIT': 'chart_author',
|
||||||
|
'#BANNER': 'image_banner',
|
||||||
|
'#BACKGROUND': 'image_background',
|
||||||
|
# '#LYRICSPATH': '',
|
||||||
|
'#CDTITLE': 'image_cd_title',
|
||||||
|
'#MUSIC': 'audio_filelist',
|
||||||
|
'#OFFSET': 'audio_offsets',
|
||||||
|
'#SAMPLESTART': 'audio_preview_times',
|
||||||
|
'#SAMPLELENGTH': 'audio_preview_times',
|
||||||
|
# '#SELECTABLE': '',
|
||||||
|
'#BPMS': 'bpm_values',
|
||||||
|
# '#STOPS': '',
|
||||||
|
# '#BGCHANGES': '',
|
||||||
|
# '#KEYSOUNDS': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static func load_chart(lines):
|
||||||
|
var metadata = {}
|
||||||
|
var notes = []
|
||||||
|
|
||||||
|
assert(lines[0].begins_with('#NOTES:'))
|
||||||
|
metadata['chart_type'] = lines[1].strip_edges().rstrip(':')
|
||||||
|
metadata['description'] = lines[2].strip_edges().rstrip(':')
|
||||||
|
metadata['difficulty_str'] = lines[3].strip_edges().rstrip(':')
|
||||||
|
metadata['numerical_meter'] = lines[4].strip_edges().rstrip(':')
|
||||||
|
metadata['groove_radar'] = lines[5].strip_edges().rstrip(':')
|
||||||
|
|
||||||
|
# Measures are separated by lines that start with a comma
|
||||||
|
# Each line has a state for each of the pads, e.g. '0000' for none pressed
|
||||||
|
# The lines become even subdivisions of the measure, so if there's 4 lines everything represents a 1/4 beat, if there's 8 lines everything represents a 1/8 beat etc.
|
||||||
|
# For this reason it's probably best to just have a float for beat-within-measure rather than integer beats.
|
||||||
|
var measures = [[]]
|
||||||
|
for i in range(6, len(lines)):
|
||||||
|
var line = lines[i].strip_edges()
|
||||||
|
if line.begins_with(','):
|
||||||
|
measures.append([])
|
||||||
|
elif line.begins_with(';'):
|
||||||
|
break
|
||||||
|
elif len(line) > 0:
|
||||||
|
measures[-1].append(line)
|
||||||
|
|
||||||
|
var ongoing_holds = {}
|
||||||
|
var num_notes := 0
|
||||||
|
var num_jumps := 0
|
||||||
|
var num_hands := 0
|
||||||
|
var num_holds := 0
|
||||||
|
var num_rolls := 0
|
||||||
|
var num_mines := 0
|
||||||
|
|
||||||
|
for measure in range(len(measures)):
|
||||||
|
var m_lines = measures[measure]
|
||||||
|
var m_length = len(m_lines) # Divide out all lines by this
|
||||||
|
for beat in m_length:
|
||||||
|
var line : String = m_lines[beat]
|
||||||
|
# Jump check at a line-level (check for multiple 1/2/4s)
|
||||||
|
var hits : int = line.count('1') + line.count('2') + line.count('4')
|
||||||
|
# Hand/quad check more complex as need to check hold/roll state as well
|
||||||
|
# TODO: are they exclusive? Does quad override hand override jump? SM5 doesn't have quads and has hands+jumps inclusive
|
||||||
|
var total_pressed : int = hits + len(ongoing_holds)
|
||||||
|
var jump : bool = hits >= 2
|
||||||
|
var hand : bool = total_pressed >= 3
|
||||||
|
# var quad : bool = total_pressed >= 4
|
||||||
|
num_notes += hits
|
||||||
|
num_jumps += int(jump)
|
||||||
|
num_hands += int(hand)
|
||||||
|
var time = measure + beat/float(m_length)
|
||||||
|
for col in len(line):
|
||||||
|
match line[col]:
|
||||||
|
'1':
|
||||||
|
notes.append(Note.NoteTap.new(time, col))
|
||||||
|
'2': # Hold
|
||||||
|
ongoing_holds[col] = len(notes)
|
||||||
|
notes.append(Note.NoteHold.new(time, col, 0.0))
|
||||||
|
num_holds += 1
|
||||||
|
'4': # Roll
|
||||||
|
ongoing_holds[col] = len(notes)
|
||||||
|
notes.append(Note.NoteRoll.new(time, col, 0.0))
|
||||||
|
num_rolls += 1
|
||||||
|
'3': # End Hold/Roll
|
||||||
|
assert(ongoing_holds.has(col))
|
||||||
|
notes[ongoing_holds[col]].set_time_release(time)
|
||||||
|
ongoing_holds.erase(col)
|
||||||
|
'M': # Mine
|
||||||
|
num_mines += 1
|
||||||
|
pass
|
||||||
|
metadata['num_notes'] = num_notes
|
||||||
|
metadata['num_taps'] = num_notes - num_jumps
|
||||||
|
metadata['num_jumps'] = num_jumps
|
||||||
|
metadata['num_hands'] = num_hands
|
||||||
|
metadata['num_holds'] = num_holds
|
||||||
|
metadata['num_rolls'] = num_rolls
|
||||||
|
metadata['num_mines'] = num_mines
|
||||||
|
metadata['notes'] = notes
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
|
static func load_file(filename: String) -> Array:
|
||||||
|
# Output is [metadata, [[meta0, chart0], ..., [metaN, chartN]]]
|
||||||
|
# Technically, declarations end with a semicolon instead of a linebreak.
|
||||||
|
# This is a PITA to do correctly in GDScript and the files in our collection are well-behaved with linebreaks anyway, so we won't bother.
|
||||||
|
var file := File.new()
|
||||||
|
match file.open(filename, File.READ):
|
||||||
|
OK:
|
||||||
|
pass
|
||||||
|
var err:
|
||||||
|
print_debug('Error loading file: ', err)
|
||||||
|
return []
|
||||||
|
var length = file.get_len()
|
||||||
|
var lines = [[]] # First list will be header, then every subsequent one is a chart
|
||||||
|
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
||||||
|
var line : String = file.get_line()
|
||||||
|
if line.begins_with('#NOTES'): # Split to a new list for each chart definition
|
||||||
|
lines.append([])
|
||||||
|
lines[-1].append(line)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
var metadata = {}
|
||||||
|
for line in lines[0]:
|
||||||
|
var tokens = line.rstrip(';').split(':')
|
||||||
|
if TAG_TRANSLATIONS.has(tokens[0]):
|
||||||
|
metadata[TAG_TRANSLATIONS[tokens[0]]] = tokens[1]
|
||||||
|
elif len(tokens) >= 2:
|
||||||
|
metadata[tokens[0]] = tokens[1]
|
||||||
|
var charts = []
|
||||||
|
|
||||||
|
for i in range(1, len(lines)):
|
||||||
|
charts.append(load_chart(lines[i]))
|
||||||
|
|
||||||
|
return [metadata, charts]
|
|
@ -0,0 +1,73 @@
|
||||||
|
extends Node
|
||||||
|
# A legacy format that is relatively easily parsed. Radial game mode.
|
||||||
|
const TAP_DURATION := 0.062500
|
||||||
|
const ID_BREAK := 4
|
||||||
|
const ID_HOLD := 2
|
||||||
|
const ID_SLIDE_END := 128
|
||||||
|
const ID3_SLIDE_CHORD := 0 # Straight line
|
||||||
|
const ID3_SLIDE_ARC_CW := 1
|
||||||
|
const ID3_SLIDE_ARC_ACW := 2
|
||||||
|
|
||||||
|
static func load_file(filename):
|
||||||
|
var file = File.new()
|
||||||
|
var err = file.open(filename, File.READ)
|
||||||
|
if err != OK:
|
||||||
|
print(err)
|
||||||
|
return err
|
||||||
|
var metadata := {}
|
||||||
|
var num_taps := 0
|
||||||
|
var num_holds := 0
|
||||||
|
var num_slides := 0
|
||||||
|
var notes := []
|
||||||
|
var beats_per_measure := 4
|
||||||
|
var length = file.get_len()
|
||||||
|
var slide_ids = {}
|
||||||
|
while (file.get_position() < (length-2)):
|
||||||
|
var noteline = file.get_csv_line()
|
||||||
|
var time_hit := (float(noteline[0]) + (float(noteline[1]))) * beats_per_measure
|
||||||
|
var duration := float(noteline[2]) * beats_per_measure
|
||||||
|
var column := int(noteline[3])
|
||||||
|
var id := int(noteline[4])
|
||||||
|
var id2 := int(noteline[5])
|
||||||
|
var id3 := int(noteline[6])
|
||||||
|
|
||||||
|
match id:
|
||||||
|
ID_HOLD:
|
||||||
|
notes.push_back(Note.NoteHold.new(time_hit, column, duration))
|
||||||
|
num_holds += 1
|
||||||
|
ID_BREAK:
|
||||||
|
notes.push_back(Note.NoteTap.new(time_hit, column, true))
|
||||||
|
num_taps += 1
|
||||||
|
ID_SLIDE_END:
|
||||||
|
# id2 is slide ID
|
||||||
|
if id2 in slide_ids:
|
||||||
|
slide_ids[id2].column_release = column
|
||||||
|
slide_ids[id2].update_slide_variables()
|
||||||
|
_:
|
||||||
|
if id2 == 0:
|
||||||
|
notes.push_back(Note.NoteTap.new(time_hit, column))
|
||||||
|
num_taps += 1
|
||||||
|
else:
|
||||||
|
# id2 is slide ID, id3 is slide pattern
|
||||||
|
# In order to properly declare the slide, we need the paired endcap which may not be the next note
|
||||||
|
var slide_type = Note.SlideType.CHORD
|
||||||
|
match id3:
|
||||||
|
ID3_SLIDE_CHORD:
|
||||||
|
slide_type = Note.SlideType.CHORD
|
||||||
|
ID3_SLIDE_ARC_CW:
|
||||||
|
slide_type = Note.SlideType.ARC_CW
|
||||||
|
ID3_SLIDE_ARC_ACW:
|
||||||
|
slide_type = Note.SlideType.ARC_ACW
|
||||||
|
_:
|
||||||
|
print('Unknown slide type: ', id3)
|
||||||
|
var note = Note.NoteStar.new(time_hit, column)
|
||||||
|
num_slides += 1
|
||||||
|
note.duration = duration
|
||||||
|
notes.push_back(note)
|
||||||
|
var slide = Note.NoteSlide.new(time_hit, column, duration, -1, slide_type)
|
||||||
|
notes.push_back(slide)
|
||||||
|
slide_ids[id2] = slide
|
||||||
|
metadata['num_taps'] = num_taps
|
||||||
|
metadata['num_holds'] = num_holds
|
||||||
|
metadata['num_slides'] = num_slides
|
||||||
|
return [metadata, notes]
|
|
@ -0,0 +1,34 @@
|
||||||
|
extends Node
|
||||||
|
# In case things need to be tested without a library
|
||||||
|
|
||||||
|
static func stress_pattern():
|
||||||
|
var notes = []
|
||||||
|
for bar in range(8):
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||||
|
for i in range(1, 8):
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (7/2.0), (bar + 3)%8))
|
||||||
|
for bar in range(8, 16):
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
||||||
|
for i in range(1, 8):
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + ((i+0.5)/2.0), (bar + i)%8))
|
||||||
|
notes.push_back(Note.make_slide(bar*4 + ((i+1)/2.0), 1, (bar + i)%8, 0))
|
||||||
|
for bar in range(16, 24):
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4, (bar+1)%8, 1))
|
||||||
|
for i in range(2, 8):
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4 + ((i+1)/2.0), (bar + i)%8, 0.5))
|
||||||
|
for bar in range(24, 32):
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||||
|
for i in range(1, 32):
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
||||||
|
if (i%2) > 0:
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 4)%8))
|
||||||
|
for bar in range(32, 48):
|
||||||
|
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||||
|
for i in range(1, 32):
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
||||||
|
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 3)%8))
|
||||||
|
return notes
|
|
@ -1,7 +1,7 @@
|
||||||
[gd_scene load_steps=6 format=2]
|
[gd_scene load_steps=6 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://scripts/ScoreText.gd" type="Script" id=1]
|
[ext_resource path="res://scripts/ScoreText.gd" type="Script" id=1]
|
||||||
[ext_resource path="res://scripts/Menu.gd" type="Script" id=2]
|
[ext_resource path="res://scripts/TouchMenu.gd" type="Script" id=2]
|
||||||
[ext_resource path="res://shaders/menu.tres" type="Material" id=3]
|
[ext_resource path="res://shaders/menu.tres" type="Material" id=3]
|
||||||
[ext_resource path="res://shaders/scoretext.tres" type="Material" id=4]
|
[ext_resource path="res://shaders/scoretext.tres" type="Material" id=4]
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,16 @@
|
||||||
[gd_scene load_steps=20 format=2]
|
[gd_scene load_steps=16 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://scripts/NoteViewport.gd" type="Script" id=1]
|
[ext_resource path="res://scripts/NoteViewport.gd" type="Script" id=1]
|
||||||
[ext_resource path="res://assets/text-4k.png" type="Texture" id=2]
|
[ext_resource path="res://assets/text-4k.png" type="Texture" id=2]
|
||||||
[ext_resource path="res://scripts/NotePainter.gd" type="Script" id=3]
|
[ext_resource path="res://scripts/NotePainter.gd" type="Script" id=3]
|
||||||
[ext_resource path="res://scripts/InputHandler.gd" type="Script" id=4]
|
[ext_resource path="res://scripts/InputHandler.gd" type="Script" id=4]
|
||||||
|
[ext_resource path="res://scenes/StepMenu.tscn" type="PackedScene" id=5]
|
||||||
[ext_resource path="res://scripts/NoteHandler.gd" type="Script" id=6]
|
[ext_resource path="res://scripts/NoteHandler.gd" type="Script" id=6]
|
||||||
[ext_resource path="res://assets/fonts/Sniglet-Regular.ttf" type="DynamicFontData" id=7]
|
[ext_resource path="res://assets/fonts/Sniglet-Regular.ttf" type="DynamicFontData" id=7]
|
||||||
[ext_resource path="res://scripts/Receptors.gd" type="Script" id=8]
|
|
||||||
[ext_resource path="res://scripts/ScreenFilter.gd" type="Script" id=9]
|
[ext_resource path="res://scripts/ScreenFilter.gd" type="Script" id=9]
|
||||||
[ext_resource path="res://scenes/Menu.tscn" type="PackedScene" id=10]
|
|
||||||
[ext_resource path="res://shaders/receptors.shader" type="Shader" id=11]
|
|
||||||
[ext_resource path="res://shaders/notemesh.shader" type="Shader" id=12]
|
[ext_resource path="res://shaders/notemesh.shader" type="Shader" id=12]
|
||||||
[ext_resource path="res://shaders/notelines.shader" type="Shader" id=13]
|
[ext_resource path="res://shaders/notelines.shader" type="Shader" id=13]
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id=1]
|
|
||||||
shader = ExtResource( 11 )
|
|
||||||
shader_param/num_receptors = 8
|
|
||||||
shader_param/receptor_offset = 0.392699
|
|
||||||
shader_param/line_color = Color( 0, 0, 1, 1 )
|
|
||||||
shader_param/dot_color = Color( 0, 0, 1, 1 )
|
|
||||||
shader_param/shadow_color = Color( 0, 0, 0, 0.57 )
|
|
||||||
shader_param/line_thickness = 0.00434783
|
|
||||||
shader_param/dot_radius = 0.026087
|
|
||||||
shader_param/shadow_thickness = 0.0173913
|
|
||||||
shader_param/px = 0.00108696
|
|
||||||
shader_param/px2 = 0.00217391
|
|
||||||
shader_param/alpha = 1.0
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id=2]
|
[sub_resource type="ShaderMaterial" id=2]
|
||||||
shader = ExtResource( 12 )
|
shader = ExtResource( 12 )
|
||||||
shader_param/bps = null
|
shader_param/bps = null
|
||||||
|
@ -75,22 +59,11 @@ func _on_NoteHandler_finished_song(song_key, score_data) -> void:
|
||||||
[sub_resource type="CanvasItemMaterial" id=6]
|
[sub_resource type="CanvasItemMaterial" id=6]
|
||||||
blend_mode = 4
|
blend_mode = 4
|
||||||
|
|
||||||
[sub_resource type="Curve" id=7]
|
[node name="StepGame" type="Control"]
|
||||||
min_value = -1.0
|
|
||||||
_data = [ Vector2( -1, -1 ), 0.0, 0.0, 0, 0, Vector2( 0, 0 ), 2.0, 2.0, 1, 1, Vector2( 1, 1 ), 0.0, 0.0, 0, 0 ]
|
|
||||||
|
|
||||||
[node name="StepGame" type="AspectRatioContainer"]
|
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
__meta__ = {
|
|
||||||
"_edit_use_anchors_": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[node name="Square" type="Control" parent="."]
|
[node name="video" type="TextureRect" parent="." groups=["VideoTexRects"]]
|
||||||
margin_right = 1080.0
|
|
||||||
margin_bottom = 1080.0
|
|
||||||
|
|
||||||
[node name="video" type="TextureRect" parent="Square" groups=["VideoTexRects"]]
|
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
|
@ -103,7 +76,7 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="ScreenFilter" type="ColorRect" parent="Square"]
|
[node name="ScreenFilter" type="ColorRect" parent="."]
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
color = Color( 0, 0, 0, 1 )
|
color = Color( 0, 0, 0, 1 )
|
||||||
|
@ -112,16 +85,21 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="Receptors" type="Control" parent="Square"]
|
[node name="Receptors" type="Control" parent="."]
|
||||||
material = SubResource( 1 )
|
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
script = ExtResource( 8 )
|
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="NoteHandler" type="Control" parent="Square"]
|
[node name="TextureRect" type="TextureRect" parent="Receptors"]
|
||||||
|
margin_right = 1080.0
|
||||||
|
margin_bottom = 1080.0
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="NoteHandler" type="Control" parent="."]
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
script = ExtResource( 6 )
|
script = ExtResource( 6 )
|
||||||
|
@ -129,28 +107,28 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="Viewport" type="Viewport" parent="Square/NoteHandler"]
|
[node name="Viewport" type="Viewport" parent="NoteHandler"]
|
||||||
size = Vector2( 1080, 1080 )
|
size = Vector2( 1080, 1080 )
|
||||||
transparent_bg = true
|
transparent_bg = true
|
||||||
usage = 1
|
usage = 1
|
||||||
render_target_v_flip = true
|
render_target_v_flip = true
|
||||||
script = ExtResource( 1 )
|
script = ExtResource( 1 )
|
||||||
|
|
||||||
[node name="Center" type="Node2D" parent="Square/NoteHandler/Viewport"]
|
[node name="Center" type="Node2D" parent="NoteHandler/Viewport"]
|
||||||
position = Vector2( 540, 540 )
|
position = Vector2( 540, 540 )
|
||||||
|
|
||||||
[node name="SlideTrailHandler" type="Node2D" parent="Square/NoteHandler/Viewport/Center"]
|
[node name="SlideTrailHandler" type="Node2D" parent="NoteHandler/Viewport/Center"]
|
||||||
|
|
||||||
[node name="JudgeText" type="MeshInstance2D" parent="Square/NoteHandler/Viewport/Center"]
|
[node name="JudgeText" type="MeshInstance2D" parent="NoteHandler/Viewport/Center"]
|
||||||
texture = ExtResource( 2 )
|
texture = ExtResource( 2 )
|
||||||
|
|
||||||
[node name="meshinstance" type="MeshInstance2D" parent="Square/NoteHandler/Viewport/Center"]
|
[node name="meshinstance" type="MeshInstance2D" parent="NoteHandler/Viewport/Center"]
|
||||||
material = SubResource( 2 )
|
material = SubResource( 2 )
|
||||||
|
|
||||||
[node name="notelines" type="MeshInstance2D" parent="Square/NoteHandler/Viewport/Center"]
|
[node name="notelines" type="MeshInstance2D" parent="NoteHandler/Viewport/Center"]
|
||||||
material = SubResource( 3 )
|
material = SubResource( 3 )
|
||||||
|
|
||||||
[node name="lbl_combo" type="Label" parent="Square/NoteHandler"]
|
[node name="lbl_combo" type="Label" parent="NoteHandler"]
|
||||||
visible = false
|
visible = false
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
|
@ -171,7 +149,7 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="Painter" type="Control" parent="Square"]
|
[node name="Painter" type="Control" parent="NoteHandler"]
|
||||||
material = SubResource( 6 )
|
material = SubResource( 6 )
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
|
@ -179,13 +157,11 @@ script = ExtResource( 3 )
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
ViewportPath = NodePath("../Viewport")
|
||||||
|
|
||||||
[node name="Menu" parent="Square" instance=ExtResource( 10 )]
|
[node name="StepMenu" parent="." instance=ExtResource( 5 )]
|
||||||
NoteHandlerPath = NodePath("../NoteHandler")
|
|
||||||
ReceptorsPath = NodePath("../Receptors")
|
|
||||||
ease_curve = SubResource( 7 )
|
|
||||||
|
|
||||||
[node name="InputHandler" type="Control" parent="Square"]
|
[node name="InputHandler" type="Control" parent="."]
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
script = ExtResource( 4 )
|
script = ExtResource( 4 )
|
||||||
|
@ -193,7 +169,7 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[connection signal="combo_changed" from="Square/NoteHandler" to="Square/NoteHandler/lbl_combo" method="_on_NoteHandler_combo_changed"]
|
[connection signal="combo_changed" from="NoteHandler" to="NoteHandler/lbl_combo" method="_on_NoteHandler_combo_changed"]
|
||||||
[connection signal="finished_song" from="Square/NoteHandler" to="Square/NoteHandler/lbl_combo" method="_on_NoteHandler_finished_song"]
|
[connection signal="finished_song" from="NoteHandler" to="NoteHandler/lbl_combo" method="_on_NoteHandler_finished_song"]
|
||||||
[connection signal="column_pressed" from="Square/InputHandler" to="Square/NoteHandler" method="_on_InputHandler_column_pressed"]
|
[connection signal="column_pressed" from="InputHandler" to="NoteHandler" method="_on_InputHandler_column_pressed"]
|
||||||
[connection signal="column_released" from="Square/InputHandler" to="Square/NoteHandler" method="_on_InputHandler_column_released"]
|
[connection signal="column_released" from="InputHandler" to="NoteHandler" method="_on_InputHandler_column_released"]
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
[gd_scene load_steps=6 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://scripts/ScoreText.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://scripts/StepMenu.gd" type="Script" id=2]
|
||||||
|
[ext_resource path="res://shaders/menu.tres" type="Material" id=3]
|
||||||
|
[ext_resource path="res://shaders/scoretext.tres" type="Material" id=4]
|
||||||
|
|
||||||
|
[sub_resource type="Curve" id=1]
|
||||||
|
min_value = -1.0
|
||||||
|
_data = [ Vector2( -1, -1 ), 0.0, 0.0, 0, 0, Vector2( 0, 0 ), 2.0, 2.0, 1, 1, Vector2( 1, 1 ), 0.0, 0.0, 0, 0 ]
|
||||||
|
|
||||||
|
[node name="StepMenu" type="Control"]
|
||||||
|
material = ExtResource( 3 )
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
rect_clip_content = true
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
ease_curve = SubResource( 1 )
|
||||||
|
|
||||||
|
[node name="ScoreText" type="Node2D" parent="."]
|
||||||
|
material = ExtResource( 4 )
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
|
||||||
|
[node name="PVMusic" type="AudioStreamPlayer" parent="."]
|
||||||
|
bus = "Preview"
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Static functions mostly for FileLoader to make use of because of deficiancies in load()
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const ERROR_CODES := [
|
||||||
|
'OK', 'FAILED', 'ERR_UNAVAILABLE', 'ERR_UNCONFIGURED', 'ERR_UNAUTHORIZED', 'ERR_PARAMETER_RANGE_ERROR',
|
||||||
|
'ERR_OUT_OF_MEMORY', 'ERR_FILE_NOT_FOUND', 'ERR_FILE_BAD_DRIVE', 'ERR_FILE_BAD_PATH','ERR_FILE_NO_PERMISSION',
|
||||||
|
'ERR_FILE_ALREADY_IN_USE', 'ERR_FILE_CANT_OPEN', 'ERR_FILE_CANT_WRITE', 'ERR_FILE_CANT_READ', 'ERR_FILE_UNRECOGNIZED',
|
||||||
|
'ERR_FILE_CORRUPT', 'ERR_FILE_MISSING_DEPENDENCIES', 'ERR_FILE_EOF', 'ERR_CANT_OPEN', 'ERR_CANT_CREATE', 'ERR_QUERY_FAILED',
|
||||||
|
'ERR_ALREADY_IN_USE', 'ERR_LOCKED', 'ERR_TIMEOUT', 'ERR_CANT_CONNECT', 'ERR_CANT_RESOLVE', 'ERR_CONNECTION_ERROR',
|
||||||
|
'ERR_CANT_ACQUIRE_RESOURCE', 'ERR_CANT_FORK', 'ERR_INVALID_DATA', 'ERR_INVALID_PARAMETER', 'ERR_ALREADY_EXISTS',
|
||||||
|
'ERR_DOES_NOT_EXIST', 'ERR_DATABASE_CANT_READ', 'ERR_DATABASE_CANT_WRITE', 'ERR_COMPILATION_FAILED', 'ERR_METHOD_NOT_FOUND',
|
||||||
|
'ERR_LINK_FAILED', 'ERR_SCRIPT_FAILED', 'ERR_CYCLIC_LINK', 'ERR_INVALID_DECLARATION', 'ERR_DUPLICATE_SYMBOL',
|
||||||
|
'ERR_PARSE_ERROR', 'ERR_BUSY', 'ERR_SKIP', 'ERR_HELP', 'ERR_BUG'
|
||||||
|
]
|
||||||
|
|
||||||
|
static func load_image(filename: String) -> ImageTexture:
|
||||||
|
var tex := ImageTexture.new()
|
||||||
|
var img := Image.new()
|
||||||
|
img.load(filename)
|
||||||
|
tex.create_from_image(img)
|
||||||
|
return tex
|
||||||
|
|
||||||
|
|
||||||
|
static func load_ogg(filename: String) -> AudioStreamOGGVorbis:
|
||||||
|
# Loads the ogg file with that exact filename
|
||||||
|
var audiostream = AudioStreamOGGVorbis.new()
|
||||||
|
var oggfile = File.new()
|
||||||
|
oggfile.open(filename, File.READ)
|
||||||
|
audiostream.set_data(oggfile.get_buffer(oggfile.get_len()))
|
||||||
|
oggfile.close()
|
||||||
|
return audiostream
|
||||||
|
|
||||||
|
|
||||||
|
static func load_video(filename: String):
|
||||||
|
return load(filename)
|
||||||
|
# This may need reenabling for some platforms:
|
||||||
|
#var videostream = VideoStreamGDNative.new()
|
||||||
|
#videostream.set_file(filename)
|
||||||
|
#return videostream
|
||||||
|
|
||||||
|
|
||||||
|
static func directory_list(directory: String, hidden: bool, sort:=true) -> Dictionary:
|
||||||
|
# Sadly there's no filelist sugar so we make our own
|
||||||
|
var output = {folders=[], files=[], err=OK}
|
||||||
|
var dir = Directory.new()
|
||||||
|
output.err = dir.open(directory)
|
||||||
|
if output.err != OK:
|
||||||
|
print_debug('Failed to open directory: ' + directory + '(Error code '+output.err+')')
|
||||||
|
return output
|
||||||
|
output.err = dir.list_dir_begin(true, !hidden)
|
||||||
|
if output.err != OK:
|
||||||
|
print_debug('Failed to begin listing directory: ' + directory + '(Error code '+output.err+')')
|
||||||
|
return output
|
||||||
|
|
||||||
|
var item = dir.get_next()
|
||||||
|
while (item != ''):
|
||||||
|
if dir.current_is_dir():
|
||||||
|
output['folders'].append(item)
|
||||||
|
else:
|
||||||
|
output['files'].append(item)
|
||||||
|
item = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
output.folders.sort()
|
||||||
|
output.files.sort()
|
||||||
|
# Maybe convert the Arrays to PoolStringArrays?
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
static func init_directory(directory: String):
|
||||||
|
var dir = Directory.new()
|
||||||
|
var err = dir.make_dir_recursive(directory)
|
||||||
|
if err != OK:
|
||||||
|
print('An error occurred while trying to create the directory: ', directory, err, ERROR_CODES[err])
|
||||||
|
return err
|
|
@ -1,6 +1,7 @@
|
||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
onready var Viewport := get_node(@'../NoteHandler/Viewport')
|
export var ViewportPath := @'../NoteHandler/Viewport'
|
||||||
|
onready var Viewport := get_node(ViewportPath)
|
||||||
|
|
||||||
func _draw():
|
func _draw():
|
||||||
draw_texture_rect(Viewport.get_texture(), Rect2(Vector2.ZERO, rect_size), false)
|
draw_texture_rect(Viewport.get_texture(), Rect2(Vector2.ZERO, rect_size), false)
|
||||||
|
|
|
@ -0,0 +1,585 @@
|
||||||
|
#tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
export var NoteHandlerPath := @'../NoteHandler'
|
||||||
|
export var ReceptorsPath := @'../Receptors'
|
||||||
|
onready var NoteHandler := get_node(NoteHandlerPath)
|
||||||
|
onready var Receptors := get_node(ReceptorsPath)
|
||||||
|
onready var ScoreText := $ScoreText
|
||||||
|
onready var PVMusic := SoundPlayer.music_player_pv
|
||||||
|
|
||||||
|
var f_scale := 1.0 setget set_f_scale
|
||||||
|
func set_f_scale(value: float) -> void:
|
||||||
|
f_scale = value
|
||||||
|
TitleFont.size = int(round(32*f_scale))
|
||||||
|
TitleFont.outline_size = int(max(round(2*f_scale), 1))
|
||||||
|
GenreFont.size = int(round(48*f_scale))
|
||||||
|
GenreFont.outline_size = int(max(round(2*f_scale), 1))
|
||||||
|
DiffNumFont.size = int(round(36*f_scale))
|
||||||
|
DiffNumFont.outline_size = int(max(round(1*f_scale), 1))
|
||||||
|
ScoreText.set_f_scale(f_scale)
|
||||||
|
func update_scale() -> void:
|
||||||
|
self.f_scale = min(rect_size.x/1080, rect_size.y/1080)
|
||||||
|
|
||||||
|
var genres = {}
|
||||||
|
|
||||||
|
enum ChartDifficulty {EASY, BASIC, ADV, EXPERT, MASTER}
|
||||||
|
enum MenuMode {SONG_SELECT, CHART_SELECT, OPTIONS, GAMEPLAY, SCORE_SCREEN}
|
||||||
|
|
||||||
|
var menu_mode = MenuMode.SONG_SELECT
|
||||||
|
var menu_mode_prev = MenuMode.SONG_SELECT
|
||||||
|
var menu_mode_prev_fade_timer := 0.0
|
||||||
|
var menu_mode_prev_fade_timer_duration := 0.25
|
||||||
|
var currently_playing := false
|
||||||
|
|
||||||
|
var selected_genre: int = 0
|
||||||
|
var selected_genre_vis: int = 0
|
||||||
|
var selected_genre_delta: float = 0.0 # For floaty display scrolling
|
||||||
|
var target_song_idx: float = 0.0 setget set_target_song_idx
|
||||||
|
var target_song_delta: float = 0.0 # For floaty display scrolling
|
||||||
|
var selected_song_idx: int setget , get_song_idx
|
||||||
|
var selected_song_key: String setget , get_song_key
|
||||||
|
var selected_difficulty = ChartDifficulty.ADV
|
||||||
|
|
||||||
|
func set_target_song_idx(index):
|
||||||
|
target_song_delta -= index - target_song_idx
|
||||||
|
target_song_idx = index
|
||||||
|
|
||||||
|
func get_song_idx() -> int:
|
||||||
|
return int(round(self.target_song_idx + target_song_delta))
|
||||||
|
|
||||||
|
func get_song_key() -> String:
|
||||||
|
var songslist = genres[genres.keys()[selected_genre]]
|
||||||
|
return songslist[int(round(self.target_song_idx)) % len(songslist)]
|
||||||
|
|
||||||
|
var scorescreen_song_key := ''
|
||||||
|
var scorescreen_score_data := {}
|
||||||
|
var scorescreen_datetime := {}
|
||||||
|
var scorescreen_saved := false
|
||||||
|
|
||||||
|
var touch_rects = []
|
||||||
|
|
||||||
|
var TitleFont: DynamicFont = preload('res://assets/MenuTitleFont.tres').duplicate()
|
||||||
|
var GenreFont: DynamicFont = preload('res://assets/MenuGenreFont.tres').duplicate()
|
||||||
|
var DiffNumFont: DynamicFont = preload('res://assets/MenuDiffNumberFont.tres').duplicate()
|
||||||
|
var ScoreFont: DynamicFont = preload('res://assets/MenuScoreFont.tres').duplicate()
|
||||||
|
var snd_interact := preload('res://assets/softclap.wav')
|
||||||
|
var snd_error := preload('res://assets/miss.wav')
|
||||||
|
|
||||||
|
export var ease_curve: Curve
|
||||||
|
|
||||||
|
|
||||||
|
class lerp_array extends Resource:
|
||||||
|
var array
|
||||||
|
func _init(array: Array):
|
||||||
|
self.array = array
|
||||||
|
|
||||||
|
func value(index: float):
|
||||||
|
# Only >= 0 for now, but should be fine since it's an arraylike anyway
|
||||||
|
var i := min(int(floor(index)), len(array)-2) # Somewhat hacky - if we pass len(array)-1 as index, it will return lerp(a[-2], a[-1], 1) == a[-1]
|
||||||
|
var f := min(index - i, 1.0)
|
||||||
|
return lerp(array[i], array[i+1], f)
|
||||||
|
|
||||||
|
func len():
|
||||||
|
return len(array)
|
||||||
|
|
||||||
|
|
||||||
|
func get_rect_center(rect: Rect2) -> Vector2:
|
||||||
|
return rect.position + rect.size*0.5
|
||||||
|
|
||||||
|
func scan_library():
|
||||||
|
var results = FileLoader.scan_library()
|
||||||
|
genres = results.genres
|
||||||
|
|
||||||
|
func save_score() -> int:
|
||||||
|
var data = {'score_data': scorescreen_score_data, 'song_key': scorescreen_song_key}
|
||||||
|
var dt = scorescreen_datetime
|
||||||
|
var filename = 'scores/%04d%02d%02dT%02d%02d%02d.json'%[dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]
|
||||||
|
match FileLoader.save_json(filename, data):
|
||||||
|
OK:
|
||||||
|
scorescreen_saved = true
|
||||||
|
return OK
|
||||||
|
var err:
|
||||||
|
print_debug('Error saving score file %s'%filename)
|
||||||
|
return err
|
||||||
|
|
||||||
|
func load_score(filename: String):
|
||||||
|
var result = FileLoader.load_json('scores/%s'%filename)
|
||||||
|
if not (result is Dictionary):
|
||||||
|
print('An error occurred while trying to access the chosen score file: ', result)
|
||||||
|
return result
|
||||||
|
var data = {}
|
||||||
|
for key in result.score_data:
|
||||||
|
var value = {}
|
||||||
|
for k2 in result.score_data[key]:
|
||||||
|
if k2 != 'MISS':
|
||||||
|
k2 = int(k2) # Could use something more robust later
|
||||||
|
value[k2] = result.score_data[key][k2]
|
||||||
|
data[int(key)] = value
|
||||||
|
scorescreen_score_data = data
|
||||||
|
scorescreen_song_key = result.song_key
|
||||||
|
scorescreen_saved = true
|
||||||
|
set_menu_mode(MenuMode.SCORE_SCREEN)
|
||||||
|
|
||||||
|
func load_preview():
|
||||||
|
var tmp = self.selected_song_key
|
||||||
|
var data = Library.all_songs[tmp]
|
||||||
|
PVMusic.stop()
|
||||||
|
PVMusic.set_stream(FileLoader.load_ogg('songs/' + data.filepath.rstrip('/') + '/' + data.audio_filelist[0]))
|
||||||
|
PVMusic.play(16*60.0/data.BPM)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
scan_library()
|
||||||
|
connect('item_rect_changed', self, 'update_scale')
|
||||||
|
NoteHandler.connect('finished_song', self, 'finished_song')
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
func _process(delta):
|
||||||
|
target_song_delta -= ease_curve.interpolate(clamp(target_song_delta, -2, 2)*0.5) * 10 * delta
|
||||||
|
if abs(target_song_delta) < 0.02: # Snap
|
||||||
|
target_song_delta = 0.0
|
||||||
|
|
||||||
|
var g_diff = selected_genre - (selected_genre_vis + selected_genre_delta)
|
||||||
|
selected_genre_delta += ease_curve.interpolate(clamp(g_diff, -1, 1)) * 10 * delta
|
||||||
|
if selected_genre_delta > 0.5:
|
||||||
|
selected_genre_delta -= 1.0
|
||||||
|
selected_genre_vis += 1
|
||||||
|
elif selected_genre_delta < -0.5:
|
||||||
|
selected_genre_delta += 1.0
|
||||||
|
selected_genre_vis -= 1
|
||||||
|
if abs(g_diff) < 0.02: # Snap
|
||||||
|
selected_genre_delta = 0.0
|
||||||
|
selected_genre_vis = selected_genre
|
||||||
|
|
||||||
|
menu_mode_prev_fade_timer = max(0.0, menu_mode_prev_fade_timer - delta)
|
||||||
|
update()
|
||||||
|
if (menu_mode == MenuMode.GAMEPLAY) and (menu_mode_prev_fade_timer <= 0.0) and not NoteHandler.running:
|
||||||
|
NoteHandler.load_track(self.selected_song_key, Library.Song.default_difficulty_keys[selected_difficulty])
|
||||||
|
NoteHandler.running = true
|
||||||
|
|
||||||
|
|
||||||
|
func draw_string_centered(font: Font, position: Vector2, string: String, color := GameTheme.COLOR_MENU_TEXT, vcenter := false) -> Vector2:
|
||||||
|
# Draws horizontally centered from the baseline. Can vcenter via ascent but not perfectly reliable.
|
||||||
|
# Returns size of the string.
|
||||||
|
var ss := font.get_string_size(string)
|
||||||
|
var v := -(font.get_descent() - font.get_height()*0.475) if vcenter else 0.0 # This VCentering is a little fudgey but works for our current fonts
|
||||||
|
draw_string(font, Vector2(position.x - ss.x*0.5, position.y + v).round(), string, color)
|
||||||
|
return ss
|
||||||
|
|
||||||
|
func draw_string_ralign(font: Font, position: Vector2, string: String, color := GameTheme.COLOR_MENU_TEXT, vcenter := false) -> Vector2:
|
||||||
|
# Draws from the bottom-right. Can vcenter via ascent but not perfectly reliable.
|
||||||
|
# Returns size of the string.
|
||||||
|
var ss := font.get_string_size(string)
|
||||||
|
var ascent := font.get_ascent() if vcenter else 0.0
|
||||||
|
draw_string(font, Vector2(position.x - ss.x, position.y + ascent*0.5).round(), string, color)
|
||||||
|
return ss
|
||||||
|
|
||||||
|
func draw_songtile(song_key, position, size, title_text:=false, difficulty=selected_difficulty, outline_px:=3.0, disabled:=false):
|
||||||
|
# Draws from top left-corner. Returns Rect2 of the image (not the outline).
|
||||||
|
# Draw difficulty-colored outline
|
||||||
|
if typeof(difficulty) == TYPE_STRING:
|
||||||
|
difficulty = Library.Song.difficulty_key_ids.get(difficulty, 0)
|
||||||
|
|
||||||
|
outline_px *= f_scale
|
||||||
|
|
||||||
|
var song_diffs = Library.all_songs[song_key]['chart_difficulties']
|
||||||
|
if not (Library.Song.default_difficulty_keys[difficulty] in song_diffs):
|
||||||
|
difficulty = Library.Song.difficulty_key_ids.get(song_diffs.keys()[-1], 0)
|
||||||
|
var diff_color := GameTheme.COLOR_DIFFICULTY[difficulty*2]
|
||||||
|
var rect := Rect2(position.x, position.y, size, size)
|
||||||
|
draw_rect(Rect2(position.x - outline_px, position.y - outline_px, size + outline_px*2, size + outline_px*2), diff_color)
|
||||||
|
draw_texture_rect(Library.get_song_tile_texture(song_key), rect, false, Color.white if not disabled else Color(0.5, 0.2, 0.1))
|
||||||
|
# Draw track difficulty rating
|
||||||
|
draw_string_ralign(DiffNumFont, position+Vector2(size-2*f_scale, size-5*f_scale), song_diffs.get(Library.Song.default_difficulty_keys[difficulty], '0'), diff_color)
|
||||||
|
if disabled:
|
||||||
|
draw_string_centered(DiffNumFont, position+Vector2(size/2, size/2), 'No Chart!', diff_color, true)
|
||||||
|
if title_text:
|
||||||
|
draw_string_centered(TitleFont, position+Vector2(size/2.0, size+40*f_scale), str(Library.all_songs[song_key].title), diff_color.lightened(0.33))
|
||||||
|
return rect
|
||||||
|
|
||||||
|
func diff_f2str(difficulty: float): # Convert .5 to +
|
||||||
|
return str(int(floor(difficulty))) + ('+' if fmod(difficulty, 1.0)>0.4 else '')
|
||||||
|
|
||||||
|
var sel_scales := lerp_array.new([1.0, 0.8, 0.64, 0.5, 0.4, 0.4, 0.4, 0.4, 0.4])
|
||||||
|
var bg_scales := lerp_array.new([0.64, 0.64, 0.64, 0.5, 0.4, 0.4, 0.4, 0.4, 0.4])
|
||||||
|
func _draw_song_select(center: Vector2) -> Array:
|
||||||
|
var size = 200 * f_scale
|
||||||
|
var spacer_x = 12 * f_scale
|
||||||
|
var spacer_y = 64 * f_scale
|
||||||
|
var title_spacer_y = 48 * f_scale
|
||||||
|
var gy: float = center.y - 500 * f_scale - size*selected_genre_delta
|
||||||
|
var touchrects := []
|
||||||
|
|
||||||
|
if len(genres) <= 0:
|
||||||
|
draw_string_centered(GenreFont, Vector2(center.x, center.y-440*f_scale), 'No Songs in Library!', Color.aqua)
|
||||||
|
draw_string_centered(DiffNumFont, Vector2(center.x, center.y-390*f_scale), FileLoader.userroot, Color.lightgreen)
|
||||||
|
return touchrects
|
||||||
|
|
||||||
|
var ssid = self.selected_song_idx
|
||||||
|
var s_delta = target_song_delta-round(target_song_delta)
|
||||||
|
for gi in [-2, -1, 0, 1, 2]:
|
||||||
|
var g = (selected_genre_vis + gi) % len(genres)
|
||||||
|
var selected: bool = (gi == 0)
|
||||||
|
var scales = sel_scales if selected else bg_scales
|
||||||
|
|
||||||
|
var subsize = size * scales.value(abs(s_delta))
|
||||||
|
var gx = center.x - (subsize + spacer_x) * s_delta
|
||||||
|
var songslist = Library.genre_songs[g].keys()
|
||||||
|
var genre_str = '%s (%d songs)'%[genres.keys()[g], len(songslist)]
|
||||||
|
draw_string_centered(GenreFont, Vector2(center.x, gy), genre_str, Color.lightblue)
|
||||||
|
|
||||||
|
var s = len(songslist)
|
||||||
|
var key = songslist[self.selected_song_idx % s]
|
||||||
|
var y = gy + 16*f_scale
|
||||||
|
var x = -subsize/2.0
|
||||||
|
var r = draw_songtile(key, Vector2(gx+x, y), subsize, selected)
|
||||||
|
touchrects.append({rect=r, song_idx=self.selected_song_idx, genre_idx=g})
|
||||||
|
|
||||||
|
var subsize_p = subsize
|
||||||
|
var subsize_n = subsize
|
||||||
|
var x_p = x
|
||||||
|
var x_n = x
|
||||||
|
for i in range(1, scales.len()):
|
||||||
|
x_p += subsize_p + spacer_x
|
||||||
|
x_n += subsize_n + spacer_x
|
||||||
|
subsize_p = size * scales.value(abs(i-s_delta))
|
||||||
|
subsize_n = size * scales.value(abs(-i-s_delta))
|
||||||
|
r = draw_songtile(songslist[(ssid+i) % s], Vector2(gx+x_p, y), subsize_p)
|
||||||
|
touchrects.append({rect=r, song_idx=ssid+i, genre_idx=g})
|
||||||
|
r = draw_songtile(songslist[(ssid-i) % s], Vector2(gx-x_n - subsize_n, y), subsize_n)
|
||||||
|
touchrects.append({rect=r, song_idx=ssid-i, genre_idx=g})
|
||||||
|
gy += size*scales.value(0) + spacer_y + (title_spacer_y if selected else 0)
|
||||||
|
var b = 1080 * f_scale
|
||||||
|
var v1 = -590 * f_scale
|
||||||
|
var v2 = -230 * f_scale
|
||||||
|
var v4 = -v2
|
||||||
|
var v3 = -v1
|
||||||
|
var ps = PoolVector2Array([center+Vector2(-b, v1), center+Vector2(b, v1), center+Vector2(b, v2), center+Vector2(-b, v2)])
|
||||||
|
var ps2 = PoolVector2Array([center+Vector2(-b, v3), center+Vector2(b, v3), center+Vector2(b, v4), center+Vector2(-b, v4)])
|
||||||
|
var cs = PoolColorArray([Color(0,0,0.1,1.25), Color(0,0,0.1,1.25), Color(0,0,0,0), Color(0,0,0,0)])
|
||||||
|
draw_polygon(ps, cs)
|
||||||
|
draw_polygon(ps2, cs)
|
||||||
|
draw_string_centered(GenreFont, Vector2(center.x, center.y-440*f_scale), 'Select Song', Color.aqua)
|
||||||
|
draw_string_centered(DiffNumFont, Vector2(center.x, center.y-390*f_scale), 'Tap to scroll, tap focused to select', Color.lightgreen)
|
||||||
|
return touchrects
|
||||||
|
|
||||||
|
func _draw_chart_select(center: Vector2) -> Array:
|
||||||
|
# Select difficulty for chosen song
|
||||||
|
var charts: Dictionary = Library.get_song_charts(self.selected_song_key)
|
||||||
|
var song_data = Library.all_songs[self.selected_song_key]
|
||||||
|
var diffs = song_data.chart_difficulties
|
||||||
|
var n = len(diffs)
|
||||||
|
var spacer_x = max(14, 70/n) * f_scale
|
||||||
|
var size = min(192, (1000-spacer_x*(n-1))/n) * f_scale
|
||||||
|
var rect_back = Rect2(center + Vector2(-300.0, 390.0)*f_scale, Vector2(600.0, 140.0)*f_scale)
|
||||||
|
draw_rect(rect_back, Color.red)
|
||||||
|
draw_string_centered(TitleFont, get_rect_center(rect_back), 'Back to song selection', Color.white, true)
|
||||||
|
draw_string_centered(GenreFont, center+Vector2(0, -360*f_scale), 'Select Difficulty', Color.aqua)
|
||||||
|
draw_string_centered(DiffNumFont, center+Vector2(0, -300*f_scale), 'Tap to show stats, tap focused to play', Color.lightgreen)
|
||||||
|
var touchrects = [{rect=rect_back, chart_idx=-1, enabled=true}] # invisible back button
|
||||||
|
var x = center.x - (size*n + spacer_x*(n-1))/2
|
||||||
|
|
||||||
|
for diff in diffs:
|
||||||
|
var i_diff = Library.Song.difficulty_key_ids.get(diff, 0)
|
||||||
|
var width = 8 if i_diff == selected_difficulty else 3
|
||||||
|
var chart_exists: bool = (diff in charts)
|
||||||
|
var r = draw_songtile(self.selected_song_key, Vector2(x, center.y-160*f_scale), size, false, i_diff, width, not chart_exists)
|
||||||
|
touchrects.append({rect=r, chart_idx=i_diff, enabled=chart_exists})
|
||||||
|
x += size + spacer_x
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(0, size-116*f_scale), str(Library.all_songs[self.selected_song_key].title))
|
||||||
|
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(-50*f_scale, size-64*f_scale), 'BPM:')
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(+50*f_scale, size-64*f_scale), str(song_data.BPM))
|
||||||
|
|
||||||
|
if len(charts) > 0:
|
||||||
|
var sel_chart: Array = charts.values()[min(selected_difficulty, len(charts)-1)]
|
||||||
|
var all_notes: Array = sel_chart[1]
|
||||||
|
var meta: Dictionary = sel_chart[0]
|
||||||
|
|
||||||
|
var notestrs = ['Taps:', 'Holds:', 'Slides:']
|
||||||
|
var notetypes = [0, 1, 2]
|
||||||
|
var note_counts = [meta.num_taps, meta.num_holds, meta.num_slides]
|
||||||
|
for i in len(notestrs):
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(-50*f_scale, size+(12+i*50)*f_scale), notestrs[i])
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(+50*f_scale, size+(12+i*50)*f_scale), str(note_counts[notetypes[i]]))
|
||||||
|
else:
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(0, size-12*f_scale), 'No available charts!', Color.red)
|
||||||
|
|
||||||
|
return touchrects
|
||||||
|
|
||||||
|
func _draw_score_screen(center: Vector2) -> Array:
|
||||||
|
var size = 192 * f_scale
|
||||||
|
var spacer_x = 12 * f_scale
|
||||||
|
var touchrects = []
|
||||||
|
var songslist = genres[genres.keys()[selected_genre]]
|
||||||
|
var song_key = scorescreen_song_key
|
||||||
|
# var song_data = Library.all_songs[song_key]
|
||||||
|
var chart: Array = Library.get_song_charts(song_key)[Library.Song.default_difficulty_keys[selected_difficulty]]
|
||||||
|
var all_notes: Array = chart[1]
|
||||||
|
var meta: Dictionary = chart[0]
|
||||||
|
|
||||||
|
var x = center.x
|
||||||
|
var y = -160*f_scale
|
||||||
|
var x_score = 110
|
||||||
|
var y_score = -380
|
||||||
|
var x2 = -360*f_scale
|
||||||
|
var x_spacing = 124*f_scale
|
||||||
|
var y_spacing = 42*f_scale
|
||||||
|
var y2 = y + y_spacing*1.5
|
||||||
|
var y3 = y2 + y_spacing
|
||||||
|
|
||||||
|
var tex_judgement_text = GameTheme.tex_judgement_text
|
||||||
|
var judgement_text_scale = 0.667
|
||||||
|
var judgement_text_size = Vector2(256, 64) * judgement_text_scale
|
||||||
|
|
||||||
|
draw_songtile(song_key, center + Vector2.LEFT*size*0.5 + Vector2(-x_score, y_score)*f_scale, size, false, selected_difficulty, 3)
|
||||||
|
draw_string_centered(TitleFont, center + Vector2.DOWN*size + Vector2(-x_score, y_score+48)*f_scale, str(Library.all_songs[song_key].title))
|
||||||
|
var notestrs = ['Taps (%d):'%meta.num_taps, 'Holds (%d) Hit:'%meta.num_holds, 'Released:', 'Stars (%d):'%meta.num_slides, 'Slides:']
|
||||||
|
var notetypes = [0, 1, -1, 2, -2]
|
||||||
|
var note_spacing = [0.0, 1.25, 2.25, 3.5, 4.5]
|
||||||
|
var judgestrs = Array(Rules.JUDGEMENT_STRINGS + ['Miss'])
|
||||||
|
var judge_scores = [1.0, 0.9, 0.75, 0.5, 0.0]
|
||||||
|
var notetype_weights = [1.0, 1.0, 1.0, 1.0, 1.0]
|
||||||
|
var notecount_total = 0
|
||||||
|
var notecount_early = 0
|
||||||
|
var notecount_late = 0
|
||||||
|
var total_score = 0.0
|
||||||
|
var total_scoremax = 0.0
|
||||||
|
|
||||||
|
for i in len(judgestrs):
|
||||||
|
# For each judgement type, print a column header
|
||||||
|
# draw_string_centered(TitleFont, Vector2(x2+x_spacing*(i+1), y2), judgestrs[i])
|
||||||
|
var dst_rect = Rect2(center+Vector2(x2+x_spacing*(i+1)-judgement_text_size.x*f_scale/2.0, y2), judgement_text_size*f_scale)
|
||||||
|
draw_texture_rect_region(tex_judgement_text, dst_rect, Rect2(0, 128*(i+3), 512, 128))
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y2+34*f_scale), 'Score')
|
||||||
|
|
||||||
|
for i in len(notestrs):
|
||||||
|
# For each note type, make a row and print scores
|
||||||
|
var idx = notetypes[i]
|
||||||
|
var note_score = 0
|
||||||
|
var note_count = 0
|
||||||
|
var y_row = y3 + y_spacing * (note_spacing[i]+1)
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(x2-20*f_scale, y_row), notestrs[i])
|
||||||
|
for j in len(judgestrs):
|
||||||
|
var score
|
||||||
|
if j == 0:
|
||||||
|
score = scorescreen_score_data[idx][0]
|
||||||
|
elif j >= len(judgestrs)-1:
|
||||||
|
score = scorescreen_score_data[idx]['MISS']
|
||||||
|
else:
|
||||||
|
score = scorescreen_score_data[idx][j] + scorescreen_score_data[idx][-j]
|
||||||
|
notecount_early += scorescreen_score_data[idx][-j]
|
||||||
|
notecount_late += scorescreen_score_data[idx][j]
|
||||||
|
if (j >= len(judgestrs)-1) and (idx == -1):
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(j+1), y_row), '^')
|
||||||
|
else:
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(j+1), y_row), str(score))
|
||||||
|
notecount_total += score # Kinda redundant, will probably refactor eventually
|
||||||
|
note_count += score
|
||||||
|
note_score += score * judge_scores[j]
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y_row), '%2.2f%%'%(note_score/max(note_count, 1)*100.0))
|
||||||
|
total_score += note_score * notetype_weights[i]
|
||||||
|
total_scoremax += note_count * notetype_weights[i]
|
||||||
|
|
||||||
|
var overall_score = total_score/max(total_scoremax, 1.0)
|
||||||
|
var score_idx = 0
|
||||||
|
for cutoff in Rules.SCORE_CUTOFFS:
|
||||||
|
if overall_score >= cutoff:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
score_idx += 1
|
||||||
|
ScoreText.position = center+Vector2(x_score, y_score)*f_scale
|
||||||
|
ScoreText.score = Rules.SCORE_STRINGS[score_idx]
|
||||||
|
ScoreText.score_sub = '%2.3f%%'%(overall_score*100.0)
|
||||||
|
ScoreText.update()
|
||||||
|
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(-150, y3+y_spacing*7), 'Early : Late')
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(-150, y3+y_spacing*8), '%3d%% : %3d%%'%[notecount_early*100/max(notecount_total, 1), notecount_late*100/max(notecount_total, 1)])
|
||||||
|
draw_string_centered(TitleFont, center+Vector2(150, y3+y_spacing*7.5), 'Max Combo: %d'%scorescreen_score_data.get('max_combo', 0)) # Safety for older saves
|
||||||
|
|
||||||
|
var txt_offset = Vector2.DOWN*10*f_scale
|
||||||
|
var rect_songs := Rect2(center+Vector2(-100.0, 300.0)*f_scale, Vector2(400.0, 100.0)*f_scale)
|
||||||
|
draw_rect(rect_songs, Color.red)
|
||||||
|
draw_string_centered(TitleFont, get_rect_center(rect_songs), 'Song Select', Color.white, true)
|
||||||
|
touchrects.append({rect=rect_songs, next_menu=MenuMode.SONG_SELECT})
|
||||||
|
|
||||||
|
var rect_save := Rect2(center+Vector2(-300.0, 300.0)*f_scale, Vector2(180.0, 100.0)*f_scale)
|
||||||
|
if not scorescreen_saved:
|
||||||
|
draw_rect(rect_save, Color(0.0, 0.01, 1.0))
|
||||||
|
draw_string_centered(TitleFont, get_rect_center(rect_save), 'Save', Color.white, true)
|
||||||
|
touchrects.append({rect=rect_save, action='save'})
|
||||||
|
else:
|
||||||
|
draw_rect(rect_save, Color.darkgray)
|
||||||
|
draw_string_centered(TitleFont, get_rect_center(rect_save), 'Saved', Color.white, true)
|
||||||
|
|
||||||
|
draw_string_centered(GenreFont, center+Vector2.UP*410*f_scale, 'Results', Color.aqua)
|
||||||
|
return touchrects
|
||||||
|
|
||||||
|
func _draw_gameplay(center: Vector2) -> Array:
|
||||||
|
var touchrects = []
|
||||||
|
|
||||||
|
var rect_songselect := Rect2(center+Vector2(+860.0, 480.0)*f_scale, Vector2(100.0, 50.0)*f_scale)
|
||||||
|
draw_rect(rect_songselect, Color.red)
|
||||||
|
draw_string_centered(TitleFont, get_rect_center(rect_songselect), 'Stop', Color.white, true)
|
||||||
|
touchrects.append({rect=rect_songselect, action='stop'})
|
||||||
|
return touchrects
|
||||||
|
|
||||||
|
|
||||||
|
func _draw():
|
||||||
|
var songs = len(Library.all_songs)
|
||||||
|
var score_screen_filter_alpha := 0.65
|
||||||
|
var size = 216
|
||||||
|
var outline_px = 3
|
||||||
|
var center = rect_size * 0.5
|
||||||
|
touch_rects = []
|
||||||
|
ScoreText.hide()
|
||||||
|
for i in MenuMode:
|
||||||
|
touch_rects.append([])
|
||||||
|
|
||||||
|
if menu_mode_prev_fade_timer > 0.0:
|
||||||
|
var progress = 1.0 - menu_mode_prev_fade_timer/menu_mode_prev_fade_timer_duration
|
||||||
|
var center_prev = lerp(center, center+Vector2(0.0, 1200.0), progress)
|
||||||
|
var center_next = center_prev + Vector2(0.0, -1200.0)
|
||||||
|
match menu_mode_prev:
|
||||||
|
MenuMode.SONG_SELECT:
|
||||||
|
_draw_song_select(center_prev)
|
||||||
|
MenuMode.CHART_SELECT:
|
||||||
|
_draw_chart_select(center_prev)
|
||||||
|
MenuMode.OPTIONS:
|
||||||
|
pass
|
||||||
|
MenuMode.GAMEPLAY:
|
||||||
|
GameTheme.set_screen_filter_alpha(lerp(0.0, score_screen_filter_alpha, progress))
|
||||||
|
MenuMode.SCORE_SCREEN:
|
||||||
|
_draw_score_screen(center_prev)
|
||||||
|
match menu_mode:
|
||||||
|
MenuMode.SONG_SELECT:
|
||||||
|
_draw_song_select(center_next)
|
||||||
|
MenuMode.CHART_SELECT:
|
||||||
|
_draw_chart_select(center_next)
|
||||||
|
MenuMode.OPTIONS:
|
||||||
|
pass
|
||||||
|
MenuMode.GAMEPLAY:
|
||||||
|
GameTheme.set_screen_filter_alpha(1.0 - progress)
|
||||||
|
MenuMode.SCORE_SCREEN:
|
||||||
|
_draw_score_screen(center_next)
|
||||||
|
ScoreText.show()
|
||||||
|
else:
|
||||||
|
match menu_mode:
|
||||||
|
MenuMode.SONG_SELECT:
|
||||||
|
GameTheme.set_screen_filter_alpha(1.0)
|
||||||
|
touch_rects[menu_mode] = _draw_song_select(center)
|
||||||
|
MenuMode.CHART_SELECT:
|
||||||
|
GameTheme.set_screen_filter_alpha(1.0)
|
||||||
|
touch_rects[menu_mode] = _draw_chart_select(center)
|
||||||
|
MenuMode.OPTIONS:
|
||||||
|
pass
|
||||||
|
MenuMode.GAMEPLAY:
|
||||||
|
GameTheme.set_screen_filter_alpha(0.0)
|
||||||
|
touch_rects[menu_mode] = _draw_gameplay(center)
|
||||||
|
MenuMode.SCORE_SCREEN:
|
||||||
|
GameTheme.set_screen_filter_alpha(score_screen_filter_alpha)
|
||||||
|
touch_rects[menu_mode] = _draw_score_screen(center)
|
||||||
|
ScoreText.show()
|
||||||
|
|
||||||
|
func set_menu_mode(mode):
|
||||||
|
Receptors.fade(mode == MenuMode.GAMEPLAY)
|
||||||
|
if mode == MenuMode.GAMEPLAY:
|
||||||
|
PVMusic.stop()
|
||||||
|
rect_clip_content = false
|
||||||
|
else:
|
||||||
|
rect_clip_content = true
|
||||||
|
menu_mode_prev = menu_mode
|
||||||
|
menu_mode = mode
|
||||||
|
menu_mode_prev_fade_timer = menu_mode_prev_fade_timer_duration
|
||||||
|
|
||||||
|
func touch_select_song(touchdict):
|
||||||
|
if (self.selected_genre == touchdict.genre_idx) and (self.selected_song_idx == touchdict.song_idx):
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||||
|
# var songslist = genres[genres.keys()[selected_genre]]
|
||||||
|
# selected_song_key = songslist[self.target_song_idx % len(songslist)]
|
||||||
|
set_menu_mode(MenuMode.CHART_SELECT)
|
||||||
|
else:
|
||||||
|
self.selected_genre = touchdict.genre_idx
|
||||||
|
self.target_song_idx = touchdict.song_idx
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -4.5)
|
||||||
|
load_preview()
|
||||||
|
|
||||||
|
func touch_select_chart(touchdict):
|
||||||
|
if touchdict.chart_idx == selected_difficulty:
|
||||||
|
if touchdict.enabled:
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||||
|
set_menu_mode(MenuMode.GAMEPLAY)
|
||||||
|
else:
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_error, 0.0)
|
||||||
|
elif touchdict.chart_idx < 0:
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -3.0, 0.7)
|
||||||
|
set_menu_mode(MenuMode.SONG_SELECT)
|
||||||
|
else:
|
||||||
|
self.selected_difficulty = touchdict.chart_idx
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -4.5)
|
||||||
|
|
||||||
|
func touch_gameplay(touchdict):
|
||||||
|
if touchdict.has('action'):
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||||
|
if touchdict.action == 'stop':
|
||||||
|
NoteHandler.stop()
|
||||||
|
|
||||||
|
func touch_score_screen(touchdict):
|
||||||
|
if touchdict.has('next_menu'):
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||||
|
set_menu_mode(touchdict.next_menu)
|
||||||
|
ScoreText.score = ''
|
||||||
|
ScoreText.score_sub = ''
|
||||||
|
# TODO: time this to coincide with the menu going fully offscreen
|
||||||
|
ScoreText.update()
|
||||||
|
elif touchdict.has('action'):
|
||||||
|
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||||
|
if touchdict.action == 'save':
|
||||||
|
save_score()
|
||||||
|
|
||||||
|
func finished_song(song_key, score_data):
|
||||||
|
scorescreen_song_key = song_key
|
||||||
|
scorescreen_score_data = score_data
|
||||||
|
scorescreen_datetime = OS.get_datetime()
|
||||||
|
scorescreen_saved = false
|
||||||
|
set_menu_mode(MenuMode.SCORE_SCREEN)
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
if !visible:
|
||||||
|
return
|
||||||
|
if (event is InputEventMouseButton): # Add this if we ever manage to be rid of the curse of Touch->Mouse emulation: (event is InputEventScreenTouch)
|
||||||
|
# print(event)
|
||||||
|
if event.pressed:
|
||||||
|
var pos = event.position - get_global_transform_with_canvas().get_origin()
|
||||||
|
match menu_mode:
|
||||||
|
MenuMode.SONG_SELECT:
|
||||||
|
for d in touch_rects[MenuMode.SONG_SELECT]:
|
||||||
|
if d.rect.has_point(pos):
|
||||||
|
touch_select_song(d)
|
||||||
|
MenuMode.CHART_SELECT:
|
||||||
|
for d in touch_rects[MenuMode.CHART_SELECT]:
|
||||||
|
if d.rect.has_point(pos):
|
||||||
|
touch_select_chart(d)
|
||||||
|
MenuMode.GAMEPLAY:
|
||||||
|
for d in touch_rects[MenuMode.GAMEPLAY]:
|
||||||
|
if d.rect.has_point(pos):
|
||||||
|
touch_gameplay(d)
|
||||||
|
MenuMode.SCORE_SCREEN:
|
||||||
|
for d in touch_rects[MenuMode.SCORE_SCREEN]:
|
||||||
|
if d.rect.has_point(pos):
|
||||||
|
touch_score_screen(d)
|
||||||
|
match menu_mode:
|
||||||
|
MenuMode.SONG_SELECT:
|
||||||
|
if event.is_action_pressed('ui_right'): # Sadly can't use match with this input system
|
||||||
|
self.target_song_idx += 1
|
||||||
|
elif event.is_action_pressed('ui_left'):
|
||||||
|
self.target_song_idx -= 1
|
||||||
|
elif event.is_action_pressed('ui_up'):
|
||||||
|
selected_genre = posmod(selected_genre - 1, len(genres))
|
||||||
|
elif event.is_action_pressed('ui_down'):
|
||||||
|
selected_genre = posmod(selected_genre + 1, len(genres))
|
||||||
|
elif event.is_action_pressed('ui_page_up'):
|
||||||
|
selected_difficulty = int(max(0, selected_difficulty - 1))
|
||||||
|
elif event.is_action_pressed('ui_page_down'):
|
||||||
|
selected_difficulty = int(min(6, selected_difficulty + 1))
|
|
@ -1,17 +1,14 @@
|
||||||
#extends Object
|
#extends Object
|
||||||
extends Node
|
extends Node
|
||||||
|
var FileHelpers := preload('res://scripts/FileHelpers.gd')
|
||||||
|
|
||||||
const ERROR_CODES := [
|
var RGT := preload('res://formats/RGT.gd')
|
||||||
'OK', 'FAILED', 'ERR_UNAVAILABLE', 'ERR_UNCONFIGURED', 'ERR_UNAUTHORIZED', 'ERR_PARAMETER_RANGE_ERROR',
|
var SM := preload('res://formats/SM.gd')
|
||||||
'ERR_OUT_OF_MEMORY', 'ERR_FILE_NOT_FOUND', 'ERR_FILE_BAD_DRIVE', 'ERR_FILE_BAD_PATH','ERR_FILE_NO_PERMISSION',
|
var SRT := preload('res://formats/SRT.gd')
|
||||||
'ERR_FILE_ALREADY_IN_USE', 'ERR_FILE_CANT_OPEN', 'ERR_FILE_CANT_WRITE', 'ERR_FILE_CANT_READ', 'ERR_FILE_UNRECOGNIZED',
|
|
||||||
'ERR_FILE_CORRUPT', 'ERR_FILE_MISSING_DEPENDENCIES', 'ERR_FILE_EOF', 'ERR_CANT_OPEN', 'ERR_CANT_CREATE', 'ERR_QUERY_FAILED',
|
const NOT_FOUND := ''
|
||||||
'ERR_ALREADY_IN_USE', 'ERR_LOCKED', 'ERR_TIMEOUT', 'ERR_CANT_CONNECT', 'ERR_CANT_RESOLVE', 'ERR_CONNECTION_ERROR',
|
|
||||||
'ERR_CANT_ACQUIRE_RESOURCE', 'ERR_CANT_FORK', 'ERR_INVALID_DATA', 'ERR_INVALID_PARAMETER', 'ERR_ALREADY_EXISTS',
|
const default_difficulty_keys = ['Z', 'B', 'A', 'E', 'M', 'R']
|
||||||
'ERR_DOES_NOT_EXIST', 'ERR_DATABASE_CANT_READ', 'ERR_DATABASE_CANT_WRITE', 'ERR_COMPILATION_FAILED', 'ERR_METHOD_NOT_FOUND',
|
|
||||||
'ERR_LINK_FAILED', 'ERR_SCRIPT_FAILED', 'ERR_CYCLIC_LINK', 'ERR_INVALID_DECLARATION', 'ERR_DUPLICATE_SYMBOL',
|
|
||||||
'ERR_PARSE_ERROR', 'ERR_BUSY', 'ERR_SKIP', 'ERR_HELP', 'ERR_BUG'
|
|
||||||
]
|
|
||||||
|
|
||||||
var userroot := OS.get_user_data_dir().rstrip('/')+'/' if OS.get_name() != 'Android' else '/storage/emulated/0/RhythmGame/'
|
var userroot := OS.get_user_data_dir().rstrip('/')+'/' if OS.get_name() != 'Android' else '/storage/emulated/0/RhythmGame/'
|
||||||
var PATHS := PoolStringArray([userroot, '/media/fridge-q/Games/RTG/slow_userdir/']) # Temporary hardcoded testing
|
var PATHS := PoolStringArray([userroot, '/media/fridge-q/Games/RTG/slow_userdir/']) # Temporary hardcoded testing
|
||||||
|
@ -21,42 +18,38 @@ var PATHS := PoolStringArray([userroot, '/media/fridge-q/Games/RTG/slow_userdir/
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
print('Library paths: ', PATHS)
|
print('Library paths: ', PATHS)
|
||||||
|
|
||||||
func find_file(name: String) -> String:
|
|
||||||
|
func find_file(name: String, print_notfound:=false) -> String:
|
||||||
# Searches through all of the paths to find the file
|
# Searches through all of the paths to find the file
|
||||||
var file := File.new()
|
var file := File.new()
|
||||||
for root in PATHS:
|
for root in PATHS:
|
||||||
var filename: String = root + name
|
var filename: String = root + name
|
||||||
if file.file_exists(filename):
|
if file.file_exists(filename):
|
||||||
return filename
|
return filename
|
||||||
return ''
|
if print_notfound:
|
||||||
|
print('File not found in any libraries: ', name)
|
||||||
|
return NOT_FOUND
|
||||||
|
|
||||||
func directory_list(directory: String, hidden: bool, sort:=true) -> Dictionary:
|
|
||||||
# Sadly there's no filelist sugar so we make our own
|
|
||||||
var output = {folders=[], files=[], err=OK}
|
|
||||||
var dir = Directory.new()
|
|
||||||
output.err = dir.open(directory)
|
|
||||||
if output.err != OK:
|
|
||||||
print_debug('Failed to open directory: ' + directory + '(Error code '+output.err+')')
|
|
||||||
return output
|
|
||||||
output.err = dir.list_dir_begin(true, !hidden)
|
|
||||||
if output.err != OK:
|
|
||||||
print_debug('Failed to begin listing directory: ' + directory + '(Error code '+output.err+')')
|
|
||||||
return output
|
|
||||||
|
|
||||||
var item = dir.get_next()
|
var fallback_audiostream = AudioStreamOGGVorbis.new()
|
||||||
while (item != ''):
|
var fallback_videostream = VideoStreamWebm.new()
|
||||||
if dir.current_is_dir():
|
var fallback_texture := ImageTexture.new()
|
||||||
output['folders'].append(item)
|
|
||||||
else:
|
func load_ogg(name: String) -> AudioStreamOGGVorbis: # Searches through all of the paths to find the file
|
||||||
output['files'].append(item)
|
match find_file(name):
|
||||||
item = dir.get_next()
|
NOT_FOUND: return fallback_audiostream
|
||||||
dir.list_dir_end()
|
var filename: return FileHelpers.load_ogg(filename)
|
||||||
|
|
||||||
|
func load_video(name: String): # Searches through all of the paths to find the file
|
||||||
|
match find_file(name):
|
||||||
|
NOT_FOUND: return fallback_videostream
|
||||||
|
var filename: return FileHelpers.load_video(filename)
|
||||||
|
|
||||||
|
func load_image(name: String) -> ImageTexture: # Searches through all of the paths to find the file
|
||||||
|
match find_file(name, true):
|
||||||
|
NOT_FOUND: return fallback_texture
|
||||||
|
var filename: return FileHelpers.load_image(filename)
|
||||||
|
|
||||||
if sort:
|
|
||||||
output.folders.sort()
|
|
||||||
output.files.sort()
|
|
||||||
# Maybe convert the Arrays to PoolStringArrays?
|
|
||||||
return output
|
|
||||||
|
|
||||||
func find_by_extensions(array, extensions=null) -> Dictionary:
|
func find_by_extensions(array, extensions=null) -> Dictionary:
|
||||||
# Both args can be Array or PoolStringArray
|
# Both args can be Array or PoolStringArray
|
||||||
|
@ -78,8 +71,8 @@ func find_by_extensions(array, extensions=null) -> Dictionary:
|
||||||
output[ext] = [filename]
|
output[ext] = [filename]
|
||||||
return output
|
return output
|
||||||
|
|
||||||
const default_difficulty_keys = ['Z', 'B', 'A', 'E', 'M', 'R']
|
|
||||||
func scan_library():
|
func scan_library() -> Dictionary:
|
||||||
print('Scanning library')
|
print('Scanning library')
|
||||||
var song_defs = {}
|
var song_defs = {}
|
||||||
var collections = {}
|
var collections = {}
|
||||||
|
@ -93,7 +86,7 @@ func scan_library():
|
||||||
print_debug('An error occurred while trying to create the songs directory: ', err)
|
print_debug('An error occurred while trying to create the songs directory: ', err)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
var songslist = directory_list(rootdir, false)
|
var songslist = FileHelpers.directory_list(rootdir, false)
|
||||||
if songslist.err != OK:
|
if songslist.err != OK:
|
||||||
print('An error occurred when trying to access the songs directory: ', songslist.err)
|
print('An error occurred when trying to access the songs directory: ', songslist.err)
|
||||||
return songslist.err
|
return songslist.err
|
||||||
|
@ -138,7 +131,7 @@ func scan_library():
|
||||||
genres[song_defs[song_key]['genre']] = [song_key]
|
genres[song_defs[song_key]['genre']] = [song_key]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
var files_by_ext = find_by_extensions(directory_list(full_folder, false).files)
|
var files_by_ext = find_by_extensions(FileHelpers.directory_list(full_folder, false).files)
|
||||||
if 'sm' in files_by_ext:
|
if 'sm' in files_by_ext:
|
||||||
var sm_filename = files_by_ext['sm'][0]
|
var sm_filename = files_by_ext['sm'][0]
|
||||||
print(sm_filename)
|
print(sm_filename)
|
||||||
|
@ -153,465 +146,6 @@ func scan_library():
|
||||||
return {song_defs=song_defs, genres=genres}
|
return {song_defs=song_defs, genres=genres}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SRT:
|
|
||||||
const TAP_DURATION := 0.062500
|
|
||||||
const ID_BREAK := 4
|
|
||||||
const ID_HOLD := 2
|
|
||||||
const ID_SLIDE_END := 128
|
|
||||||
const ID3_SLIDE_CHORD := 0 # Straight line
|
|
||||||
const ID3_SLIDE_ARC_CW := 1
|
|
||||||
const ID3_SLIDE_ARC_ACW := 2
|
|
||||||
|
|
||||||
static func load_file(filename):
|
|
||||||
var file = File.new()
|
|
||||||
var err = file.open(filename, File.READ)
|
|
||||||
if err != OK:
|
|
||||||
print(err)
|
|
||||||
return err
|
|
||||||
var metadata := {}
|
|
||||||
var num_taps := 0
|
|
||||||
var num_holds := 0
|
|
||||||
var num_slides := 0
|
|
||||||
var notes := []
|
|
||||||
var beats_per_measure := 4
|
|
||||||
var length = file.get_len()
|
|
||||||
var slide_ids = {}
|
|
||||||
while (file.get_position() < (length-2)):
|
|
||||||
var noteline = file.get_csv_line()
|
|
||||||
var time_hit := (float(noteline[0]) + (float(noteline[1]))) * beats_per_measure
|
|
||||||
var duration := float(noteline[2]) * beats_per_measure
|
|
||||||
var column := int(noteline[3])
|
|
||||||
var id := int(noteline[4])
|
|
||||||
var id2 := int(noteline[5])
|
|
||||||
var id3 := int(noteline[6])
|
|
||||||
|
|
||||||
match id:
|
|
||||||
ID_HOLD:
|
|
||||||
notes.push_back(Note.NoteHold.new(time_hit, column, duration))
|
|
||||||
num_holds += 1
|
|
||||||
ID_BREAK:
|
|
||||||
notes.push_back(Note.NoteTap.new(time_hit, column, true))
|
|
||||||
num_taps += 1
|
|
||||||
ID_SLIDE_END:
|
|
||||||
# id2 is slide ID
|
|
||||||
if id2 in slide_ids:
|
|
||||||
slide_ids[id2].column_release = column
|
|
||||||
slide_ids[id2].update_slide_variables()
|
|
||||||
_:
|
|
||||||
if id2 == 0:
|
|
||||||
notes.push_back(Note.NoteTap.new(time_hit, column))
|
|
||||||
num_taps += 1
|
|
||||||
else:
|
|
||||||
# id2 is slide ID, id3 is slide pattern
|
|
||||||
# In order to properly declare the slide, we need the paired endcap which may not be the next note
|
|
||||||
var slide_type = Note.SlideType.CHORD
|
|
||||||
match id3:
|
|
||||||
ID3_SLIDE_CHORD:
|
|
||||||
slide_type = Note.SlideType.CHORD
|
|
||||||
ID3_SLIDE_ARC_CW:
|
|
||||||
slide_type = Note.SlideType.ARC_CW
|
|
||||||
ID3_SLIDE_ARC_ACW:
|
|
||||||
slide_type = Note.SlideType.ARC_ACW
|
|
||||||
_:
|
|
||||||
print('Unknown slide type: ', id3)
|
|
||||||
var note = Note.NoteStar.new(time_hit, column)
|
|
||||||
num_slides += 1
|
|
||||||
note.duration = duration
|
|
||||||
notes.push_back(note)
|
|
||||||
var slide = Note.NoteSlide.new(time_hit, column, duration, -1, slide_type)
|
|
||||||
notes.push_back(slide)
|
|
||||||
slide_ids[id2] = slide
|
|
||||||
metadata['num_taps'] = num_taps
|
|
||||||
metadata['num_holds'] = num_holds
|
|
||||||
metadata['num_slides'] = num_slides
|
|
||||||
return [metadata, notes]
|
|
||||||
|
|
||||||
|
|
||||||
class RGT:
|
|
||||||
# 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]
|
|
||||||
|
|
||||||
|
|
||||||
class SM:
|
|
||||||
# Stepmania simfile
|
|
||||||
|
|
||||||
const NOTE_VALUES = {
|
|
||||||
'0': 'None',
|
|
||||||
'1': 'Tap',
|
|
||||||
'2': 'HoldStart',
|
|
||||||
'3': 'HoldRollEnd',
|
|
||||||
'4': 'RollStart',
|
|
||||||
'M': 'Mine',
|
|
||||||
# These three are less likely to show up anywhere, no need to implement
|
|
||||||
'K': 'Keysound',
|
|
||||||
'L': 'Lift',
|
|
||||||
'F': 'Fake',
|
|
||||||
}
|
|
||||||
|
|
||||||
const CHART_DIFFICULTIES = {
|
|
||||||
'Beginner': 0,
|
|
||||||
'Easy': 1,
|
|
||||||
'Medium': 2,
|
|
||||||
'Hard': 3,
|
|
||||||
'Challenge': 4,
|
|
||||||
'Edit': 5,
|
|
||||||
# Some will just write whatever for special difficulties, but we should at least color-code these standard ones
|
|
||||||
}
|
|
||||||
|
|
||||||
const TAG_TRANSLATIONS = {
|
|
||||||
'#TITLE': 'title',
|
|
||||||
'#SUBTITLE': 'subtitle',
|
|
||||||
'#ARTIST': 'artist',
|
|
||||||
'#TITLETRANSLIT': 'title_transliteration',
|
|
||||||
'#SUBTITLETRANSLIT': 'subtitle_transliteration',
|
|
||||||
'#ARTISTTRANSLIT': 'artist_transliteration',
|
|
||||||
'#GENRE': 'genre',
|
|
||||||
'#CREDIT': 'chart_author',
|
|
||||||
'#BANNER': 'image_banner',
|
|
||||||
'#BACKGROUND': 'image_background',
|
|
||||||
# '#LYRICSPATH': '',
|
|
||||||
'#CDTITLE': 'image_cd_title',
|
|
||||||
'#MUSIC': 'audio_filelist',
|
|
||||||
'#OFFSET': 'audio_offsets',
|
|
||||||
'#SAMPLESTART': 'audio_preview_times',
|
|
||||||
'#SAMPLELENGTH': 'audio_preview_times',
|
|
||||||
# '#SELECTABLE': '',
|
|
||||||
'#BPMS': 'bpm_values',
|
|
||||||
# '#STOPS': '',
|
|
||||||
# '#BGCHANGES': '',
|
|
||||||
# '#KEYSOUNDS': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
static func load_chart(lines):
|
|
||||||
var metadata = {}
|
|
||||||
var notes = []
|
|
||||||
|
|
||||||
assert(lines[0].begins_with('#NOTES:'))
|
|
||||||
metadata['chart_type'] = lines[1].strip_edges().rstrip(':')
|
|
||||||
metadata['description'] = lines[2].strip_edges().rstrip(':')
|
|
||||||
metadata['difficulty_str'] = lines[3].strip_edges().rstrip(':')
|
|
||||||
metadata['numerical_meter'] = lines[4].strip_edges().rstrip(':')
|
|
||||||
metadata['groove_radar'] = lines[5].strip_edges().rstrip(':')
|
|
||||||
|
|
||||||
# Measures are separated by lines that start with a comma
|
|
||||||
# Each line has a state for each of the pads, e.g. '0000' for none pressed
|
|
||||||
# The lines become even subdivisions of the measure, so if there's 4 lines everything represents a 1/4 beat, if there's 8 lines everything represents a 1/8 beat etc.
|
|
||||||
# For this reason it's probably best to just have a float for beat-within-measure rather than integer beats.
|
|
||||||
var measures = [[]]
|
|
||||||
for i in range(6, len(lines)):
|
|
||||||
var line = lines[i].strip_edges()
|
|
||||||
if line.begins_with(','):
|
|
||||||
measures.append([])
|
|
||||||
elif line.begins_with(';'):
|
|
||||||
break
|
|
||||||
elif len(line) > 0:
|
|
||||||
measures[-1].append(line)
|
|
||||||
|
|
||||||
var ongoing_holds = {}
|
|
||||||
var num_notes := 0
|
|
||||||
var num_jumps := 0
|
|
||||||
var num_hands := 0
|
|
||||||
var num_holds := 0
|
|
||||||
var num_rolls := 0
|
|
||||||
var num_mines := 0
|
|
||||||
|
|
||||||
for measure in range(len(measures)):
|
|
||||||
var m_lines = measures[measure]
|
|
||||||
var m_length = len(m_lines) # Divide out all lines by this
|
|
||||||
for beat in m_length:
|
|
||||||
var line : String = m_lines[beat]
|
|
||||||
# Jump check at a line-level (check for multiple 1/2/4s)
|
|
||||||
var hits : int = line.count('1') + line.count('2') + line.count('4')
|
|
||||||
# Hand/quad check more complex as need to check hold/roll state as well
|
|
||||||
# TODO: are they exclusive? Does quad override hand override jump? SM5 doesn't have quads and has hands+jumps inclusive
|
|
||||||
var total_pressed : int = hits + len(ongoing_holds)
|
|
||||||
var jump : bool = hits >= 2
|
|
||||||
var hand : bool = total_pressed >= 3
|
|
||||||
# var quad : bool = total_pressed >= 4
|
|
||||||
num_notes += hits
|
|
||||||
num_jumps += int(jump)
|
|
||||||
num_hands += int(hand)
|
|
||||||
var time = measure + beat/float(m_length)
|
|
||||||
for col in len(line):
|
|
||||||
match line[col]:
|
|
||||||
'1':
|
|
||||||
notes.append(Note.NoteTap.new(time, col))
|
|
||||||
'2': # Hold
|
|
||||||
ongoing_holds[col] = len(notes)
|
|
||||||
notes.append(Note.NoteHold.new(time, col, 0.0))
|
|
||||||
num_holds += 1
|
|
||||||
'4': # Roll
|
|
||||||
ongoing_holds[col] = len(notes)
|
|
||||||
notes.append(Note.NoteRoll.new(time, col, 0.0))
|
|
||||||
num_rolls += 1
|
|
||||||
'3': # End Hold/Roll
|
|
||||||
assert(ongoing_holds.has(col))
|
|
||||||
notes[ongoing_holds[col]].set_time_release(time)
|
|
||||||
ongoing_holds.erase(col)
|
|
||||||
'M': # Mine
|
|
||||||
num_mines += 1
|
|
||||||
pass
|
|
||||||
metadata['num_notes'] = num_notes
|
|
||||||
metadata['num_taps'] = num_notes - num_jumps
|
|
||||||
metadata['num_jumps'] = num_jumps
|
|
||||||
metadata['num_hands'] = num_hands
|
|
||||||
metadata['num_holds'] = num_holds
|
|
||||||
metadata['num_rolls'] = num_rolls
|
|
||||||
metadata['num_mines'] = num_mines
|
|
||||||
metadata['notes'] = notes
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
static func load_file(filename: String) -> Array:
|
|
||||||
# Output is [metadata, [[meta0, chart0], ..., [metaN, chartN]]]
|
|
||||||
# Technically, declarations end with a semicolon instead of a linebreak.
|
|
||||||
# This is a PITA to do correctly in GDScript and the files in our collection are well-behaved with linebreaks anyway, so we won't bother.
|
|
||||||
var file := File.new()
|
|
||||||
match file.open(filename, File.READ):
|
|
||||||
OK:
|
|
||||||
pass
|
|
||||||
var err:
|
|
||||||
print_debug('Error loading file: ', err)
|
|
||||||
return []
|
|
||||||
var length = file.get_len()
|
|
||||||
var lines = [[]] # First list will be header, then every subsequent one is a chart
|
|
||||||
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
|
||||||
var line : String = file.get_line()
|
|
||||||
if line.begins_with('#NOTES'): # Split to a new list for each chart definition
|
|
||||||
lines.append([])
|
|
||||||
lines[-1].append(line)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
var metadata = {}
|
|
||||||
for line in lines[0]:
|
|
||||||
var tokens = line.rstrip(';').split(':')
|
|
||||||
if TAG_TRANSLATIONS.has(tokens[0]):
|
|
||||||
metadata[TAG_TRANSLATIONS[tokens[0]]] = tokens[1]
|
|
||||||
elif len(tokens) >= 2:
|
|
||||||
metadata[tokens[0]] = tokens[1]
|
|
||||||
var charts = []
|
|
||||||
|
|
||||||
for i in range(1, len(lines)):
|
|
||||||
charts.append(load_chart(lines[i]))
|
|
||||||
|
|
||||||
return [metadata, charts]
|
|
||||||
|
|
||||||
|
|
||||||
class Test:
|
|
||||||
static func stress_pattern():
|
|
||||||
var notes = []
|
|
||||||
for bar in range(8):
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
|
||||||
for i in range(1, 8):
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (7/2.0), (bar + 3)%8))
|
|
||||||
for bar in range(8, 16):
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
|
||||||
for i in range(1, 8):
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + ((i+0.5)/2.0), (bar + i)%8))
|
|
||||||
notes.push_back(Note.make_slide(bar*4 + ((i+1)/2.0), 1, (bar + i)%8, 0))
|
|
||||||
for bar in range(16, 24):
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4, (bar+1)%8, 1))
|
|
||||||
for i in range(2, 8):
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4 + ((i+1)/2.0), (bar + i)%8, 0.5))
|
|
||||||
for bar in range(24, 32):
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
|
||||||
for i in range(1, 32):
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
|
||||||
if (i%2) > 0:
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 4)%8))
|
|
||||||
for bar in range(32, 48):
|
|
||||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
|
||||||
for i in range(1, 32):
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
|
||||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 3)%8))
|
|
||||||
return notes
|
|
||||||
|
|
||||||
|
|
||||||
func load_folder(folder, filename='song'):
|
func load_folder(folder, filename='song'):
|
||||||
var file = File.new()
|
var file = File.new()
|
||||||
var err = file.open('%s/%s.json' % [folder, filename], File.READ)
|
var err = file.open('%s/%s.json' % [folder, filename], File.READ)
|
||||||
|
@ -629,6 +163,7 @@ func load_folder(folder, filename='song'):
|
||||||
result.directory = folder
|
result.directory = folder
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
func load_filelist(filelist: Array, directory=''):
|
func load_filelist(filelist: Array, directory=''):
|
||||||
var charts = {}
|
var charts = {}
|
||||||
var key := 0
|
var key := 0
|
||||||
|
@ -636,7 +171,7 @@ func load_filelist(filelist: Array, directory=''):
|
||||||
var extension: String = name.rsplit('.', true, 1)[-1]
|
var extension: String = name.rsplit('.', true, 1)[-1]
|
||||||
name = directory.rstrip('/') + '/' + name
|
name = directory.rstrip('/') + '/' + name
|
||||||
var filename = find_file(name)
|
var filename = find_file(name)
|
||||||
if filename != '':
|
if filename != NOT_FOUND:
|
||||||
match extension:
|
match extension:
|
||||||
'rgtm': # multiple charts
|
'rgtm': # multiple charts
|
||||||
var res = RGT.load_file(filename)
|
var res = RGT.load_file(filename)
|
||||||
|
@ -659,63 +194,15 @@ func load_filelist(filelist: Array, directory=''):
|
||||||
pass
|
pass
|
||||||
return charts
|
return charts
|
||||||
|
|
||||||
func direct_load_ogg(filename: String) -> AudioStreamOGGVorbis:
|
|
||||||
# Loads the ogg file with that exact filename
|
|
||||||
var audiostream = AudioStreamOGGVorbis.new()
|
|
||||||
var oggfile = File.new()
|
|
||||||
oggfile.open(filename, File.READ)
|
|
||||||
audiostream.set_data(oggfile.get_buffer(oggfile.get_len()))
|
|
||||||
oggfile.close()
|
|
||||||
return audiostream
|
|
||||||
|
|
||||||
var fallback_audiostream = AudioStreamOGGVorbis.new()
|
|
||||||
func load_ogg(name: String) -> AudioStreamOGGVorbis:
|
|
||||||
# Searches through all of the paths to find the file
|
|
||||||
match find_file(name):
|
|
||||||
'': return fallback_audiostream
|
|
||||||
var filename: return direct_load_ogg(filename)
|
|
||||||
|
|
||||||
var fallback_videostream = VideoStreamWebm.new()
|
|
||||||
func load_video(name: String):
|
|
||||||
match find_file(name):
|
|
||||||
'': return fallback_videostream
|
|
||||||
var filename:
|
|
||||||
return load(filename)
|
|
||||||
# var videostream = VideoStreamGDNative.new()
|
|
||||||
# videostream.set_file(filename1)
|
|
||||||
# return videostream
|
|
||||||
|
|
||||||
func direct_load_image(filename: String) -> ImageTexture:
|
|
||||||
var tex := ImageTexture.new()
|
|
||||||
var img := Image.new()
|
|
||||||
img.load(filename)
|
|
||||||
tex.create_from_image(img)
|
|
||||||
return tex
|
|
||||||
|
|
||||||
var fallback_texture := ImageTexture.new()
|
|
||||||
func load_image(name: String) -> ImageTexture:
|
|
||||||
var filename = find_file(name)
|
|
||||||
if filename != '':
|
|
||||||
return direct_load_image(filename)
|
|
||||||
print('File not found: ', name)
|
|
||||||
return fallback_texture
|
|
||||||
|
|
||||||
|
|
||||||
func init_directory(directory: String):
|
|
||||||
var dir = Directory.new()
|
|
||||||
var err = dir.make_dir_recursive(directory)
|
|
||||||
if err != OK:
|
|
||||||
print('An error occurred while trying to create the scores directory: ', err, ERROR_CODES[err])
|
|
||||||
return err
|
|
||||||
|
|
||||||
func save_json(filename: String, data: Dictionary):
|
func save_json(filename: String, data: Dictionary):
|
||||||
filename = userroot + filename
|
filename = userroot + filename
|
||||||
var dir = filename.rsplit('/', true, 1)[0]
|
var dir = filename.rsplit('/', true, 1)[0]
|
||||||
match FileLoader.init_directory(dir):
|
match FileHelpers.init_directory(dir):
|
||||||
OK:
|
OK:
|
||||||
pass
|
pass
|
||||||
var err:
|
var err:
|
||||||
print_debug('Error making directory for JSON file: ', err, ERROR_CODES[err])
|
print_debug('Error making directory for JSON file: ', err, FileHelpers.ERROR_CODES[err])
|
||||||
return err
|
return err
|
||||||
var json = JSON.print(data)
|
var json = JSON.print(data)
|
||||||
var file = File.new()
|
var file = File.new()
|
||||||
|
@ -725,9 +212,10 @@ func save_json(filename: String, data: Dictionary):
|
||||||
file.close()
|
file.close()
|
||||||
return OK
|
return OK
|
||||||
var err:
|
var err:
|
||||||
print_debug('Error saving JSON file: ', err, ERROR_CODES[err])
|
print_debug('Error saving JSON file: ', err, FileHelpers.ERROR_CODES[err])
|
||||||
return err
|
return err
|
||||||
|
|
||||||
|
|
||||||
func load_json(filename: String):
|
func load_json(filename: String):
|
||||||
var file = File.new()
|
var file = File.new()
|
||||||
var err
|
var err
|
||||||
|
@ -736,7 +224,7 @@ func load_json(filename: String):
|
||||||
if file.file_exists(filename1):
|
if file.file_exists(filename1):
|
||||||
err = file.open(filename1, File.READ)
|
err = file.open(filename1, File.READ)
|
||||||
if err != OK:
|
if err != OK:
|
||||||
print('An error occurred while trying to open file: ', filename1, err, ERROR_CODES[err])
|
print('An error occurred while trying to open file: ', filename1, err, FileHelpers.ERROR_CODES[err])
|
||||||
continue # return err
|
continue # return err
|
||||||
var result_json = JSON.parse(file.get_as_text())
|
var result_json = JSON.parse(file.get_as_text())
|
||||||
file.close()
|
file.close()
|
||||||
|
|
Loading…
Reference in New Issue