Hold+Slide release scoring! Practically feature-complete!
This commit is contained in:
parent
2e02e279c4
commit
794e9dd4a0
|
@ -114,3 +114,19 @@ func load_folder(folder):
|
|||
var result = result_json.result
|
||||
result.directory = folder
|
||||
return result
|
||||
|
||||
|
||||
func load_ogg(filename) -> AudioStreamOGGVorbis:
|
||||
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
|
||||
|
||||
func load_image(filename) -> ImageTexture:
|
||||
var tex = ImageTexture.new()
|
||||
var img = Image.new()
|
||||
img.load(filename)
|
||||
tex.create_from_image(img)
|
||||
return tex
|
50
Menu.gd
50
Menu.gd
|
@ -44,7 +44,7 @@ func scan_library():
|
|||
if dir.file_exists(key + "/song.json"):
|
||||
song_defs[key] = FileLoader.load_folder("%s/%s" % [rootdir, key])
|
||||
print("Loaded song directory: %s" % key)
|
||||
song_images[key] = load("%s/%s/%s" % [rootdir, key, song_defs[key]["tile_filename"]])
|
||||
song_images[key] = FileLoader.load_image("%s/%s/%s" % [rootdir, key, song_defs[key]["tile_filename"]])
|
||||
if song_defs[key]["genre"] in genres:
|
||||
genres[song_defs[key]["genre"]].append(key)
|
||||
else:
|
||||
|
@ -67,7 +67,11 @@ func save_score():
|
|||
data.song_key = scorescreen_song_key
|
||||
var json = JSON.print(data)
|
||||
var file = File.new()
|
||||
var err = file.open(rootdir + "/{year}{month}{day}T{hour}{minute}{second}.json".format(scorescreen_datetime), File.WRITE)
|
||||
# var filename = rootdir + "/{year}{month}{day}T{hour}{minute}{second}.json".format(scorescreen_datetime)
|
||||
# So uh. Can't zero-pad using the string.format() method. This sucks.
|
||||
var dt = scorescreen_datetime
|
||||
var filename = rootdir + "/%04d%02d%02dT%02d%02d%02d.json"%[dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]
|
||||
var err = file.open(filename, File.WRITE)
|
||||
if err != OK:
|
||||
print(err)
|
||||
return err
|
||||
|
@ -109,7 +113,7 @@ func load_score(filename):
|
|||
func _ready():
|
||||
scan_library()
|
||||
$"/root/main/NoteHandler".connect("finished_song", self, "finished_song")
|
||||
load_score("20191210T235010.json")
|
||||
load_score("20191211T234131.json") # For testing purposes
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta):
|
||||
|
@ -229,11 +233,11 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
var y = center.y - 200
|
||||
var x_songtile = x - 120
|
||||
var x_score = x + 120
|
||||
var x2 = x - 360
|
||||
var x_spacing = 116
|
||||
var y_spacing = 48
|
||||
var x2 = x - 370
|
||||
var x_spacing = 124
|
||||
var y_spacing = 42
|
||||
var y1 = y
|
||||
var y2 = y + size + y_spacing*2
|
||||
var y2 = y + size + y_spacing*1.5
|
||||
|
||||
var tex_judgement_text = $"/root/main/NoteHandler".tex_judgement_text
|
||||
var judgement_text_scale = 0.667
|
||||
|
@ -242,10 +246,12 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
|
||||
draw_songtile(song_key, Vector2(x_songtile-size/2.0, y), size, false, selected_difficulty, 3)
|
||||
draw_string_centered(TitleFont, Vector2(x_songtile, y+size), song_defs[song_key]["title"], Color(0.95, 0.95, 1.0))
|
||||
var notestrs = ["Tap", "Hold", "Slide"]
|
||||
var notestrs = ["Taps:", "Holds Hit:", "Released:", "Slides Hit:", "Slid:"]
|
||||
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, 2.0, 2.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
|
||||
|
@ -260,24 +266,30 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
|
||||
for i in len(notestrs):
|
||||
# For each note type, make a row and print scores
|
||||
draw_string_centered(TitleFont, Vector2(x2, y2+y_spacing*(i+1)), notestrs[i]+"s:", Color(0.95, 0.95, 1.0))
|
||||
var idx = notetypes[i]
|
||||
var note_score = 0
|
||||
var note_count = 0
|
||||
# var y_row = y2+y_spacing*(i+1)
|
||||
var y_row = y2 + y_spacing * (note_spacing[i]+1)
|
||||
draw_string_centered(TitleFont, Vector2(x2, y_row), notestrs[i], Color(0.95, 0.95, 1.0))
|
||||
for j in len(judgestrs):
|
||||
var score
|
||||
if j == 0:
|
||||
score = scorescreen_score_data[i][0]
|
||||
score = scorescreen_score_data[idx][0]
|
||||
elif j >= len(judgestrs)-1:
|
||||
score = scorescreen_score_data[i]["MISS"]
|
||||
score = scorescreen_score_data[idx]["MISS"]
|
||||
else:
|
||||
score = scorescreen_score_data[i][j] + scorescreen_score_data[i][-j]
|
||||
notecount_early += scorescreen_score_data[i][-j]
|
||||
notecount_late += scorescreen_score_data[i][j]
|
||||
draw_string_centered(TitleFont, Vector2(x2+x_spacing*(j+1), y2+y_spacing*(i+1)), str(score), Color(0.95, 0.95, 1.0))
|
||||
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, Vector2(x2+x_spacing*(j+1), y_row), "^", Color(0.95, 0.95, 1.0))
|
||||
else:
|
||||
draw_string_centered(TitleFont, Vector2(x2+x_spacing*(j+1), y_row), str(score), Color(0.95, 0.95, 1.0))
|
||||
notecount_total += score # Kinda redundant, will probably refactor eventually
|
||||
note_count += score
|
||||
note_score += score * judge_scores[j]
|
||||
draw_string_centered(TitleFont, Vector2(x2+x_spacing*(len(judgestrs)+1), y2+y_spacing*(i+1)), "%2.2f%%"%(note_score/note_count*100.0), Color(0.95, 0.95, 1.0))
|
||||
draw_string_centered(TitleFont, Vector2(x2+x_spacing*(len(judgestrs)+1), y_row), "%2.2f%%"%(note_score/note_count*100.0), Color(0.95, 0.95, 1.0))
|
||||
total_score += note_score * notetype_weights[i]
|
||||
total_scoremax += note_count * notetype_weights[i]
|
||||
|
||||
|
@ -295,8 +307,8 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
$ScoreText.score_sub = "%2.3f%%"%(overall_score*100.0)
|
||||
$ScoreText.update()
|
||||
|
||||
draw_string_centered(TitleFont, Vector2(x, y2+y_spacing*4), "Early : Late", Color(0.95, 0.95, 1.0))
|
||||
draw_string_centered(TitleFont, Vector2(x, y2+y_spacing*5), "%3d%% : %3d%%"%[notecount_early*100/notecount_total, notecount_late*100/notecount_total], Color(0.95, 0.95, 1.0))
|
||||
draw_string_centered(TitleFont, Vector2(x, y2+y_spacing*7), "Early : Late", Color(0.95, 0.95, 1.0))
|
||||
draw_string_centered(TitleFont, Vector2(x, y2+y_spacing*8), "%3d%% : %3d%%"%[notecount_early*100/notecount_total, notecount_late*100/notecount_total], Color(0.95, 0.95, 1.0))
|
||||
|
||||
var rect_songselect := Rect2(-100.0, 300.0, 400.0, 100.0)
|
||||
draw_rect(rect_songselect, Color.red)
|
||||
|
|
4
Note.gd
4
Note.gd
|
@ -6,6 +6,7 @@ extends Node
|
|||
enum {NOTE_TAP, NOTE_HOLD, NOTE_SLIDE, NOTE_ARROW, NOTE_TOUCH, NOTE_TOUCH_HOLD}
|
||||
enum SlideType {CHORD, ARC_CW, ARC_ACW}
|
||||
const DEATH_DELAY := 1.0 # This is touchy with the judgement windows and variable bpm.
|
||||
const RELEASE_SCORE_TYPES := [NOTE_HOLD, NOTE_SLIDE, NOTE_TOUCH_HOLD]
|
||||
|
||||
class NoteBase:
|
||||
var time_hit: float
|
||||
|
@ -25,6 +26,7 @@ class NoteTap extends NoteBase:
|
|||
class NoteHold extends NoteBase:
|
||||
var type := NOTE_HOLD
|
||||
var time_release: float
|
||||
var time_released := INF
|
||||
var duration: float
|
||||
var is_held: bool
|
||||
func _init(time_hit: float, duration: float, column: int):
|
||||
|
@ -42,6 +44,8 @@ class NoteSlide extends NoteBase:
|
|||
var column_release: int
|
||||
var slide_type: int
|
||||
var slide_id: int
|
||||
var progress := INF
|
||||
var missed_slide := false
|
||||
var values: Dictionary
|
||||
|
||||
func _init(time_hit: float, duration: float, column: int, column_release: int, slide_type: int):
|
||||
|
|
125
NoteHandler.gd
125
NoteHandler.gd
|
@ -62,6 +62,7 @@ var next_note_to_load := 0
|
|||
var active_judgement_texts := []
|
||||
var scores := {}
|
||||
|
||||
var active_slide_trails := []
|
||||
var slide_trail_meshes := {}
|
||||
var slide_trail_mesh_instances := {}
|
||||
|
||||
|
@ -116,6 +117,11 @@ func initialise_scores():
|
|||
scores[type] = {}
|
||||
for key in TextJudgement:
|
||||
scores[type][key] = 0
|
||||
# Release types
|
||||
for type in [Note.NOTE_HOLD, Note.NOTE_SLIDE]:
|
||||
scores[-type] = {}
|
||||
for key in TextJudgement:
|
||||
scores[-type][key] = 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
|
||||
|
@ -270,8 +276,24 @@ func activate_note(note, judgement):
|
|||
Note.NOTE_HOLD:
|
||||
note.is_held = true
|
||||
Note.NOTE_SLIDE:
|
||||
pass # Set up slide trail?
|
||||
return
|
||||
# Set up slide trail?
|
||||
active_slide_trails.append(note)
|
||||
note.progress = 0.0
|
||||
|
||||
func activate_note_release(note, judgement):
|
||||
# Only for Hold, Slide
|
||||
SFXPlayer.play(SFXPlayer.Type.NON_POSITIONAL, self, snd_judgement[judgement], db_judgement[judgement], pitch_judgement[judgement])
|
||||
scores[-note.type][judgement] += 1
|
||||
|
||||
match note.type:
|
||||
Note.NOTE_HOLD:
|
||||
note.is_held = false
|
||||
note.time_released = t
|
||||
active_judgement_texts.append({col=note.column, judgement=judgement, time=t})
|
||||
Note.NOTE_SLIDE:
|
||||
active_judgement_texts.append({col=note.column_release, judgement=judgement, time=t})
|
||||
Note.NOTE_TOUCH_HOLD:
|
||||
pass
|
||||
|
||||
func button_pressed(col):
|
||||
for note in active_notes:
|
||||
|
@ -283,37 +305,65 @@ func button_pressed(col):
|
|||
var hit_delta = get_realtime_precise() - real_time(note.time_hit) # Judgement times are in seconds not gametime
|
||||
if hit_delta >= 0.0:
|
||||
if hit_delta > Rules.JUDGEMENT_TIMES_POST[-1]:
|
||||
continue # missed
|
||||
continue # missed, don't consume input
|
||||
for i in Rules.JUDGEMENT_TIERS:
|
||||
if hit_delta <= Rules.JUDGEMENT_TIMES_POST[i]:
|
||||
activate_note(note, i)
|
||||
return
|
||||
return # Consume input because one press shouldn't trigger two notes
|
||||
else:
|
||||
if -hit_delta > Rules.JUDGEMENT_TIMES_PRE[-1]:
|
||||
continue # too far away
|
||||
continue # too far away, don't consume input
|
||||
for i in Rules.JUDGEMENT_TIERS:
|
||||
if -hit_delta <= Rules.JUDGEMENT_TIMES_POST[i]:
|
||||
if -hit_delta <= Rules.JUDGEMENT_TIMES_PRE[i]:
|
||||
activate_note(note, -i)
|
||||
return
|
||||
|
||||
func touchbutton_pressed(col):
|
||||
button_pressed(col)
|
||||
|
||||
|
||||
func do_hold_release(note):
|
||||
var hit_delta = get_realtime_precise() - real_time(note.time_release) # Judgement times are in seconds not gametime
|
||||
if hit_delta >= 0.0:
|
||||
for i in Rules.JUDGEMENT_TIERS-1:
|
||||
if hit_delta <= Rules.JUDGEMENT_TIMES_RELEASE_POST[i]:
|
||||
activate_note_release(note, i)
|
||||
return
|
||||
activate_note_release(note, Rules.JUDGEMENT_TIERS-1) # No "miss" for releasing, only worst judgement.
|
||||
return
|
||||
else:
|
||||
for i in Rules.JUDGEMENT_TIERS-1:
|
||||
if -hit_delta <= Rules.JUDGEMENT_TIMES_RELEASE_PRE[i]:
|
||||
activate_note_release(note, -i)
|
||||
return
|
||||
activate_note_release(note, Rules.JUDGEMENT_TIERS-1) # No "miss" for releasing, only worst judgement.
|
||||
return
|
||||
|
||||
func do_slide_release(note):
|
||||
var hit_delta = get_realtime_precise() - real_time(note.time_release) # Judgement times are in seconds not gametime
|
||||
if hit_delta >= 0.0:
|
||||
for i in Rules.JUDGEMENT_TIERS:
|
||||
if hit_delta <= Rules.JUDGEMENT_TIMES_SLIDE_POST[i]:
|
||||
activate_note_release(note, i)
|
||||
return
|
||||
else:
|
||||
for i in Rules.JUDGEMENT_TIERS:
|
||||
if -hit_delta <= Rules.JUDGEMENT_TIMES_SLIDE_PRE[i]:
|
||||
activate_note_release(note, -i)
|
||||
return
|
||||
|
||||
func check_hold_release(col):
|
||||
for note in active_notes:
|
||||
if note.column != col:
|
||||
continue
|
||||
if note.type == Note.NOTE_HOLD:
|
||||
if note.is_held == true:
|
||||
note.is_held = false
|
||||
pass
|
||||
do_hold_release(note) # Separate function since there's no need to "consume" releases
|
||||
|
||||
func button_released(col):
|
||||
# We only care about hold release.
|
||||
# For that particular case, we want both to be unheld.
|
||||
if $"/root/main/InputHandler".touchbuttons_pressed[col] == 0:
|
||||
check_hold_release(col)
|
||||
|
||||
func touchbutton_released(col):
|
||||
if $"/root/main/InputHandler".buttons_pressed[col] == 0:
|
||||
check_hold_release(col)
|
||||
|
@ -345,10 +395,14 @@ func _draw():
|
|||
make_tap_mesh(mesh, note_center, scale, color)
|
||||
Note.NOTE_HOLD:
|
||||
if note.is_held:
|
||||
position = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats
|
||||
color = GameTheme.COLOR_ARRAY_HOLD_HELD
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius * max(position, 1.0)
|
||||
elif position > 1.0:
|
||||
color = GameTheme.COLOR_ARRAY_DOUBLE_MISS_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD_MISS
|
||||
if note.time_released != INF:
|
||||
position = (t+GameTheme.note_forecast_beats-note.time_released)/GameTheme.note_forecast_beats
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius * position
|
||||
else:
|
||||
color = GameTheme.COLOR_ARRAY_DOUBLE_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD
|
||||
var position_rel : float = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats
|
||||
|
@ -374,7 +428,8 @@ func _draw():
|
|||
var star_pos : Vector2 = note.get_position(trail_progress)
|
||||
var star_angle : float = note.get_angle(trail_progress)
|
||||
make_star_mesh(mesh, star_pos, 1.33, star_angle, color)
|
||||
# slide_trail_mesh_instances[note.slide_id].material.set_shader_param("trail_progress", trail_progress)
|
||||
if note.progress != INF:
|
||||
slide_trail_mesh_instances[note.slide_id].material.set_shader_param("trail_progress", note.progress)
|
||||
if t > note.time_release:
|
||||
trail_alpha = max(1 - (t - note.time_release)/Note.DEATH_DELAY, 0.0)
|
||||
slide_trail_mesh_instances[note.slide_id].material.set_shader_param("base_alpha", trail_alpha*0.88)
|
||||
|
@ -392,6 +447,29 @@ func _draw():
|
|||
make_judgement_text(textmesh, TextJudgement[text.judgement], text.col, (t-text.time)/GameTheme.judge_text_duration)
|
||||
$JudgeText.set_mesh(textmesh)
|
||||
|
||||
|
||||
func _input(event):
|
||||
var pos
|
||||
if event is InputEventScreenTouch:
|
||||
if event.pressed:
|
||||
pos = event.position - get_global_transform_with_canvas().get_origin()
|
||||
else:
|
||||
return
|
||||
elif event is InputEventScreenDrag:
|
||||
pos = event.position - get_global_transform_with_canvas().get_origin()
|
||||
else:
|
||||
return
|
||||
|
||||
for i in range(len(active_slide_trails)-1, -1, -1):
|
||||
var note = active_slide_trails[i]
|
||||
var center = note.get_position(note.progress)
|
||||
if (pos - center).length_squared() < 10000.0:
|
||||
note.progress += 0.09
|
||||
if note.progress >= 1.0:
|
||||
do_slide_release(note)
|
||||
active_slide_trails.remove(i)
|
||||
|
||||
|
||||
func _init():
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
|
||||
GameTheme.init_radial_values()
|
||||
|
@ -440,14 +518,7 @@ func load_track(data: Dictionary, difficulty_idx: int):
|
|||
bpm = data.bpm_values[0]
|
||||
sync_offset_audio = data.audio_offsets[0]
|
||||
sync_offset_video = data.video_offsets[0]
|
||||
var audiostream = AudioStreamOGGVorbis.new()
|
||||
# var asb = load(data.directory + "/" + data.audio_filelist[0])
|
||||
# audiostream.set_data(asb.get_data())
|
||||
# Unbelievably stupid bug, infuriating workaround
|
||||
var oggfile = File.new()
|
||||
oggfile.open(data.directory + "/" + data.audio_filelist[0], File.READ)
|
||||
audiostream.set_data(oggfile.get_buffer(oggfile.get_len()))
|
||||
oggfile.close()
|
||||
var audiostream = FileLoader.load_ogg(data.directory + "/" + data.audio_filelist[0])
|
||||
var videostream = load(data.directory + "/" + data.video_filelist[0])
|
||||
|
||||
$"/root/main/music".set_stream(audiostream)
|
||||
|
@ -533,13 +604,26 @@ func _process(delta):
|
|||
if note.type == Note.NOTE_SLIDE:
|
||||
$SlideTrailHandler.remove_child(slide_trail_mesh_instances[note.slide_id])
|
||||
slide_trail_mesh_instances.erase(note.slide_id)
|
||||
var idx = active_slide_trails.find(note)
|
||||
if idx >= 0:
|
||||
active_slide_trails.remove(idx)
|
||||
active_judgement_texts.append({col=note.column_release, judgement="MISS", time=t})
|
||||
scores[-Note.NOTE_SLIDE]["MISS"] += 1
|
||||
note.missed_slide = true
|
||||
SFXPlayer.play(SFXPlayer.Type.NON_POSITIONAL, self, snd_judgement["MISS"], db_judgement["MISS"])
|
||||
active_notes.remove(i)
|
||||
elif note.time_activated == INF:
|
||||
if ((t-note.time_hit) > miss_time) and not note.missed:
|
||||
active_judgement_texts.append({col=note.column, judgement="MISS", time=t})
|
||||
scores[note.type]["MISS"] += 1
|
||||
if Note.RELEASE_SCORE_TYPES.has(note.type):
|
||||
scores[-note.type]["MISS"] += 1
|
||||
note.missed = true
|
||||
SFXPlayer.play(SFXPlayer.Type.NON_POSITIONAL, self, snd_judgement["MISS"], db_judgement["MISS"])
|
||||
if note.type == Note.NOTE_SLIDE:
|
||||
# Even if you miss the hit you can still slide, we're so nice
|
||||
active_slide_trails.append(note)
|
||||
note.progress = 0.0
|
||||
|
||||
# Clean out expired judgement texts
|
||||
# By design they will always be in order so we can ignore anything past the first index
|
||||
|
@ -574,6 +658,7 @@ func _process(delta):
|
|||
# next_note_to_load = 0
|
||||
if (len(active_notes) < 1) and (next_note_to_load >= len(all_notes)) and not get_node("/root/main/music").is_playing():
|
||||
self.running = false
|
||||
self.timers_set = false
|
||||
emit_signal("finished_song", song_key, scores)
|
||||
|
||||
# Redraw
|
||||
|
|
2
Rules.gd
2
Rules.gd
|
@ -13,7 +13,7 @@ const JUDGEMENT_TIMES_PRE := [0.040, 0.090, 0.125, 0.150]
|
|||
const JUDGEMENT_TIMES_POST := [0.040, 0.090, 0.125, 0.150]
|
||||
const JUDGEMENT_TIMES_RELEASE_PRE := [0.040, 0.090, 0.125, 0.150]
|
||||
const JUDGEMENT_TIMES_RELEASE_POST := [0.090, 0.140, 0.175, 0.200] # Small grace period
|
||||
const JUDGEMENT_TIMES_SLIDE_PRE := [0.090, 0.140, 0.175, 0.200] # Small grace period, sort-of
|
||||
const JUDGEMENT_TIMES_SLIDE_PRE := [0.090, 0.240, 0.375, 60.000] # Small grace period, sort-of. Just be generous, really.
|
||||
const JUDGEMENT_TIMES_SLIDE_POST := [0.090, 0.140, 0.175, 0.200]
|
||||
|
||||
const SCORE_STRINGS = ["SSS", "SS", "S", "A⁺", "A", "B⁺", "B", "C⁺", "C", "F"]
|
||||
|
|
Loading…
Reference in New Issue