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..8795cf42 --- /dev/null +++ b/Classes/Notes/Note.cs @@ -0,0 +1,55 @@ +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 Texture2D Texture; + + public Note( + string name, + string tooltip, + Texture2D texture = null, + 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; + Texture = texture; + Tooltip = tooltip; + } + + public void OnHit(BattleDirector BD, Timing timing) + { + NoteEffect(BD, this, timing); + } + + public Note Clone() + { + //Eventually could look into something more robust, but for now shallow copy is preferable. + //We only would want val and name to be copied by value + Note newNote = new Note(Name, Tooltip, Texture, Owner, _baseVal, NoteEffect); + return newNote; + } +} diff --git a/Classes/Notes/assets/double_note.png b/Classes/Notes/assets/double_note.png new file mode 100644 index 00000000..f9f8afc6 Binary files /dev/null and b/Classes/Notes/assets/double_note.png differ diff --git a/scenes/NoteManager/assets/right-arrow.png.import b/Classes/Notes/assets/double_note.png.import similarity index 66% rename from scenes/NoteManager/assets/right-arrow.png.import rename to Classes/Notes/assets/double_note.png.import index 5a9baa01..ea62f194 100644 --- a/scenes/NoteManager/assets/right-arrow.png.import +++ b/Classes/Notes/assets/double_note.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://bucvj4fquqpkr" -path="res://.godot/imported/right-arrow.png-b3005485b42777a77a9d39fbba48875d.ctex" +uid="uid://caw70lr5e1yiq" +path="res://.godot/imported/double_note.png-1b788aee0b7f76d502303d178d821d3b.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://scenes/NoteManager/assets/right-arrow.png" -dest_files=["res://.godot/imported/right-arrow.png-b3005485b42777a77a9d39fbba48875d.ctex"] +source_file="res://Classes/Notes/assets/double_note.png" +dest_files=["res://.godot/imported/double_note.png-1b788aee0b7f76d502303d178d821d3b.ctex"] [params] diff --git a/Classes/Notes/assets/single_note.png b/Classes/Notes/assets/single_note.png new file mode 100644 index 00000000..4f62a191 Binary files /dev/null and b/Classes/Notes/assets/single_note.png differ diff --git a/Classes/Notes/assets/single_note.png.import b/Classes/Notes/assets/single_note.png.import new file mode 100644 index 00000000..63160ba7 --- /dev/null +++ b/Classes/Notes/assets/single_note.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3chrsxrulapd" +path="res://.godot/imported/single_note.png-edadc3d6779f4cc26ac823d186717719.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Notes/assets/single_note.png" +dest_files=["res://.godot/imported/single_note.png-edadc3d6779f4cc26ac823d186717719.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 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..f0a117cc --- /dev/null +++ b/Classes/Relics/RelicTemplate.cs @@ -0,0 +1,31 @@ +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 = "", + string tooltip = "", + Texture2D texture = null, + RelicEffect[] EffectTags = null + ) + { + Effects = EffectTags; + Name = name; + Tooltip = tooltip; + Texture = texture; + } + + public RelicTemplate Clone() + { + RelicTemplate newRelic = new RelicTemplate(Name, Tooltip, Texture, Effects); + return newRelic; + } +} diff --git a/Classes/Relics/assets/relic_Breakfast.png b/Classes/Relics/assets/relic_Breakfast.png new file mode 100644 index 00000000..1c6bbe1d Binary files /dev/null and b/Classes/Relics/assets/relic_Breakfast.png differ diff --git a/Classes/Relics/assets/relic_Breakfast.png.import b/Classes/Relics/assets/relic_Breakfast.png.import new file mode 100644 index 00000000..6a21aa03 --- /dev/null +++ b/Classes/Relics/assets/relic_Breakfast.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csjx2hb4tdlw8" +path="res://.godot/imported/relic_Breakfast.png-c1b968058adbb855fcf957a2aec74dc2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Relics/assets/relic_Breakfast.png" +dest_files=["res://.godot/imported/relic_Breakfast.png-c1b968058adbb855fcf957a2aec74dc2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Classes/Relics/assets/relic_GoodVibes.png b/Classes/Relics/assets/relic_GoodVibes.png new file mode 100644 index 00000000..3ab27727 Binary files /dev/null and b/Classes/Relics/assets/relic_GoodVibes.png differ diff --git a/Classes/Relics/assets/relic_GoodVibes.png.import b/Classes/Relics/assets/relic_GoodVibes.png.import new file mode 100644 index 00000000..c2ff344d --- /dev/null +++ b/Classes/Relics/assets/relic_GoodVibes.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dg4tnp7plxmp7" +path="res://.godot/imported/relic_GoodVibes.png-cd102b29bb163411bb7ce8cf724ef0c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Relics/assets/relic_GoodVibes.png" +dest_files=["res://.godot/imported/relic_GoodVibes.png-cd102b29bb163411bb7ce8cf724ef0c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Funk Engine.csproj b/Funk Engine.csproj index 7051324c..f8d51ba9 100644 --- a/Funk Engine.csproj +++ b/Funk Engine.csproj @@ -6,6 +6,9 @@ true FunkEngine + + + diff --git a/Globals/FunkEngineNameSpace.cs b/Globals/FunkEngineNameSpace.cs new file mode 100644 index 00000000..7d422ea6 --- /dev/null +++ b/Globals/FunkEngineNameSpace.cs @@ -0,0 +1,57 @@ +using Godot; + +namespace FunkEngine; + +public enum ArrowType +{ + Up = 0, + Down = 1, + Left = 2, + Right = 3, +} + +public enum BattleEffectTrigger +{ + NotePlaced, + NoteHit, + SelfNoteHit, + OnPickup, +} + +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..45bc8588 --- /dev/null +++ b/Globals/Scribe.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq; +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", + "Basic enemy note, deals damage to player.", + GD.Load("res://scenes/BattleDirector/assets/Character1.png"), + null, + 1, + (director, note, timing) => + { + director.Player.TakeDamage(4 - (int)timing); + } + ), + new Note( + "PlayerBase", + "Basic player note, deals damage to enemy", + GD.Load("res://Classes/Notes/assets/single_note.png"), + null, + 1, + (director, note, timing) => + { + director.Enemy.TakeDamage((int)timing); + } + ), + new Note( + "PlayerDouble", + "Basic player note, deals double damage to enemy", + GD.Load("res://Classes/Notes/assets/double_note.png"), + null, + 1, + (director, note, timing) => + { + // can change later, but I want it like this instead of changing base + // in case we have some relic that messes with timing + director.Enemy.TakeDamage(2 * (int)timing); + } + ), + }; + + public static readonly RelicTemplate[] RelicDictionary = new[] + { + new RelicTemplate( + "Breakfast", //Reference ha ha, Item to give when relic pool is empty. + "Increases max hp.", //TODO: Description can include the relics values? + GD.Load("res://Classes/Relics/assets/relic_Breakfast.png"), + new RelicEffect[] + { + new RelicEffect( + BattleEffectTrigger.OnPickup, + 10, + (director, val) => + { + StageProducer.PlayerStats.MaxHealth += val; + StageProducer.PlayerStats.CurrentHealth += val; + } + ), + } + ), + new RelicTemplate( + "Good Vibes", + "Good vibes, heals the player whenever they place a note.", //TODO: Description can include the relics values? + GD.Load("res://Classes/Relics/assets/relic_GoodVibes.png"), + new RelicEffect[] + { + new RelicEffect( + BattleEffectTrigger.NotePlaced, + 1, + (director, val) => + { + director.Player.Heal(val); + } + ), + } + ), + }; + + //TODO: Item pool(s) + + public static RelicTemplate[] GetRandomRelics(RelicTemplate[] ownedRelics, int count) + { + var availableRelics = Scribe + .RelicDictionary.Where(r => !ownedRelics.Any(o => o.Name == r.Name)) + .ToArray(); + + availableRelics = availableRelics + .OrderBy(_ => StageProducer.GlobalRng.Randi()) + .Take(count) + .Select(r => r.Clone()) + .ToArray(); + + for (int i = availableRelics.Length; i < count; i++) + { + availableRelics = availableRelics.Append(RelicDictionary[0].Clone()).ToArray(); + } + return availableRelics; + } +} diff --git a/Globals/StageProducer.cs b/Globals/StageProducer.cs new file mode 100644 index 00000000..d304eaaa --- /dev/null +++ b/Globals/StageProducer.cs @@ -0,0 +1,181 @@ +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 bool _isInitialized = false; + + private Stages _curStage = Stages.Title; + private Node _curScene; + public static MapGrid.Room CurRoom { get; private set; } + public static Vector2I MapSize { get; private set; } = new Vector2I(7, 6); //For now, make width an odd number + + public static MapGrid Map { get; } = new MapGrid(); + + //Hold here to persist between changes + //TODO: Allow for permanent changes and battle temporary stat changes. + public static PlayerStats PlayerStats; + + public class MapGrid + { + private int[,] _map; + private Room[] _rooms; + private int _curIdx = 0; + private int _curRoom = 0; + + public Room[] GetRooms() + { + return _rooms; + } + + 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(); + } + + public int Idx { get; private set; } + public int[] Children { get; private set; } = Array.Empty(); + public int X { get; private set; } + public int Y { get; private set; } + 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 = (width / 2); + _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); + } + CreateCommonChildren(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); + } + } + + //Asserts that if there is a room at the same x, but y+1 they are connected + private void CreateCommonChildren(int width, int height) + { + foreach (Room room in _rooms) + { + Vector2I curPos = new Vector2I(room.X, room.Y); + if (room.Y + 1 >= height) + continue; + if (_map[curPos.X, curPos.Y + 1] == 0) + continue; + GD.Print("Added child on same X."); + room.AddChild(_map[curPos.X, curPos.Y + 1]); + } + } + + //Adds a boss room at the end of rooms, all max height rooms connect to it. + private void AddBossRoom(int width, int height) + { + _rooms = _rooms.Append(new Room(_curIdx, width / 2, 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(MapSize.X, MapSize.Y, 3); + _seed = GlobalRng.Seed; + _lastRngState = GlobalRng.State; + PlayerStats = new PlayerStats(); + + CurRoom = Map.GetRooms()[0]; + _isInitialized = true; + } + + public void TransitionFromRoom(int nextRoomIdx) + { + //CurRoom = Map.GetRooms()[nextRoomIdx]; + TransitionStage(Stages.Battle); + } + + public void TransitionStage(Stages nextStage) + { + GD.Print(GetTree().CurrentScene); + switch (nextStage) + { + case Stages.Title: + _isInitialized = false; + GetTree().ChangeSceneToFile("res://scenes/SceneTransitions/TitleScreen.tscn"); + break; + case Stages.Battle: + GetTree().ChangeSceneToFile("res://scenes/BattleDirector/test_battle_scene.tscn"); + break; + case Stages.Map: + GetTree().ChangeSceneToFile("res://scenes/Maps/cartographer.tscn"); + if (!_isInitialized) + { + StartGame(); + } + break; + case Stages.Quit: + GD.Print("Exiting game"); + GetTree().Quit(); + return; + default: + GD.Print($"Error Scene Transition is {nextStage}"); + 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/SaveData/SaveData.json b/SaveData/SaveData.json new file mode 100644 index 00000000..1aeaf3cc --- /dev/null +++ b/SaveData/SaveData.json @@ -0,0 +1,9 @@ +{ + "AccountName": null, + "Notes": { + "PlayerBase": 2, + "PlayerDouble": 1 + }, + "Relics": {}, + "Settings": {} +} \ No newline at end of file diff --git a/SaveData/SaveSystem.cs b/SaveData/SaveSystem.cs new file mode 100644 index 00000000..7cc7af03 --- /dev/null +++ b/SaveData/SaveSystem.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using Godot; + +// TODO: implement saving + +public static class SaveSystem +{ + private static string SavePath => "res://SaveData/SaveData.json"; // Update if needed + + // Loads only the notes section + public static Dictionary LoadNotes() + { + var saveData = LoadSaveData(); + if (saveData != null && saveData.Notes != null) + { + return saveData.Notes; + } + else + { + return new Dictionary(); + } + } + + // This method loads the entire save data + public static SaveData LoadSaveData() + { + string path = ProjectSettings.GlobalizePath(SavePath); + if (!File.Exists(path)) + { + GD.PrintErr("Can't load save game"); + return null; + } + + string json = File.ReadAllText(path); + SaveData data = JsonSerializer.Deserialize(json); + return data; + } +} + +public class SaveData +{ + public string AccountName { get; set; } + public Dictionary Notes { get; set; } = new Dictionary(); + public Dictionary Relics { get; set; } = new Dictionary(); + public Dictionary Settings { get; set; } = new Dictionary(); +} diff --git a/project.godot b/project.godot index ba8f8e20..94e0cab0 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] @@ -56,6 +58,16 @@ arrowRight={ , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) ] } +Pause={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +Inventory={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null) +] +} [rendering] 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.cs b/scenes/BattleDirector/NotePlacementBar.cs deleted file mode 100644 index d86a3c9a..00000000 --- a/scenes/BattleDirector/NotePlacementBar.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using Godot; - -public partial class NotePlacementBar : Node -{ - const int MaxValue = 80; - int currentBarValue; - int currentCombo; - int comboMult; - int notesToIncreaseCombo; - - [Export] - TextureProgressBar notePlacementBar; - - [Export] - TextEdit currentComboMultText; - - // Called when the node enters the scene tree for the first time. - public override void _Ready() - { - notePlacementBar.MaxValue = MaxValue; - currentBarValue = 0; - currentCombo = 0; - comboMult = 1; - notesToIncreaseCombo = 4; - } - - public void ComboText(string text) - { - var feedbackScene = ResourceLoader.Load( - "res://scenes/BattleDirector/TextParticle.tscn" - ); - TextParticle newText = feedbackScene.Instantiate(); - AddChild(newText); - newText.Text = text + $" {currentCombo}"; - } - - // Hitting a note increases combo, combo mult, and note placement bar - public void HitNote() - { - currentCombo++; - DetermineComboMult(); - currentBarValue += comboMult; - UpdateNotePlacementBar(currentBarValue); - UpdateComboMultText(); - } - - // Missing a note resets combo - public void MissNote() - { - currentCombo = 0; - DetermineComboMult(); - UpdateComboMultText(); - } - - // Placing a note resets the note placement bar - public void PlacedNote() - { - currentBarValue = 0; - UpdateNotePlacementBar(currentBarValue); - } - - public bool CanPlaceNote() - { - return currentBarValue >= MaxValue; - } - - private void DetermineComboMult() - { - comboMult = currentCombo / notesToIncreaseCombo + 1; - } - - public void UpdateNotePlacementBar(int newValue) - { - notePlacementBar.Value = newValue; - } - - public void UpdateComboMultText() - { - currentComboMultText.Text = $"x{comboMult.ToString()}"; - } -} diff --git a/scenes/BattleDirector/NotePlacementBar.tscn b/scenes/BattleDirector/NotePlacementBar.tscn index c8dc9577..7f1133c2 100644 --- a/scenes/BattleDirector/NotePlacementBar.tscn +++ b/scenes/BattleDirector/NotePlacementBar.tscn @@ -1,6 +1,9 @@ -[gd_scene load_steps=6 format=3 uid="uid://duhiilcv4tat3"] +[gd_scene load_steps=9 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"] +[ext_resource type="Texture2D" uid="uid://cnyr5usjdv0ni" path="res://scenes/BattleDirector/assets/temp_note_queue.png" id="2_3tw16"] +[ext_resource type="Texture2D" uid="uid://c3chrsxrulapd" path="res://Classes/Notes/assets/single_note.png" id="3_6ylx6"] +[ext_resource type="Texture2D" uid="uid://caw70lr5e1yiq" path="res://Classes/Notes/assets/double_note.png" id="4_6w8ha"] [sub_resource type="Gradient" id="Gradient_0u6yv"] colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1) @@ -10,17 +13,17 @@ gradient = SubResource("Gradient_0u6yv") width = 26 height = 100 -[sub_resource type="Gradient" id="Gradient_lyd0l"] +[sub_resource type="Gradient" id="Gradient_xvck1"] offsets = PackedFloat32Array(0, 0.485915, 0.739437, 1) colors = PackedColorArray(0, 1, 0, 1, 0.68, 0, 0.272, 1, 0, 0.64, 0.608, 1, 0.4, 0, 0, 1) -[sub_resource type="GradientTexture2D" id="GradientTexture2D_k7kvy"] -gradient = SubResource("Gradient_lyd0l") +[sub_resource type="GradientTexture2D" id="GradientTexture2D_0bqho"] +gradient = SubResource("Gradient_xvck1") width = 24 height = 98 fill_to = Vector2(0, 1) -[node name="NotePlacementBar" type="Control" node_paths=PackedStringArray("notePlacementBar", "currentComboMultText")] +[node name="NotePlacementBar" type="Control" node_paths=PackedStringArray("notePlacementBar", "currentComboMultText", "_currentNote", "_nextNote")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -30,6 +33,8 @@ grow_vertical = 2 script = ExtResource("1_456es") notePlacementBar = NodePath("ProgressBar") currentComboMultText = NodePath("TextEdit") +_currentNote = NodePath("NoteQueueSprite/CurrentNote") +_nextNote = NodePath("NoteQueueSprite/NextNote") [node name="ProgressBar" type="TextureProgressBar" parent="."] layout_mode = 0 @@ -37,7 +42,7 @@ offset_right = 26.0 offset_bottom = 100.0 fill_mode = 3 texture_under = SubResource("GradientTexture2D_hhds4") -texture_progress = SubResource("GradientTexture2D_k7kvy") +texture_progress = SubResource("GradientTexture2D_0bqho") texture_progress_offset = Vector2(1, 1) [node name="TextEdit" type="TextEdit" parent="."] @@ -54,3 +59,15 @@ deselect_on_focus_loss_enabled = false drag_and_drop_selection_enabled = false virtual_keyboard_enabled = false middle_mouse_paste_enabled = false + +[node name="NoteQueueSprite" type="Sprite2D" parent="."] +position = Vector2(84, -24) +texture = ExtResource("2_3tw16") + +[node name="CurrentNote" type="Sprite2D" parent="NoteQueueSprite"] +position = Vector2(-14, -1) +texture = ExtResource("3_6ylx6") + +[node name="NextNote" type="Sprite2D" parent="NoteQueueSprite"] +position = Vector2(16, -2) +texture = ExtResource("4_6w8ha") 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/assets/CoolBG.jpg b/scenes/BattleDirector/assets/CoolBG.jpg index 60734eb2..5848378a 100644 Binary files a/scenes/BattleDirector/assets/CoolBG.jpg and b/scenes/BattleDirector/assets/CoolBG.jpg differ diff --git a/scenes/BattleDirector/assets/temp_note_queue.png b/scenes/BattleDirector/assets/temp_note_queue.png new file mode 100644 index 00000000..c6a556d0 Binary files /dev/null and b/scenes/BattleDirector/assets/temp_note_queue.png differ diff --git a/scenes/BattleDirector/assets/temp_note_queue.png.import b/scenes/BattleDirector/assets/temp_note_queue.png.import new file mode 100644 index 00000000..cf432095 --- /dev/null +++ b/scenes/BattleDirector/assets/temp_note_queue.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnyr5usjdv0ni" +path="res://.godot/imported/temp_note_queue.png-b6e43682068ed18b8bf4cd7cd8229404.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scenes/BattleDirector/assets/temp_note_queue.png" +dest_files=["res://.godot/imported/temp_note_queue.png-b6e43682068ed18b8bf4cd7cd8229404.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/BattleDirector/scripts/BattleDirector.cs b/scenes/BattleDirector/scripts/BattleDirector.cs new file mode 100644 index 00000000..9a0822c1 --- /dev/null +++ b/scenes/BattleDirector/scripts/BattleDirector.cs @@ -0,0 +1,267 @@ +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; + + private bool _battleEnd; + + #endregion + + #region Note Handling + private void PlayerAddNote(ArrowType type, int beat) + { + if (!NotePlacementBar.CanPlaceNote()) + return; + if (CD.AddNoteToLane(type, beat % CM.BeatsPerLoop, NotePlacementBar.PlacedNote(), false)) + { + 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() + { + //TODO: Should come from transition into battle + _curSong = new SongData + { + Bpm = 120, + SongLength = Audio.Stream.GetLength(), + NumLoops = 5, + }; + TimeKeeper.Bpm = _curSong.Bpm; + + Player = new PlayerPuppet(); + AddChild(Player); + Player.Defeated += CheckBattleStatus; + EventizeRelics(); + NotePlacementBar.Setup(StageProducer.PlayerStats); + + //TODO: Refine + Enemy = new PuppetTemplate(); + Enemy.SetPosition(new Vector2(400, 0)); + AddChild(Enemy); + Enemy.Defeated += CheckBattleStatus; + Enemy.Init(GD.Load("res://scenes/BattleDirector/assets/Enemy1.png"), "Enemy"); + + //TODO: This is a temporary measure + Button startButton = new Button(); + startButton.Text = "Start"; + startButton.Position = GetViewportRect().Size / 2; + AddChild(startButton); + startButton.Pressed += () => + { + var timer = GetTree().CreateTimer(AudioServer.GetTimeToNextMix()); + timer.Timeout += Begin; + startButton.QueueFree(); + }; + } + + //TODO: This will all change + private void Begin() + { + CM.PrepChart(_curSong); + CD.Prep(); + CD.TimedInput += OnTimedInput; + + //TODO: Make enemies, can put this in an enemy subclass + Enemy.Sprite.Scale *= 2; + 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(); + if (_battleEnd) + GetNode("/root/StageProducer").TransitionStage(Stages.Map); + } + #endregion + + #region Input&Timing + + public override void _UnhandledInput(InputEvent @event) + { + //this one is for calling a debug key to insta-kill the enemy + if (@event is InputEventKey eventKey && eventKey.Pressed && !eventKey.Echo) + { + if (eventKey.Keycode == Key.Key0) + { + //DebugKillEnemy(); + } + } + + if (@event.IsActionPressed("Pause")) + { + var pauseMenu = GD.Load("res://scenes/UI/Pause.tscn"); + GetNode("UILayer").AddChild(pauseMenu.Instantiate()); + GetTree().Paused = true; + } + if (@event.IsActionPressed("Inventory")) + { + var invenMenu = GD.Load("res://scenes/UI/inventory.tscn") + .Instantiate(); + GetNode("UILayer").AddChild(invenMenu); + invenMenu.Display(Player.Stats); + GetTree().Paused = true; + } + } + + 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; + } + + Timing timed = CheckTiming(beatDif); + + 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; + } + + private void CheckBattleStatus(PuppetTemplate puppet) + { + if (puppet == Player) + { + GD.Print("Player is Dead"); + GetNode("/root/StageProducer").TransitionStage(Stages.Title); + return; + } + + //will have to adjust this to account for when we have multiple enemies at once + if (puppet == Enemy) + { + GD.Print("Enemy is dead"); + ShowRewardSelection(3); + _battleEnd = true; + } + } + + private void ShowRewardSelection(int amount) + { + var rewardUI = GD.Load("res://scenes/UI/RewardSelectionUI.tscn") + .Instantiate(); + AddChild(rewardUI); + rewardUI.Initialize(Player.Stats, amount); + GetTree().Paused = true; + } + + #endregion + + #region BattleEffect Handling + + private delegate void NotePlacedHandler(BattleDirector BD); + private event NotePlacedHandler NotePlaced; + + private void EventizeRelics() + { + foreach (var relic in Player.Stats.CurRelics) + { + foreach (var effect in relic.Effects) + { + switch (effect.GetTrigger()) //TODO: Look into a way to get eventhandler from string + { + case BattleEffectTrigger.NotePlaced: + NotePlaced += effect.OnTrigger; + break; + } + } + } + } + #endregion + + private void DebugKillEnemy() + { + Enemy.TakeDamage(1000); + } +} diff --git a/scenes/BattleDirector/scripts/Conductor.cs b/scenes/BattleDirector/scripts/Conductor.cs new file mode 100644 index 00000000..55ce9f50 --- /dev/null +++ b/scenes/BattleDirector/scripts/Conductor.cs @@ -0,0 +1,152 @@ +using System; +using FunkEngine; +using Godot; + +public partial class Conductor : Node +{ + [Export] + private ChartManager CM; + + public delegate void TimedInputHandler(Note note, ArrowType type, int beat, double beatDif); + public event TimedInputHandler TimedInput; + + //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, + }; + + //Returns first note of lane without modifying lane data + private Note GetNoteAt(ArrowType dir, int beat) + { + return GetNote(_laneData[(int)dir][beat]); + } + + //Get note of a note arrow + private Note GetNote(NoteArrow arrow) + { + return arrow.NoteRef; + } + + private bool IsNoteActive(ArrowType type, int beat) + { + return _laneData[(int)type][beat] != null && _laneData[(int)type][beat].IsActive; + } + + public bool AddNoteToLane(ArrowType type, int beat, Note note, bool isActive = true) + { + beat %= CM.BeatsPerLoop; + Note newNote = note.Clone(); + + if (beat == 0) + return false; + + NoteArrow arrow; + if (isActive) //Currently an enemy note. + { + arrow = CM.AddArrowToLane(type, beat, newNote); + } + else + { + arrow = CM.AddArrowToLane(type, beat, newNote, new Color(1, 0.43f, 0.26f)); + } + + arrow.IsActive = isActive; + _laneData[(int)type][beat] = arrow; + return true; + } + + public void Prep() //TODO: Streamline battle initialization + { + _laneData = new NoteArrow[][] + { + new NoteArrow[CM.BeatsPerLoop], + new NoteArrow[CM.BeatsPerLoop], + new NoteArrow[CM.BeatsPerLoop], + new NoteArrow[CM.BeatsPerLoop], + }; + AddExampleNotes(); + } + + private void AddExampleNotes() + { + GD.Print(CM.BeatsPerLoop); + for (int i = 1; i < 15; i++) + { + AddNoteToLane(ArrowType.Up, i * 4, Scribe.NoteDictionary[0]); + } + + for (int i = 1; i < 15; i++) + { + AddNoteToLane(ArrowType.Left, 4 * i + 1, Scribe.NoteDictionary[0]); + } + + for (int i = 0; i < 10; i++) + { + AddNoteToLane(ArrowType.Right, 3 * i + 32, Scribe.NoteDictionary[0]); + } + + for (int i = 0; i < 3; i++) + { + AddNoteToLane(ArrowType.Down, 8 * i + 16, Scribe.NoteDictionary[0]); + } + } + + //Check all lanes for misses from missed inputs + public void CheckMiss() + { + //On current beat, if prev beat is active and not inputted + double realBeat = TimeKeeper.CurrentTime / (60 / (double)TimeKeeper.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) + ) + continue; + 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(); + TimedInput?.Invoke( + GetNoteAt((ArrowType)i, _laneLastBeat[i]), + (ArrowType)i, + _laneLastBeat[i], + 1 + ); + _laneLastBeat[i] = (_laneLastBeat[i] + 1) % CM.BeatsPerLoop; + } + } + + public void CheckNoteTiming(ArrowType type) + { + double realBeat = TimeKeeper.CurrentTime / (60 / (double)TimeKeeper.Bpm) % CM.BeatsPerLoop; + int curBeat = (int)Math.Round(realBeat); + GD.Print("Cur beat " + curBeat + "Real: " + realBeat.ToString("#.###")); + if (curBeat % CM.BeatsPerLoop == 0) + return; //Ignore note 0 //TODO: Double check this works as intended. + if (_laneData[(int)type][curBeat % CM.BeatsPerLoop] == null) + { + TimedInput?.Invoke(null, type, curBeat, Math.Abs(realBeat - curBeat)); + return; + } + + if (!_laneData[(int)type][curBeat % CM.BeatsPerLoop].IsActive) + return; + double beatDif = Math.Abs(realBeat - curBeat); + _laneData[(int)type][curBeat % CM.BeatsPerLoop].NoteHit(); + _laneLastBeat[(int)type] = (curBeat) % CM.BeatsPerLoop; + TimedInput?.Invoke(GetNoteAt(type, curBeat), type, curBeat, beatDif); + } +} diff --git a/scenes/BattleDirector/scripts/NotePlacementBar.cs b/scenes/BattleDirector/scripts/NotePlacementBar.cs new file mode 100644 index 00000000..4beb175e --- /dev/null +++ b/scenes/BattleDirector/scripts/NotePlacementBar.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Godot; + +public partial class NotePlacementBar : Node +{ + const int MaxValue = 80; + private int _currentBarValue; + private int _currentCombo; + int comboMult; + int notesToIncreaseCombo; + + [Export] + TextureProgressBar notePlacementBar; + + [Export] + TextEdit currentComboMultText; + + [Export] + private Sprite2D _currentNote; + private Note _currentNoteInstance; + + [Export] + private Sprite2D _nextNote; + + private Note[] _noteDeck; + private Queue _noteQueue = new Queue(); + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + notePlacementBar.MaxValue = MaxValue; + _currentBarValue = 0; + _currentCombo = 0; + comboMult = 1; + notesToIncreaseCombo = 4; + } + + public void Setup(PlayerStats playerStats) + { + //Create during battle copies of player notes + _noteDeck = (Note[])playerStats.CurNotes.Clone(); //Do NOT ever change these notes directly :) + if (_noteDeck.Length <= 0) + { + GD.PushError("Imminent crash: Note Deck is empty!"); + GetTree().Paused = true; + } + ShuffleNoteQueue(); + ProgressQueue(); + } + + // Fisher-Yates shuffle from: https://stackoverflow.com/a/1262619 + private void ShuffleNoteQueue() + { + List tempList = new List(_noteDeck); + + int n = tempList.Count; + while (n > 1) + { + n--; + int k = StageProducer.GlobalRng.RandiRange(0, n); + (tempList[k], tempList[n]) = (tempList[n], tempList[k]); + } + + _noteQueue = new Queue(tempList); + } + + private void ProgressQueue() + { + if (_noteQueue.Count == 0) + { + ShuffleNoteQueue(); + } + if (_currentNoteInstance == null) + { + _currentNoteInstance = _noteQueue.Dequeue(); + if (_noteQueue.Count == 0) + { + ShuffleNoteQueue(); + } + } + UpdateNoteQueue(); + } + + private void UpdateNoteQueue() + { + _currentNote.Texture = _currentNoteInstance.Texture; + _nextNote.Texture = _noteQueue.Count > 0 ? _noteQueue.Peek().Texture : null; + } + + private Note GetNote(bool getNextNote = false) + { + Note result; + if (!getNextNote) + { + result = _currentNoteInstance; + _currentNoteInstance = null; + } + else + { + result = _noteQueue.Dequeue(); + } + ProgressQueue(); + return result; + } + + public void ComboText(string text) + { + TextParticle newText = new TextParticle(); + AddChild(newText); + newText.Text = text + $" {_currentCombo}"; + } + + // Hitting a note increases combo, combo mult, and note placement bar + public void HitNote() + { + _currentCombo++; + DetermineComboMult(); + _currentBarValue += comboMult; + UpdateNotePlacementBar(_currentBarValue); + UpdateComboMultText(); + } + + // Missing a note resets combo + public void MissNote() + { + _currentCombo = 0; + DetermineComboMult(); + UpdateComboMultText(); + } + + // Placing a note resets the note placement bar + public Note PlacedNote() + { + _currentBarValue = 0; + UpdateNotePlacementBar(_currentBarValue); + return GetNote(); + } + + public bool CanPlaceNote() + { + return _currentBarValue >= MaxValue; + } + + private void DetermineComboMult() + { + comboMult = _currentCombo / notesToIncreaseCombo + 1; + } + + private void UpdateNotePlacementBar(int newValue) + { + notePlacementBar.Value = newValue; + } + + private void UpdateComboMultText() + { + currentComboMultText.Text = $"x{comboMult.ToString()}"; + } +} diff --git a/scenes/BattleDirector/TextParticle.cs b/scenes/BattleDirector/scripts/TextParticle.cs similarity index 84% rename from scenes/BattleDirector/TextParticle.cs rename to scenes/BattleDirector/scripts/TextParticle.cs index 273d4967..905c93a2 100644 --- a/scenes/BattleDirector/TextParticle.cs +++ b/scenes/BattleDirector/scripts/TextParticle.cs @@ -17,7 +17,4 @@ public override void _Ready() tween.SetParallel(false); tween.TweenCallback(Callable.From(QueueFree)); } - - // Called every frame. 'delta' is the elapsed time since the previous frame. - public override void _Process(double delta) { } } diff --git a/scenes/BattleDirector/test_battle_scene.tscn b/scenes/BattleDirector/test_battle_scene.tscn index 772d4487..b01d68d6 100644 --- a/scenes/BattleDirector/test_battle_scene.tscn +++ b/scenes/BattleDirector/test_battle_scene.tscn @@ -1,24 +1,30 @@ -[gd_scene load_steps=9 format=3 uid="uid://b0mrgr7h0ty1y"] +[gd_scene load_steps=7 format=3 uid="uid://b0mrgr7h0ty1y"] -[ext_resource type="Script" path="res://scenes/BattleDirector/BattleDirector.cs" id="1_cwqqr"] +[ext_resource type="Script" path="res://scenes/BattleDirector/scripts/BattleDirector.cs" id="1_cwqqr"] [ext_resource type="PackedScene" uid="uid://dfevfib11kou1" path="res://scenes/ChartViewport/ChartViewport.tscn" id="2_cupb3"] -[ext_resource type="PackedScene" uid="uid://bgomxovxs7sr8" path="res://scenes/BattleDirector/HealthBar.tscn" id="3_pp0u0"] +[ext_resource type="Script" path="res://scenes/BattleDirector/scripts/Conductor.cs" id="2_pcp76"] [ext_resource type="Texture2D" uid="uid://ci0g72j8q4ec2" path="res://scenes/BattleDirector/assets/CoolBG.jpg" id="4_13o87"] -[ext_resource type="Texture2D" uid="uid://b6fkei0i83vte" path="res://scenes/BattleDirector/assets/Character1.png" id="5_elveq"] -[ext_resource type="Texture2D" uid="uid://veedngaorx3l" path="res://scenes/BattleDirector/assets/Enemy1.png" id="6_0k4pw"] [ext_resource type="PackedScene" uid="uid://duhiilcv4tat3" path="res://scenes/BattleDirector/NotePlacementBar.tscn" id="7_3ko4g"] [ext_resource type="AudioStream" uid="uid://cv6lqjj6lu36h" path="res://Audio/335571__magntron__gamemusic_120bpm.mp3" id="8_caqms"] -[node name="ProtoBattleDirector" type="Node2D" node_paths=PackedStringArray("CM", "IH", "NotePlacementBar", "Audio")] +[node name="ProtoBattleDirector" type="Node2D" node_paths=PackedStringArray("CM", "NotePlacementBar", "CD", "Audio")] +process_mode = 1 script = ExtResource("1_cwqqr") CM = NodePath("SubViewport") -IH = NodePath("SubViewport/SubViewport/noteManager") NotePlacementBar = NodePath("NotePlacementBar") +CD = NodePath("Conductor") Audio = NodePath("AudioStreamPlayer") +metadata/_edit_lock_ = true [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] stream = ExtResource("8_caqms") +[node name="UILayer" type="CanvasLayer" parent="."] + +[node name="Conductor" type="Node" parent="." node_paths=PackedStringArray("CM")] +script = ExtResource("2_pcp76") +CM = NodePath("../SubViewport") + [node name="SubViewport" parent="." instance=ExtResource("2_cupb3")] offset_left = 80.0 offset_top = 160.0 @@ -40,21 +46,8 @@ offset_right = 673.0 offset_bottom = 360.0 color = Color(0.165656, 0.165656, 0.165656, 1) -[node name="PlayerHP" parent="." instance=ExtResource("3_pp0u0")] -offset_left = 92.0 -offset_top = 8.0 -offset_right = 132.0 -offset_bottom = 48.0 -SpriteText = ExtResource("5_elveq") - -[node name="EnemyHP" parent="." instance=ExtResource("3_pp0u0")] -offset_left = 403.0 -offset_top = 8.0 -offset_right = 443.0 -offset_bottom = 52.0 -SpriteText = ExtResource("6_0k4pw") - [node name="NotePlacementBar" parent="." instance=ExtResource("7_3ko4g")] +z_index = 1 offset_left = 16.0 offset_top = 164.0 offset_right = 16.0 diff --git a/scenes/ChartViewport/ChartManager.cs b/scenes/ChartViewport/ChartManager.cs index 4ecae9e4..24e1bd12 100644 --- a/scenes/ChartViewport/ChartManager.cs +++ b/scenes/ChartViewport/ChartManager.cs @@ -1,7 +1,7 @@ using System; using System.Linq; +using FunkEngine; using Godot; -using ArrowType = NoteArrow.ArrowType; /** * @class ChartManager @@ -39,7 +39,7 @@ public void OnNoteReleased(ArrowType type) EmitSignal(nameof(NoteReleased), (int)type); } - public void PrepChart(BattleDirector.SongData songData) + public void PrepChart(SongData songData) { _loopLen = songData.SongLength / songData.NumLoops; TimeKeeper.LoopLength = (float)_loopLen; @@ -59,16 +59,16 @@ public void PrepChart(BattleDirector.SongData songData) tween .TweenMethod( Callable.From((Vector2 scale) => TweenArrows(scale)), - new Vector2(0.07f, 0.07f), - new Vector2(0.07f, 0.07f) * 1.25f, + Vector2.One * .8f, + Vector2.One, 60f / TimeKeeper.Bpm / 2 ) .SetEase(Tween.EaseType.Out) .SetTrans(Tween.TransitionType.Elastic); tween.TweenMethod( Callable.From((Vector2 scale) => TweenArrows(scale)), - new Vector2(0.07f, 0.07f) * 1.25f, - new Vector2(0.07f, 0.07f), + Vector2.One, + Vector2.One * .8f, 60f / TimeKeeper.Bpm / 2 ); tween.SetLoops().Play(); @@ -98,11 +98,21 @@ private void TweenArrows(Vector2 scale) } } - public NoteArrow AddArrowToLane(Note note, int noteIdx) + public NoteArrow AddArrowToLane( + ArrowType type, + int beat, + Note note, + Color colorOverride = default + ) { - var newNote = CreateNote(note.Type, note.Beat); - CreateNote(note.Type, note.Beat + BeatsPerLoop); //Create a dummy arrow for looping visuals - newNote.NoteIdx = noteIdx; + var newNote = CreateNote(type, beat); //TODO: Notes on track have unqiue visuals + var loopArrow = CreateNote(type, beat + BeatsPerLoop); //Create a dummy arrow for looping visuals + if (colorOverride != default) + { + newNote.Modulate = colorOverride; + loopArrow.Modulate = colorOverride; + } + newNote.NoteRef = note; return newNote; } @@ -110,11 +120,22 @@ private NoteArrow CreateNote(ArrowType arrow, int beat = 0) { var noteScene = ResourceLoader.Load("res://scenes/NoteManager/note.tscn"); NoteArrow newArrow = noteScene.Instantiate(); - newArrow.Init(IH.Arrows[(int)arrow]); + newArrow.Init(IH.Arrows[(int)arrow], beat); _arrowGroup.AddChild(newArrow); newArrow.Bounds = (float)((double)beat / BeatsPerLoop * (ChartLength / 2)); newArrow.Position += Vector2.Right * newArrow.Bounds * 10; //temporary fix for notes spawning and instantly calling loop from originating at 0,0 return newArrow; } + + public override void _ExitTree() + { + GD.Print("[DEBUG] Stopping tweens before exiting the scene..."); + + foreach (var tween in GetTree().GetProcessedTweens()) + { + tween.Stop(); + GD.Print("[DEBUG] Stopped tween."); + } + } } diff --git a/scenes/Maps/cartographer.tscn b/scenes/Maps/cartographer.tscn new file mode 100644 index 00000000..a6497a78 --- /dev/null +++ b/scenes/Maps/cartographer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://cydmo2lbnj1de"] + +[ext_resource type="Script" path="res://scenes/Maps/scripts/Cartographer.cs" id="1_u4q3n"] + +[node name="Cartographer" type="Node2D"] +script = ExtResource("1_u4q3n") diff --git a/scenes/Maps/scripts/Cartographer.cs b/scenes/Maps/scripts/Cartographer.cs new file mode 100644 index 00000000..638dcd75 --- /dev/null +++ b/scenes/Maps/scripts/Cartographer.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using FunkEngine; +using Godot; + +public partial class Cartographer : Node2D +{ + public override void _Ready() + { + DrawMap(); + } + + private Vector2 GetPosition(int x, int y) + { + return new Vector2((float)x * 640 / StageProducer.MapSize.X - 1 + 64, y * 48 + 16); + } + + private void DrawMap() + { + var rooms = StageProducer.Map.GetRooms(); + foreach (StageProducer.MapGrid.Room room in rooms) + { + DrawMapSprite(room); + foreach (int roomIdx in room.Children) + { + Line2D newLine = new Line2D(); + newLine.AddPoint(GetPosition(room.X, room.Y)); + newLine.AddPoint(GetPosition(rooms[roomIdx].X, rooms[roomIdx].Y)); + AddChild(newLine); + } + } + } + + private void DrawMapSprite(StageProducer.MapGrid.Room room) + { + var newSprite = new Button(); + AddChild(newSprite); + //button is disabled if it is not a child of current room. + if (!StageProducer.CurRoom.Children.Contains(room.Idx)) + { + newSprite.Disabled = true; + newSprite.FocusMode = Control.FocusModeEnum.None; + } + else + { + newSprite.GrabFocus(); + newSprite.Pressed += () => + { + EnterStage(room.Idx); + }; + } + newSprite.Icon = (Texture2D)GD.Load("res://icon.svg"); //TODO: Room types icons + newSprite.Scale *= .25f; + newSprite.ZIndex = 1; + newSprite.Position = GetPosition(room.X, room.Y) - newSprite.Size * 2; + } + + private void EnterStage(int roomIdx) + { + GetNode("/root/StageProducer").TransitionFromRoom(roomIdx); + } +} diff --git a/scenes/NoteManager/assets/outline_white.png b/scenes/NoteManager/assets/outline_white.png new file mode 100644 index 00000000..8dfe2e4e Binary files /dev/null and b/scenes/NoteManager/assets/outline_white.png differ diff --git a/scenes/NoteManager/assets/outline_white.png.import b/scenes/NoteManager/assets/outline_white.png.import new file mode 100644 index 00000000..b8324285 --- /dev/null +++ b/scenes/NoteManager/assets/outline_white.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xtygvpk7s8e4" +path="res://.godot/imported/outline_white.png-eff17a86b93281db885f81fdb608ab83.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scenes/NoteManager/assets/outline_white.png" +dest_files=["res://.godot/imported/outline_white.png-eff17a86b93281db885f81fdb608ab83.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/NoteManager/assets/right-arrow.png b/scenes/NoteManager/assets/right-arrow.png deleted file mode 100644 index d5c416b7..00000000 Binary files a/scenes/NoteManager/assets/right-arrow.png and /dev/null differ diff --git a/scenes/NoteManager/note.tscn b/scenes/NoteManager/note.tscn index e0b2aa76..7c95c57c 100644 --- a/scenes/NoteManager/note.tscn +++ b/scenes/NoteManager/note.tscn @@ -1,9 +1,8 @@ [gd_scene load_steps=3 format=3 uid="uid://ck3bfqy30rjbq"] -[ext_resource type="Texture2D" uid="uid://bucvj4fquqpkr" path="res://scenes/NoteManager/assets/right-arrow.png" id="1_kubiy"] +[ext_resource type="Texture2D" uid="uid://xtygvpk7s8e4" path="res://scenes/NoteManager/assets/outline_white.png" id="1_vs4mw"] [ext_resource type="Script" path="res://scenes/NoteManager/scripts/NoteArrow.cs" id="2_lbl4b"] [node name="Right-arrow" type="Sprite2D"] -scale = Vector2(0.07, 0.07) -texture = ExtResource("1_kubiy") +texture = ExtResource("1_vs4mw") script = ExtResource("2_lbl4b") diff --git a/scenes/NoteManager/note_manager.tscn b/scenes/NoteManager/note_manager.tscn index 82ab15fd..d6b1b6ca 100644 --- a/scenes/NoteManager/note_manager.tscn +++ b/scenes/NoteManager/note_manager.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=5 format=3 uid="uid://bn8txx53xlguw"] [ext_resource type="Script" path="res://scenes/NoteManager/scripts/InputHandler.cs" id="1_2oeuf"] -[ext_resource type="Texture2D" uid="uid://bucvj4fquqpkr" path="res://scenes/NoteManager/assets/right-arrow.png" id="2_ldmuy"] +[ext_resource type="Texture2D" uid="uid://xtygvpk7s8e4" path="res://scenes/NoteManager/assets/outline_white.png" id="2_7ue4e"] [ext_resource type="Script" path="res://scenes/NoteManager/scripts/NoteChecker.cs" id="3_0cioe"] [ext_resource type="Texture2D" uid="uid://b0tvsewgnf2x7" path="res://icon.svg" id="4_foklt"] @@ -13,28 +13,24 @@ script = ExtResource("1_2oeuf") [node name="arrowUp" type="Sprite2D" parent="noteCheckers"] position = Vector2(0, 24) rotation = -1.5708 -scale = Vector2(0.09, 0.09) -texture = ExtResource("2_ldmuy") +texture = ExtResource("2_7ue4e") script = ExtResource("3_0cioe") [node name="arrowLeft" type="Sprite2D" parent="noteCheckers"] position = Vector2(0, 76) rotation = 3.14159 -scale = Vector2(0.09, 0.09) -texture = ExtResource("2_ldmuy") +texture = ExtResource("2_7ue4e") script = ExtResource("3_0cioe") [node name="arrowDown" type="Sprite2D" parent="noteCheckers"] position = Vector2(0, 129) rotation = 1.5708 -scale = Vector2(0.09, 0.09) -texture = ExtResource("2_ldmuy") +texture = ExtResource("2_7ue4e") script = ExtResource("3_0cioe") [node name="arrowRight" type="Sprite2D" parent="noteCheckers"] position = Vector2(0, 181) -scale = Vector2(0.09, 0.09) -texture = ExtResource("2_ldmuy") +texture = ExtResource("2_7ue4e") script = ExtResource("3_0cioe") [node name="ui" type="Node2D" parent="."] diff --git a/scenes/NoteManager/scripts/InputHandler.cs b/scenes/NoteManager/scripts/InputHandler.cs index 466c346a..b1961d4d 100644 --- a/scenes/NoteManager/scripts/InputHandler.cs +++ b/scenes/NoteManager/scripts/InputHandler.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using FunkEngine; using Godot; -using ArrowType = NoteArrow.ArrowType; /** * @class InputHandler @@ -15,14 +15,6 @@ public partial class InputHandler : Node2D [Signal] public delegate void NoteReleasedEventHandler(ArrowType arrowType); - public struct ArrowData - { - public Color Color; - public string Key; - public NoteChecker Node; - public ArrowType Type; - } - public ArrowData[] Arrows = new ArrowData[] { new ArrowData() diff --git a/scenes/NoteManager/scripts/NoteArrow.cs b/scenes/NoteManager/scripts/NoteArrow.cs index 934ed28b..cb80d1b4 100644 --- a/scenes/NoteManager/scripts/NoteArrow.cs +++ b/scenes/NoteManager/scripts/NoteArrow.cs @@ -1,3 +1,4 @@ +using FunkEngine; using Godot; /** @@ -6,23 +7,19 @@ */ public partial class NoteArrow : Sprite2D { //TextRect caused issues later :) - public enum ArrowType - { - Up = 0, - Down = 1, - Left = 2, - Right = 3, - } - - public int NoteIdx; + public ArrowType Type; + public int Beat; public float Bounds; public bool IsActive; + public Note NoteRef; - public void Init(InputHandler.ArrowData parentArrowData) + public void Init(ArrowData parentArrowData, int beat) { ZIndex = 1; - SelfModulate = parentArrowData.Color; + Type = parentArrowData.Type; + Beat = beat; + Position += Vector2.Down * (parentArrowData.Node.GlobalPosition.Y); RotationDegrees = parentArrowData.Node.RotationDegrees; } diff --git a/scenes/Puppets/HealthBar.tscn b/scenes/Puppets/HealthBar.tscn new file mode 100644 index 00000000..203235a8 --- /dev/null +++ b/scenes/Puppets/HealthBar.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=6 format=3 uid="uid://bgomxovxs7sr8"] + +[ext_resource type="Script" path="res://scenes/Puppets/scripts/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 + +[node name="ProgressBar" type="TextureProgressBar"] +offset_right = 150.0 +offset_bottom = 20.0 +texture_under = SubResource("GradientTexture2D_ti0cv") +texture_progress = SubResource("GradientTexture2D_r4hau") +texture_progress_offset = Vector2(2, 2) +script = ExtResource("1_b1t4i") diff --git a/scenes/Puppets/scripts/HealthBar.cs b/scenes/Puppets/scripts/HealthBar.cs new file mode 100644 index 00000000..cfd757ba --- /dev/null +++ b/scenes/Puppets/scripts/HealthBar.cs @@ -0,0 +1,31 @@ +using System; +using Godot; + +public partial class HealthBar : TextureProgressBar +{ + public override void _Ready() + { + Value = MaxValue; + } + + //initializes health + public void SetHealth(int max, int current) + { + MaxValue = max; + Value = current; + } + + //For effects changes max hp, and changes hp by a similar amount + public void ChangeMax(int change) + { + MaxValue += change; + Value += change; + } + + //Changes hp value, for damage or heal, returns resulting hp. + public int ChangeHP(int amount) + { + Value += amount; + return (int)Value; + } +} diff --git a/scenes/Puppets/scripts/PlayerPuppet.cs b/scenes/Puppets/scripts/PlayerPuppet.cs new file mode 100644 index 00000000..c831fb6c --- /dev/null +++ b/scenes/Puppets/scripts/PlayerPuppet.cs @@ -0,0 +1,32 @@ +using System; +using Godot; + +public partial class PlayerPuppet : PuppetTemplate +{ + public PlayerStats Stats; + + public override void _Ready() + { + Stats = StageProducer.PlayerStats; + + _currentHealth = Stats.CurrentHealth; + _maxHealth = Stats.MaxHealth; + + Init(GD.Load("res://scenes/BattleDirector/assets/Character1.png"), "Player"); + SetPosition(new Vector2(80, 0)); + Sprite.Position += Vector2.Down * 30; //TEMP + base._Ready(); + } + + public override void TakeDamage(int amount) + { + base.TakeDamage(amount); + Stats.CurrentHealth = _currentHealth; + } + + public override void Heal(int amount) + { + base.Heal(amount); + Stats.CurrentHealth = _currentHealth; + } +} diff --git a/scenes/Puppets/scripts/PlayerStats.cs b/scenes/Puppets/scripts/PlayerStats.cs new file mode 100644 index 00000000..a42bd9cf --- /dev/null +++ b/scenes/Puppets/scripts/PlayerStats.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; +using FunkEngine; +using Godot; + +public partial class PlayerStats : Resource +{ + public int MaxHealth = 100; + public int CurrentHealth = 100; + public Note[] CurNotes = new Note[] + { + Scribe.NoteDictionary[1].Clone(), + Scribe.NoteDictionary[1].Clone(), + Scribe.NoteDictionary[2].Clone(), + }; //TODO: Get a better method than .Clone + + public RelicTemplate[] CurRelics = Array.Empty(); + public int Attack = 1; + + public void AddRelic(RelicTemplate relic) + { + foreach (RelicEffect effect in relic.Effects) + { + if (effect.GetTrigger() == BattleEffectTrigger.OnPickup) + { + effect.OnTrigger(null); + } + } + CurRelics = CurRelics.Append(relic).ToArray(); + GD.Print("Adding relic: " + relic.Name); + } +} diff --git a/scenes/Puppets/scripts/PuppetTemplate.cs b/scenes/Puppets/scripts/PuppetTemplate.cs new file mode 100644 index 00000000..dfe122fe --- /dev/null +++ b/scenes/Puppets/scripts/PuppetTemplate.cs @@ -0,0 +1,74 @@ +using System; +using Godot; + +/** Essentially a battle entity. Has HP and can be healed or damaged. + * TODO: Look into interfaces + */ +public partial class PuppetTemplate : Node2D +{ + public delegate void DefeatedHandler(PuppetTemplate self); + public event DefeatedHandler Defeated; + + protected HealthBar _healthBar; + public Sprite2D Sprite = new Sprite2D(); + + protected int _maxHealth = 100; + protected int _currentHealth = 100; + + //Stats would go here. + + public string UniqName = ""; //Eventually make subclasses/scenes/real stuff + + public override void _Ready() + { + _healthBar = GD.Load("res://scenes/Puppets/HealthBar.tscn") + .Instantiate(); + AddChild(_healthBar); + + Sprite.Position = new Vector2(75, 86); + AddChild(Sprite); //TODO: DECIDE Whether to replace with packedscenes/robust subclasses + + _healthBar.SetHealth(_maxHealth, _currentHealth); + } + + public void Init(Texture2D texture, string name) + { + Sprite.Texture = texture; + UniqName = name; + } + + public virtual void TakeDamage(int amount) + { + if (_currentHealth <= 0) + return; //TEMP Only fire once. + _currentHealth = _healthBar.ChangeHP(-amount); + if (_currentHealth <= 0) + { + Defeated?.Invoke(this); + } + if (amount != 0) + { + TextParticle newText = new TextParticle(); + newText.Modulate = Colors.Red; + Sprite.AddChild(newText); + newText.Text = $"-{amount}"; + } + } + + public virtual void Heal(int amount) + { + if (amount != 0) + { + TextParticle newText = new TextParticle(); + newText.Modulate = Colors.Green; + Sprite.AddChild(newText); + newText.Text = $"+{amount}"; + } + _currentHealth = _healthBar.ChangeHP(amount); + } + + public int GetCurrentHealth() + { + return _currentHealth; + } +} diff --git a/scenes/SceneTransitions/TitleScreen.tscn b/scenes/SceneTransitions/TitleScreen.tscn new file mode 100644 index 00000000..0305acd0 --- /dev/null +++ b/scenes/SceneTransitions/TitleScreen.tscn @@ -0,0 +1,106 @@ +[gd_scene load_steps=4 format=3 uid="uid://bm41yti6ij2j"] + +[ext_resource type="Texture2D" uid="uid://b0tvsewgnf2x7" path="res://icon.svg" id="1_r5xy8"] +[ext_resource type="Script" path="res://scenes/SceneTransitions/scripts/SceneChange.cs" id="2_7f3m6"] +[ext_resource type="Texture2D" uid="uid://b6fkei0i83vte" path="res://scenes/BattleDirector/assets/Character1.png" id="2_cf582"] + +[node name="Title" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="SecretLabel" type="Label" parent="."] +visible = false +layout_mode = 0 +offset_right = 49.0 +offset_bottom = 23.0 +text = "(Control nodes are fucking weird weird.)" + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.5 + +[node name="Control" type="CenterContainer" parent="VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="Control" type="Control" parent="VBoxContainer/MarginContainer/Control"] +layout_mode = 2 + +[node name="Godot" type="Sprite2D" parent="VBoxContainer/MarginContainer/Control/Control"] +texture = ExtResource("1_r5xy8") + +[node name="Rabb" type="Sprite2D" parent="VBoxContainer/MarginContainer/Control/Control"] +texture = ExtResource("2_cf582") + +[node name="Label" type="Label" parent="VBoxContainer/MarginContainer/Control"] +layout_mode = 2 +text = "Insert Title Screen Here" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.3 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.5 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Start Game" +script = ExtResource("2_7f3m6") +ScenePath = 3 +_startFocused = true + +[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer/HBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.5 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer3"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Load Game (Experimental)" +script = ExtResource("2_7f3m6") +ScenePath = 1 + +[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.5 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Quit Game" +script = ExtResource("2_7f3m6") +ScenePath = 2 diff --git a/scenes/SceneTransitions/scripts/SceneChange.cs b/scenes/SceneTransitions/scripts/SceneChange.cs new file mode 100644 index 00000000..81590bbc --- /dev/null +++ b/scenes/SceneTransitions/scripts/SceneChange.cs @@ -0,0 +1,28 @@ +using System; +using FunkEngine; +using Godot; + +public partial class SceneChange : Button +{ + [Export] + public Stages ScenePath; + + [Export] + private bool _startFocused = false; + + public override void _Ready() + { + if (_startFocused) + { + GrabFocus(); + } + Pressed += OnButtonPressed; + GD.Print($"[DEBUG] Scene Path: '{ScenePath}'"); + } + + private void OnButtonPressed() + { + GD.Print($"✅ Loading scene: {ScenePath}"); + GetNode("/root/StageProducer").TransitionStage(ScenePath); + } +} diff --git a/scenes/SceneTransitions/testTransition.tscn b/scenes/SceneTransitions/testTransition.tscn new file mode 100644 index 00000000..e9bce855 --- /dev/null +++ b/scenes/SceneTransitions/testTransition.tscn @@ -0,0 +1,33 @@ +[gd_scene load_steps=2 format=3 uid="uid://dbeplni2du158"] + +[ext_resource type="Script" path="res://scenes/SceneTransitions/scripts/SceneChange.cs" id="1_n6d5u"] + +[node name="Control" type="Control"] +layout_mode = 3 +anchors_preset = 0 +offset_right = 40.0 +offset_bottom = 40.0 + +[node name="Node2D" type="Node2D" parent="."] + +[node name="StartButton" type="Button" parent="."] +layout_mode = 0 +offset_left = 120.0 +offset_top = 56.0 +offset_right = 128.0 +offset_bottom = 64.0 +scale = Vector2(2.48, 2.48) +text = "start battle" +script = ExtResource("1_n6d5u") +ScenePath = "res://scenes/BattleDirector/test_battle_scene.tscn" + +[node name="ExitButton" type="Button" parent="."] +layout_mode = 0 +offset_left = 471.0 +offset_top = 95.0 +offset_right = 508.0 +offset_bottom = 126.0 +scale = Vector2(2.56, 2.56) +text = "exit" +script = ExtResource("1_n6d5u") +ScenePath = "exit" diff --git a/scenes/UI/Pause.tscn b/scenes/UI/Pause.tscn new file mode 100644 index 00000000..488eef4b --- /dev/null +++ b/scenes/UI/Pause.tscn @@ -0,0 +1,77 @@ +[gd_scene load_steps=2 format=3 uid="uid://dmfk0csl7cd27"] + +[ext_resource type="Script" path="res://scenes/UI/scripts/PauseMenu.cs" id="1_6jc8n"] + +[node name="PauseMenu" type="Control" node_paths=PackedStringArray("pauseButtons")] +process_mode = 2 +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_6jc8n") +pauseButtons = [NodePath("MarginContainer/VBoxContainer/MarginContainer/ResumeButton"), NodePath("MarginContainer/VBoxContainer/MarginContainer2/QuitButton"), NodePath("MarginContainer/VBoxContainer/MarginContainer3/PlaceHolderButton")] + +[node name="ColorRect" type="ColorRect" parent="."] +layout_mode = 2 +offset_right = 640.0 +offset_bottom = 360.0 +color = Color(0.237539, 0.237539, 0.237539, 0.466667) + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 200 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 200 +theme_override_constants/margin_bottom = 20 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="ResumeButton" type="Button" parent="MarginContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 +focus_neighbor_top = NodePath("../../MarginContainer3/PlaceHolderButton") +focus_neighbor_bottom = NodePath("../../MarginContainer2/QuitButton") +text = "Resume" + +[node name="MarginContainer2" type="MarginContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="QuitButton" type="Button" parent="MarginContainer/VBoxContainer/MarginContainer2"] +layout_mode = 2 +focus_neighbor_top = NodePath("../../MarginContainer/ResumeButton") +focus_neighbor_bottom = NodePath("../../MarginContainer3/PlaceHolderButton") +text = "Quit" + +[node name="MarginContainer3" type="MarginContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="PlaceHolderButton" type="Button" parent="MarginContainer/VBoxContainer/MarginContainer3"] +layout_mode = 2 +focus_neighbor_top = NodePath("../../MarginContainer2/QuitButton") +focus_neighbor_bottom = NodePath("../../MarginContainer/ResumeButton") +text = "Quit to Title" diff --git a/scenes/UI/RewardSelectionUI.tscn b/scenes/UI/RewardSelectionUI.tscn new file mode 100644 index 00000000..1a9a0837 --- /dev/null +++ b/scenes/UI/RewardSelectionUI.tscn @@ -0,0 +1,84 @@ +[gd_scene load_steps=2 format=3 uid="uid://c6icx2yriud6y"] + +[ext_resource type="Script" path="res://scenes/UI/scripts/RewardSelect.cs" id="1_1m6an"] + +[node name="CanvasLayer" type="CanvasLayer" node_paths=PackedStringArray("ButtonContainer", "_description", "_acceptButton", "_skipButton")] +process_mode = 2 +script = ExtResource("1_1m6an") +ButtonContainer = NodePath("MarginContainer/PanelContainer/VBoxContainer/ScrollContainer/CenterContainer/HBoxContainer") +_description = NodePath("MarginContainer/PanelContainer/VBoxContainer/DescBox/Description") +_acceptButton = NodePath("MarginContainer/PanelContainer/VBoxContainer/MarginContainer/HBoxContainer/AcceptButton") +_skipButton = NodePath("MarginContainer/PanelContainer/VBoxContainer/MarginContainer/HBoxContainer/SkipButton") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/margin_left = 50 +theme_override_constants/margin_top = 50 +theme_override_constants/margin_right = 50 +theme_override_constants/margin_bottom = 50 + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/PanelContainer"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/PanelContainer/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/PanelContainer/VBoxContainer/ScrollContainer/CenterContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="DescBox" type="MarginContainer" parent="MarginContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="ColorRect" type="ColorRect" parent="MarginContainer/PanelContainer/VBoxContainer/DescBox"] +layout_mode = 2 +color = Color(0, 0, 0, 0.552941) + +[node name="Description" type="Label" parent="MarginContainer/PanelContainer/VBoxContainer/DescBox"] +layout_mode = 2 +size_flags_vertical = 1 +autowrap_mode = 2 +clip_text = true +text_overrun_behavior = 1 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/PanelContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="AcceptButton" type="Button" parent="MarginContainer/PanelContainer/VBoxContainer/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Accept" + +[node name="SkipButton" type="Button" parent="MarginContainer/PanelContainer/VBoxContainer/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Skip +" diff --git a/scenes/UI/display_button.tscn b/scenes/UI/display_button.tscn new file mode 100644 index 00000000..3dc408b8 --- /dev/null +++ b/scenes/UI/display_button.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://cymo26khpw4m1"] + +[ext_resource type="Script" path="res://scenes/UI/scripts/DisplayButton.cs" id="1_7lpm6"] + +[node name="DisplayButton" type="Button"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_7lpm6") diff --git a/scenes/UI/inventory.tscn b/scenes/UI/inventory.tscn new file mode 100644 index 00000000..7be623fc --- /dev/null +++ b/scenes/UI/inventory.tscn @@ -0,0 +1,96 @@ +[gd_scene load_steps=2 format=3 uid="uid://be6fb2sr5i515"] + +[ext_resource type="Script" path="res://scenes/UI/scripts/Inventory.cs" id="1_8rcwd"] + +[node name="Inventory" type="Control" node_paths=PackedStringArray("Relics", "Notes", "Description", "Tabs")] +process_mode = 2 +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_8rcwd") +Relics = NodePath("InvenVBox/Tabs/Relics/RelicBox/RelicGrid") +Notes = NodePath("InvenVBox/Tabs/Notes/NotesBox/NotesGrid") +Description = NodePath("InvenVBox/DescBox/Description") +Tabs = NodePath("InvenVBox/Tabs") + +[node name="BG" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.152941, 0.145098, 0.2, 0.533333) + +[node name="InvenVBox" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Tabs" type="TabContainer" parent="InvenVBox"] +self_modulate = Color(10, 10, 10, 0.751) +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 +clip_tabs = false + +[node name="Notes" type="MarginContainer" parent="InvenVBox/Tabs"] +layout_mode = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 30 +theme_override_constants/margin_bottom = 30 +metadata/_tab_index = 0 + +[node name="NotesBox" type="ScrollContainer" parent="InvenVBox/Tabs/Notes"] +layout_mode = 2 +follow_focus = true + +[node name="NotesGrid" type="GridContainer" parent="InvenVBox/Tabs/Notes/NotesBox"] +layout_mode = 2 +size_flags_vertical = 4 +columns = 5 + +[node name="Relics" type="MarginContainer" parent="InvenVBox/Tabs"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 30 +theme_override_constants/margin_bottom = 30 +metadata/_tab_index = 1 + +[node name="RelicBox" type="ScrollContainer" parent="InvenVBox/Tabs/Relics"] +layout_mode = 2 +follow_focus = true + +[node name="RelicGrid" type="GridContainer" parent="InvenVBox/Tabs/Relics/RelicBox"] +layout_mode = 2 +size_flags_vertical = 4 +columns = 5 + +[node name="DescBox" type="MarginContainer" parent="InvenVBox"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.4 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="ColorRect" type="ColorRect" parent="InvenVBox/DescBox"] +layout_mode = 2 +color = Color(0, 0, 0, 0.552941) + +[node name="Description" type="Label" parent="InvenVBox/DescBox"] +layout_mode = 2 +size_flags_vertical = 1 +autowrap_mode = 2 +clip_text = true +text_overrun_behavior = 1 diff --git a/scenes/UI/scripts/DisplayButton.cs b/scenes/UI/scripts/DisplayButton.cs new file mode 100644 index 00000000..ec74d8ae --- /dev/null +++ b/scenes/UI/scripts/DisplayButton.cs @@ -0,0 +1,23 @@ +using System; +using Godot; + +public partial class DisplayButton : Button +{ + //TODO: Make various menus change descriptions when focus changes, instead of on click. + [Export] + public Texture2D Texture; + + [Export] + public string Description; + + [Export] + public string DisplayName; + + public void Display(Texture2D texture, string description, string name) + { + Texture = texture; + Description = description; + DisplayName = name; + Icon = Texture; + } +} diff --git a/scenes/UI/scripts/Inventory.cs b/scenes/UI/scripts/Inventory.cs new file mode 100644 index 00000000..e146f5e0 --- /dev/null +++ b/scenes/UI/scripts/Inventory.cs @@ -0,0 +1,75 @@ +using System; +using Godot; + +public partial class Inventory : Control +{ + [Export] + private GridContainer Relics; + + [Export] + private GridContainer Notes; + + [Export] + private Label Description; + + [Export] + private TabContainer Tabs; + + public void Display(PlayerStats playerStats) + { + foreach (RelicTemplate relic in playerStats.CurRelics) + { + var newButton = GD.Load("res://scenes/UI/display_button.tscn") + .Instantiate(); + newButton.Display(relic.Texture, relic.Tooltip, relic.Name); + newButton.Pressed += () => + { + DoDescription(newButton); + }; + Relics.AddChild(newButton); + } + foreach (Note note in playerStats.CurNotes) + { + var newButton = GD.Load("res://scenes/UI/display_button.tscn") + .Instantiate(); + newButton.Display(note.Texture, note.Tooltip, note.Name); + newButton.Pressed += () => + { + DoDescription(newButton); + }; + Notes.AddChild(newButton); + } + + Tabs.TabChanged += ClearDescription; + } + + public override void _Ready() + { + Tabs.GetTabBar().GrabFocus(); + } + + public override void _Input(InputEvent @event) + { + if (@event.IsActionPressed("Pause") || @event.IsActionPressed("Inventory")) + { + Resume(); + GetViewport().SetInputAsHandled(); + } + } + + private void Resume() + { + GetTree().Paused = false; + QueueFree(); //Hacky and shortsighted (probably?) + } + + private void DoDescription(DisplayButton dispButton) + { + Description.Text = $"{dispButton.DisplayName}: {dispButton.Description}"; + } + + private void ClearDescription(long newTab) + { + Description.Text = ""; + } +} diff --git a/scenes/UI/scripts/PauseMenu.cs b/scenes/UI/scripts/PauseMenu.cs new file mode 100644 index 00000000..38316369 --- /dev/null +++ b/scenes/UI/scripts/PauseMenu.cs @@ -0,0 +1,43 @@ +using System; +using FunkEngine; +using Godot; + +public partial class PauseMenu : Control +{ + [Export] + public Button[] pauseButtons; + + public override void _Ready() + { + pauseButtons[0].Pressed += Resume; + pauseButtons[1].Pressed += Quit; + pauseButtons[2].Pressed += QuitToMainMenu; + pauseButtons[0].GrabFocus(); + } + + public override void _Input(InputEvent @event) + { + if (@event.IsActionPressed("Pause")) + { + Resume(); + GetViewport().SetInputAsHandled(); + } + } + + private void Resume() + { + GetTree().Paused = false; + QueueFree(); //Hacky and shortsighted (probably?) + } + + private void Quit() + { + GetNode("/root/StageProducer").TransitionStage(Stages.Quit); + } + + private void QuitToMainMenu() + { + GetTree().Paused = false; + GetNode("/root/StageProducer").TransitionStage(Stages.Title); + } +} diff --git a/scenes/UI/scripts/RewardSelect.cs b/scenes/UI/scripts/RewardSelect.cs new file mode 100644 index 00000000..865ddd10 --- /dev/null +++ b/scenes/UI/scripts/RewardSelect.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using Godot; + +public partial class RewardSelect : CanvasLayer +{ + [Export] + public HBoxContainer ButtonContainer; + + [Export] + private Label _description; + + [Export] + private Button _acceptButton; + + [Export] + private Button _skipButton; + + private PlayerStats _player; + private RelicTemplate[] _choices; + private RelicTemplate _selection; + + public void Initialize(PlayerStats player, int amount) + { + _player = player; + GenerateRelicChoices(amount); + + _acceptButton.Pressed += OnSelect; + _skipButton.Pressed += OnSkip; + } + + public override void _Process(double delta) + { + _acceptButton.Visible = _selection != null; + } + + private void GenerateRelicChoices(int amount = 1) + { + //should probably change this so that the amount of relics offered can be changed when BD calls it + //i.e less options when killing trash mobs/basic/weak enemies + _choices = Scribe.GetRandomRelics(_player.CurRelics, amount); + + foreach (var relic in _choices) + { + var button = new DisplayButton(); + button.Display(relic.Texture, relic.Tooltip, relic.Name); + button.Pressed += () => OnRelicSelected(relic); + ButtonContainer.AddChild(button); + button.GrabFocus(); + } + } + + private void OnRelicSelected(RelicTemplate choiceRelic) + { + _selection = choiceRelic; + _description.Text = $"{choiceRelic.Name}: {choiceRelic.Tooltip}"; + } + + private void OnSelect() + { + if (_selection == null) + return; + GD.Print("Relic selected: " + _selection.Name); + _player.AddRelic(_selection); + GetTree().Paused = false; + QueueFree(); + } + + private void OnSkip() + { + GD.Print("Relic skipped."); + GetTree().Paused = false; + QueueFree(); + } +} diff --git a/scenes/main.tscn b/scenes/main.tscn deleted file mode 100644 index 03a5f3fa..00000000 --- a/scenes/main.tscn +++ /dev/null @@ -1,3 +0,0 @@ -[gd_scene format=3 uid="uid://diq2m6va1x52k"] - -[node name="Main" type="Node2D"] diff --git a/scripts/Main.cs b/scripts/Main.cs deleted file mode 100644 index 52278fa9..00000000 --- a/scripts/Main.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System; -using Godot; -using static InputHandler; - -public partial class Main : Node2D { }