diff --git a/RhythmGameUtilities/Enums/JudgmentType.cs b/RhythmGameUtilities/Enums/JudgmentType.cs new file mode 100644 index 0000000..9f07bb6 --- /dev/null +++ b/RhythmGameUtilities/Enums/JudgmentType.cs @@ -0,0 +1,19 @@ +namespace RhythmGameUtilities +{ + + public enum JudgmentType + { + + PERFECT, + + NICE, + + GOOD, + + OK, + + MISS + + } + +} diff --git a/RhythmGameUtilities/Scripts/SongManager.cs b/RhythmGameUtilities/Scripts/SongManager.cs new file mode 100644 index 0000000..1a6d58a --- /dev/null +++ b/RhythmGameUtilities/Scripts/SongManager.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RhythmGameUtilities +{ + + public class SongManager + { + + public Stats stats { get; protected set; } + + public Song song { get; protected set; } + + protected readonly List _remainingNotes = new(); + + protected int _tick; + + public SongManager(Song song, Difficulty difficulty, float comboStep = Stats.DefaultComboStep) + { + this.song = song; + + stats = new Stats { difficulty = difficulty, comboStep = comboStep }; + + _remainingNotes = GetAllNotes(difficulty).Where(note => note.HandPosition < 5).ToList(); + } + + public Note[] GetAllNotes(Difficulty difficulty) + { + if (song.Difficulties?[difficulty] == null) + { + throw new InvalidOperationException($"{difficulty} difficulty not found in song!"); + } + + if (!song.Difficulties.TryGetValue(difficulty, out var notes)) + { + throw new InvalidOperationException($"No notes found for the {difficulty} difficulty!"); + } + + return notes; + } + + public List GetRemainingNotes() + { + return _remainingNotes.ToList(); + } + + public List GetActiveNotes(float tickBuffer) + { + var activeNotes = GetRemainingNotes().Where(note => + _tick > note.Position - tickBuffer / 2 && _tick < note.Position + tickBuffer / 2).ToList(); + + return activeNotes; + } + + protected void ClearMissedNotes() + { + var notesToClear = GetRemainingNotes().Where(note => note.Position + song.BaseBPM / 2 < _tick); + + foreach (var note in notesToClear) + { + ClearMissedNote(note); + } + } + + protected virtual void ClearMissedNote(Note note) + { + stats.AddJudgment(JudgmentType.MISS); + stats.ClearCombo(); + } + + protected virtual void HitNoteHandler(Note note, float accuracy, JudgmentType judgmentType) + { + Console.WriteLine($"Hit note with {accuracy} accuracy and a hit note type of {judgmentType}!"); + } + + protected virtual void MissNoteHandler(Note note) + { + Console.WriteLine("Missed note!"); + } + + protected virtual JudgmentType CalculateJudgmentTypeFromAccuracy(float accuracy) + { + return accuracy switch + { + > 0.8f => JudgmentType.PERFECT, + > 0.6f => JudgmentType.NICE, + > 0.3f => JudgmentType.GOOD, + > 0 => JudgmentType.OK, + _ => JudgmentType.MISS + }; + } + + protected virtual int CalculateScoreForHitNoteType(JudgmentType judgmentType) + { + return judgmentType switch + { + JudgmentType.PERFECT => 1000, + JudgmentType.NICE => 500, + JudgmentType.GOOD => 200, + JudgmentType.OK => 100, + _ => 0 + }; + } + + public virtual void Tick(float time) + { + _tick = Utilities.ConvertSecondsToTicks(time, song.Resolution, song.BaseBPM); + } + + public virtual void Cleanup() + { + ClearMissedNotes(); + } + + } + +} diff --git a/RhythmGameUtilities/Structs/Stats.cs b/RhythmGameUtilities/Structs/Stats.cs new file mode 100644 index 0000000..c191486 --- /dev/null +++ b/RhythmGameUtilities/Structs/Stats.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Newtonsoft.Json; + +namespace RhythmGameUtilities +{ + + public class Stats + { + + public const float DefaultComboStep = 0.25f; + + public Difficulty difficulty { get; internal set; } + + public float comboStep { get; internal set; } = DefaultComboStep; + + public int score { get; private set; } + + public float combo { get; private set; } = 1; + + public int comboMultiplier => (int)Math.Floor(combo); + + private readonly Dictionary _judgmentTypeCount = new(); + + public ReadOnlyDictionary judgmentTypeCount => new(_judgmentTypeCount); + + public void AddToScore(int points) + { + combo += comboStep; + score += points * comboMultiplier; + } + + public void ClearCombo() + { + combo = 1; + } + + public void AddJudgment(JudgmentType judgmentType, int count = 1) + { + if (!_judgmentTypeCount.TryAdd(judgmentType, count)) + { + _judgmentTypeCount[judgmentType] += count; + } + } + + public string ToJSON() + { + return JsonConvert.SerializeObject(this); + } + + public static Song FromJSON(string input) + { + return JsonConvert.DeserializeObject(input); + } + + public override string ToString() + { + var output = new StringBuilder(); + + foreach (var judgment in Enum.GetValues(typeof(JudgmentType))) + { + if (_judgmentTypeCount.TryGetValue((JudgmentType)judgment, out var count)) + { + output.AppendLine($"{(JudgmentType)judgment}: {count}"); + } + } + + return output.ToString().Trim(); + } + + } + +} diff --git a/UnityPackage/Enums/JudgmentType.cs b/UnityPackage/Enums/JudgmentType.cs new file mode 100644 index 0000000..9f07bb6 --- /dev/null +++ b/UnityPackage/Enums/JudgmentType.cs @@ -0,0 +1,19 @@ +namespace RhythmGameUtilities +{ + + public enum JudgmentType + { + + PERFECT, + + NICE, + + GOOD, + + OK, + + MISS + + } + +} diff --git a/UnityPackage/Enums/JudgmentType.cs.meta b/UnityPackage/Enums/JudgmentType.cs.meta new file mode 100644 index 0000000..6e5a5c6 --- /dev/null +++ b/UnityPackage/Enums/JudgmentType.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c1bfc6ab3da34a4d85f0a0fca8608b95 +timeCreated: 1716364481 \ No newline at end of file diff --git a/UnityPackage/Scripts/SongManager.cs b/UnityPackage/Scripts/SongManager.cs new file mode 100644 index 0000000..1a6d58a --- /dev/null +++ b/UnityPackage/Scripts/SongManager.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RhythmGameUtilities +{ + + public class SongManager + { + + public Stats stats { get; protected set; } + + public Song song { get; protected set; } + + protected readonly List _remainingNotes = new(); + + protected int _tick; + + public SongManager(Song song, Difficulty difficulty, float comboStep = Stats.DefaultComboStep) + { + this.song = song; + + stats = new Stats { difficulty = difficulty, comboStep = comboStep }; + + _remainingNotes = GetAllNotes(difficulty).Where(note => note.HandPosition < 5).ToList(); + } + + public Note[] GetAllNotes(Difficulty difficulty) + { + if (song.Difficulties?[difficulty] == null) + { + throw new InvalidOperationException($"{difficulty} difficulty not found in song!"); + } + + if (!song.Difficulties.TryGetValue(difficulty, out var notes)) + { + throw new InvalidOperationException($"No notes found for the {difficulty} difficulty!"); + } + + return notes; + } + + public List GetRemainingNotes() + { + return _remainingNotes.ToList(); + } + + public List GetActiveNotes(float tickBuffer) + { + var activeNotes = GetRemainingNotes().Where(note => + _tick > note.Position - tickBuffer / 2 && _tick < note.Position + tickBuffer / 2).ToList(); + + return activeNotes; + } + + protected void ClearMissedNotes() + { + var notesToClear = GetRemainingNotes().Where(note => note.Position + song.BaseBPM / 2 < _tick); + + foreach (var note in notesToClear) + { + ClearMissedNote(note); + } + } + + protected virtual void ClearMissedNote(Note note) + { + stats.AddJudgment(JudgmentType.MISS); + stats.ClearCombo(); + } + + protected virtual void HitNoteHandler(Note note, float accuracy, JudgmentType judgmentType) + { + Console.WriteLine($"Hit note with {accuracy} accuracy and a hit note type of {judgmentType}!"); + } + + protected virtual void MissNoteHandler(Note note) + { + Console.WriteLine("Missed note!"); + } + + protected virtual JudgmentType CalculateJudgmentTypeFromAccuracy(float accuracy) + { + return accuracy switch + { + > 0.8f => JudgmentType.PERFECT, + > 0.6f => JudgmentType.NICE, + > 0.3f => JudgmentType.GOOD, + > 0 => JudgmentType.OK, + _ => JudgmentType.MISS + }; + } + + protected virtual int CalculateScoreForHitNoteType(JudgmentType judgmentType) + { + return judgmentType switch + { + JudgmentType.PERFECT => 1000, + JudgmentType.NICE => 500, + JudgmentType.GOOD => 200, + JudgmentType.OK => 100, + _ => 0 + }; + } + + public virtual void Tick(float time) + { + _tick = Utilities.ConvertSecondsToTicks(time, song.Resolution, song.BaseBPM); + } + + public virtual void Cleanup() + { + ClearMissedNotes(); + } + + } + +} diff --git a/UnityPackage/Scripts/SongManager.cs.meta b/UnityPackage/Scripts/SongManager.cs.meta new file mode 100644 index 0000000..099a45a --- /dev/null +++ b/UnityPackage/Scripts/SongManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 65e9bdd1e2ec450cbd0d14c4fd11beda +timeCreated: 1716564946 \ No newline at end of file diff --git a/UnityPackage/Structs/Stats.cs b/UnityPackage/Structs/Stats.cs new file mode 100644 index 0000000..c191486 --- /dev/null +++ b/UnityPackage/Structs/Stats.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Newtonsoft.Json; + +namespace RhythmGameUtilities +{ + + public class Stats + { + + public const float DefaultComboStep = 0.25f; + + public Difficulty difficulty { get; internal set; } + + public float comboStep { get; internal set; } = DefaultComboStep; + + public int score { get; private set; } + + public float combo { get; private set; } = 1; + + public int comboMultiplier => (int)Math.Floor(combo); + + private readonly Dictionary _judgmentTypeCount = new(); + + public ReadOnlyDictionary judgmentTypeCount => new(_judgmentTypeCount); + + public void AddToScore(int points) + { + combo += comboStep; + score += points * comboMultiplier; + } + + public void ClearCombo() + { + combo = 1; + } + + public void AddJudgment(JudgmentType judgmentType, int count = 1) + { + if (!_judgmentTypeCount.TryAdd(judgmentType, count)) + { + _judgmentTypeCount[judgmentType] += count; + } + } + + public string ToJSON() + { + return JsonConvert.SerializeObject(this); + } + + public static Song FromJSON(string input) + { + return JsonConvert.DeserializeObject(input); + } + + public override string ToString() + { + var output = new StringBuilder(); + + foreach (var judgment in Enum.GetValues(typeof(JudgmentType))) + { + if (_judgmentTypeCount.TryGetValue((JudgmentType)judgment, out var count)) + { + output.AppendLine($"{(JudgmentType)judgment}: {count}"); + } + } + + return output.ToString().Trim(); + } + + } + +} diff --git a/UnityPackage/Structs/Stats.cs.meta b/UnityPackage/Structs/Stats.cs.meta new file mode 100644 index 0000000..c6cb96a --- /dev/null +++ b/UnityPackage/Structs/Stats.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f29b66e6d53146f1bfdfbb72b33f0019 +timeCreated: 1716366934 \ No newline at end of file