diff --git a/Classes/Note.cs b/Classes/Note.cs deleted file mode 100644 index 193bbc3f..00000000 --- a/Classes/Note.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Godot; - -/** - * @class Note - * @brief Data structure class for holding data and methods for a battle time note. WIP - */ -public partial class Note : Resource -{ - public int Beat; - public NoteArrow.ArrowType Type; - - public Note(NoteArrow.ArrowType type = NoteArrow.ArrowType.Up, int beat = 0) - { - Beat = beat; - Type = type; - } -} diff --git a/Classes/Notes/Note.cs b/Classes/Notes/Note.cs new file mode 100644 index 00000000..f5ac1ea4 --- /dev/null +++ b/Classes/Notes/Note.cs @@ -0,0 +1,47 @@ +using System; +using FunkEngine; +using Godot; + +/** + * @class Note + * @brief Data structure class for holding data and methods for a battle time note. WIP + */ +public partial class Note : Resource +{ + public PuppetTemplate Owner; + public string Name; + private int _baseVal; + private Action NoteEffect; //TODO: Where/How to deal with timing. + + //public string Tooltip; + + public Note( + string name, + PuppetTemplate owner = null, + int baseVal = 1, + Action noteEffect = null + ) + { + Name = name; + Owner = owner; + NoteEffect = + noteEffect + ?? ( + (BD, source, Timing) => + { + BD.GetTarget(this).TakeDamage(source._baseVal); + } + ); + _baseVal = baseVal; + } + + public void OnHit(BattleDirector BD, Timing timing) + { + NoteEffect(BD, this, timing); + } + + public Note Clone() + { + return (Note)MemberwiseClone(); + } +} diff --git a/Classes/Relics/RelicEffect.cs b/Classes/Relics/RelicEffect.cs new file mode 100644 index 00000000..93ade4f0 --- /dev/null +++ b/Classes/Relics/RelicEffect.cs @@ -0,0 +1,31 @@ +using System; +using FunkEngine; +using Godot; + +public partial class RelicEffect : IBattleEvent +{ + private BattleEffectTrigger Trigger { get; set; } + public int BaseValue; + private Action OnRelicEffect; + + public RelicEffect( + BattleEffectTrigger trigger, + int val, + Action onRelicEffect + ) + { + BaseValue = val; + Trigger = trigger; + OnRelicEffect = onRelicEffect; + } + + public void OnTrigger(BattleDirector battleDirector) + { + OnRelicEffect(battleDirector, BaseValue); + } + + public BattleEffectTrigger GetTrigger() + { + return Trigger; + } +} diff --git a/Classes/Relics/RelicTemplate.cs b/Classes/Relics/RelicTemplate.cs new file mode 100644 index 00000000..892e1bdf --- /dev/null +++ b/Classes/Relics/RelicTemplate.cs @@ -0,0 +1,22 @@ +using System; +using FunkEngine; +using Godot; + +public partial class RelicTemplate : Resource +{ + public RelicEffect[] Effects; + public string Name; + + //public Texture2D Texture + //public string Tooltip + public RelicTemplate(string Name = "", RelicEffect[] EffectTags = null) + { + Effects = EffectTags; + this.Name = Name; + } + + public RelicTemplate Clone() + { + return (RelicTemplate)MemberwiseClone(); + } +} diff --git a/Globals/FunkEngineNameSpace.cs b/Globals/FunkEngineNameSpace.cs new file mode 100644 index 00000000..6bf17004 --- /dev/null +++ b/Globals/FunkEngineNameSpace.cs @@ -0,0 +1,56 @@ +using Godot; + +namespace FunkEngine; + +public enum ArrowType +{ + Up = 0, + Down = 1, + Left = 2, + Right = 3, +} + +public enum BattleEffectTrigger +{ + NotePlaced, + NoteHit, + SelfNoteHit, +} + +public enum Timing +{ + Miss = 0, + Bad = 1, + Okay = 2, + Good = 3, + Perfect = 4, +} + +public enum Stages +{ + Title, + Battle, + Quit, + Map, +} + +public struct SongData +{ + public int Bpm; + public double SongLength; + public int NumLoops; +} + +public struct ArrowData +{ + public Color Color; + public string Key; + public NoteChecker Node; + public ArrowType Type; +} + +public interface IBattleEvent +{ + void OnTrigger(BattleDirector BD); + BattleEffectTrigger GetTrigger(); +} diff --git a/Globals/Scribe.cs b/Globals/Scribe.cs new file mode 100644 index 00000000..b1b9884a --- /dev/null +++ b/Globals/Scribe.cs @@ -0,0 +1,49 @@ +using System; +using FunkEngine; +using Godot; + +/** + * Global for storing defined data, e.g. Notes and Relic Dictionaries. + */ +public partial class Scribe : Node +{ + public static readonly Note[] NoteDictionary = new[] + { + new Note( + "EnemyBase", + null, + 1, + (director, note, timing) => + { + director.Player.TakeDamage(4 - (int)timing); + } + ), + new Note( + "PlayerBase", + null, + 1, + (director, note, timing) => + { + director.Enemy.TakeDamage((int)timing); + } + ), + }; + + public static readonly RelicTemplate[] RelicDictionary = new[] + { + new RelicTemplate( + "Good Vibes", + new RelicEffect[] + { + new RelicEffect( + BattleEffectTrigger.NotePlaced, + 5, + (director, val) => + { + director.Player.Heal(val); + } + ), + } + ), + }; +} diff --git a/Globals/StageProducer.cs b/Globals/StageProducer.cs new file mode 100644 index 00000000..6b34afe1 --- /dev/null +++ b/Globals/StageProducer.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using FunkEngine; +using Godot; + +public partial class StageProducer : Node +{ + //Generate a map, starting as a width x height grid, pick a starting spot and do (path) paths from that to the last + //row, connecting the path, then connect all at the end to the boss room. + public static RandomNumberGenerator GlobalRng = new RandomNumberGenerator(); + private ulong _seed; + private ulong _lastRngState; + + private Stages _curStage = Stages.Title; + private Node _curScene; + + private MapGrid _map = new MapGrid(); + + public class MapGrid + { + private int[,] _map; + private Room[] _rooms; + private int _curIdx = 0; + private int _curRoom = 0; + + public class Room + { + public Room(int idx, int x, int y) + { + Idx = idx; + X = x; + Y = y; + } + + public void SetType(string type) + { + Type = type; + } + + public void AddChild(int newIdx) + { + if (Children.Contains(newIdx)) + return; + Children = Children.Append(newIdx).ToArray(); + } + + private int Idx; + private int[] Children = Array.Empty(); + private int X; + private int Y; + private string Type; + } + + public void InitMapGrid(int width, int height, int paths) + { + _curIdx = 0; + _rooms = Array.Empty(); + _map = new int[width, height]; //x,y + + int startX = GlobalRng.RandiRange(0, width - 1); //TODO: Replace with seeding + _rooms = _rooms.Append(new Room(_curIdx, startX, 0)).ToArray(); + _map[startX, 0] = _curIdx++; + + for (int i = 0; i < paths; i++) + { + GeneratePath_r(startX, 0, width, height); + } + + AddBossRoom(width, height); + } + + //Start at x, y, assume prev room exists. Picks new x pos within +/- 1, attaches recursively + private void GeneratePath_r(int x, int y, int width, int height) + { + int nextX = GlobalRng.RandiRange(Math.Max(x - 1, 0), Math.Min(x + 1, width - 1)); + if (_map[nextX, y + 1] == 0) + { + _rooms = _rooms.Append(new Room(_curIdx, nextX, y + 1)).ToArray(); + _map[nextX, y + 1] = _curIdx; + _rooms[_map[x, y]].AddChild(_curIdx++); + } + else + { + _rooms[_map[x, y]].AddChild(_map[nextX, y + 1]); + } + if (y < height - 2) + { + GeneratePath_r(nextX, y + 1, width, height); + } + } + + private void AddBossRoom(int width, int height) + { + _rooms = _rooms.Append(new Room(_curIdx, 0, height)).ToArray(); + _rooms[_curIdx].SetType("Boss"); + for (int i = 0; i < width; i++) //Attach all last rooms to a boss room + { + if (_map[i, height - 1] != 0) + { + _rooms[_map[i, height - 1]].AddChild(_curIdx); + } + } + } + } + + public void StartGame() + { + _map.InitMapGrid(2, 2, 1); + _seed = GlobalRng.Seed; + _lastRngState = GlobalRng.State; + } + + public void TransitionStage(Stages nextStage) + { + GD.Print(GetTree().CurrentScene); + switch (nextStage) + { + case Stages.Title: + GetTree().ChangeSceneToFile("res://scenes/SceneTransitions/TitleScreen.tscn"); + break; + case Stages.Battle: + GetTree().ChangeSceneToFile("res://scenes/BattleDirector/test_battle_scene.tscn"); + break; + } + + _curStage = nextStage; + } +} diff --git a/scripts/TimeKeeper.cs b/Globals/TimeKeeper.cs similarity index 100% rename from scripts/TimeKeeper.cs rename to Globals/TimeKeeper.cs diff --git a/README.md b/README.md index 727c86ea..65055df1 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,6 @@ Current team members include: #### Attributions: -Note icon: Next icons created by Pixel perfect - Flaticon - First Song: gameMusic by Magntron - freesound.org diff --git a/project.godot b/project.godot index ba8f8e20..76f74626 100644 --- a/project.godot +++ b/project.godot @@ -11,13 +11,15 @@ config_version=5 [application] config/name="Funk Engine" -run/main_scene="res://scenes/BattleDirector/test_battle_scene.tscn" +run/main_scene="res://scenes/SceneTransitions/TitleScreen.tscn" config/features=PackedStringArray("4.3", "C#", "Forward Plus") config/icon="res://icon.svg" [autoload] -TimeKeeper="*res://scripts/TimeKeeper.cs" +TimeKeeper="*res://Globals/TimeKeeper.cs" +Scribe="*res://Globals/Scribe.cs" +StageProducer="*res://Globals/StageProducer.cs" [display] diff --git a/scenes/BattleDirector/BattleDirector.cs b/scenes/BattleDirector/BattleDirector.cs deleted file mode 100644 index 6de6518c..00000000 --- a/scenes/BattleDirector/BattleDirector.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Godot; - -/** - * @class BattleDirector - * @brief Higher priority director to manage battle effects. Can directly access managers, which should signal up to Director WIP - */ -public partial class BattleDirector : Node2D -{ - #region Declarations - private HealthBar Player; - private HealthBar Enemy; - - [Export] - private ChartManager CM; - - [Export] - private InputHandler IH; - - [Export] - private NotePlacementBar NotePlacementBar; - - [Export] - private AudioStreamPlayer Audio; - - private double _timingInterval = .1; //secs - - [Signal] - public delegate void PlayerDamageEventHandler(int damage); - - [Signal] - public delegate void EnemyDamageEventHandler(int damage); - - private SongData _curSong; - - public struct SongData - { - public int Bpm; - public double SongLength; - public int NumLoops; - } - #endregion - - #region Note Handling - //Assume queue structure for notes in each lane. - //Can eventually make this its own structure - private NoteArrow[][] _laneData = Array.Empty(); - private int[] _laneLastBeat = new int[] - { //Temporary (hopefully) measure to bridge from note queue structure to ordered array - 0, - 0, - 0, - 0, - }; - private Note[] _notes = Array.Empty(); - - //Returns first note of lane without modifying lane data - private Note GetNoteAt(NoteArrow.ArrowType dir, int beat) - { - return GetNote(_laneData[(int)dir][beat]); - } - - //Get note of a note arrow - private Note GetNote(NoteArrow arrow) - { - return _notes[arrow.NoteIdx]; - } - - private bool AddNoteToLane(Note note, bool isActive = true) - { - note.Beat %= CM.BeatsPerLoop; - //Don't add dupe notes - if (note.Beat == 0 || _notes.Any(nt => nt.Type == note.Type && nt.Beat == note.Beat)) - { - return false; //Beat at 0 is too messy. - } - _notes = _notes.Append(note).ToArray(); - //Get noteArrow from CM - var arrow = CM.AddArrowToLane(note, _notes.Length - 1); - arrow.IsActive = isActive; - _laneData[(int)note.Type][note.Beat] = arrow; - return true; - } - #endregion - - //Creeate dummy notes - private void AddExampleNotes() - { - GD.Print(CM.BeatsPerLoop); - for (int i = 1; i < 15; i++) - { - Note exampleNote = new Note(NoteArrow.ArrowType.Up, i * 4); - AddNoteToLane(exampleNote); - } - for (int i = 1; i < 15; i++) - { - Note exampleNote = new Note(NoteArrow.ArrowType.Left, 4 * i + 1); - AddNoteToLane(exampleNote); - } - for (int i = 0; i < 10; i++) - { - Note exampleNote = new Note(NoteArrow.ArrowType.Right, 3 * i + 32); - AddNoteToLane(exampleNote); - } - for (int i = 0; i < 3; i++) - { - Note exampleNote = new Note(NoteArrow.ArrowType.Down, 8 * i + 16); - AddNoteToLane(exampleNote); - } - } - - public override void _Ready() - { - _curSong = new SongData - { - Bpm = 120, - SongLength = Audio.Stream.GetLength(), - NumLoops = 5, - }; - - var timer = GetTree().CreateTimer(AudioServer.GetTimeToNextMix()); - timer.Timeout += Begin; - } - - private void Begin() - { - CM.PrepChart(_curSong); - _laneData = new NoteArrow[][] - { - new NoteArrow[CM.BeatsPerLoop], - new NoteArrow[CM.BeatsPerLoop], - new NoteArrow[CM.BeatsPerLoop], - new NoteArrow[CM.BeatsPerLoop], - }; - AddExampleNotes(); - - Player = GetNode("PlayerHP"); - Player.GetNode("Sprite2D").Scale *= .5f; //TEMP - Player.GetNode("Sprite2D").Position += Vector2.Down * 30; //TEMP - Enemy = GetNode("EnemyHP"); - - //TEMP - var enemTween = CreateTween(); - enemTween - .TweenProperty(Enemy.GetNode("Sprite2D"), "position", Vector2.Down * 5, 1f) - .AsRelative(); - enemTween - .TweenProperty(Enemy.GetNode("Sprite2D"), "position", Vector2.Up * 5, 1f) - .AsRelative(); - enemTween.SetTrans(Tween.TransitionType.Spring); - enemTween.SetEase(Tween.EaseType.In); - enemTween.SetLoops(); - enemTween.Play(); - - CM.Connect(nameof(InputHandler.NotePressed), new Callable(this, nameof(OnNotePressed))); - CM.Connect(nameof(InputHandler.NoteReleased), new Callable(this, nameof(OnNoteReleased))); - - Audio.Play(); - } - - public override void _Process(double delta) - { - TimeKeeper.CurrentTime = Audio.GetPlaybackPosition(); - CheckMiss(); - } - - #region Input&Timing - private void OnNotePressed(NoteArrow.ArrowType type) - { - CheckNoteTiming(type); - } - - private void OnNoteReleased(NoteArrow.ArrowType arrowType) { } - - //Check all lanes for misses from missed inputs - private void CheckMiss() - { - //On current beat, if prev beat is active and not inputted - double realBeat = TimeKeeper.CurrentTime / (60 / (double)_curSong.Bpm) % CM.BeatsPerLoop; - for (int i = 0; i < _laneData.Length; i++) - { - if ( - _laneLastBeat[i] < Math.Floor(realBeat) - || (_laneLastBeat[i] == CM.BeatsPerLoop - 1 && Math.Floor(realBeat) == 0) - ) - { //If above, a note has been missed - //GD.Print("Last beat " + _laneLastBeat[i]); - if ( - _laneData[i][_laneLastBeat[i]] == null - || !_laneData[i][_laneLastBeat[i]].IsActive - ) - { - _laneLastBeat[i] = (_laneLastBeat[i] + 1) % CM.BeatsPerLoop; - continue; - } - //Note exists and has been missed - _laneData[i][_laneLastBeat[i]].NoteHit(); - HandleTiming((NoteArrow.ArrowType)i, 1); - _laneLastBeat[i] = (_laneLastBeat[i] + 1) % CM.BeatsPerLoop; - } - } - } - - private void CheckNoteTiming(NoteArrow.ArrowType type) - { - double realBeat = TimeKeeper.CurrentTime / (60 / (double)_curSong.Bpm) % CM.BeatsPerLoop; - int curBeat = (int)Math.Round(realBeat); - GD.Print("Cur beat " + curBeat + "Real: " + realBeat.ToString("#.###")); - if ( - _laneData[(int)type][curBeat % CM.BeatsPerLoop] == null - || !_laneData[(int)type][curBeat % CM.BeatsPerLoop].IsActive - ) - { - _laneLastBeat[(int)type] = (curBeat) % CM.BeatsPerLoop; - PlayerAddNote(type, curBeat); - return; - } - double beatDif = Math.Abs(realBeat - curBeat); - _laneData[(int)type][curBeat % CM.BeatsPerLoop].NoteHit(); - _laneLastBeat[(int)type] = (curBeat) % CM.BeatsPerLoop; - HandleTiming(type, beatDif); - } - - private void HandleTiming(NoteArrow.ArrowType type, double beatDif) - { - if (beatDif < _timingInterval * 1) - { - GD.Print("Perfect"); - Enemy.TakeDamage(3); - NotePlacementBar.HitNote(); - NotePlacementBar.ComboText("Perfect!"); - } - else if (beatDif < _timingInterval * 2) - { - GD.Print("Good"); - Enemy.TakeDamage(1); - NotePlacementBar.HitNote(); - NotePlacementBar.ComboText("Good"); - } - else if (beatDif < _timingInterval * 3) - { - GD.Print("Ok"); - Player.TakeDamage(1); - NotePlacementBar.HitNote(); - NotePlacementBar.ComboText("Okay"); - } - else - { - GD.Print("Miss"); - Player.TakeDamage(2); - NotePlacementBar.MissNote(); - NotePlacementBar.ComboText("Miss"); - } - } - #endregion - - private void PlayerAddNote(NoteArrow.ArrowType type, int beat) - { - // can also add some sort of keybind here to also have pressed - // in case the user just presses the note too early and spawns a note - GD.Print( - $"Player trying to place {type} typed note at beat: " - + beat - + " Verdict: " - + NotePlacementBar.CanPlaceNote() - ); - if (NotePlacementBar.CanPlaceNote()) - { - Note exampleNote = new Note(type, beat % CM.BeatsPerLoop); - if (AddNoteToLane(exampleNote, false)) - NotePlacementBar.PlacedNote(); - } - } -} diff --git a/scenes/BattleDirector/HealthBar.cs b/scenes/BattleDirector/HealthBar.cs deleted file mode 100644 index c0ad3e2e..00000000 --- a/scenes/BattleDirector/HealthBar.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using Godot; - -public partial class HealthBar : Control -{ - const int MaxHealth = 100; - int _health = MaxHealth; - - [Export] - public TextureProgressBar PlayerHealthBar; - - [Export] - public Texture2D SpriteText; - - //we can change this to a Texture Progress bar once we have art assets for it - - - public override void _Ready() - { - if (PlayerHealthBar != null) - { - SetHealth(MaxHealth, MaxHealth); - } - GetNode("Sprite2D").Texture = SpriteText; - } - - public void SetHealth(int max, int current) - { - PlayerHealthBar.MaxValue = max; - PlayerHealthBar.Value = current; - _updateHealthBar(); - } - - private void _updateHealthBar() - { - PlayerHealthBar.Value = _health; - } - - public void TakeDamage(int damage) - { - _health -= damage; - if (_health <= 0) - { - GD.Print("You are dead"); - } - _updateHealthBar(); - } -} diff --git a/scenes/BattleDirector/HealthBar.tscn b/scenes/BattleDirector/HealthBar.tscn deleted file mode 100644 index 0f72abd7..00000000 --- a/scenes/BattleDirector/HealthBar.tscn +++ /dev/null @@ -1,47 +0,0 @@ -[gd_scene load_steps=8 format=3 uid="uid://bgomxovxs7sr8"] - -[ext_resource type="Script" path="res://scenes/BattleDirector/HealthBar.cs" id="1_b1t4i"] - -[sub_resource type="Gradient" id="Gradient_ve5ki"] -colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1) - -[sub_resource type="GradientTexture2D" id="GradientTexture2D_ti0cv"] -gradient = SubResource("Gradient_ve5ki") -width = 150 -height = 20 - -[sub_resource type="Gradient" id="Gradient_soqhm"] -colors = PackedColorArray(0, 1, 0.0999999, 1, 1, 1, 1, 1) - -[sub_resource type="GradientTexture2D" id="GradientTexture2D_r4hau"] -gradient = SubResource("Gradient_soqhm") -width = 146 -height = 16 - -[sub_resource type="Gradient" id="Gradient_58kj0"] -offsets = PackedFloat32Array(1) -colors = PackedColorArray(1, 1, 1, 1) - -[sub_resource type="GradientTexture2D" id="GradientTexture2D_wwca1"] -gradient = SubResource("Gradient_58kj0") - -[node name="Control" type="Control" node_paths=PackedStringArray("PlayerHealthBar")] -layout_mode = 3 -anchors_preset = 0 -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("1_b1t4i") -PlayerHealthBar = NodePath("ProgressBar") - -[node name="ProgressBar" type="TextureProgressBar" parent="."] -layout_mode = 0 -offset_right = 150.0 -offset_bottom = 20.0 -texture_under = SubResource("GradientTexture2D_ti0cv") -texture_progress = SubResource("GradientTexture2D_r4hau") -texture_progress_offset = Vector2(2, 2) - -[node name="Sprite2D" type="Sprite2D" parent="."] -position = Vector2(75, 86) -scale = Vector2(2, 2) -texture = SubResource("GradientTexture2D_wwca1") diff --git a/scenes/BattleDirector/NotePlacementBar.tscn b/scenes/BattleDirector/NotePlacementBar.tscn index c8dc9577..2b61556e 100644 --- a/scenes/BattleDirector/NotePlacementBar.tscn +++ b/scenes/BattleDirector/NotePlacementBar.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=6 format=3 uid="uid://duhiilcv4tat3"] -[ext_resource type="Script" path="res://scenes/BattleDirector/NotePlacementBar.cs" id="1_456es"] +[ext_resource type="Script" path="res://scenes/BattleDirector/scripts/NotePlacementBar.cs" id="1_456es"] [sub_resource type="Gradient" id="Gradient_0u6yv"] colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1) diff --git a/scenes/BattleDirector/TextParticle.tscn b/scenes/BattleDirector/TextParticle.tscn deleted file mode 100644 index 6491a671..00000000 --- a/scenes/BattleDirector/TextParticle.tscn +++ /dev/null @@ -1,9 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://bd23wwbv7i4gg"] - -[ext_resource type="Script" path="res://scenes/BattleDirector/TextParticle.cs" id="1_j0ufq"] - -[node name="Control" type="Label"] -anchors_preset = -1 -anchor_right = 0.08 -text = "900000" -script = ExtResource("1_j0ufq") diff --git a/scenes/BattleDirector/assets/Character1.png b/scenes/BattleDirector/assets/Character1.png index 2175831d..f416d5d2 100644 Binary files a/scenes/BattleDirector/assets/Character1.png and b/scenes/BattleDirector/assets/Character1.png differ diff --git a/scenes/BattleDirector/scripts/BattleDirector.cs b/scenes/BattleDirector/scripts/BattleDirector.cs new file mode 100644 index 00000000..8338ddfb --- /dev/null +++ b/scenes/BattleDirector/scripts/BattleDirector.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using FunkEngine; +using Godot; + +/** + * @class BattleDirector + * @brief Higher priority director to manage battle effects. Can directly access managers, which should signal up to Director WIP + */ +public partial class BattleDirector : Node2D +{ //TODO: Maybe move some Director functionality to a sub node. + #region Declarations + + public PlayerPuppet Player; + public PuppetTemplate Enemy; + + [Export] + private ChartManager CM; + + [Export] + private NotePlacementBar NotePlacementBar; + + [Export] + private Conductor CD; + + [Export] + private AudioStreamPlayer Audio; + + private double _timingInterval = .1; //secs, maybe make somewhat note dependent + + private SongData _curSong; + + #endregion + + #region Note Handling + private void PlayerAddNote(ArrowType type, int beat) + { + GD.Print($"Player trying to place {type} typed note at beat: " + beat); + if (!NotePlacementBar.CanPlaceNote()) + return; + if (CD.AddNoteToLane(type, beat % CM.BeatsPerLoop, false)) + { + NotePlacementBar.PlacedNote(); + NotePlaced?.Invoke(this); + GD.Print("Note Placed."); + } + } + + public PuppetTemplate GetTarget(Note note) + { + if (note.Owner == Player) + { + return Enemy; + } + + return Player; + } + #endregion + + #region Initialization + public override void _Ready() + { + _curSong = new SongData + { + Bpm = 120, + SongLength = Audio.Stream.GetLength(), + NumLoops = 5, + }; + TimeKeeper.Bpm = _curSong.Bpm; + + Player = new PlayerPuppet(); + AddChild(Player); + EventizeRelics(); + //TODO: Refine + foreach (var note in Player.Stats.CurNotes) + { + note.Owner = Player; + CD.Notes = CD.Notes.Append(note).ToArray(); + } + Note enemNote = Scribe.NoteDictionary[0].Clone(); + CD.Notes = CD.Notes.Append(enemNote).ToArray(); + + Enemy = new PuppetTemplate(); + Enemy.SetPosition(new Vector2(400, 0)); + AddChild(Enemy); + Enemy.Init(GD.Load("res://scenes/BattleDirector/assets/Enemy1.png"), "Enemy"); + Enemy.Sprite.Scale *= 2; + + var timer = GetTree().CreateTimer(AudioServer.GetTimeToNextMix()); + timer.Timeout += Begin; + } + + //TODO: This will all change + private void Begin() + { + CM.PrepChart(_curSong); + CD.Prep(); + CD.TimedInput += OnTimedInput; + + //TEMP TODO: Make enemies, can put this in an enemy subclass + var enemTween = CreateTween(); + enemTween.TweenProperty(Enemy.Sprite, "position", Vector2.Down * 5, 1f).AsRelative(); + enemTween.TweenProperty(Enemy.Sprite, "position", Vector2.Up * 5, 1f).AsRelative(); + enemTween.SetTrans(Tween.TransitionType.Spring); + enemTween.SetEase(Tween.EaseType.In); + enemTween.SetLoops(); + enemTween.Play(); + + CM.Connect(nameof(InputHandler.NotePressed), new Callable(this, nameof(OnNotePressed))); + CM.Connect(nameof(InputHandler.NoteReleased), new Callable(this, nameof(OnNoteReleased))); + + Audio.Play(); + } + + public override void _Process(double delta) + { + TimeKeeper.CurrentTime = Audio.GetPlaybackPosition(); + CD.CheckMiss(); + } + #endregion + + #region Input&Timing + private void OnNotePressed(ArrowType type) + { + CD.CheckNoteTiming(type); + } + + private void OnNoteReleased(ArrowType arrowType) { } + + private void OnTimedInput(Note note, ArrowType arrowType, int beat, double beatDif) + { + GD.Print(arrowType + " " + beat + " difference: " + beatDif); + if (note == null) + { + PlayerAddNote(arrowType, beat); + return; + } + //TODO: Evaluate Timing as a function + Timing timed = CheckTiming(beatDif); + GD.Print(timed); + + if (timed == Timing.Miss) + { + note.OnHit(this, timed); + NotePlacementBar.MissNote(); + } + else + { + note.OnHit(this, timed); + NotePlacementBar.HitNote(); + } + NotePlacementBar.ComboText(timed.ToString()); + } + + private Timing CheckTiming(double beatDif) + { + if (beatDif < _timingInterval * 1) + { + return Timing.Perfect; + } + + if (beatDif < _timingInterval * 2) + { + return Timing.Good; + } + + if (beatDif < _timingInterval * 3) + { + return Timing.Okay; + } + + return Timing.Miss; + } + + #endregion + + #region BattleEffect Handling + + private delegate void NotePlacedHandler(BattleDirector BD); + private event NotePlacedHandler NotePlaced; + + private void EventizeRelics() + { + foreach (var relic in Player.Stats.CurRelics) + { + GetNode