diff --git a/scripts/FileHelpers.gd b/scripts/FileHelpers.gd index 26362a4..f1c7b1e 100644 --- a/scripts/FileHelpers.gd +++ b/scripts/FileHelpers.gd @@ -68,6 +68,27 @@ static func directory_list(directory: String, hidden: bool, sort:=true) -> Dicti return output +static func find_by_extensions(array, extensions=null) -> Dictionary: + # Both args can be Array or PoolStringArray + # If extensions omitted, do all extensions + var output = {} + if extensions: + for ext in extensions: + output[ext] = [] + for filename in array: + for ext in extensions: + if filename.ends_with(ext): + output[ext].append(filename) + else: + for filename in array: + var ext = filename.rsplit('.', false, 1)[1] + if ext in output: + output[ext].append(filename) + else: + output[ext] = [filename] + return output + + static func init_directory(directory: String): var dir = Directory.new() var err = dir.make_dir_recursive(directory) diff --git a/scripts/NoteHandler.gd b/scripts/NoteHandler.gd index 2981526..dd3eec0 100644 --- a/scripts/NoteHandler.gd +++ b/scripts/NoteHandler.gd @@ -1,4 +1,5 @@ extends Control +var RadialMeshTools := preload('res://scripts/RadialMeshTools.gd') var screen_height := 1080 @@ -18,11 +19,6 @@ onready var notelines = $'Viewport/Center/notelines' onready var meshinstance = $'Viewport/Center/meshinstance' onready var lbl_combo = $lbl_combo -const SQRT2 := sqrt(2) -const DEG45 := deg2rad(45.0) -const DEG90 := deg2rad(90.0) -const DEG135 := deg2rad(135.0) - var time_zero_msec: int = 0 var time: float = 0.0 var t: float = 0.0 # Game time @@ -42,37 +38,7 @@ var slide_trail_mesh_instances := {} var noteline_array_image := Image.new() -# Text UVs -var text_UV_arrays := [] -func make_text_UV(row: int, column: int) -> PoolVector2Array: - return PoolVector2Array([Vector2(column/4.0, row/8.0), Vector2((column+1)/4.0, row/8.0), Vector2(column/4.0, (row+1)/8.0), Vector2((column+1)/4.0, (row+1)/8.0)]) -func make_text_UVs(): - for row in 8: - for column in 4: - text_UV_arrays.append(make_text_UV(row, column)) -enum TextStyle {STRAIGHT=0, ARC=1, ARC_EARLY=2, ARC_LATE=3} -enum TextWord {NICE=0, OK=4, NG=8, PERFECT=12, GREAT=16, GOOD=20, ALMOST=24, MISS=28} -const TextJudgement := { - 0: TextWord.PERFECT + TextStyle.ARC, - 1: TextWord.GREAT + TextStyle.ARC_LATE, - -1: TextWord.GREAT + TextStyle.ARC_EARLY, - 2: TextWord.GOOD + TextStyle.ARC_LATE, - -2: TextWord.GOOD + TextStyle.ARC_EARLY, - 3: TextWord.ALMOST + TextStyle.ARC_LATE, - -3: TextWord.ALMOST + TextStyle.ARC_EARLY, - 'MISS': TextWord.MISS + TextStyle.ARC -} -const TextJudgementStraight := { - 0: TextWord.PERFECT + TextStyle.STRAIGHT, - 1: TextWord.GREAT + TextStyle.STRAIGHT, - -1: TextWord.GREAT + TextStyle.STRAIGHT, - 2: TextWord.GOOD + TextStyle.STRAIGHT, - -2: TextWord.GOOD + TextStyle.STRAIGHT, - 3: TextWord.ALMOST + TextStyle.STRAIGHT, - -3: TextWord.ALMOST + TextStyle.STRAIGHT, - 'MISS': TextWord.MISS + TextStyle.STRAIGHT -} - +#---------------------------------------------------------------------------------------------------------------------------------------------- var current_combo := 0 func increment_combo(): current_combo += 1 @@ -87,132 +53,16 @@ func initialise_scores(): scores = {} for type in [Note.NOTE_TAP, Note.NOTE_HOLD, Note.NOTE_STAR]: scores[type] = {} - for key in TextJudgement: + for key in RadialMeshTools.TextJudgement: scores[type][key] = 0 # Release types for type in [Note.NOTE_HOLD, Note.NOTE_SLIDE]: scores[Note.RELEASE_SCORE_TYPES[type]] = {} - for key in TextJudgement: + for key in RadialMeshTools.TextJudgement: scores[Note.RELEASE_SCORE_TYPES[type]][key] = 0 scores['max_combo'] = 0 current_combo = 0 -func make_text_mesh(mesh: ArrayMesh, text_id: int, pos: Vector2, angle: float, alpha:=1.0, scale:=1.0): - var r := GameTheme.judge_text_size2 * scale - var vertex_array := PoolVector2Array([ - pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG2), # TODO: fix this UV/vertex order mess - pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG1), - pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG4), - pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG3) - ]) - var arrays = [] - arrays.resize(Mesh.ARRAY_MAX) - arrays[Mesh.ARRAY_VERTEX] = vertex_array - arrays[Mesh.ARRAY_TEX_UV] = text_UV_arrays[text_id] - arrays[Mesh.ARRAY_COLOR] = GameTheme.color_array_text(alpha) - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) - -func make_judgement_text(mesh: ArrayMesh, text_id: int, col: int, progress:=0.0): - make_text_mesh(mesh, text_id, - GameTheme.RADIAL_UNIT_VECTORS[col] * GameTheme.receptor_ring_radius * lerp(0.85, 0.85*0.75, progress), - GameTheme.RADIAL_COL_ANGLES[col]-PI/2.0, lerp(1.0, 0.0, progress), lerp(1.0, 0.75, progress) - ) - -# ---------------------------------------------------------------------------------------------------------------------------------------------------- -# Helper functions to generate meshes from vertex arrays -func make_tap_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, color_array:=GameTheme.COLOR_ARRAY_TAP): - var dim = GameTheme.sprite_size2 * scale - var vertex_array = PoolVector2Array([note_center + Vector2(-dim, -dim), note_center + Vector2(dim, -dim), note_center + Vector2(-dim, dim), note_center + Vector2(dim, dim)]) - var arrays = [] - arrays.resize(Mesh.ARRAY_MAX) - arrays[Mesh.ARRAY_VERTEX] = vertex_array - arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_TAP - arrays[Mesh.ARRAY_COLOR] = color_array - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) - -func make_hold_mesh(mesh: ArrayMesh, note_center: Vector2, note_center_rel: Vector2, scale:=1.0, angle:=0.0, color_array = GameTheme.COLOR_ARRAY_HOLD): - var dim = GameTheme.sprite_size2 * scale - var dim2 = dim * SQRT2 - var a1 = angle - DEG45 - var a2 = angle + DEG45 - var a3 = angle - DEG90 - var a4 = angle + DEG90 - var a5 = angle - DEG135 - var a6 = angle + DEG135 - var vertex_array = PoolVector2Array([ - note_center + polar2cartesian(dim2, a1), note_center + polar2cartesian(dim2, a2), - note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4), - note_center_rel + polar2cartesian(dim, a3), note_center_rel + polar2cartesian(dim, a4), - note_center_rel + polar2cartesian(dim2, a5), note_center_rel + polar2cartesian(dim2, a6) - ]) - var arrays = [] - arrays.resize(Mesh.ARRAY_MAX) - arrays[Mesh.ARRAY_VERTEX] = vertex_array - arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_HOLD - arrays[Mesh.ARRAY_COLOR] = color_array - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) - -func make_star_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, angle:=0.0, color_array:=GameTheme.COLOR_ARRAY_STAR): - var dim = GameTheme.sprite_size2 * scale * SQRT2 - var a1 = angle - DEG45 - var a2 = angle + DEG45 - var a3 = angle - DEG135 - var a4 = angle + DEG135 - var vertex_array = PoolVector2Array([ - note_center + polar2cartesian(dim, a1), note_center + polar2cartesian(dim, a2), - note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4) - ]) - var arrays = [] - arrays.resize(Mesh.ARRAY_MAX) - arrays[Mesh.ARRAY_VERTEX] = vertex_array - arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_STAR - arrays[Mesh.ARRAY_COLOR] = color_array - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) - -#func make_arrow_mesh(mesh: ArrayMesh, vertex_array, color_array = GameTheme.COLOR_ARRAY_TAP): -# var arrays = [] -# arrays.resize(Mesh.ARRAY_MAX) -# arrays[Mesh.ARRAY_VERTEX] = vertex_array -# arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_ARROW -# arrays[Mesh.ARRAY_COLOR] = color_array -# mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) - - -const slide_arrows_per_unit_length := 10 -func make_slide_trail_mesh(note) -> ArrayMesh: - # Generates a mesh centered around origin. Make sure the MeshInstance2D that draws this is centered on the screen. - var mesh = ArrayMesh.new() - var arrays = [] - arrays.resize(Mesh.ARRAY_MAX) - var vertices := PoolVector2Array() - var uvs := PoolVector2Array() - var colors := PoolColorArray() - var size := GameTheme.sprite_size2 * sqrt(2) - var color := GameTheme.COLOR_DOUBLE_SLIDE if note.double_hit else GameTheme.COLOR_SLIDE - - match note.get_points(): - [var positions, var angles]: - var trail_length : int = len(positions) - vertices.resize(3*trail_length) - uvs.resize(3*trail_length) - colors.resize(3*trail_length) - for i in trail_length: - var idx = (trail_length-i-1)*3 # We want the earliest ones to be drawn last so that loops/sharp bends will have the first pass on top - var u = GameTheme.UV_ARRAY_SLIDE_ARROW if i%3 else GameTheme.UV_ARRAY_SLIDE_ARROW2 - for j in 3: - uvs[idx+j] = u[j] - colors[idx+j] = Color(color.r, color.g, color.b, (1.0+float(i))/float(trail_length)) - var angle : float = angles[i] - var offset : Vector2 = positions[i] * GameTheme.receptor_ring_radius - vertices[idx] = offset - vertices[idx+1] = offset + polar2cartesian(size, angle+PI*0.75) - vertices[idx+2] = offset + polar2cartesian(size, angle-PI*0.75) - - arrays[Mesh.ARRAY_VERTEX] = vertices - arrays[Mesh.ARRAY_TEX_UV] = uvs - arrays[Mesh.ARRAY_COLOR] = colors - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) - return mesh #---------------------------------------------------------------------------------------------------------------------------------------------- func make_judgement_column(judgement, column: int): @@ -345,11 +195,11 @@ func _draw(): match note.type: Note.NOTE_TAP: color = GameTheme.color_array_tap(clamp((note.time_death-t)/Note.DEATH_DELAY, 0.0, 1.0), note.double_hit) - make_tap_mesh(mesh, note_center, scale, color) + RadialMeshTools.make_tap_mesh(mesh, note_center, scale, color) Note.NOTE_STAR: color = GameTheme.color_array_star(clamp((note.time_death-t)/Note.DEATH_DELAY, 0.0, 1.0), note.double_hit) var angle = fmod(t/note.duration, 1.0)*TAU - make_star_mesh(mesh, note_center, scale, angle, color) + RadialMeshTools.make_star_mesh(mesh, note_center, scale, angle, color) Note.NOTE_HOLD: if note.is_held: position = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats @@ -376,7 +226,7 @@ func _draw(): if position_rel < GameTheme.INNER_NOTE_CIRCLE_RATIO: position_rel = GameTheme.INNER_NOTE_CIRCLE_RATIO var note_center_rel = (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position_rel * GameTheme.receptor_ring_radius) - make_hold_mesh(mesh, note_center, note_center_rel, scale, GameTheme.RADIAL_COL_ANGLES[note.column], color) + RadialMeshTools.make_hold_mesh(mesh, note_center, note_center_rel, scale, GameTheme.RADIAL_COL_ANGLES[note.column], color) Note.NOTE_SLIDE: var trail_alpha := 1.0 if position < GameTheme.INNER_NOTE_CIRCLE_RATIO: @@ -387,7 +237,7 @@ func _draw(): var trail_progress : float = clamp((t - note.time_hit - GameTheme.SLIDE_DELAY)/(note.duration - GameTheme.SLIDE_DELAY), 0.0, 1.0) var star_pos : Vector2 = note.get_position(trail_progress) * GameTheme.receptor_ring_radius var star_angle : float = note.get_angle(trail_progress) - make_star_mesh(mesh, star_pos, 1.33, star_angle) + RadialMeshTools.make_star_mesh(mesh, star_pos, 1.33, star_angle) if note.progress != INF: slide_trail_mesh_instances[note.slide_id].material.set_shader_param('trail_progress', note.progress) if t > note.time_release: @@ -403,7 +253,7 @@ func _draw(): var textmesh := ArrayMesh.new() for text in active_judgement_texts: - make_judgement_text(textmesh, TextJudgement[text.judgement], text.col, (t-text.time)/GameTheme.judge_text_duration) + RadialMeshTools.make_judgement_text(textmesh, RadialMeshTools.TextJudgement[text.judgement], text.col, (t-text.time)/GameTheme.judge_text_duration) JudgeText.set_mesh(textmesh) @@ -436,7 +286,6 @@ func _input(event): func _init(): Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN) GameTheme.init_radial_values() - make_text_UVs() initialise_scores() func set_time(seconds: float): @@ -507,7 +356,7 @@ func load_track(song_key: String, difficulty_key: String): Note.process_note_list(all_notes, false) for note in all_notes: if note.type == Note.NOTE_SLIDE: - slide_trail_meshes[note.slide_id] = make_slide_trail_mesh(note) + slide_trail_meshes[note.slide_id] = RadialMeshTools.make_slide_trail_mesh(note) initialise_scores() # Remove old score diff --git a/scripts/RadialMeshTools.gd b/scripts/RadialMeshTools.gd new file mode 100644 index 0000000..855e989 --- /dev/null +++ b/scripts/RadialMeshTools.gd @@ -0,0 +1,203 @@ +extends Node + +const SQRT2 := sqrt(2) +const DEG45 := deg2rad(45.0) +const DEG90 := deg2rad(90.0) +const DEG135 := deg2rad(135.0) + +# ---------------------------------------------------------------------------------------------------------------------------------------------------- +# Helper functions to generate meshes from vertex arrays +static func make_tap_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, color_array:=GameTheme.COLOR_ARRAY_TAP): + var dim = GameTheme.sprite_size2 * scale + var vertex_array = PoolVector2Array([note_center + Vector2(-dim, -dim), note_center + Vector2(dim, -dim), note_center + Vector2(-dim, dim), note_center + Vector2(dim, dim)]) + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_TAP + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +static func make_hold_mesh(mesh: ArrayMesh, note_center: Vector2, note_center_rel: Vector2, scale:=1.0, angle:=0.0, color_array = GameTheme.COLOR_ARRAY_HOLD): + var dim = GameTheme.sprite_size2 * scale + var dim2 = dim * SQRT2 + var a1 = angle - DEG45 + var a2 = angle + DEG45 + var a3 = angle - DEG90 + var a4 = angle + DEG90 + var a5 = angle - DEG135 + var a6 = angle + DEG135 + var vertex_array = PoolVector2Array([ + note_center + polar2cartesian(dim2, a1), note_center + polar2cartesian(dim2, a2), + note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4), + note_center_rel + polar2cartesian(dim, a3), note_center_rel + polar2cartesian(dim, a4), + note_center_rel + polar2cartesian(dim2, a5), note_center_rel + polar2cartesian(dim2, a6) + ]) + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_HOLD + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +static func make_star_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, angle:=0.0, color_array:=GameTheme.COLOR_ARRAY_STAR): + var dim = GameTheme.sprite_size2 * scale * SQRT2 + var a1 = angle - DEG45 + var a2 = angle + DEG45 + var a3 = angle - DEG135 + var a4 = angle + DEG135 + var vertex_array = PoolVector2Array([ + note_center + polar2cartesian(dim, a1), note_center + polar2cartesian(dim, a2), + note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4) + ]) + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_STAR + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +#func make_arrow_mesh(mesh: ArrayMesh, vertex_array, color_array = GameTheme.COLOR_ARRAY_TAP): +# var arrays = [] +# arrays.resize(Mesh.ARRAY_MAX) +# arrays[Mesh.ARRAY_VERTEX] = vertex_array +# arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_ARROW +# arrays[Mesh.ARRAY_COLOR] = color_array +# mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + + +const slide_arrows_per_unit_length := 10 +static func make_slide_trail_mesh(note) -> ArrayMesh: + # Generates a mesh centered around origin. Make sure the MeshInstance2D that draws this is centered on the screen. + var mesh = ArrayMesh.new() + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + var vertices := PoolVector2Array() + var uvs := PoolVector2Array() + var colors := PoolColorArray() + var size := GameTheme.sprite_size2 * sqrt(2) + var color := GameTheme.COLOR_DOUBLE_SLIDE if note.double_hit else GameTheme.COLOR_SLIDE + + match note.get_points(): + [var positions, var angles]: + var trail_length : int = len(positions) + vertices.resize(3*trail_length) + uvs.resize(3*trail_length) + colors.resize(3*trail_length) + for i in trail_length: + var idx = (trail_length-i-1)*3 # We want the earliest ones to be drawn last so that loops/sharp bends will have the first pass on top + var u = GameTheme.UV_ARRAY_SLIDE_ARROW if i%3 else GameTheme.UV_ARRAY_SLIDE_ARROW2 + for j in 3: + uvs[idx+j] = u[j] + colors[idx+j] = Color(color.r, color.g, color.b, (1.0+float(i))/float(trail_length)) + var angle : float = angles[i] + var offset : Vector2 = positions[i] * GameTheme.receptor_ring_radius + vertices[idx] = offset + vertices[idx+1] = offset + polar2cartesian(size, angle+PI*0.75) + vertices[idx+2] = offset + polar2cartesian(size, angle-PI*0.75) + + arrays[Mesh.ARRAY_VERTEX] = vertices + arrays[Mesh.ARRAY_TEX_UV] = uvs + arrays[Mesh.ARRAY_COLOR] = colors + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + return mesh + + + +#---------------------------------------------------------------------------------------------------------------------------------------------- +# Text UVs + +# Old dynamic code: +#var text_UV_arrays := [] +#func make_text_UV(row: int, column: int) -> PoolVector2Array: +# return PoolVector2Array([Vector2(column/4.0, row/8.0), Vector2((column+1)/4.0, row/8.0), Vector2(column/4.0, (row+1)/8.0), Vector2((column+1)/4.0, (row+1)/8.0)]) +#func make_text_UVs(): +# for row in 8: +# for column in 4: +# text_UV_arrays.append(make_text_UV(row, column)) + +# This is replaced by a quick hacky python codegen: +#>>> def make_text_UV(row, column): +#... return f'PoolVector2Array([Vector2({column}/4.0, {row}/8.0), Vector2({column+1}/4.0, {row}/8.0), Vector2({column}/4.0, {row+1}/8.0), Vector2({column+1}/4.0, {row+1}/8.0)])' +#>>> for row in range(8): +#... for col in range(4): +#... print(make_text_UV(row, col) + ',') + +const text_UV_arrays := [ + PoolVector2Array([Vector2(0/4.0, 0/8.0), Vector2(1/4.0, 0/8.0), Vector2(0/4.0, 1/8.0), Vector2(1/4.0, 1/8.0)]), + PoolVector2Array([Vector2(1/4.0, 0/8.0), Vector2(2/4.0, 0/8.0), Vector2(1/4.0, 1/8.0), Vector2(2/4.0, 1/8.0)]), + PoolVector2Array([Vector2(2/4.0, 0/8.0), Vector2(3/4.0, 0/8.0), Vector2(2/4.0, 1/8.0), Vector2(3/4.0, 1/8.0)]), + PoolVector2Array([Vector2(3/4.0, 0/8.0), Vector2(4/4.0, 0/8.0), Vector2(3/4.0, 1/8.0), Vector2(4/4.0, 1/8.0)]), + PoolVector2Array([Vector2(0/4.0, 1/8.0), Vector2(1/4.0, 1/8.0), Vector2(0/4.0, 2/8.0), Vector2(1/4.0, 2/8.0)]), + PoolVector2Array([Vector2(1/4.0, 1/8.0), Vector2(2/4.0, 1/8.0), Vector2(1/4.0, 2/8.0), Vector2(2/4.0, 2/8.0)]), + PoolVector2Array([Vector2(2/4.0, 1/8.0), Vector2(3/4.0, 1/8.0), Vector2(2/4.0, 2/8.0), Vector2(3/4.0, 2/8.0)]), + PoolVector2Array([Vector2(3/4.0, 1/8.0), Vector2(4/4.0, 1/8.0), Vector2(3/4.0, 2/8.0), Vector2(4/4.0, 2/8.0)]), + PoolVector2Array([Vector2(0/4.0, 2/8.0), Vector2(1/4.0, 2/8.0), Vector2(0/4.0, 3/8.0), Vector2(1/4.0, 3/8.0)]), + PoolVector2Array([Vector2(1/4.0, 2/8.0), Vector2(2/4.0, 2/8.0), Vector2(1/4.0, 3/8.0), Vector2(2/4.0, 3/8.0)]), + PoolVector2Array([Vector2(2/4.0, 2/8.0), Vector2(3/4.0, 2/8.0), Vector2(2/4.0, 3/8.0), Vector2(3/4.0, 3/8.0)]), + PoolVector2Array([Vector2(3/4.0, 2/8.0), Vector2(4/4.0, 2/8.0), Vector2(3/4.0, 3/8.0), Vector2(4/4.0, 3/8.0)]), + PoolVector2Array([Vector2(0/4.0, 3/8.0), Vector2(1/4.0, 3/8.0), Vector2(0/4.0, 4/8.0), Vector2(1/4.0, 4/8.0)]), + PoolVector2Array([Vector2(1/4.0, 3/8.0), Vector2(2/4.0, 3/8.0), Vector2(1/4.0, 4/8.0), Vector2(2/4.0, 4/8.0)]), + PoolVector2Array([Vector2(2/4.0, 3/8.0), Vector2(3/4.0, 3/8.0), Vector2(2/4.0, 4/8.0), Vector2(3/4.0, 4/8.0)]), + PoolVector2Array([Vector2(3/4.0, 3/8.0), Vector2(4/4.0, 3/8.0), Vector2(3/4.0, 4/8.0), Vector2(4/4.0, 4/8.0)]), + PoolVector2Array([Vector2(0/4.0, 4/8.0), Vector2(1/4.0, 4/8.0), Vector2(0/4.0, 5/8.0), Vector2(1/4.0, 5/8.0)]), + PoolVector2Array([Vector2(1/4.0, 4/8.0), Vector2(2/4.0, 4/8.0), Vector2(1/4.0, 5/8.0), Vector2(2/4.0, 5/8.0)]), + PoolVector2Array([Vector2(2/4.0, 4/8.0), Vector2(3/4.0, 4/8.0), Vector2(2/4.0, 5/8.0), Vector2(3/4.0, 5/8.0)]), + PoolVector2Array([Vector2(3/4.0, 4/8.0), Vector2(4/4.0, 4/8.0), Vector2(3/4.0, 5/8.0), Vector2(4/4.0, 5/8.0)]), + PoolVector2Array([Vector2(0/4.0, 5/8.0), Vector2(1/4.0, 5/8.0), Vector2(0/4.0, 6/8.0), Vector2(1/4.0, 6/8.0)]), + PoolVector2Array([Vector2(1/4.0, 5/8.0), Vector2(2/4.0, 5/8.0), Vector2(1/4.0, 6/8.0), Vector2(2/4.0, 6/8.0)]), + PoolVector2Array([Vector2(2/4.0, 5/8.0), Vector2(3/4.0, 5/8.0), Vector2(2/4.0, 6/8.0), Vector2(3/4.0, 6/8.0)]), + PoolVector2Array([Vector2(3/4.0, 5/8.0), Vector2(4/4.0, 5/8.0), Vector2(3/4.0, 6/8.0), Vector2(4/4.0, 6/8.0)]), + PoolVector2Array([Vector2(0/4.0, 6/8.0), Vector2(1/4.0, 6/8.0), Vector2(0/4.0, 7/8.0), Vector2(1/4.0, 7/8.0)]), + PoolVector2Array([Vector2(1/4.0, 6/8.0), Vector2(2/4.0, 6/8.0), Vector2(1/4.0, 7/8.0), Vector2(2/4.0, 7/8.0)]), + PoolVector2Array([Vector2(2/4.0, 6/8.0), Vector2(3/4.0, 6/8.0), Vector2(2/4.0, 7/8.0), Vector2(3/4.0, 7/8.0)]), + PoolVector2Array([Vector2(3/4.0, 6/8.0), Vector2(4/4.0, 6/8.0), Vector2(3/4.0, 7/8.0), Vector2(4/4.0, 7/8.0)]), + PoolVector2Array([Vector2(0/4.0, 7/8.0), Vector2(1/4.0, 7/8.0), Vector2(0/4.0, 8/8.0), Vector2(1/4.0, 8/8.0)]), + PoolVector2Array([Vector2(1/4.0, 7/8.0), Vector2(2/4.0, 7/8.0), Vector2(1/4.0, 8/8.0), Vector2(2/4.0, 8/8.0)]), + PoolVector2Array([Vector2(2/4.0, 7/8.0), Vector2(3/4.0, 7/8.0), Vector2(2/4.0, 8/8.0), Vector2(3/4.0, 8/8.0)]), + PoolVector2Array([Vector2(3/4.0, 7/8.0), Vector2(4/4.0, 7/8.0), Vector2(3/4.0, 8/8.0), Vector2(4/4.0, 8/8.0)]) +] + +enum TextStyle {STRAIGHT=0, ARC=1, ARC_EARLY=2, ARC_LATE=3} +enum TextWord {NICE=0, OK=4, NG=8, PERFECT=12, GREAT=16, GOOD=20, ALMOST=24, MISS=28} +const TextJudgement := { + 0: TextWord.PERFECT + TextStyle.ARC, + 1: TextWord.GREAT + TextStyle.ARC_LATE, + -1: TextWord.GREAT + TextStyle.ARC_EARLY, + 2: TextWord.GOOD + TextStyle.ARC_LATE, + -2: TextWord.GOOD + TextStyle.ARC_EARLY, + 3: TextWord.ALMOST + TextStyle.ARC_LATE, + -3: TextWord.ALMOST + TextStyle.ARC_EARLY, + 'MISS': TextWord.MISS + TextStyle.ARC +} +const TextJudgementStraight := { + 0: TextWord.PERFECT + TextStyle.STRAIGHT, + 1: TextWord.GREAT + TextStyle.STRAIGHT, + -1: TextWord.GREAT + TextStyle.STRAIGHT, + 2: TextWord.GOOD + TextStyle.STRAIGHT, + -2: TextWord.GOOD + TextStyle.STRAIGHT, + 3: TextWord.ALMOST + TextStyle.STRAIGHT, + -3: TextWord.ALMOST + TextStyle.STRAIGHT, + 'MISS': TextWord.MISS + TextStyle.STRAIGHT +} + +static func make_text_mesh(mesh: ArrayMesh, text_id: int, pos: Vector2, angle: float, alpha:=1.0, scale:=1.0): + var r := GameTheme.judge_text_size2 * scale + var vertex_array := PoolVector2Array([ + pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG2), # TODO: fix this UV/vertex order mess + pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG1), + pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG4), + pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG3) + ]) + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_TEX_UV] = text_UV_arrays[text_id] + arrays[Mesh.ARRAY_COLOR] = GameTheme.color_array_text(alpha) + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +static func make_judgement_text(mesh: ArrayMesh, text_id: int, col: int, progress:=0.0): + make_text_mesh(mesh, text_id, + GameTheme.RADIAL_UNIT_VECTORS[col] * GameTheme.receptor_ring_radius * lerp(0.85, 0.85*0.75, progress), + GameTheme.RADIAL_COL_ANGLES[col]-PI/2.0, lerp(1.0, 0.0, progress), lerp(1.0, 0.75, progress) + ) + diff --git a/singletons/FileLoader.gd b/singletons/FileLoader.gd index 556d285..1840836 100644 --- a/singletons/FileLoader.gd +++ b/singletons/FileLoader.gd @@ -51,27 +51,6 @@ func load_image(name: String) -> ImageTexture: # Searches through all of the pa var filename: return FileHelpers.load_image(filename) -func find_by_extensions(array, extensions=null) -> Dictionary: - # Both args can be Array or PoolStringArray - # If extensions omitted, do all extensions - var output = {} - if extensions: - for ext in extensions: - output[ext] = [] - for filename in array: - for ext in extensions: - if filename.ends_with(ext): - output[ext].append(filename) - else: - for filename in array: - var ext = filename.rsplit('.', false, 1)[1] - if ext in output: - output[ext].append(filename) - else: - output[ext] = [filename] - return output - - func scan_library() -> Dictionary: print('Scanning library') var song_defs = {} @@ -131,7 +110,7 @@ func scan_library() -> Dictionary: genres[song_defs[song_key]['genre']] = [song_key] else: - var files_by_ext = find_by_extensions(FileHelpers.directory_list(full_folder, false).files) + var files_by_ext = FileHelpers.find_by_extensions(FileHelpers.directory_list(full_folder, false).files) if 'sm' in files_by_ext: var sm_filename = files_by_ext['sm'][0] print(sm_filename) diff --git a/singletons/Library.gd b/singletons/Library.gd index db10b7c..78934aa 100644 --- a/singletons/Library.gd +++ b/singletons/Library.gd @@ -2,6 +2,7 @@ extends Node const difficulty_translations = {'01': 'Z', '02': 'B', '03': 'A', '04': 'E', '05': 'M', '06': 'R', '10': '宴'} # A bit redundant now but might be useful later for other hacks + class MultilangStr: # Automatically propogate higher langs to lower ones if lower ones are missing. # e.g. if we don't have a proper english title, return the transliterated one instead @@ -28,6 +29,7 @@ class MultilangStr: func _to_string() -> String: return self[GameTheme.display_language] + class Song: var title: MultilangStr var subtitle: MultilangStr @@ -98,6 +100,7 @@ var genre_songs = [] # Dictionaries of key: Song var tile_tex_cache = {} # We'll need some way of managing this later since holding all the tiles in memory might be expensive var charts_cache = {} + func add_song(key: String, data: Dictionary): if not data.has('index'): data['index'] = key @@ -109,6 +112,7 @@ func add_song(key: String, data: Dictionary): genre_songs.append({}) genre_songs[genre_ids[song.genre]][key] = song + func get_song_tile_texture(song_key): if song_key in tile_tex_cache: return tile_tex_cache[song_key] @@ -118,6 +122,7 @@ func get_song_tile_texture(song_key): else: print_debug('Invalid song_key: ', song_key) + func get_song_charts(song_key): if song_key in charts_cache: return charts_cache[song_key] @@ -143,5 +148,6 @@ func get_song_charts(song_key): else: print_debug('Invalid song_key: ', song_key) + func initialize(): pass diff --git a/singletons/Note.gd b/singletons/Note.gd index ccad4cd..9bd1646 100644 --- a/singletons/Note.gd +++ b/singletons/Note.gd @@ -13,6 +13,7 @@ const RELEASE_SCORE_TYPES := { NOTE_ROLL: -NOTE_ROLL } + class NoteBase extends Resource: var time_hit: float setget set_time_hit var time_death: float @@ -26,26 +27,31 @@ class NoteBase extends Resource: time_hit = value time_death = time_hit + DEATH_DELAY + class NoteHittableBase extends NoteBase: const hittable := true + class NoteTapBase extends NoteHittableBase: func _init(time_hit: float, column: int, is_break:=false): self.time_hit = time_hit self.column = column self.is_break = is_break + class NoteTap extends NoteTapBase: var type := NOTE_TAP func _init(time_hit: float, column: int, is_break:=false).(time_hit, column, is_break): pass + class NoteStar extends NoteTapBase: # Fancy charts have naked slides which necessitates separation of Star and Slide :( var type := NOTE_STAR var duration := 1.0 # This is required for the spin speed func _init(time_hit: float, column: int, is_break:=false).(time_hit, column, is_break): pass + class NoteHoldBase extends NoteHittableBase: var time_release: float setget set_time_release var time_released := INF @@ -72,16 +78,19 @@ class NoteHoldBase extends NoteHittableBase: time_release = time_hit + duration time_death = time_release + DEATH_DELAY + class NoteHold extends NoteHoldBase: var type := NOTE_HOLD func _init(time_hit: float, column: int, duration: float).(time_hit, column, duration): pass + class NoteRoll extends NoteHoldBase: var type := NOTE_ROLL func _init(time_hit: float, column: int, duration: float).(time_hit, column, duration): pass + class NoteSlide extends NoteBase: # Fancy charts have naked slides which necessitates separation of Star and Slide :( const hittable := false var type := NOTE_SLIDE @@ -204,6 +213,7 @@ class NoteSlide extends NoteBase: # Fancy charts have naked slides which necess return values.curve2d.get_baked_length() return 0.0 + static func copy_note(note: NoteBase): # Honestly disappointed I couldn't find a better, more OOP solution for this. var newnote: NoteBase @@ -223,16 +233,20 @@ static func copy_note(note: NoteBase): newnote.double_hit = note.double_hit return newnote + static func make_slide(time_hit: float, duration: float, column: int, column_release: int, slide_type:=SlideType.CHORD) -> NoteSlide: return NoteSlide.new(time_hit, column, duration, column_release, slide_type) + static func make_touch(time_hit: float, location: Vector2) -> Dictionary: return {type=NOTE_TOUCH, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, location=location, double_hit=false} + static func make_touch_hold(time_hit: float, duration: float, location: Vector2) -> Dictionary: var time_release := time_hit + duration return {type=NOTE_TOUCH_HOLD, time_hit=time_hit, time_release=time_release, time_death=time_release+DEATH_DELAY, location=location, double_hit=false} + static func process_note_list(note_array: Array, check_doubles:=true): # Preprocess double hits, assign Slide IDs # If this were performance-critical, we'd single iterate it @@ -260,11 +274,13 @@ static func process_note_list(note_array: Array, check_doubles:=true): note_array[i].slide_id = slide_id slide_id += 1 + # These should probably get their own singleton later const ORBIT_INNER_RADIUS = sin(deg2rad(22.5)) # ~0.38 const ORBIT_KAPPA = (sqrt(2)-1) * 4.0 / 3.0 # This is the length of control points along a tangent to approximate a circle (multiply by desired radius) -func curve2d_make_orbit(curve2d, rad_in, rad_out, ccw, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS): + +static func curve2d_make_orbit(curve2d, rad_in, rad_out, ccw, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS): var d_sign = -1 if ccw else 1 var rad_2 = rad_in+PI*3/8*d_sign var rad_2t = rad_2+PI*0.5*d_sign @@ -285,7 +301,8 @@ func curve2d_make_orbit(curve2d, rad_in, rad_out, ccw, rad_max_arc:=PI*0.25, kap curve2d.add_point(polar2cartesian(inner_radius, rad_3), polar2cartesian(k, rad_3t)) # curve2d.add_point(polar2cartesian(1.0, rad_out)) -func curve2d_make_sideorbit(curve2d: Curve2D, rad_in: float, rad_out: float, ccw: bool, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS): + +static func curve2d_make_sideorbit(curve2d: Curve2D, rad_in: float, rad_out: float, ccw: bool, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS): var d_sign := -1 if ccw else 1 var sideorbit_center := polar2cartesian(inner_radius, rad_in-PI*0.5*d_sign) var rad_orbit_in := rad_in + PI*0.5*d_sign