diff --git a/.gitignore b/.gitignore index 161c6b19..a930fc00 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ .idea/.idea.Funk Engine/.idea/ + +*.DotSettings.user +export_presets.cfg \ No newline at end of file diff --git a/Audio/335571__magntron__gamemusic_120bpm.mp3 b/Audio/335571__magntron__gamemusic_120bpm.mp3 deleted file mode 100644 index de6a511f..00000000 Binary files a/Audio/335571__magntron__gamemusic_120bpm.mp3 and /dev/null differ diff --git a/Audio/335571__magntron__gamemusic_120bpm.mp3.import b/Audio/335571__magntron__gamemusic_120bpm.mp3.import deleted file mode 100644 index 9c209bfe..00000000 --- a/Audio/335571__magntron__gamemusic_120bpm.mp3.import +++ /dev/null @@ -1,19 +0,0 @@ -[remap] - -importer="mp3" -type="AudioStreamMP3" -uid="uid://cv6lqjj6lu36h" -path="res://.godot/imported/335571__magntron__gamemusic_120bpm.mp3-a87b357c4b3c9199709863b47f78bd2a.mp3str" - -[deps] - -source_file="res://Audio/335571__magntron__gamemusic_120bpm.mp3" -dest_files=["res://.godot/imported/335571__magntron__gamemusic_120bpm.mp3-a87b357c4b3c9199709863b47f78bd2a.mp3str"] - -[params] - -loop=true -loop_offset=0 -bpm=0 -beat_count=0 -bar_beats=4 diff --git a/Audio/Song1.ogg b/Audio/Song1.ogg new file mode 100644 index 00000000..93d476e9 Binary files /dev/null and b/Audio/Song1.ogg differ diff --git a/Audio/Song1.ogg.import b/Audio/Song1.ogg.import new file mode 100644 index 00000000..8e5bd965 --- /dev/null +++ b/Audio/Song1.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://iq0xxe5cggs8" +path="res://.godot/imported/Song1.ogg-1d785b9ae3fbaa8393048e39af66ed86.oggvorbisstr" + +[deps] + +source_file="res://Audio/Song1.ogg" +dest_files=["res://.godot/imported/Song1.ogg-1d785b9ae3fbaa8393048e39af66ed86.oggvorbisstr"] + +[params] + +loop=true +loop_offset=0.0 +bpm=0.0 +beat_count=0 +bar_beats=4 diff --git a/Audio/Song2.ogg b/Audio/Song2.ogg new file mode 100644 index 00000000..063129c0 Binary files /dev/null and b/Audio/Song2.ogg differ diff --git a/Audio/Song2.ogg.import b/Audio/Song2.ogg.import new file mode 100644 index 00000000..cfb5f8ae --- /dev/null +++ b/Audio/Song2.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://ckis6k6vuums" +path="res://.godot/imported/Song2.ogg-b95c04f3512de6fa42e0f9c35aba831f.oggvorbisstr" + +[deps] + +source_file="res://Audio/Song2.ogg" +dest_files=["res://.godot/imported/Song2.ogg-b95c04f3512de6fa42e0f9c35aba831f.oggvorbisstr"] + +[params] + +loop=true +loop_offset=0.0 +bpm=0.0 +beat_count=0 +bar_beats=4 diff --git a/Audio/Song3.ogg b/Audio/Song3.ogg new file mode 100644 index 00000000..58f8028c Binary files /dev/null and b/Audio/Song3.ogg differ diff --git a/Audio/Song3.ogg.import b/Audio/Song3.ogg.import new file mode 100644 index 00000000..3d5d0664 --- /dev/null +++ b/Audio/Song3.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://ceyw5mjkem2pi" +path="res://.godot/imported/Song3.ogg-d4e6a5f1a550561df18989fb495ba591.oggvorbisstr" + +[deps] + +source_file="res://Audio/Song3.ogg" +dest_files=["res://.godot/imported/Song3.ogg-d4e6a5f1a550561df18989fb495ba591.oggvorbisstr"] + +[params] + +loop=true +loop_offset=0.0 +bpm=0.0 +beat_count=0 +bar_beats=4 diff --git a/Audio/midi/Song1.mid b/Audio/midi/Song1.mid new file mode 100644 index 00000000..3330b86a Binary files /dev/null and b/Audio/midi/Song1.mid differ diff --git a/Audio/midi/Song2.mid b/Audio/midi/Song2.mid new file mode 100644 index 00000000..c657321a Binary files /dev/null and b/Audio/midi/Song2.mid differ diff --git a/Audio/midi/Song3.mid b/Audio/midi/Song3.mid new file mode 100644 index 00000000..58e53a49 Binary files /dev/null and b/Audio/midi/Song3.mid differ diff --git a/Audio/midi/florestan-subset.sf2 b/Audio/midi/florestan-subset.sf2 new file mode 100644 index 00000000..56a930ac Binary files /dev/null and b/Audio/midi/florestan-subset.sf2 differ diff --git a/Classes/MidiMaestro/MidiMaestro.cs b/Classes/MidiMaestro/MidiMaestro.cs new file mode 100644 index 00000000..882d47d9 --- /dev/null +++ b/Classes/MidiMaestro/MidiMaestro.cs @@ -0,0 +1,117 @@ +using System; +using System.Linq; +using FunkEngine; +using Godot; +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using Melanchall.DryWetMidi.Multimedia; + +public partial class MidiMaestro : Resource +{ + private MidiFile _midiFile; + + //The four note rows that we care about + private midiNoteInfo[] _upNotes; + private midiNoteInfo[] _downNotes; + private midiNoteInfo[] _leftNotes; + private midiNoteInfo[] _rightNotes; + + //private MidiFile strippedSong; + + private SongData songData; + + //The path relative to the Audio folder. Will change later + public MidiMaestro(string filePath) + { + if (!OS.HasFeature("editor")) + { + filePath = OS.GetExecutablePath().GetBaseDir() + "/" + filePath; + } + + if (!FileAccess.FileExists(filePath)) + { + GD.PrintErr("ERROR: Unable to load level Midi file: " + filePath); + } + + _midiFile = MidiFile.Read(filePath); + + //Strip out the notes from the midi file + foreach (var track in _midiFile.GetTrackChunks()) + { + string trackName = track.Events.OfType().FirstOrDefault()?.Text; + midiNoteInfo[] noteEvents = track + .GetNotes() + .Select(note => new midiNoteInfo(note, _midiFile.GetTempoMap())) + .ToArray(); + + switch (trackName) + { + case "Up": + _upNotes = noteEvents; + break; + case "Down": + _downNotes = noteEvents; + break; + case "Left": + _leftNotes = noteEvents; + break; + case "Right": + _rightNotes = noteEvents; + break; + } + } + + //Populate the song data + songData = new SongData + { + //TODO: Allow for changes in this data + Bpm = 120, + //Fudge the numbers a bit if we have a really short song + SongLength = + _midiFile.GetDuration().Seconds < 20 + ? 20 + : _midiFile.GetDuration().Seconds, + NumLoops = 1, + }; + } + + public midiNoteInfo[] GetNotes(ArrowType arrowType) + { + return arrowType switch + { + ArrowType.Up => _upNotes, + ArrowType.Down => _downNotes, + ArrowType.Left => _leftNotes, + ArrowType.Right => _rightNotes, + _ => throw new ArgumentOutOfRangeException(nameof(arrowType), arrowType, null), + }; + } + + public SongData GetSongData() + { + return songData; + } +} + +//A facade to wrap the midi notes. This is a simple class that wraps a Note object from the DryWetMidi library. +public class midiNoteInfo +{ + private readonly Melanchall.DryWetMidi.Interaction.Note _note; + private readonly TempoMap _tempoMap; + + public midiNoteInfo(Melanchall.DryWetMidi.Interaction.Note note, TempoMap tempoMap) + { + _note = note; + _tempoMap = tempoMap; + } + + public long GetStartTimeTicks() => _note.Time; + + public float GetStartTimeSeconds() => + _note.TimeAs(_tempoMap).Milliseconds / 1000f + + _note.TimeAs(_tempoMap).Seconds; + + public long GetEndTime() => _note.EndTime; + + public long GetDuration() => _note.Length; +} diff --git a/Classes/MidiMaestro/SongTemplate.cs b/Classes/MidiMaestro/SongTemplate.cs new file mode 100644 index 00000000..bf63c2b9 --- /dev/null +++ b/Classes/MidiMaestro/SongTemplate.cs @@ -0,0 +1,25 @@ +namespace FunkEngine.Classes.MidiMaestro; + +public partial class SongTemplate +{ + public string Name; + public string AudioLocation; + public string MIDILocation; + public string EnemyScenePath; + public SongData SongData; + + public SongTemplate( + SongData songData, + string name = "", + string audioLocation = "", + string midiLocation = "", + string enemyScenePath = "" + ) + { + Name = name; + AudioLocation = audioLocation; + MIDILocation = midiLocation; + SongData = songData; + EnemyScenePath = enemyScenePath; + } +} diff --git a/Classes/Notes/Note.cs b/Classes/Notes/Note.cs index 8795cf42..cc6fed18 100644 --- a/Classes/Notes/Note.cs +++ b/Classes/Notes/Note.cs @@ -6,15 +6,16 @@ * @class Note * @brief Data structure class for holding data and methods for a battle time note. WIP */ -public partial class Note : Resource +public partial class Note : Resource, IDisplayable { public PuppetTemplate Owner; - public string Name; + public string Name { get; set; } private int _baseVal; + public float CostModifier { get; private set; } private Action NoteEffect; //TODO: Where/How to deal with timing. - public string Tooltip; - public Texture2D Texture; + public string Tooltip { get; set; } + public Texture2D Texture { get; set; } public Note( string name, @@ -22,7 +23,8 @@ public Note( Texture2D texture = null, PuppetTemplate owner = null, int baseVal = 1, - Action noteEffect = null + Action noteEffect = null, + float costModifier = 1.0f ) { Name = name; @@ -38,6 +40,7 @@ public Note( _baseVal = baseVal; Texture = texture; Tooltip = tooltip; + CostModifier = costModifier; } public void OnHit(BattleDirector BD, Timing timing) @@ -49,7 +52,7 @@ 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); + Note newNote = new Note(Name, Tooltip, Texture, Owner, _baseVal, NoteEffect, CostModifier); return newNote; } } diff --git a/Classes/Notes/assets/heal_note.png b/Classes/Notes/assets/heal_note.png new file mode 100644 index 00000000..5fe43120 Binary files /dev/null and b/Classes/Notes/assets/heal_note.png differ diff --git a/Classes/Notes/assets/heal_note.png.import b/Classes/Notes/assets/heal_note.png.import new file mode 100644 index 00000000..22f3a997 --- /dev/null +++ b/Classes/Notes/assets/heal_note.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdf3g3174du4r" +path="res://.godot/imported/heal_note.png-09ca289a296eee82d33c64101a4e593a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Notes/assets/heal_note.png" +dest_files=["res://.godot/imported/heal_note.png-09ca289a296eee82d33c64101a4e593a.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/Notes/assets/quarter_note.png b/Classes/Notes/assets/quarter_note.png new file mode 100644 index 00000000..a8cbd41b Binary files /dev/null and b/Classes/Notes/assets/quarter_note.png differ diff --git a/Classes/Notes/assets/quarter_note.png.import b/Classes/Notes/assets/quarter_note.png.import new file mode 100644 index 00000000..0729e611 --- /dev/null +++ b/Classes/Notes/assets/quarter_note.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uksjoqp7p0gq" +path="res://.godot/imported/quarter_note.png-2b4c9985d99038807abfd63e553c2d6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Notes/assets/quarter_note.png" +dest_files=["res://.godot/imported/quarter_note.png-2b4c9985d99038807abfd63e553c2d6a.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/Notes/assets/vampire_note.png b/Classes/Notes/assets/vampire_note.png new file mode 100644 index 00000000..d00b6a3f Binary files /dev/null and b/Classes/Notes/assets/vampire_note.png differ diff --git a/Classes/Notes/assets/vampire_note.png.import b/Classes/Notes/assets/vampire_note.png.import new file mode 100644 index 00000000..65be637e --- /dev/null +++ b/Classes/Notes/assets/vampire_note.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dg0lmu0pip4lr" +path="res://.godot/imported/vampire_note.png-4331f817a6feee1f1066a9e9f95934c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Notes/assets/vampire_note.png" +dest_files=["res://.godot/imported/vampire_note.png-4331f817a6feee1f1066a9e9f95934c8.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 index 93ade4f0..c5a52109 100644 --- a/Classes/Relics/RelicEffect.cs +++ b/Classes/Relics/RelicEffect.cs @@ -5,23 +5,32 @@ public partial class RelicEffect : IBattleEvent { private BattleEffectTrigger Trigger { get; set; } - public int BaseValue; - private Action OnRelicEffect; + private int _baseValue; + public int Value; + private Action _onRelicEffect; + private bool _effectPersists = false; public RelicEffect( BattleEffectTrigger trigger, int val, - Action onRelicEffect + Action onRelicEffect ) { - BaseValue = val; + _baseValue = val; + Value = _baseValue; Trigger = trigger; - OnRelicEffect = onRelicEffect; + _onRelicEffect = onRelicEffect; + } + + public void OnBattleEnd() + { + if (!_effectPersists) + Value = _baseValue; } public void OnTrigger(BattleDirector battleDirector) { - OnRelicEffect(battleDirector, BaseValue); + _onRelicEffect(battleDirector, this, Value); } public BattleEffectTrigger GetTrigger() diff --git a/Classes/Relics/RelicTemplate.cs b/Classes/Relics/RelicTemplate.cs index f0a117cc..da8daba4 100644 --- a/Classes/Relics/RelicTemplate.cs +++ b/Classes/Relics/RelicTemplate.cs @@ -2,13 +2,13 @@ using FunkEngine; using Godot; -public partial class RelicTemplate : Resource +public partial class RelicTemplate : Resource, IDisplayable { public RelicEffect[] Effects; - public string Name; + public string Name { get; set; } - public Texture2D Texture; - public string Tooltip; + public Texture2D Texture { get; set; } + public string Tooltip { get; set; } public RelicTemplate( string name = "", diff --git a/Classes/Relics/assets/Auroboros.png b/Classes/Relics/assets/Auroboros.png new file mode 100644 index 00000000..b264c935 Binary files /dev/null and b/Classes/Relics/assets/Auroboros.png differ diff --git a/Classes/Relics/assets/Auroboros.png.import b/Classes/Relics/assets/Auroboros.png.import new file mode 100644 index 00000000..d0051264 --- /dev/null +++ b/Classes/Relics/assets/Auroboros.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqlrv1vra4mbn" +path="res://.godot/imported/Auroboros.png-27bfe2114f3955a0f6ef5ceb4e65adbf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Relics/assets/Auroboros.png" +dest_files=["res://.godot/imported/Auroboros.png-27bfe2114f3955a0f6ef5ceb4e65adbf.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/Colorboros.png b/Classes/Relics/assets/Colorboros.png new file mode 100644 index 00000000..2303779b Binary files /dev/null and b/Classes/Relics/assets/Colorboros.png differ diff --git a/Classes/Relics/assets/Colorboros.png.import b/Classes/Relics/assets/Colorboros.png.import new file mode 100644 index 00000000..81d7b2ea --- /dev/null +++ b/Classes/Relics/assets/Colorboros.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccd6pskxcwpxg" +path="res://.godot/imported/Colorboros.png-56197d792c620150c773fb4ecce6289c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Classes/Relics/assets/Colorboros.png" +dest_files=["res://.godot/imported/Colorboros.png-56197d792c620150c773fb4ecce6289c.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 f8d51ba9..0fb7dbfb 100644 --- a/Funk Engine.csproj +++ b/Funk Engine.csproj @@ -1,4 +1,4 @@ - + net6.0 net7.0 @@ -8,6 +8,7 @@ + diff --git a/Funk Engine.sln.DotSettings.user b/Funk Engine.sln.DotSettings.user deleted file mode 100644 index 6125b70c..00000000 --- a/Funk Engine.sln.DotSettings.user +++ /dev/null @@ -1,3 +0,0 @@ - - ForceIncluded - ForceIncluded \ No newline at end of file diff --git a/Globals/BGAudioPlayer.tscn b/Globals/BGAudioPlayer.tscn new file mode 100644 index 00000000..932b5464 --- /dev/null +++ b/Globals/BGAudioPlayer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://csx04jwaoo20u"] + +[ext_resource type="Script" path="res://Globals/BgAudioPlayer.cs" id="1_w7def"] + +[node name="BgAudioPlayer" type="AudioStreamPlayer"] +script = ExtResource("1_w7def") diff --git a/Globals/BgAudioPlayer.cs b/Globals/BgAudioPlayer.cs new file mode 100644 index 00000000..95fae32a --- /dev/null +++ b/Globals/BgAudioPlayer.cs @@ -0,0 +1,30 @@ +using Godot; + +public partial class BgAudioPlayer : AudioStreamPlayer +{ + private readonly AudioStream _levelMusic = (AudioStream) + ResourceLoader.Load("res://scenes/SceneTransitions/assets/titleSong.ogg"); + + private void PlayMusic(AudioStream music, float volume) + { + if (Playing && music.Equals(Stream)) + { + return; + } + + Stream = music; + VolumeDb = volume; + Play(); + } + + public void PlayLevelMusic(float volume = -10f) + { + PlayMusic(_levelMusic, volume); + } + + public void StopMusic() + { + Stop(); + Stream = null; + } +} diff --git a/Globals/FunkEngineNameSpace.cs b/Globals/FunkEngineNameSpace.cs index 7d422ea6..1c31fb5a 100644 --- a/Globals/FunkEngineNameSpace.cs +++ b/Globals/FunkEngineNameSpace.cs @@ -1,7 +1,25 @@ -using Godot; +using System; +using System.Linq; +using FunkEngine.Classes.MidiMaestro; +using Godot; namespace FunkEngine; +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 enum ArrowType { Up = 0, @@ -10,14 +28,6 @@ public enum ArrowType Right = 3, } -public enum BattleEffectTrigger -{ - NotePlaced, - NoteHit, - SelfNoteHit, - OnPickup, -} - public enum Timing { Miss = 0, @@ -27,27 +37,153 @@ public enum Timing Perfect = 4, } +public struct BattleConfig +{ + public Stages RoomType; + public MapGrid.Room BattleRoom; + public string EnemyScenePath; + public SongTemplate CurSong; +} + +public enum BattleEffectTrigger +{ + NotePlaced, + NoteHit, + SelfNoteHit, + OnPickup, + OnLoop, +} + public enum Stages { Title, Battle, + Chest, + Boss, Quit, Map, + Controls, } -public struct SongData +public class MapGrid { - public int Bpm; - public double SongLength; - public int NumLoops; -} + private int[,] _map; + private Room[] _rooms; + private int _curIdx = 0; -public struct ArrowData -{ - public Color Color; - public string Key; - public NoteChecker Node; - public ArrowType Type; + 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(Stages 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; } + public Stages Type { get; private set; } + } + + 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(); + _rooms[0].SetType(Stages.Battle); + _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 = StageProducer.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++); + _rooms[^1].SetType(PickRoomType(x, y)); + } + else + { + _rooms[_map[x, y]].AddChild(_map[nextX, y + 1]); + } + if (y < height - 2) + { + GeneratePath_r(nextX, y + 1, width, height); + } + } + + private Stages PickRoomType(int x, int y) + { + if (y % 3 == 0) + return Stages.Chest; + if (StageProducer.GlobalRng.Randf() < .08) + return Stages.Chest; + return Stages.Battle; + } + + //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(Stages.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 interface IBattleEvent @@ -55,3 +191,10 @@ public interface IBattleEvent void OnTrigger(BattleDirector BD); BattleEffectTrigger GetTrigger(); } + +public interface IDisplayable +{ + string Name { get; set; } + string Tooltip { get; set; } + Texture2D Texture { get; set; } +} diff --git a/Globals/Scribe.cs b/Globals/Scribe.cs index 45bc8588..16c87a9f 100644 --- a/Globals/Scribe.cs +++ b/Globals/Scribe.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using FunkEngine; +using FunkEngine.Classes.MidiMaestro; using Godot; /** @@ -13,12 +14,12 @@ public partial class Scribe : Node new Note( "EnemyBase", "Basic enemy note, deals damage to player.", - GD.Load("res://scenes/BattleDirector/assets/Character1.png"), + null, null, 1, (director, note, timing) => { - director.Player.TakeDamage(4 - (int)timing); + director.Player.TakeDamage(3 - (int)timing); } ), new Note( @@ -45,6 +46,41 @@ public partial class Scribe : Node director.Enemy.TakeDamage(2 * (int)timing); } ), + new Note( + "PlayerHeal", + "Basic player note, heals player", + GD.Load("res://Classes/Notes/assets/heal_note.png"), + null, + 1, + (director, note, timing) => + { + director.Player.Heal((int)timing); + } + ), + new Note( + "PlayerVampire", + "Steals health from enemy", + GD.Load("res://Classes/Notes/assets/vampire_note.png"), + null, + 1, + (director, note, timing) => + { + director.Player.Heal((int)timing); + director.Enemy.TakeDamage((int)timing); + } + ), + new Note( + "PlayerQuarter", + "Basic note at a quarter of the cost", + GD.Load("res://Classes/Notes/assets/quarter_note.png"), + null, + 1, + (director, note, timing) => + { + director.Enemy.TakeDamage((int)timing); + }, + 0.25f + ), }; public static readonly RelicTemplate[] RelicDictionary = new[] @@ -58,7 +94,7 @@ public partial class Scribe : Node new RelicEffect( BattleEffectTrigger.OnPickup, 10, - (director, val) => + (director, self, val) => { StageProducer.PlayerStats.MaxHealth += val; StageProducer.PlayerStats.CurrentHealth += val; @@ -68,20 +104,93 @@ public partial class Scribe : Node ), new RelicTemplate( "Good Vibes", - "Good vibes, heals the player whenever they place a note.", //TODO: Description can include the relics values? + "Heals the player whenever they place a note.", GD.Load("res://Classes/Relics/assets/relic_GoodVibes.png"), new RelicEffect[] { new RelicEffect( BattleEffectTrigger.NotePlaced, - 1, - (director, val) => + 2, + (director, self, val) => { director.Player.Heal(val); } ), } ), + new RelicTemplate( + "Auroboros", + "Bigger number, better person. Increases combo multiplier every riff.", + GD.Load("res://Classes/Relics/assets/Auroboros.png"), + new RelicEffect[] + { + new RelicEffect( + BattleEffectTrigger.OnLoop, + 1, + (director, self, val) => + { + director.NotePlacementBar.IncreaseBonusMult(val); + self.Value++; + } + ), + } + ), + new RelicTemplate( + "Colorboros", + "Taste the rainbow. Charges the freestyle bar every riff.", + GD.Load("res://Classes/Relics/assets/Colorboros.png"), + new RelicEffect[] + { + new RelicEffect( + BattleEffectTrigger.OnLoop, + 20, + (director, self, val) => + { + director.NotePlacementBar.IncreaseCharge(val); + self.Value++; + } + ), + } + ), + }; + + public static readonly SongTemplate[] SongDictionary = new[] + { + new SongTemplate( + new SongData + { + Bpm = 120, + SongLength = -1, + NumLoops = 5, + }, + "Song1", + "Audio/Song1.ogg", + "Audio/midi/Song1.mid" + ), + new SongTemplate( + new SongData + { + Bpm = 60, + SongLength = -1, + NumLoops = 1, + }, + "Song2", + "Audio/Song2.ogg", + "Audio/midi/Song2.mid", + "res://scenes/Puppets/Enemies/Parasifly/Parasifly.tscn" + ), + new SongTemplate( + new SongData + { + Bpm = 120, + SongLength = -1, + NumLoops = 1, + }, + "Song3", + "Audio/Song3.ogg", + "Audio/midi/Song3.mid", + "res://scenes/Puppets/Enemies/TheGWS/GWS.tscn" + ), }; //TODO: Item pool(s) @@ -89,7 +198,7 @@ public partial class Scribe : Node public static RelicTemplate[] GetRandomRelics(RelicTemplate[] ownedRelics, int count) { var availableRelics = Scribe - .RelicDictionary.Where(r => !ownedRelics.Any(o => o.Name == r.Name)) + .RelicDictionary.Where(r => ownedRelics.All(o => o.Name != r.Name)) .ToArray(); availableRelics = availableRelics @@ -104,4 +213,19 @@ public static RelicTemplate[] GetRandomRelics(RelicTemplate[] ownedRelics, int c } return availableRelics; } + + public static Note[] GetRandomRewardNotes(int count) + { + var availableNotes = Scribe + .NoteDictionary.Where(r => r.Name.Contains("Player")) //TODO: Classifications/pools + .ToArray(); + + availableNotes = availableNotes + .OrderBy(_ => StageProducer.GlobalRng.Randi()) + .Take(count) + .Select(r => r.Clone()) + .ToArray(); + + return availableNotes; + } } diff --git a/Globals/StageProducer.cs b/Globals/StageProducer.cs index d304eaaa..d8928bc4 100644 --- a/Globals/StageProducer.cs +++ b/Globals/StageProducer.cs @@ -10,165 +10,74 @@ public partial class StageProducer : Node public static RandomNumberGenerator GlobalRng = new RandomNumberGenerator(); private ulong _seed; private ulong _lastRngState; - private bool _isInitialized = false; + public static bool IsInitialized; - private Stages _curStage = Stages.Title; + private Stages _curStage = Stages.Title; //TODO: State Machine kinda deal? 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(); + public static BattleConfig Config; + //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); + GlobalRng.Randomize(); _seed = GlobalRng.Seed; _lastRngState = GlobalRng.State; PlayerStats = new PlayerStats(); CurRoom = Map.GetRooms()[0]; - _isInitialized = true; + IsInitialized = true; + } + + public static void ChangeCurRoom(MapGrid.Room room) + { + CurRoom = room; } public void TransitionFromRoom(int nextRoomIdx) { - //CurRoom = Map.GetRooms()[nextRoomIdx]; - TransitionStage(Stages.Battle); + TransitionStage(Map.GetRooms()[nextRoomIdx].Type, nextRoomIdx); } - public void TransitionStage(Stages nextStage) + public void TransitionStage(Stages nextStage, int nextRoomIdx = -1) { - GD.Print(GetTree().CurrentScene); switch (nextStage) { case Stages.Title: - _isInitialized = false; + IsInitialized = false; GetTree().ChangeSceneToFile("res://scenes/SceneTransitions/TitleScreen.tscn"); break; case Stages.Battle: + Config = MakeConfig(nextStage, nextRoomIdx); + GetTree().ChangeSceneToFile("res://scenes/BattleDirector/test_battle_scene.tscn"); + break; + case Stages.Boss: + Config = MakeConfig(nextStage, nextRoomIdx); GetTree().ChangeSceneToFile("res://scenes/BattleDirector/test_battle_scene.tscn"); break; + case Stages.Controls: + GetTree().ChangeSceneToFile("res://scenes/Remapping/Remap.tscn"); + break; + case Stages.Chest: + Config = MakeConfig(nextStage, nextRoomIdx); + GetTree().ChangeSceneToFile("res://scenes/ChestScene/ChestScene.tscn"); + break; case Stages.Map: GetTree().ChangeSceneToFile("res://scenes/Maps/cartographer.tscn"); - if (!_isInitialized) + if (!IsInitialized) { StartGame(); } break; case Stages.Quit: - GD.Print("Exiting game"); GetTree().Quit(); return; default: @@ -178,4 +87,25 @@ public void TransitionStage(Stages nextStage) _curStage = nextStage; } + + private BattleConfig MakeConfig(Stages nextRoom, int nextRoomIdx) + { + BattleConfig result = new BattleConfig(); + result.BattleRoom = Map.GetRooms()[nextRoomIdx]; + result.RoomType = nextRoom; + switch (nextRoom) + { + case Stages.Battle: + int songIdx = GlobalRng.RandiRange(1, 2); + result.CurSong = Scribe.SongDictionary[songIdx]; + result.EnemyScenePath = Scribe.SongDictionary[songIdx].EnemyScenePath; + break; + case Stages.Boss: + result.EnemyScenePath = "res://scenes/Puppets/Enemies/BossBlood/Boss1.tscn"; + result.CurSong = Scribe.SongDictionary[0]; + break; + } + + return result; + } } diff --git a/Globals/TimeKeeper.cs b/Globals/TimeKeeper.cs index 7f09ddff..6db364ef 100644 --- a/Globals/TimeKeeper.cs +++ b/Globals/TimeKeeper.cs @@ -7,4 +7,9 @@ public partial class TimeKeeper : Node public static float ChartLength; public static float LoopLength; public static float Bpm; + + public static double PosMod(double i, double mod) + { + return (i % mod + mod) % mod; + } } diff --git a/README.md b/README.md index 65055df1..1dae4551 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,17 @@ Current team members include: ### Artists - Ares Atlas +- Evelyn Fu +- Emily Wen #### Attributions: -First Song: gameMusic by Magntron - freesound.org - - +Music: +- Title Screen: [Crystal Cave - Cynicmusic](https://opengameart.org/content/crystal-cave-song18) +- Battle Song 1: [gameMusic - Magntron](https://freesound.org/people/Magntron/sounds/335571/) + +Images: +- Input Buttons: [inputKeys - Nicolae (Xelu) Berbece](https://thoseawesomeguys.com/prompts/) +- Title Screen Font: [04B-30 - Yuji Oshimoto](http://04.jp.org/) +- Light map texture: [Godot Engine docs - Godot Foundation](https://docs.godotengine.org/en/stable/tutorials/2d/2d_lights_and_shadows.html) \ No newline at end of file diff --git a/project.godot b/project.godot index 94e0cab0..d98a7e8c 100644 --- a/project.godot +++ b/project.godot @@ -10,16 +10,17 @@ config_version=5 [application] -config/name="Funk Engine" +config/name="ProjectFunkEngine" run/main_scene="res://scenes/SceneTransitions/TitleScreen.tscn" config/features=PackedStringArray("4.3", "C#", "Forward Plus") -config/icon="res://icon.svg" +config/icon="res://scenes/BattleDirector/assets/Character1.png" [autoload] TimeKeeper="*res://Globals/TimeKeeper.cs" Scribe="*res://Globals/Scribe.cs" StageProducer="*res://Globals/StageProducer.cs" +BgAudioPlayer="*res://Globals/BGAudioPlayer.tscn" [display] @@ -32,30 +33,42 @@ window/stretch/scale_mode="integer" project/assembly_name="Funk Engine" +[game] + +input_scheme="ARROWS" + [input] arrowUp={ "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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) , 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":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":88,"key_label":0,"unicode":120,"location":0,"echo":false,"script":null) +, 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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) ] } arrowDown={ "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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) , 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":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null) +, 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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) ] } arrowLeft={ "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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) , 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":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":90,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null) +, 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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) ] } arrowRight={ "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":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , 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) +, 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":86,"key_label":0,"unicode":118,"location":0,"echo":false,"script":null) +, 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":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null) ] } Pause={ @@ -68,6 +81,11 @@ Inventory={ "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) ] } +Secondary={ +"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":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [rendering] diff --git a/scenes/BattleDirector/NotePlacementBar.tscn b/scenes/BattleDirector/NotePlacementBar.tscn index 7f1133c2..b0935a83 100644 --- a/scenes/BattleDirector/NotePlacementBar.tscn +++ b/scenes/BattleDirector/NotePlacementBar.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=3 uid="uid://duhiilcv4tat3"] +[gd_scene load_steps=12 format=3 uid="uid://duhiilcv4tat3"] [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"] @@ -10,20 +10,46 @@ colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1) [sub_resource type="GradientTexture2D" id="GradientTexture2D_hhds4"] gradient = SubResource("Gradient_0u6yv") -width = 26 +width = 34 height = 100 [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) +offsets = PackedFloat32Array(0, 0.373239, 0.690141, 1) +colors = PackedColorArray(0, 1, 0, 1, 1, 0, 0.4, 1, 0, 1, 0.95, 1, 1, 0, 0, 1) [sub_resource type="GradientTexture2D" id="GradientTexture2D_0bqho"] gradient = SubResource("Gradient_xvck1") -width = 24 +width = 32 height = 98 fill_to = Vector2(0, 1) -[node name="NotePlacementBar" type="Control" node_paths=PackedStringArray("notePlacementBar", "currentComboMultText", "_currentNote", "_nextNote")] +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_fy2uu"] +lifetime_randomness = 0.32 +particle_flag_disable_z = true +emission_shape = 3 +emission_box_extents = Vector3(40, 5, 1) +inherit_velocity_ratio = 1.0 +direction = Vector3(0, 1, 0) +spread = 13.548 +initial_velocity_min = 124.08 +initial_velocity_max = 176.41 +gravity = Vector3(0, 98, 0) +scale_min = 2.0 +scale_max = 3.0 +turbulence_noise_strength = 0.1 +turbulence_noise_scale = 5.0 +turbulence_influence_min = 0.0 +turbulence_influence_max = 0.018 + +[sub_resource type="Gradient" id="Gradient_2uknl"] +offsets = PackedFloat32Array(0) +colors = PackedColorArray(0.46, 0.2162, 0.39905, 1) + +[sub_resource type="GradientTexture1D" id="GradientTexture1D_d62c7"] +gradient = SubResource("Gradient_2uknl") +width = 1 + +[node name="NotePlacementBar" type="Control" node_paths=PackedStringArray("notePlacementBar", "currentComboMultText", "_particles", "_currentNote", "_nextNote", "fullBarParticles")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -33,25 +59,55 @@ grow_vertical = 2 script = ExtResource("1_456es") notePlacementBar = NodePath("ProgressBar") currentComboMultText = NodePath("TextEdit") -_currentNote = NodePath("NoteQueueSprite/CurrentNote") -_nextNote = NodePath("NoteQueueSprite/NextNote") +_particles = NodePath("ProgressBar/Rock") +_currentNote = NodePath("NoteQueueSprite/NextNote") +_nextNote = NodePath("NoteQueueSprite/CurrentNote") +fullBarParticles = NodePath("ProgressBar/CPUParticles2D") [node name="ProgressBar" type="TextureProgressBar" parent="."] layout_mode = 0 -offset_right = 26.0 -offset_bottom = 100.0 +offset_left = 41.0 +offset_top = 33.0 +offset_right = 75.0 +offset_bottom = 133.0 fill_mode = 3 texture_under = SubResource("GradientTexture2D_hhds4") texture_progress = SubResource("GradientTexture2D_0bqho") texture_progress_offset = Vector2(1, 1) +[node name="Rock" type="GPUParticles2D" parent="ProgressBar"] +z_index = 1 +position = Vector2(-1, -32) +emitting = false +amount = 22 +process_material = SubResource("ParticleProcessMaterial_fy2uu") +texture = SubResource("GradientTexture1D_d62c7") +lifetime = 2.0 +preprocess = 0.1 +explosiveness = 0.3 +randomness = 0.05 + +[node name="CPUParticles2D" type="CPUParticles2D" parent="ProgressBar"] +position = Vector2(14, 98) +emitting = false +amount = 10 +lifetime = 1.5 +direction = Vector2(0, -1) +spread = 15.0 +gravity = Vector2(0, 0) +initial_velocity_min = 25.0 +initial_velocity_max = 50.0 +scale_amount_max = 2.0 +color = Color(1, 1, 0.0745098, 1) + [node name="TextEdit" type="TextEdit" parent="."] +z_as_relative = false +custom_minimum_size = Vector2(80, 35) layout_mode = 0 -offset_top = -41.0 -offset_right = 50.0 -offset_bottom = -6.0 +offset_right = 80.0 +offset_bottom = 35.0 mouse_filter = 2 -text = "x1" +text = " x1" context_menu_enabled = false shortcut_keys_enabled = false selecting_enabled = false @@ -61,13 +117,16 @@ virtual_keyboard_enabled = false middle_mouse_paste_enabled = false [node name="NoteQueueSprite" type="Sprite2D" parent="."] -position = Vector2(84, -24) +position = Vector2(23, 66) +rotation = -1.5708 texture = ExtResource("2_3tw16") -[node name="CurrentNote" type="Sprite2D" parent="NoteQueueSprite"] -position = Vector2(-14, -1) +[node name="NextNote" type="Sprite2D" parent="NoteQueueSprite"] +position = Vector2(-16, 0) +rotation = 1.5708 texture = ExtResource("3_6ylx6") -[node name="NextNote" type="Sprite2D" parent="NoteQueueSprite"] -position = Vector2(16, -2) +[node name="CurrentNote" type="Sprite2D" parent="NoteQueueSprite"] +position = Vector2(16, 0) +rotation = 1.5708 texture = ExtResource("4_6w8ha") diff --git a/scenes/BattleDirector/assets/BattleFrame1.png b/scenes/BattleDirector/assets/BattleFrame1.png new file mode 100644 index 00000000..223cc97d Binary files /dev/null and b/scenes/BattleDirector/assets/BattleFrame1.png differ diff --git a/scenes/BattleDirector/assets/BattleFrame1.png.import b/scenes/BattleDirector/assets/BattleFrame1.png.import new file mode 100644 index 00000000..2e4b1b49 --- /dev/null +++ b/scenes/BattleDirector/assets/BattleFrame1.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbjotl0v1ymia" +path="res://.godot/imported/BattleFrame1.png-09281473fcee09b42b1810f8f32b75f4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scenes/BattleDirector/assets/BattleFrame1.png" +dest_files=["res://.godot/imported/BattleFrame1.png-09281473fcee09b42b1810f8f32b75f4.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/assets/CoolBG.jpg b/scenes/BattleDirector/assets/CoolBG.jpg deleted file mode 100644 index 5848378a..00000000 Binary files a/scenes/BattleDirector/assets/CoolBG.jpg and /dev/null differ diff --git a/scenes/BattleDirector/assets/bgupdate.png b/scenes/BattleDirector/assets/bgupdate.png new file mode 100644 index 00000000..68b7ee08 Binary files /dev/null and b/scenes/BattleDirector/assets/bgupdate.png differ diff --git a/scenes/BattleDirector/assets/bgupdate.png.import b/scenes/BattleDirector/assets/bgupdate.png.import new file mode 100644 index 00000000..905115ed --- /dev/null +++ b/scenes/BattleDirector/assets/bgupdate.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qhwve7fik4do" +path="res://.godot/imported/bgupdate.png-7e94b2cdf31dd024c5ed51b6fbe048af.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scenes/BattleDirector/assets/bgupdate.png" +dest_files=["res://.godot/imported/bgupdate.png-7e94b2cdf31dd024c5ed51b6fbe048af.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/assets/temp_note_queue.png b/scenes/BattleDirector/assets/temp_note_queue.png index c6a556d0..8ce76dec 100644 Binary files a/scenes/BattleDirector/assets/temp_note_queue.png and b/scenes/BattleDirector/assets/temp_note_queue.png differ diff --git a/scenes/BattleDirector/scripts/BattleDirector.cs b/scenes/BattleDirector/scripts/BattleDirector.cs index 9a0822c1..48b943e2 100644 --- a/scenes/BattleDirector/scripts/BattleDirector.cs +++ b/scenes/BattleDirector/scripts/BattleDirector.cs @@ -14,13 +14,13 @@ public partial class BattleDirector : Node2D #region Declarations public PlayerPuppet Player; - public PuppetTemplate Enemy; + public EnemyPuppet Enemy; [Export] private ChartManager CM; [Export] - private NotePlacementBar NotePlacementBar; + public NotePlacementBar NotePlacementBar; [Export] private Conductor CD; @@ -28,24 +28,25 @@ public partial class BattleDirector : Node2D [Export] private AudioStreamPlayer Audio; + [Export] + private Button _focusedButton; //Initially start button + private double _timingInterval = .1; //secs, maybe make somewhat note dependent + private double _lastBeat; private SongData _curSong; - private bool _battleEnd; - #endregion #region Note Handling - private void PlayerAddNote(ArrowType type, int beat) + private bool 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."); - } + return false; + if (!CD.AddNoteToLane(type, beat % CM.BeatsPerLoop, NotePlacementBar.PlacedNote(), false)) + return false; + NotePlaced?.Invoke(this); + return true; } public PuppetTemplate GetTarget(Note note) @@ -63,69 +64,67 @@ public PuppetTemplate GetTarget(Note note) public override void _Ready() { //TODO: Should come from transition into battle - _curSong = new SongData + _curSong = StageProducer.Config.CurSong.SongData; + Audio.SetStream(GD.Load(StageProducer.Config.CurSong.AudioLocation)); + if (_curSong.SongLength <= 0) { - Bpm = 120, - SongLength = Audio.Stream.GetLength(), - NumLoops = 5, - }; + _curSong.SongLength = Audio.Stream.GetLength(); + } TimeKeeper.Bpm = _curSong.Bpm; - Player = new PlayerPuppet(); + Player = GD.Load("res://scenes/Puppets/PlayerPuppet.tscn") + .Instantiate(); AddChild(Player); Player.Defeated += CheckBattleStatus; EventizeRelics(); NotePlacementBar.Setup(StageProducer.PlayerStats); //TODO: Refine - Enemy = new PuppetTemplate(); - Enemy.SetPosition(new Vector2(400, 0)); + Enemy = GD.Load(StageProducer.Config.EnemyScenePath) + .Instantiate(); 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 += () => + AddEnemyEffects(); + + CM.PrepChart(_curSong); + CD.Prep(); + CD.TimedInput += OnTimedInput; + + CM.Connect(nameof(InputHandler.NotePressed), new Callable(this, nameof(OnNotePressed))); + CM.Connect(nameof(InputHandler.NoteReleased), new Callable(this, nameof(OnNoteReleased))); + + _focusedButton.GrabFocus(); + _focusedButton.Pressed += () => { var timer = GetTree().CreateTimer(AudioServer.GetTimeToNextMix()); timer.Timeout += Begin; - startButton.QueueFree(); + _focusedButton.QueueFree(); + _focusedButton = null; }; } //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))); - + CM.BeginTweens(); Audio.Play(); } + private void EndBattle() + { + StageProducer.ChangeCurRoom(StageProducer.Config.BattleRoom); + GetNode("/root/StageProducer").TransitionStage(Stages.Map); + } + public override void _Process(double delta) { + _focusedButton?.GrabFocus(); TimeKeeper.CurrentTime = Audio.GetPlaybackPosition(); - CD.CheckMiss(); - if (_battleEnd) - GetNode("/root/StageProducer").TransitionStage(Stages.Map); + double realBeat = TimeKeeper.CurrentTime / (60 / (double)TimeKeeper.Bpm) % CM.BeatsPerLoop; + CD.CheckMiss(realBeat); + if (realBeat < _lastBeat) + ChartLooped?.Invoke(this); + _lastBeat = realBeat; } #endregion @@ -138,24 +137,9 @@ public override void _UnhandledInput(InputEvent @event) { if (eventKey.Keycode == Key.Key0) { - //DebugKillEnemy(); + 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) @@ -167,26 +151,28 @@ 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); + if (PlayerAddNote(arrowType, beat)) + return; //Miss on empty note. This does not apply to inactive existing notes as a balance decision for now. + NotePlacementBar.MissNote(); + CM.ComboText(Timing.Miss.ToString(), arrowType, NotePlacementBar.GetCurrentCombo()); + Player.TakeDamage(4); return; } Timing timed = CheckTiming(beatDif); + note.OnHit(this, timed); if (timed == Timing.Miss) { - note.OnHit(this, timed); NotePlacementBar.MissNote(); } else { - note.OnHit(this, timed); NotePlacementBar.HitNote(); } - NotePlacementBar.ComboText(timed.ToString()); + CM.ComboText(timed.ToString(), arrowType, NotePlacementBar.GetCurrentCombo()); } private Timing CheckTiming(double beatDif) @@ -213,29 +199,37 @@ private void CheckBattleStatus(PuppetTemplate puppet) { if (puppet == Player) { - GD.Print("Player is Dead"); - GetNode("/root/StageProducer").TransitionStage(Stages.Title); + BattleLost(); return; } + else if (puppet == Enemy) + BattleWon(); //will have to adjust this to account for when we have multiple enemies at once + } - //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 BattleWon() + { + Audio.StreamPaused = true; + CleanUpRelics(); + ShowRewardSelection(3); } - private void ShowRewardSelection(int amount) + private void BattleLost() { - var rewardUI = GD.Load("res://scenes/UI/RewardSelectionUI.tscn") - .Instantiate(); - AddChild(rewardUI); - rewardUI.Initialize(Player.Stats, amount); + Audio.StreamPaused = true; + AddChild(GD.Load("res://scenes/UI/EndScreen.tscn").Instantiate()); GetTree().Paused = true; } + private void ShowRewardSelection(int amount) + { + string type = "Note"; + if (StageProducer.Config.RoomType == Stages.Boss) + type = "Relic"; + var rewardSelect = RewardSelect.CreateSelection(this, Player.Stats, amount, type); + rewardSelect.GetNode