diff --git a/Classes/Events/EventDatabase.cs b/Classes/Events/EventDatabase.cs
index 89a97a82..fea84d92 100644
--- a/Classes/Events/EventDatabase.cs
+++ b/Classes/Events/EventDatabase.cs
@@ -17,7 +17,7 @@ public class EventDatabase
["EVENT_EVENT1_OPTION1", "EVENT_EVENT1_OPTION2", "EVENT_EVENT1_OPTION3"],
["EVENT_EVENT1_OUTCOME1", "EVENT_EVENT1_OUTCOME2", "EVENT_EVENT1_OUTCOME3"],
[
- (self, node) =>
+ (self, _) =>
{
int randIndex = StageProducer.GlobalRng.RandiRange(
0,
@@ -33,7 +33,7 @@ public class EventDatabase
localizedName
);
},
- (self, node) =>
+ (self, _) =>
{
int randIndex = StageProducer.GlobalRng.RandiRange(
0,
@@ -49,12 +49,12 @@ public class EventDatabase
localizedName
);
},
- (self, node) =>
+ (self, _) =>
{
string stolenMoney = (StageProducer.PlayerStats.Money / 2).ToString();
StageProducer.PlayerStats.Money /= 2;
- self.OutcomeDescriptions[2] = self.OutcomeDescriptions[0] = string.Format(
+ self.OutcomeDescriptions[2] = string.Format(
TranslationServer.Translate("EVENT_EVENT1_OUTCOME3"),
stolenMoney
);
@@ -176,18 +176,18 @@ public class EventDatabase
["EVENT_EVENT3_OPTION1", "EVENT_EVENT3_OPTION2", "EVENT_EVENT3_OPTION3"],
["EVENT_EVENT3_OUTCOME1", "EVENT_EVENT3_OUTCOME2", "EVENT_EVENT3_OUTCOME3"],
[
- (self, node) =>
+ (_, _) =>
{
StageProducer.PlayerStats.CurrentHealth = Math.Min(
StageProducer.PlayerStats.CurrentHealth + 10,
StageProducer.PlayerStats.MaxHealth
);
},
- (self, node) =>
+ (_, _) =>
{
StageProducer.PlayerStats.MaxComboBar -= 5;
},
- (self, node) =>
+ (_, _) =>
{
StageProducer.PlayerStats.Money -= 30;
StageProducer.PlayerStats.AddNote(Scribe.NoteDictionary[3]);
diff --git a/Classes/MidiMaestro/MidiMaestro.cs b/Classes/MidiMaestro/MidiMaestro.cs
index 2c7bfa8f..be5d6c33 100644
--- a/Classes/MidiMaestro/MidiMaestro.cs
+++ b/Classes/MidiMaestro/MidiMaestro.cs
@@ -15,25 +15,12 @@ public partial class MidiMaestro : Resource
private readonly NoteInfo[] _leftNotes;
private readonly NoteInfo[] _rightNotes;
- //private MidiFile strippedSong;
/**
* Constructor loads resource file and populates lane note arrays with NoteInfo
* A string file path to a valid songMap .tres file
*/
public MidiMaestro(NoteChart savedChart)
{
- /*if (!OS.HasFeature("editor"))
- {
- filePath = OS.GetExecutablePath().GetBaseDir() + "/" + filePath;
- }
-
- if (!FileAccess.FileExists(filePath))
- {
- GD.PushError("ERROR: Unable to load level songMap resource file: " + filePath);
- }
-
- NoteChart savedChart = ResourceLoader.Load(filePath);*/
-
if (savedChart != null)
{
_upNotes = savedChart.GetLane(ArrowType.Up).ToArray();
diff --git a/Globals/SaveSystem.cs b/Globals/Configkeeper.cs
similarity index 79%
rename from Globals/SaveSystem.cs
rename to Globals/Configkeeper.cs
index 8e478d50..85e635bd 100644
--- a/Globals/SaveSystem.cs
+++ b/Globals/Configkeeper.cs
@@ -7,7 +7,7 @@
/**
* SaveSystem: Manages FileI/O of configs and save files.
*/
-public static class SaveSystem
+public static class Configkeeper
{
#region Config
private const string UserConfigPath = "user://Options.cfg";
@@ -31,8 +31,6 @@ public static class SaveSystem
private const bool DefaultTypeIsArrow = false;
private const bool DefaultVerticalScroll = true;
private const bool DefaultHighCon = false;
- private const bool DefaultFirstTime = true;
- private const bool DefaultHasWon = false;
public enum ConfigSettings
{
@@ -53,8 +51,6 @@ public enum ConfigSettings
LanguageKey,
TypeIsArrow,
HighContrast,
- FirstTime,
- HasWon,
VerticalScroll,
}
@@ -81,8 +77,6 @@ private static void InitConfig()
UpdateConfig(ConfigSettings.LanguageKey, DefaultLanguage);
UpdateConfig(ConfigSettings.TypeIsArrow, DefaultTypeIsArrow);
UpdateConfig(ConfigSettings.HighContrast, DefaultHighCon);
- UpdateConfig(ConfigSettings.FirstTime, DefaultFirstTime);
- UpdateConfig(ConfigSettings.HasWon, DefaultHasWon);
UpdateConfig(ConfigSettings.VerticalScroll, DefaultVerticalScroll);
}
@@ -151,12 +145,6 @@ public static void UpdateConfig(ConfigSettings setting, Variant value)
case ConfigSettings.HighContrast:
_curConfigData.SetValue("Options", "HighContrast", value);
break;
- case ConfigSettings.FirstTime:
- _curConfigData.SetValue("Game", "FirstTime", value);
- break;
- case ConfigSettings.HasWon:
- _curConfigData.SetValue("Game", "HasWon", value);
- break;
default:
GD.PushError("SaveSystem.UpdateConfig: Invalid config setting passed. " + setting);
break;
@@ -298,10 +286,6 @@ public static Variant GetConfigValue(ConfigSettings setting)
return _curConfigData.GetValue("Options", "VerticalScroll", DefaultVerticalScroll);
case ConfigSettings.HighContrast:
return _curConfigData.GetValue("Options", "HighContrast", DefaultHighCon);
- case ConfigSettings.FirstTime:
- return _curConfigData.GetValue("Game", "FirstTime", DefaultFirstTime);
- case ConfigSettings.HasWon:
- return _curConfigData.GetValue("Game", "HasWon", DefaultHasWon);
default:
GD.PushError("Invalid config setting passed. " + setting);
return float.MinValue;
@@ -399,110 +383,4 @@ public static void ClearConfig()
InitConfig();
}
#endregion
-
- #region Save
-
- private const string UserSavePath = "user://MidnighRiff.save";
-
- public class SaveFile
- {
- public ulong RngSeed { get; init; }
- public ulong RngState { get; init; }
- public int LastRoomIdx { get; init; }
- public int Area { get; init; }
-
- public int Money { get; init; }
- public int[] NoteIds { get; init; }
- public int[] RelicIds { get; init; }
- public int[] BattlePool { get; init; }
- public int[] EventPool { get; init; }
- public int PlayerHealth { get; init; }
- public int Shortcuts { get; init; }
- public int PlayerMaxCombo { get; init; }
-
- public SaveFile(
- ulong rngSeed,
- ulong rngState,
- int lastRoomIdx,
- int[] noteIds,
- int[] relicIds,
- int[] battlePool,
- int[] eventPool,
- int playerHealth,
- int area,
- int money,
- int shortcuts,
- int playerMaxCombo
- )
- {
- RngSeed = rngSeed;
- RngState = rngState;
- LastRoomIdx = lastRoomIdx;
- NoteIds = noteIds;
- RelicIds = relicIds;
- BattlePool = battlePool ?? [];
- EventPool = eventPool ?? [];
- PlayerHealth = playerHealth;
- Area = area;
- Money = money;
- Shortcuts = shortcuts;
- PlayerMaxCombo = playerMaxCombo;
- }
- }
-
- public static void SaveGame()
- {
- int[] relicIds = StageProducer.PlayerStats.CurRelics.Select(r => r.Id).ToArray();
- int[] noteIds = StageProducer.PlayerStats.CurNotes.Select(r => r.Id).ToArray();
- SaveFile sv = new SaveFile(
- StageProducer.GlobalRng.Seed,
- StageProducer.GlobalRng.State,
- StageProducer.CurRoom,
- noteIds,
- relicIds,
- StageProducer.BattlePool?.ToArray(),
- EventScene.EventPool?.ToArray(),
- StageProducer.PlayerStats.CurrentHealth,
- StageProducer.CurLevel.Id,
- StageProducer.PlayerStats.Money,
- StageProducer.PlayerStats.Shortcuts,
- StageProducer.PlayerStats.MaxComboBar
- );
- string json = JsonSerializer.Serialize(sv);
-
- FileAccess file = FileAccess.Open(UserSavePath, FileAccess.ModeFlags.Write);
-
- file.StoreLine(json);
- file.Close();
- }
-
- /**
- * Returns null if invalid save or save 404's.
- */
- public static SaveFile LoadGame()
- {
- if (!FileAccess.FileExists(UserSavePath))
- return null;
- FileAccess file = FileAccess.Open(UserSavePath, FileAccess.ModeFlags.Read);
- string json = file.GetAsText();
-
- file.Close();
- SaveFile sv;
- try
- {
- sv = JsonSerializer.Deserialize(json);
- }
- catch (JsonException)
- {
- GD.PushWarning("Cannot deserialize save file, returning null.");
- return null;
- }
- return sv;
- }
-
- public static void ClearSave()
- {
- DirAccess.RemoveAbsolute(UserSavePath);
- }
- #endregion
}
diff --git a/Globals/Configkeeper.cs.uid b/Globals/Configkeeper.cs.uid
new file mode 100644
index 00000000..55fcc578
--- /dev/null
+++ b/Globals/Configkeeper.cs.uid
@@ -0,0 +1 @@
+uid://bbkyrplc17j0w
diff --git a/Globals/Savekeeper.cs b/Globals/Savekeeper.cs
new file mode 100644
index 00000000..68cf96fe
--- /dev/null
+++ b/Globals/Savekeeper.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections.Generic;
+using Godot;
+using FileAccess = Godot.FileAccess;
+
+///
+/// v1 of a drag and drop save system.
+/// Does not work with Godot resources for safer handling.
+/// Assumptions:
+/// T needs ToString() and T.TryParse(string value, out T result) -> bool
+/// Objects need to implement their own Serialize and Deserialize
+///
+public partial class Savekeeper : Node
+{
+ private const string SaveFileDirectory = "user://saves";
+ private const string SaveFileExtension = ".save";
+ private const string DefaultSaveFileName = "MidnightRiff";
+
+ private const char Delimiter = '|';
+
+ public delegate void SavingHandler();
+
+ ///
+ /// Event that a save is underway. Subscribe to serialize data before writing.
+ ///
+ public static event SavingHandler Saving;
+
+ public delegate void GameSavedHandler();
+ public static event GameSavedHandler GameSaved;
+
+ public delegate void LoadedHandler();
+ public static event LoadedHandler Loaded;
+
+ public override void _EnterTree()
+ {
+ DirAccess.MakeDirAbsolute(SaveFileDirectory);
+ }
+
+ public static Dictionary GameSaveObjects { get; private set; } =
+ new Dictionary();
+
+ private static void HandlePrevSave()
+ {
+ return; //Placeholder to do things, e.g. make a backup
+ }
+
+ public static void RecordSave(string savePath = DefaultSaveFileName + SaveFileExtension)
+ {
+ Saving?.Invoke();
+ Callable.From(() => SaveToFile(savePath)).CallDeferred();
+ }
+
+ public static bool SaveToFile(string savePath = DefaultSaveFileName + SaveFileExtension)
+ {
+ if (string.IsNullOrEmpty(savePath))
+ return false;
+ FileAccess file = FileAccess.Open(
+ SaveFileDirectory + "/" + savePath,
+ FileAccess.ModeFlags.Write
+ );
+ if (file == null)
+ return false;
+
+ foreach ((string key, string value) in GameSaveObjects)
+ file.StoreLine(key + Delimiter + value);
+ file.Close();
+
+ GameSaved?.Invoke();
+ return true;
+ }
+
+ public static bool LoadFromFile(string savePath = DefaultSaveFileName + SaveFileExtension)
+ {
+ if (string.IsNullOrEmpty(savePath))
+ return false;
+ FileAccess file = FileAccess.Open(
+ SaveFileDirectory + "/" + savePath,
+ FileAccess.ModeFlags.Read
+ );
+ if (file == null)
+ return false;
+
+ HandlePrevSave();
+
+ while (file.GetPosition() < file.GetLength())
+ {
+ string line = file.GetLine().Trim();
+ int idx = line.IndexOf(Delimiter);
+ if (idx == -1)
+ continue;
+
+ string key = line.Substring(0, idx);
+ string value = SanitizeSaveString(line.Substring(idx));
+ if (value == null)
+ continue;
+
+ GameSaveObjects[key] = value;
+ }
+
+ Loaded?.Invoke();
+ return true;
+ }
+
+ private static string SanitizeSaveString(string saveString)
+ {
+ if (string.IsNullOrEmpty(saveString))
+ return null;
+ saveString = saveString.Trim();
+ if (saveString.Length == 0 || saveString[0] != Delimiter)
+ return null;
+ return saveString.Substring(1);
+ }
+
+ const string InvalidFormatString = "InvalidString";
+
+ //Ex: Position.X|45.8|
+ public static string Format(string valName, object value)
+ {
+ if (value == null)
+ return "";
+ if (value is string s && !s.IsValidFileName())
+ return valName + Delimiter + InvalidFormatString + Delimiter;
+
+ return valName + Delimiter + value + Delimiter;
+ }
+
+ private const string ArrayDelimiter = "*";
+
+ //Ex: ValidIds|[12*1*4*5]|
+ public static string FormatArray(string valName, T[] value)
+ {
+ if (value == null)
+ return "";
+ String retString = valName + Delimiter + '[';
+
+ foreach (object o in value)
+ {
+ if (o is string s && !s.IsValidFileName())
+ {
+ retString += InvalidFormatString + ArrayDelimiter;
+ continue;
+ }
+ retString += o.ToString() + ArrayDelimiter;
+ }
+ retString += "]" + Delimiter;
+ return retString;
+ }
+
+ public readonly struct ParseResult(
+ T value,
+ bool success,
+ int nextIdx,
+ string message = "Success!"
+ )
+ {
+ public T Value { get; init; } = value;
+ public bool Success { get; init; } = success;
+ public int NextIdx { get; init; } = nextIdx;
+ public string Message { get; init; } = message;
+ }
+
+ public delegate bool TryParseHandler(string value, out T result); //https://stackoverflow.com/questions/2961656/generic-tryparse
+
+ public static bool StringParse(string value, out string result)
+ {
+ result = value;
+ return true;
+ }
+
+ public static ParseResult Parse(
+ string valName,
+ string saveString,
+ int startIdx,
+ TryParseHandler handler
+ )
+ {
+ if (string.IsNullOrEmpty(valName) || string.IsNullOrEmpty(saveString))
+ return new ParseResult(
+ default,
+ false,
+ startIdx,
+ $"String was empty! {valName} {saveString}"
+ );
+
+ ParseResult success = ParseToSubStringValue(saveString, valName, startIdx);
+ if (!success.Success)
+ return new ParseResult(default, false, startIdx, success.Message);
+ string value = success.Value;
+ int finalIdx = success.NextIdx;
+
+ if (handler(value, out var result))
+ return new ParseResult(result, true, finalIdx);
+
+ return new ParseResult(
+ default,
+ false,
+ startIdx,
+ $"Unable to parse from: \"{value}\" to type: {typeof(T)}"
+ );
+ }
+
+ public static ParseResult ParseArray(
+ string valName,
+ string saveString,
+ int startIdx,
+ TryParseHandler handler
+ )
+ {
+ if (string.IsNullOrEmpty(valName) || string.IsNullOrEmpty(saveString))
+ return new ParseResult(
+ default,
+ false,
+ startIdx,
+ $"String was empty! {valName} {saveString}"
+ );
+
+ ParseResult success = ParseToSubStringValue(saveString, valName, startIdx);
+ if (!success.Success)
+ return new ParseResult(default, false, startIdx, success.Message);
+ string value = success.Value;
+ int finalIdx = success.NextIdx;
+
+ string[] values = value.Replace("[", "").Replace("]", "").Split(ArrayDelimiter);
+ List list = new List();
+ foreach (String s in values)
+ {
+ if (string.IsNullOrEmpty(s))
+ continue;
+ if (!handler(s, out var result))
+ return new ParseResult(
+ list.ToArray(),
+ false,
+ finalIdx,
+ $"Unable to parse from: \"{s}\" to type: {typeof(T)}. Returning any successful values."
+ );
+
+ list.Add(result);
+ }
+
+ return new ParseResult(list.ToArray(), true, finalIdx);
+ }
+
+ private static ParseResult ParseToSubStringValue(
+ string saveString,
+ string valName,
+ int startIdx
+ )
+ {
+ int nextIdx = saveString.IndexOf(valName, startIdx, StringComparison.Ordinal);
+ if (nextIdx == -1)
+ return new ParseResult(default, false, startIdx, $"Name not found! {valName}");
+
+ nextIdx += valName.Length + 1;
+ int finalIdx = saveString.IndexOf(Delimiter, nextIdx);
+ if (finalIdx == -1)
+ return new ParseResult(
+ default,
+ false,
+ startIdx,
+ $"No final delimiter found around value! \n String received: {saveString}, from position {nextIdx}"
+ );
+ string value = saveString.Substring(nextIdx, finalIdx - nextIdx);
+
+ return new ParseResult(value, true, finalIdx);
+ }
+
+ #region Project Specific
+
+ public const string DefaultRunSaveHeader = "CurrentGame";
+
+ public static void ClearRun()
+ {
+ if (GameSaveObjects.ContainsKey(DefaultRunSaveHeader))
+ {
+ GameSaveObjects.Remove(DefaultRunSaveHeader);
+ SaveToFile();
+ }
+ }
+
+ #endregion
+}
diff --git a/Globals/Savekeeper.cs.uid b/Globals/Savekeeper.cs.uid
new file mode 100644
index 00000000..18d0030f
--- /dev/null
+++ b/Globals/Savekeeper.cs.uid
@@ -0,0 +1 @@
+uid://ckw1qew68cetv
diff --git a/Globals/Scribe.cs b/Globals/Scribe.cs
index 7cab15df..80f0a6d2 100644
--- a/Globals/Scribe.cs
+++ b/Globals/Scribe.cs
@@ -715,7 +715,7 @@ e is BattleDirector.Harbinger.OnDamageInstanceArgs dmgArgs
),
};
- private static string DefaultNoteChartPath = "Audio/songMaps/";
+ private static string DefaultNoteChartPath = "res://Audio/songMaps/";
public static readonly SongTemplate[] SongDictionary = new[] //Generalize and make pools for areas/room types
{
@@ -800,13 +800,13 @@ e is BattleDirector.Harbinger.OnDamageInstanceArgs dmgArgs
ResourceLoader.Load(DefaultNoteChartPath + "FrostWaltz.tres")
),
new SongTemplate( // 16
- name: "Astrorat",
- enemyScenePath: [P_Astrorat.LoadPath],
+ "Astrorat",
+ [P_Astrorat.LoadPath],
ResourceLoader.Load(DefaultNoteChartPath + "Astrorat.tres")
),
new SongTemplate( // 17
- name: "CatGirl",
- enemyScenePath: [P_Midriff.LoadPath],
+ "CatGirl",
+ [P_Midriff.LoadPath],
ResourceLoader.Load(DefaultNoteChartPath + "Jammin' Forest.tres")
),
};
diff --git a/Globals/StageProducer.cs b/Globals/StageProducer.cs
index b0a3e6e0..62f8a742 100644
--- a/Globals/StageProducer.cs
+++ b/Globals/StageProducer.cs
@@ -16,7 +16,7 @@ public partial class StageProducer : Node
public static readonly RandomNumberGenerator GlobalRng = new();
public static MapLevels CurLevel { get; private set; }
- public static List BattlePool { get; private set; }
+ public static List BattlePool { get; private set; } = [];
public static MapGrid Map { get; private set; } = new();
private Stages _curStage = Stages.Title;
@@ -34,6 +34,11 @@ public partial class StageProducer : Node
#region Initialization
public override void _EnterTree()
{
+ Savekeeper.Saving += SerializeRun;
+ Savekeeper.Saving += SerializePersist;
+ Savekeeper.LoadFromFile();
+ DeserializePersist();
+
InitFromCfg();
LiveInstance = this;
@@ -48,22 +53,22 @@ public override void _EnterTree()
public void InitFromCfg()
{
OptionsMenu.ChangeVolume(
- SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.Volume).As()
+ Configkeeper.GetConfigValue(Configkeeper.ConfigSettings.Volume).As()
);
TranslationServer.SetLocale(
- SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.LanguageKey).As()
+ Configkeeper.GetConfigValue(Configkeeper.ConfigSettings.LanguageKey).As()
);
ContrastFilter = GD.Load("res://Globals/ContrastFilter/ContrastFilter.tscn")
.Instantiate();
- ContrastFilter.Visible = SaveSystem
- .GetConfigValue(SaveSystem.ConfigSettings.HighContrast)
+ ContrastFilter.Visible = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.HighContrast)
.AsBool();
GetTree().Root.CallDeferred("add_child", ContrastFilter);
- InputHandler.UseArrows = SaveSystem
- .GetConfigValue(SaveSystem.ConfigSettings.TypeIsArrow)
+ InputHandler.UseArrows = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.TypeIsArrow)
.AsBool();
- BattleDirector.VerticalScroll = SaveSystem
- .GetConfigValue(SaveSystem.ConfigSettings.VerticalScroll)
+ BattleDirector.VerticalScroll = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.VerticalScroll)
.AsBool();
}
@@ -77,7 +82,7 @@ private void GenerateMapConsistent()
private void StartNewGame()
{
GlobalRng.Randomize();
- if ((bool)SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.FirstTime))
+ if (GetPersistantVal(PersistKeys.TutorialDone) == 0)
CurLevel = MapLevels.GetLevelFromId(0);
else
CurLevel = MapLevels.GetLevelFromId(1);
@@ -86,8 +91,8 @@ private void StartNewGame()
PlayerStats = new PlayerStats();
CurRoom = Map.GetRooms()[0].Idx;
- BattlePool = null;
- EventScene.EventPool = null;
+ BattlePool = [];
+ EventScene.EventPool = [];
Scribe.InitRelicPools();
IsInitialized = true;
MapGrid.ForceEliteBattles = false;
@@ -95,36 +100,12 @@ private void StartNewGame()
private bool LoadGame()
{
- SaveSystem.SaveFile sv = SaveSystem.LoadGame();
- if (sv == null)
+ if (!DeserializeRun())
{
GD.PushWarning("Can't load game, either file 404 or invalid file.");
return false;
}
- GlobalRng.Seed = sv.RngSeed;
- CurLevel = MapLevels.GetLevelFromId(sv.Area);
- BattlePool = sv.BattlePool.ToList();
- EventScene.EventPool = sv.EventPool.ToList();
- GenerateMapConsistent();
- GlobalRng.State = sv.RngState;
- CurRoom = sv.LastRoomIdx;
-
- Scribe.InitRelicPools();
- PlayerStats = new PlayerStats();
- PlayerStats.CurNotes = [];
- foreach (int noteId in sv.NoteIds)
- {
- PlayerStats.AddNote(Scribe.NoteDictionary[noteId]);
- }
- foreach (int relicId in sv.RelicIds)
- {
- PlayerStats.AddRelic(Scribe.RelicDictionary[relicId]);
- }
- PlayerStats.CurrentHealth = sv.PlayerHealth;
- PlayerStats.Money = sv.Money;
- PlayerStats.Shortcuts = sv.Shortcuts;
- PlayerStats.MaxComboBar = sv.PlayerMaxCombo;
IsInitialized = true;
return true;
}
@@ -360,13 +341,282 @@ public static bool IsMoreLevels()
public void ProgressLevels()
{
- GD.Print(CurLevel.Id);
CurLevel = CurLevel.GetNextLevel();
Map = new();
GenerateMapConsistent();
CurRoom = Map.GetRooms()[0].Idx;
- BattlePool = null;
+ BattlePool = [];
+ }
+
+ #endregion
+
+ #region Persistent Data
+ private const string PersistenceHeader = "PersistVals";
+
+ public enum PersistKeys
+ {
+ TutorialDone = 0,
+ HasWon = 1,
+ } //Relative order needs to be preserved between versions.
+
+ private static int[] PersistantValues { get; set; } = [0, 0]; //Dumb and hacky right now. Literally doing this to avoid bool spam for now.
+ private const string PersistentIntValName = "PersistInts";
+
+ public static int GetPersistantVal(PersistKeys key)
+ {
+ return PersistantValues[(int)key];
+ }
+
+ public static void UpdatePersistantValues(PersistKeys key, int newVal)
+ {
+ PersistantValues[(int)key] = newVal;
+ SerializePersist();
+ Savekeeper.SaveToFile();
+ }
+
+ private static void SerializePersist()
+ {
+ string saveString = "";
+ saveString += Savekeeper.FormatArray(PersistentIntValName, PersistantValues);
+ Savekeeper.GameSaveObjects[PersistenceHeader] = saveString;
+ }
+
+ private void DeserializePersist()
+ {
+ if (!Savekeeper.GameSaveObjects.TryGetValue(PersistenceHeader, out var loadPers))
+ {
+ GD.PushWarning("Savekeeper does not contain persistence key!");
+ return;
+ }
+
+ int idx = 0;
+ var success = Savekeeper.ParseArray(PersistentIntValName, loadPers, idx, int.TryParse);
+ if (success.Success)
+ {
+ int[] tempVals = success.Value;
+ for (int i = 0; i < tempVals.Length && i < PersistantValues.Length; i++) //Manually update to safeguard against saves breaking when values are added.
+ PersistantValues[i] = tempVals[i];
+ return;
+ }
+ GD.PushWarning(
+ $"Error deserializing persistent values: {loadPers} Error: {success.Message}"
+ );
+ }
+ #endregion
+
+ #region Saving
+
+ enum RunSaveValues
+ { //Maintain in order of needing to be saved & loaded
+ RngSeed,
+ Area,
+ BattlePool,
+ EventPool,
+ RngState,
+ LastRoomIdx,
+ NoteIds,
+ RelicIds,
+ PlayerHealth,
+ Money,
+ Shortcuts,
+ PlayerMaxCombo,
+ }
+
+ private void SerializeRun()
+ {
+ if (!IsInitialized)
+ return;
+ string saveString = "";
+ saveString +=
+ Savekeeper.Format(RunSaveValues.RngSeed.ToString(), GlobalRng.Seed)
+ + Savekeeper.Format(RunSaveValues.Area.ToString(), CurLevel.Id)
+ + Savekeeper.FormatArray(RunSaveValues.BattlePool.ToString(), BattlePool.ToArray())
+ + Savekeeper.FormatArray(
+ RunSaveValues.EventPool.ToString(),
+ EventScene.EventPool.ToArray()
+ )
+ + Savekeeper.Format(RunSaveValues.RngState.ToString(), GlobalRng.State)
+ + Savekeeper.Format(RunSaveValues.LastRoomIdx.ToString(), CurRoom)
+ + Savekeeper.FormatArray(
+ RunSaveValues.NoteIds.ToString(),
+ PlayerStats.CurNotes.Select(r => r.Id).ToArray()
+ )
+ + Savekeeper.FormatArray(
+ RunSaveValues.RelicIds.ToString(),
+ PlayerStats.CurRelics.Select(r => r.Id).ToArray()
+ )
+ + Savekeeper.Format(RunSaveValues.PlayerHealth.ToString(), PlayerStats.CurrentHealth)
+ + Savekeeper.Format(RunSaveValues.Money.ToString(), PlayerStats.Money)
+ + Savekeeper.Format(RunSaveValues.Shortcuts.ToString(), PlayerStats.Shortcuts)
+ + Savekeeper.Format(RunSaveValues.PlayerMaxCombo.ToString(), PlayerStats.MaxComboBar);
+
+ Savekeeper.GameSaveObjects[Savekeeper.DefaultRunSaveHeader] = saveString;
+ }
+
+ private bool DeserializeRun() //TODO: This is really verbose and bad.
+ {
+ if (!Savekeeper.GameSaveObjects.ContainsKey(Savekeeper.DefaultRunSaveHeader))
+ return false;
+ int idx = 0;
+ string loadRun = Savekeeper.GameSaveObjects[Savekeeper.DefaultRunSaveHeader];
+
+ var ulongSuccess = Savekeeper.Parse(
+ RunSaveValues.RngSeed.ToString(),
+ loadRun,
+ idx,
+ ulong.TryParse
+ );
+ if (!ulongSuccess.Success)
+ return false;
+ GlobalRng.Seed = ulongSuccess.Value;
+ idx = ulongSuccess.NextIdx;
+
+ var intSuccess = Savekeeper.Parse(
+ RunSaveValues.Area.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!intSuccess.Success)
+ return false;
+ CurLevel = MapLevels.GetLevelFromId(intSuccess.Value);
+ idx = intSuccess.NextIdx;
+
+ var bPoolSuccess = Savekeeper.ParseArray(
+ RunSaveValues.BattlePool.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (bPoolSuccess.Success)
+ {
+ BattlePool = bPoolSuccess.Value.ToList();
+ idx = bPoolSuccess.NextIdx;
+ }
+ else
+ {
+ GD.PushWarning("Could not parse battle pool!");
+ BattlePool = [];
+ }
+
+ var ePoolSuccess = Savekeeper.ParseArray(
+ RunSaveValues.EventPool.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (ePoolSuccess.Success)
+ {
+ EventScene.EventPool = ePoolSuccess.Value.ToList();
+ idx = ePoolSuccess.NextIdx;
+ }
+ else
+ {
+ GD.PushWarning("Could not parse event pool!");
+ EventScene.EventPool = [];
+ }
+
+ GenerateMapConsistent();
+
+ ulongSuccess = Savekeeper.Parse(
+ RunSaveValues.RngState.ToString(),
+ loadRun,
+ idx,
+ ulong.TryParse
+ );
+ if (!ulongSuccess.Success)
+ return false;
+ GlobalRng.State = ulongSuccess.Value;
+ idx = ulongSuccess.NextIdx;
+
+ intSuccess = Savekeeper.Parse(
+ RunSaveValues.LastRoomIdx.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!intSuccess.Success)
+ return false;
+ CurRoom = intSuccess.Value;
+ idx = intSuccess.NextIdx;
+
+ Scribe.InitRelicPools();
+ PlayerStats = new PlayerStats();
+ PlayerStats.CurNotes = [];
+
+ var noteSuccess = Savekeeper.ParseArray(
+ RunSaveValues.NoteIds.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!noteSuccess.Success)
+ return false;
+ foreach (int noteId in noteSuccess.Value)
+ {
+ PlayerStats.AddNote(Scribe.NoteDictionary[noteId]);
+ }
+ idx = noteSuccess.NextIdx;
+
+ var relicSuccess = Savekeeper.ParseArray(
+ RunSaveValues.RelicIds.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!relicSuccess.Success)
+ return false;
+ foreach (int relicId in relicSuccess.Value)
+ {
+ PlayerStats.AddRelic(Scribe.RelicDictionary[relicId]);
+ }
+ idx = relicSuccess.NextIdx;
+
+ intSuccess = Savekeeper.Parse(
+ RunSaveValues.PlayerHealth.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!intSuccess.Success)
+ return false;
+ PlayerStats.CurrentHealth = intSuccess.Value;
+ idx = intSuccess.NextIdx;
+
+ intSuccess = Savekeeper.Parse(
+ RunSaveValues.Money.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!intSuccess.Success)
+ return false;
+ PlayerStats.Money = intSuccess.Value;
+ idx = intSuccess.NextIdx;
+
+ intSuccess = Savekeeper.Parse(
+ RunSaveValues.Shortcuts.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!intSuccess.Success)
+ return false;
+ PlayerStats.Shortcuts = intSuccess.Value;
+ idx = intSuccess.NextIdx;
+
+ intSuccess = Savekeeper.Parse(
+ RunSaveValues.PlayerMaxCombo.ToString(),
+ loadRun,
+ idx,
+ int.TryParse
+ );
+ if (!intSuccess.Success)
+ return false;
+ PlayerStats.MaxComboBar = intSuccess.Value;
+
+ return true;
}
#endregion
diff --git a/Globals/Translations/Translations.csv b/Globals/Translations/Translations.csv
index 47d68594..2160ce13 100644
--- a/Globals/Translations/Translations.csv
+++ b/Globals/Translations/Translations.csv
@@ -206,4 +206,22 @@ CREDITS_ADDITIONAL_ART,Art,额外美术
CREDITS_ADDITIONAL_HELP,Additional Help,额外协助
CREDITS_OTHER_PLAYTESTERS,All our other playtesters,以及所有其他测试玩家
CREDITS_SPECIAL_THANKS,Special Thanks,特别鸣谢
-CREDITS_THANKS_PLAYERS,And to you the player!,以及屏幕前的你!
\ No newline at end of file
+CREDITS_THANKS_PLAYERS,And to you the player!,以及屏幕前的你!
+BOSSBLOOD_EFFECT1,"Heal and convert an empty note into a Boss Blood note.","每循环治疗 点生命,并将一个普通音符转化为血之音符。"
+CYBERFOX_EFFECT1,Gains one dodge.,获得一次闪避。
+EFFIGY_EFFECT1,"Only takes 1 damage at a time, and reflects 1 damage to the player.",每次仅受到1点伤害,并向玩家反弹1点伤害。
+EFFIGY_EFFECT2,Sets player's health to 1 if defeated.,被击败时将玩家的生命值设为1。
+HOLOGRAEME_EFFECT1,"Player only plays every other loop with notes hidden. Learn the pattern!",玩家每隔一次循环才能进行游戏,且期间音符会被隐藏。记住这个模式!
+HOLOGRAEME_EFFECT2,"Holograeme only has 1 health, and is immune to relic damage.","全息格莱姆仅有1点生命值,且免疫遗物伤害。"
+KEYTHULU_EFFECT1,"Every loop mindcrush ticks down by one, when it reaches zero, the player is defeated.","每次循环,心灵碾压计数会减1。当计数归零时,玩家即被击败。"
+LWS_EFFECT1,Converts an empty note into a Lesser Wolf Snake note.,将一个基础音符转化为次级狼蛇音符。
+PARASIFLY_EFFECT1,All enemies gains block the first time fatal damage is dealt.,首次受到致命伤害时,所有敌人获得格挡。
+STRAWMAN_EFFECT1,Player cannot take fatal damage.,玩家不会受到致命伤害。
+GWS_EFFECT1,Adds Great Wolf Snake notes after the first loop.,首次循环后,添加高级狼蛇音符。
+TURTLE_EFFECT1,Gains one block and drains some of player's charge to heal.,获得一层格挡,并消耗玩家部分充能以进行治疗。
+GWS_NOTE_DESCRIPTION,"If timed badly, deals high damage to the player. Damage increases each loop.","若时机不佳,则对玩家造成高额伤害,且伤害随每次循环递增。"
+PARASIFLY_NOTE_DESCRIPTION,Parasifly gains block if timed badly.,若时机不佳,寄生蝇获得格挡。
+BOSSBLOOD_NOTE_DESCRIPTION,Boss Blood heals if timed badly.,若时机不佳,首领之血会进行治疗。
+SPIDER_NOTE_DESCRIPTION,Player gets poisoned if timed badly.,若时机不佳,玩家会中毒。
+LWS_NOTE_DESCRIPTION,"If timed badly, deals damage to the player. Damage increases each loop.","若时机不佳,则对玩家造成伤害,且伤害随每次循环递增。"
+MUSHROOM_NOTE_DESCRIPTION,Player gets poisoned if timed badly.,若时机不佳,玩家会中毒。
\ No newline at end of file
diff --git a/Scenes/BattleDirector/Assets/BattleEndSymbol.png b/Scenes/BattleDirector/Assets/BattleEndSymbol.png
new file mode 100644
index 00000000..a4beda98
Binary files /dev/null and b/Scenes/BattleDirector/Assets/BattleEndSymbol.png differ
diff --git a/Scenes/BattleDirector/Assets/BattleEndSymbol.png.import b/Scenes/BattleDirector/Assets/BattleEndSymbol.png.import
new file mode 100644
index 00000000..0ca42b8d
--- /dev/null
+++ b/Scenes/BattleDirector/Assets/BattleEndSymbol.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmw2tkpmv22ew"
+path="res://.godot/imported/BattleEndSymbol.png-5c282cbc73fc3f3efd9a51295ca4bc9d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Scenes/BattleDirector/Assets/BattleEndSymbol.png"
+dest_files=["res://.godot/imported/BattleEndSymbol.png-5c282cbc73fc3f3efd9a51295ca4bc9d.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/BattleStartSymbol.png b/Scenes/BattleDirector/Assets/BattleStartSymbol.png
new file mode 100644
index 00000000..84a88910
Binary files /dev/null and b/Scenes/BattleDirector/Assets/BattleStartSymbol.png differ
diff --git a/Scenes/BattleDirector/Assets/BattleStartSymbol.png.import b/Scenes/BattleDirector/Assets/BattleStartSymbol.png.import
new file mode 100644
index 00000000..8bf06276
--- /dev/null
+++ b/Scenes/BattleDirector/Assets/BattleStartSymbol.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5r2idc5x21ro"
+path="res://.godot/imported/BattleStartSymbol.png-a8cb230df43bb6721a2442dfafc1c8b6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Scenes/BattleDirector/Assets/BattleStartSymbol.png"
+dest_files=["res://.godot/imported/BattleStartSymbol.png-a8cb230df43bb6721a2442dfafc1c8b6.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/DamageInstanceSymbol.png b/Scenes/BattleDirector/Assets/DamageInstanceSymbol.png
new file mode 100644
index 00000000..506c2239
Binary files /dev/null and b/Scenes/BattleDirector/Assets/DamageInstanceSymbol.png differ
diff --git a/Scenes/BattleDirector/Assets/DamageInstanceSymbol.png.import b/Scenes/BattleDirector/Assets/DamageInstanceSymbol.png.import
new file mode 100644
index 00000000..a6a8bb92
--- /dev/null
+++ b/Scenes/BattleDirector/Assets/DamageInstanceSymbol.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dosjbiqlrb3cr"
+path="res://.godot/imported/DamageInstanceSymbol.png-9cd27c768e2ba03d6c47f5ac093aa1d8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Scenes/BattleDirector/Assets/DamageInstanceSymbol.png"
+dest_files=["res://.godot/imported/DamageInstanceSymbol.png-9cd27c768e2ba03d6c47f5ac093aa1d8.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/LoopSymbol.png b/Scenes/BattleDirector/Assets/LoopSymbol.png
new file mode 100644
index 00000000..8f1265e3
Binary files /dev/null and b/Scenes/BattleDirector/Assets/LoopSymbol.png differ
diff --git a/Scenes/BattleDirector/Assets/LoopSymbol.png.import b/Scenes/BattleDirector/Assets/LoopSymbol.png.import
new file mode 100644
index 00000000..a7522f00
--- /dev/null
+++ b/Scenes/BattleDirector/Assets/LoopSymbol.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://r7oc2aa06s24"
+path="res://.godot/imported/LoopSymbol.png-4d5a564a81d7d7d2248da6ec1ceb0061.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Scenes/BattleDirector/Assets/LoopSymbol.png"
+dest_files=["res://.godot/imported/LoopSymbol.png-4d5a564a81d7d7d2248da6ec1ceb0061.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/BattleScene.tscn b/Scenes/BattleDirector/BattleScene.tscn
index 2e60464c..3dea38a2 100644
--- a/Scenes/BattleDirector/BattleScene.tscn
+++ b/Scenes/BattleDirector/BattleScene.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=13 format=3 uid="uid://b0mrgr7h0ty1y"]
+[gd_scene load_steps=14 format=3 uid="uid://b0mrgr7h0ty1y"]
[ext_resource type="Script" uid="uid://bttu0wmy2fp64" path="res://Scenes/BattleDirector/Scripts/BattleDirector.cs" id="1_jmdo1"]
[ext_resource type="Script" uid="uid://pl57giqyhckb" path="res://Scenes/UI/Scripts/MenuModule.cs" id="2_ka0ws"]
@@ -10,6 +10,7 @@
[ext_resource type="Script" uid="uid://cp6t6haqyef7o" path="res://Scenes/AreaBasedBackground.cs" id="7_6k2qj"]
[ext_resource type="Texture2D" uid="uid://dbjotl0v1ymia" path="res://SharedAssets/BattleFrame1.png" id="7_klvil"]
[ext_resource type="Theme" uid="uid://d37e3tpsbxwak" path="res://Scenes/UI/Assets/GeneralTheme.tres" id="8_62qim"]
+[ext_resource type="PackedScene" uid="uid://bejjkejyeffek" path="res://Scenes/BattleDirector/EnemyDescriptions.tscn" id="11_gbbxr"]
[sub_resource type="Gradient" id="Gradient_8uy3a"]
offsets = PackedFloat32Array(0, 0.766234, 1)
@@ -20,7 +21,7 @@ gradient = SubResource("Gradient_8uy3a")
fill_from = Vector2(1, 0)
fill_to = Vector2(0.738532, 1)
-[node name="ProtoBattleDirector" type="Node2D" node_paths=PackedStringArray("PuppetMarkers", "_countdownLabel", "CD", "CM", "DW", "NPB", "Audio", "FocusedButton")]
+[node name="ProtoBattleDirector" type="Node2D" node_paths=PackedStringArray("PuppetMarkers", "_countdownLabel", "CD", "CM", "DW", "NPB", "Audio", "Descriptions", "FocusedButton")]
process_mode = 1
script = ExtResource("1_jmdo1")
PuppetMarkers = [NodePath("PlayerMarker"), NodePath("Enemy1Marker"), NodePath("Enemy2Marker"), NodePath("Enemy3Marker")]
@@ -30,6 +31,7 @@ CM = NodePath("VPContainer")
DW = NodePath("3D/SubViewport/3DWizard")
NPB = NodePath("NotePlacementBar")
Audio = NodePath("AudioStreamPlayer")
+Descriptions = NodePath("EnemyDescriptions")
FocusedButton = NodePath("StartButton")
metadata/_edit_lock_ = true
@@ -100,10 +102,10 @@ offset_bottom = 360.0
texture = ExtResource("7_klvil")
[node name="StartButton" type="Button" parent="."]
-offset_left = 241.0
-offset_top = 230.0
-offset_right = 443.0
-offset_bottom = 267.0
+offset_left = 190.0
+offset_top = 200.0
+offset_right = 450.0
+offset_bottom = 248.0
theme = ExtResource("8_62qim")
text = "BATTLE_ROOM_BEGIN_BUTTON"
@@ -142,3 +144,14 @@ theme_override_constants/shadow_offset_y = 3
theme_override_constants/shadow_outline_size = 3
theme_override_font_sizes/font_size = 64
text = "5"
+
+[node name="EnemyDescriptions" parent="." instance=ExtResource("11_gbbxr")]
+anchors_preset = 0
+anchor_right = 0.0
+anchor_bottom = 0.0
+offset_left = 320.0
+offset_top = 183.0
+offset_right = 320.0
+offset_bottom = 183.0
+grow_horizontal = 1
+grow_vertical = 1
diff --git a/Scenes/BattleDirector/EnemyDescriptions.tscn b/Scenes/BattleDirector/EnemyDescriptions.tscn
new file mode 100644
index 00000000..31a82b3f
--- /dev/null
+++ b/Scenes/BattleDirector/EnemyDescriptions.tscn
@@ -0,0 +1,50 @@
+[gd_scene load_steps=3 format=3 uid="uid://bejjkejyeffek"]
+
+[ext_resource type="Script" uid="uid://da8no3g3kbob7" path="res://Scenes/BattleDirector/Scripts/EnemyDescriptions.cs" id="1_r3tcc"]
+[ext_resource type="Texture2D" uid="uid://djd6iw2g84bba" path="res://Scenes/UI/Assets/UI_CenterFrame.png" id="2_mg2uj"]
+
+[node name="EnemyDescriptions" type="Control" node_paths=PackedStringArray("DescriptionsContainer")]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_r3tcc")
+DescriptionsContainer = NodePath("Center/MarginContainer/MarginContainer/VBoxContainer")
+
+[node name="Center" type="CenterContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="Center"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/margin_top = 230
+
+[node name="UiCenterFrame" type="NinePatchRect" parent="Center/MarginContainer"]
+custom_minimum_size = Vector2(280, 80)
+layout_mode = 2
+texture = ExtResource("2_mg2uj")
+patch_margin_left = 12
+patch_margin_top = 12
+patch_margin_right = 12
+patch_margin_bottom = 12
+
+[node name="MarginContainer" type="MarginContainer" parent="Center/MarginContainer"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 6
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 6
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Center/MarginContainer/MarginContainer"]
+layout_mode = 2
+mouse_filter = 2
diff --git a/Scenes/BattleDirector/Scripts/BattleDirector.cs b/Scenes/BattleDirector/Scripts/BattleDirector.cs
index 19b14d52..032b3f2f 100644
--- a/Scenes/BattleDirector/Scripts/BattleDirector.cs
+++ b/Scenes/BattleDirector/Scripts/BattleDirector.cs
@@ -34,6 +34,9 @@ public partial class BattleDirector : Node2D
[Export]
private AudioStreamPlayer Audio;
+ [Export]
+ public EnemyDescriptions Descriptions;
+
[Export]
public Button FocusedButton; //Initial start button
@@ -139,6 +142,7 @@ public override void _Ready()
{
FocusedButton.QueueFree();
FocusedButton = null;
+ Descriptions.QueueFree();
StartCountdown();
};
@@ -201,6 +205,7 @@ private void InitEnemies()
_enemies[i] = enemy;
AddEnemyEffects(enemy);
}
+ Descriptions.Setup(_enemies[0]);
}
public override void _Process(double delta)
@@ -236,9 +241,9 @@ private void UpdateBeat(Beat beat)
#region Input&Timing
public override void _UnhandledInput(InputEvent @event)
{
+ return;
if (@event is InputEventKey eventKey && eventKey.Pressed && !eventKey.Echo)
{
- return;
if (eventKey.Keycode == Key.Key0)
{
DebugKillEnemy();
@@ -389,7 +394,7 @@ private void OnBattleLost()
return;
}
Audio.StreamPaused = true;
- SaveSystem.ClearSave();
+ Savekeeper.ClearRun();
AddChild(GD.Load(EndScreen.LoadPath).Instantiate());
ProcessMode = ProcessModeEnum.Disabled;
}
diff --git a/Scenes/BattleDirector/Scripts/EnemyDescriptions.cs b/Scenes/BattleDirector/Scripts/EnemyDescriptions.cs
new file mode 100644
index 00000000..880ac793
--- /dev/null
+++ b/Scenes/BattleDirector/Scripts/EnemyDescriptions.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using FunkEngine;
+using Godot;
+
+public partial class EnemyDescriptions : Control
+{
+ [Export]
+ private VBoxContainer DescriptionsContainer;
+
+ private const string _loopIconPath = "res://Scenes/BattleDirector/Assets/LoopSymbol.png";
+ private const string _damageInstanceIconPath =
+ "res://Scenes/BattleDirector/Assets/DamageInstanceSymbol.png";
+ private const string _battleStartIconPath =
+ "res://Scenes/BattleDirector/Assets/BattleStartSymbol.png";
+ private const string _battleEndIconPath =
+ "res://Scenes/BattleDirector/Assets/BattleEndSymbol.png";
+
+ private bool _isVisible = false;
+ private const string TranslationKeySuffix = "_NOTE_DESCRIPTION";
+
+ public void Setup(EnemyPuppet enemy)
+ {
+ if (enemy.InitialNote.Amount > 0)
+ {
+ string desc = NoteDescBuilder(Scribe.NoteDictionary[enemy.InitialNote.NoteId].Name);
+ AddDescriptionRow(Scribe.NoteDictionary[enemy.InitialNote.NoteId].Texture, desc);
+ _isVisible = true;
+ }
+
+ foreach (var effect in enemy.GetBattleEvents())
+ {
+ if (effect.Description == null)
+ continue;
+ Texture2D icon = GetTriggerIcon(effect.GetTrigger());
+ AddDescriptionRow(icon, effect.Description);
+ _isVisible = true;
+ }
+
+ Visible = _isVisible;
+ }
+
+ private void AddDescriptionRow(Texture2D iconTexture, string text)
+ {
+ HBoxContainer hbox = new HBoxContainer();
+
+ TextureRect icon = new TextureRect();
+ icon.Texture = iconTexture;
+ icon.StretchMode = TextureRect.StretchModeEnum.Keep;
+
+ Label desc = new Label();
+ desc.Text = text;
+ desc.SizeFlagsHorizontal = SizeFlags.ExpandFill;
+ desc.AutowrapMode = TextServer.AutowrapMode.WordSmart;
+
+ hbox.AddChild(icon);
+ hbox.AddChild(desc);
+ DescriptionsContainer.AddChild(hbox);
+ }
+
+ private string NoteDescBuilder(string noteName)
+ {
+ return noteName.ToUpper() + TranslationKeySuffix;
+ }
+
+ private Texture2D GetTriggerIcon(BattleEffectTrigger trigger)
+ {
+ //TODO: add more as we get more enemy effect triggers
+ return trigger switch
+ {
+ BattleEffectTrigger.OnLoop => GD.Load(_loopIconPath),
+ BattleEffectTrigger.OnDamageInstance => GD.Load(_damageInstanceIconPath),
+ BattleEffectTrigger.OnBattleStart => GD.Load(_battleStartIconPath),
+ BattleEffectTrigger.OnBattleEnd => GD.Load(_battleEndIconPath),
+ _ => null,
+ };
+ }
+}
diff --git a/Scenes/BattleDirector/Scripts/EnemyDescriptions.cs.uid b/Scenes/BattleDirector/Scripts/EnemyDescriptions.cs.uid
new file mode 100644
index 00000000..3a488050
--- /dev/null
+++ b/Scenes/BattleDirector/Scripts/EnemyDescriptions.cs.uid
@@ -0,0 +1 @@
+uid://da8no3g3kbob7
diff --git a/Scenes/BattleDirector/Scripts/NotePlacementBar.cs b/Scenes/BattleDirector/Scripts/NotePlacementBar.cs
index d69e5314..94c9ccff 100644
--- a/Scenes/BattleDirector/Scripts/NotePlacementBar.cs
+++ b/Scenes/BattleDirector/Scripts/NotePlacementBar.cs
@@ -230,8 +230,8 @@ public Note NotePlaced()
{
if (!CanPlaceNote())
GD.PushWarning("Note is attempting placement without a full bar!");
- string inputType = SaveSystem
- .GetConfigValue(SaveSystem.ConfigSettings.InputType)
+ string inputType = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.InputType)
.ToString();
Note placedNote = GetNote(Input.IsActionPressed(inputType + "_secondaryPlacement"));
CurrentBarValue -= placedNote.CostModifier * MaxValue;
diff --git a/Scenes/BattleDirector/Tutorial/Toriel.cs b/Scenes/BattleDirector/Tutorial/Toriel.cs
index 1ab5d190..bc4ed1ce 100644
--- a/Scenes/BattleDirector/Tutorial/Toriel.cs
+++ b/Scenes/BattleDirector/Tutorial/Toriel.cs
@@ -58,7 +58,9 @@ public override void _Process(double delta)
_nextButton?.GrabFocus();
}
UpdateInputSprites();
- string scheme = SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.InputType).As();
+ string scheme = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.InputType)
+ .As();
if (_waitingForPlace && Input.IsActionPressed(scheme + "_arrowRight"))
{
GetViewport().SetInputAsHandled();
@@ -68,7 +70,9 @@ public override void _Process(double delta)
private void UpdateInputSprites()
{
- string prefix = SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.InputType).ToString();
+ string prefix = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.InputType)
+ .ToString();
_inputSprites[0].Texture = GD.Load(
ControlSettings.GetTextureForInput(prefix + "_arrowUp")
);
@@ -337,6 +341,7 @@ public void OnPlaceDialogue4()
public void BossDialogue()
{
_dialogueBox.Visible = true;
+ _currentDirector.Descriptions.Visible = false;
GetTree().SetPause(true);
_currentDirector.FocusedButton.Visible = false;
_dialogueLabel.Text = Tr("TUTORIAL_BOSS");
@@ -351,6 +356,7 @@ public void BossDialogueReady()
GetTree().SetPause(false);
_currentDirector.FocusedButton.Visible = true;
_currentDirector.FocusedButton.GrabFocus();
+ _currentDirector.Descriptions.Visible = true;
}
#endregion
}
diff --git a/Scenes/CustomSong/CustomScore.cs b/Scenes/CustomSong/CustomScore.cs
index 83a1c8af..99773c1c 100644
--- a/Scenes/CustomSong/CustomScore.cs
+++ b/Scenes/CustomSong/CustomScore.cs
@@ -28,9 +28,12 @@ private enum ScoringVals
public delegate void FinishedHandler();
public event FinishedHandler Finished;
+ private BattleDirector.Harbinger.NoteHitHandler _noteHitListener;
+ private BattleDirector.Harbinger.NotePlacedHandler _notePlacedListener;
+
public void ListenToDirector()
{
- BattleDirector.Harbinger.Instance.NoteHit += e =>
+ _noteHitListener = e =>
{
if (e is not BattleDirector.Harbinger.NoteHitArgs nArgs)
return;
@@ -44,10 +47,19 @@ public void ListenToDirector()
break;
}
};
- BattleDirector.Harbinger.Instance.NotePlaced += _ =>
+ _notePlacedListener = _ =>
{
score[(int)ScoringVals.NotesPlaced] += 1;
};
+
+ BattleDirector.Harbinger.Instance.NotePlaced += _notePlacedListener;
+ BattleDirector.Harbinger.Instance.NoteHit += _noteHitListener;
+ }
+
+ public override void _ExitTree()
+ {
+ BattleDirector.Harbinger.Instance.NotePlaced -= _notePlacedListener;
+ BattleDirector.Harbinger.Instance.NoteHit -= _noteHitListener;
}
public CustomScore ShowResults(BattleDirector battleDirector, float enemyPercent)
diff --git a/Scenes/EventScene/EventScene.cs b/Scenes/EventScene/EventScene.cs
index 287995c2..2de1aa6e 100644
--- a/Scenes/EventScene/EventScene.cs
+++ b/Scenes/EventScene/EventScene.cs
@@ -26,7 +26,7 @@ public partial class EventScene : Node
[Export]
private MarginContainer _continueContainer;
- public static List EventPool;
+ public static List EventPool = [];
private static readonly Theme ButtonTheme = GD.Load(
"res://Scenes/UI/Assets/GeneralTheme.tres"
diff --git a/Scenes/Maps/Scripts/Cartographer.cs b/Scenes/Maps/Scripts/Cartographer.cs
index 66e421d4..00dddba2 100644
--- a/Scenes/Maps/Scripts/Cartographer.cs
+++ b/Scenes/Maps/Scripts/Cartographer.cs
@@ -38,7 +38,7 @@ public partial class Cartographer : Node2D
public override void _Ready()
{
DrawMap();
- SaveSystem.SaveGame();
+ Savekeeper.RecordSave();
if (
StageProducer.GetCurRoom().Type == Stages.Boss
&& StageProducer.GetCurRoom().Children.Length == 0
@@ -74,10 +74,10 @@ public override void _Process(double delta)
public override void _EnterTree()
{
BgAudioPlayer.LiveInstance.PlayLevelMusic();
- if (!SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.FirstTime).AsBool())
+ if (StageProducer.GetPersistantVal(StageProducer.PersistKeys.TutorialDone) == 1)
return;
BattleDirector.VerticalScroll = true;
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.VerticalScroll, true);
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.VerticalScroll, true);
}
private Vector2 GetPosition(int x, int y)
diff --git a/Scenes/NoteManager/Assets/NoteWIthOutline.png b/Scenes/NoteManager/Assets/NoteWithOutline.png
similarity index 100%
rename from Scenes/NoteManager/Assets/NoteWIthOutline.png
rename to Scenes/NoteManager/Assets/NoteWithOutline.png
diff --git a/Scenes/NoteManager/Assets/NoteWIthOutline.png.import b/Scenes/NoteManager/Assets/NoteWithOutline.png.import
similarity index 69%
rename from Scenes/NoteManager/Assets/NoteWIthOutline.png.import
rename to Scenes/NoteManager/Assets/NoteWithOutline.png.import
index e9bef490..caa50296 100644
--- a/Scenes/NoteManager/Assets/NoteWIthOutline.png.import
+++ b/Scenes/NoteManager/Assets/NoteWithOutline.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://coav3xvksq4jy"
-path="res://.godot/imported/NoteWIthOutline.png-00b7213af7b9958e4dcd747d842ce073.ctex"
+path="res://.godot/imported/NoteWithOutline.png-fd6e84a72270a62aa6b38d41c0180c0f.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://Scenes/NoteManager/Assets/NoteWIthOutline.png"
-dest_files=["res://.godot/imported/NoteWIthOutline.png-00b7213af7b9958e4dcd747d842ce073.ctex"]
+source_file="res://Scenes/NoteManager/Assets/NoteWithOutline.png"
+dest_files=["res://.godot/imported/NoteWithOutline.png-fd6e84a72270a62aa6b38d41c0180c0f.ctex"]
[params]
diff --git a/Scenes/NoteManager/Scripts/InputHandler.cs b/Scenes/NoteManager/Scripts/InputHandler.cs
index c61dfdb2..9d927e89 100644
--- a/Scenes/NoteManager/Scripts/InputHandler.cs
+++ b/Scenes/NoteManager/Scripts/InputHandler.cs
@@ -65,10 +65,12 @@ public override void _Ready()
public override void _Process(double delta)
{
//TODO: Add change control scheme signal, so we don't query each frame.
- string scheme = SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.InputType).As();
+ string scheme = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.InputType)
+ .As();
if (Input.GetConnectedJoypads().Count <= 0 && scheme == "CONTROLLER")
{
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.InputType, "WASD");
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.InputType, "WASD");
}
if (BattleDirector.PlayerDisabled)
@@ -92,7 +94,7 @@ public override void _UnhandledInput(InputEvent @event)
{
if (@event is InputEventJoypadButton)
{ //Force Controller if controller was pressed
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.InputType, "CONTROLLER");
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.InputType, "CONTROLLER");
}
}
@@ -127,35 +129,16 @@ private void UpdateArrowSprites()
{
if (!UseArrows)
return;
- Arrows[(int)ArrowType.Left].Node.Texture = GD.Load(
- ArrowFolderPath + "New_Arrow.png"
- );
- Arrows[(int)ArrowType.Left].Node.Outline.Texture = GD.Load(
- ArrowFolderPath + "Arrow_Outline.png"
- );
- Arrows[(int)ArrowType.Left].Node.RotationDegrees = 180f;
+ Texture2D arrowTexture = GD.Load(ArrowFolderPath + "New_Arrow.png");
+ Texture2D outlineTexture = GD.Load(ArrowFolderPath + "Arrow_Outline.png");
+ float[] rotations = [270f, 90f, 180f]; //Up, down, left
- Arrows[(int)ArrowType.Up].Node.Texture = GD.Load(
- ArrowFolderPath + "New_Arrow.png"
- );
- Arrows[(int)ArrowType.Up].Node.Outline.Texture = GD.Load(
- ArrowFolderPath + "Arrow_Outline.png"
- );
- Arrows[(int)ArrowType.Up].Node.RotationDegrees = 270f;
-
- Arrows[(int)ArrowType.Down].Node.Texture = GD.Load(
- ArrowFolderPath + "New_Arrow.png"
- );
- Arrows[(int)ArrowType.Down].Node.Outline.Texture = GD.Load(
- ArrowFolderPath + "Arrow_Outline.png"
- );
- Arrows[(int)ArrowType.Down].Node.RotationDegrees = 90f;
-
- Arrows[(int)ArrowType.Right].Node.Texture = GD.Load(
- ArrowFolderPath + "New_Arrow.png"
- );
- Arrows[(int)ArrowType.Right].Node.Outline.Texture = GD.Load(
- ArrowFolderPath + "Arrow_Outline.png"
- );
+ for (int i = 0; i < Arrows.Length; i++)
+ {
+ Arrows[i].Node.Texture = arrowTexture;
+ Arrows[i].Node.Outline.Texture = outlineTexture;
+ if (i < rotations.Length)
+ Arrows[i].Node.RotationDegrees = rotations[i];
+ }
}
}
diff --git a/Scenes/Puppets/Enemies/BossBlood/P_BossBlood.cs b/Scenes/Puppets/Enemies/BossBlood/P_BossBlood.cs
index 9198c3b9..0fc7033e 100644
--- a/Scenes/Puppets/Enemies/BossBlood/P_BossBlood.cs
+++ b/Scenes/Puppets/Enemies/BossBlood/P_BossBlood.cs
@@ -31,13 +31,14 @@ public override void _Ready()
{
eff.Owner.Heal(val);
e.BD.RandApplyNote(eff.Owner, 14, 1);
- }
+ },
+ "BOSSBLOOD_EFFECT1"
),
new EnemyEffect(
this,
BattleEffectTrigger.OnDamageInstance,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
if (e is not BattleDirector.Harbinger.OnDamageInstanceArgs dArgs)
return;
diff --git a/Scenes/Puppets/Enemies/CyberFox/P_CyberFox.cs b/Scenes/Puppets/Enemies/CyberFox/P_CyberFox.cs
index 985adf80..ea08af13 100644
--- a/Scenes/Puppets/Enemies/CyberFox/P_CyberFox.cs
+++ b/Scenes/Puppets/Enemies/CyberFox/P_CyberFox.cs
@@ -36,11 +36,12 @@ public override void _Ready()
this,
BattleEffectTrigger.OnLoop,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
e.BD.AddStatus(Targetting.First, StatusEffect.Dodge, 1);
_effectNode.TriggerGlitch(1f);
- }
+ },
+ "CYBERFOX_EFFECT1"
),
};
}
diff --git a/Scenes/Puppets/Enemies/Effigy/P_Effigy.cs b/Scenes/Puppets/Enemies/Effigy/P_Effigy.cs
index fb510cd6..1944bae1 100644
--- a/Scenes/Puppets/Enemies/Effigy/P_Effigy.cs
+++ b/Scenes/Puppets/Enemies/Effigy/P_Effigy.cs
@@ -20,7 +20,7 @@ public override void _Ready()
this,
BattleEffectTrigger.OnDamageInstance,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
if (e is not BattleDirector.Harbinger.OnDamageInstanceArgs dArgs)
return;
@@ -29,22 +29,24 @@ public override void _Ready()
dArgs.Dmg.ModifyDamage(-dArgs.Dmg.Damage + 1);
if (dArgs.Dmg.Source != null)
dArgs.Dmg.Source.TakeDamage(new DamageInstance(1, null, dArgs.Dmg.Source));
- }
+ },
+ "EFFIGY_EFFECT1"
),
new EnemyEffect(
this,
BattleEffectTrigger.OnBattleEnd,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
e.BD.DealDamage(Targetting.Player, e.BD.Player.GetCurrentHealth() - 1, null);
- }
+ },
+ "EFFIGY_EFFECT2"
),
new EnemyEffect(
this,
BattleEffectTrigger.OnBattleStart,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
_tutorialInstance = Toriel.AttachNewToriel(e.BD);
_tutorialInstance.BossDialogue();
diff --git a/Scenes/Puppets/Enemies/EnemyEffect.cs b/Scenes/Puppets/Enemies/EnemyEffect.cs
index 8fb1bc1d..6bd8724f 100644
--- a/Scenes/Puppets/Enemies/EnemyEffect.cs
+++ b/Scenes/Puppets/Enemies/EnemyEffect.cs
@@ -8,12 +8,14 @@ public class EnemyEffect : IBattleEvent
private int _baseValue;
public int Value;
private Action _onEnemyEffect;
+ public string Description { get; private set; }
public EnemyEffect(
EnemyPuppet owner,
BattleEffectTrigger trigger,
int val,
- Action onEnemyEffect
+ Action onEnemyEffect,
+ string description = null
)
{
Owner = owner;
@@ -21,6 +23,7 @@ Action onEnemyEffect
Value = _baseValue;
Trigger = trigger;
_onEnemyEffect = onEnemyEffect;
+ Description = description;
}
public void OnTrigger(BattleEventArgs e)
diff --git a/Scenes/Puppets/Enemies/Holograeme/P_Holograeme.cs b/Scenes/Puppets/Enemies/Holograeme/P_Holograeme.cs
index 468906b9..988b630c 100644
--- a/Scenes/Puppets/Enemies/Holograeme/P_Holograeme.cs
+++ b/Scenes/Puppets/Enemies/Holograeme/P_Holograeme.cs
@@ -46,7 +46,8 @@ public override void _Ready()
BattleDirector.PlayerDisabled = true;
BattleDirector.VerticalScrollRotation = 0f;
e.BD.AddStatus(Targetting.Player, StatusEffect.Disable);
- }
+ },
+ "HOLOGRAEME_EFFECT1"
),
new EnemyEffect(
this,
@@ -98,7 +99,8 @@ e is not BattleDirector.Harbinger.OnDamageInstanceArgs dArgs
{
dArgs.Dmg.ModifyDamage(0, 0);
}
- }
+ },
+ "HOLOGRAEME_EFFECT2"
),
};
}
diff --git a/Scenes/Puppets/Enemies/Keythulu/P_Keythulu.cs b/Scenes/Puppets/Enemies/Keythulu/P_Keythulu.cs
index fb91b3ac..b22e5c0e 100644
--- a/Scenes/Puppets/Enemies/Keythulu/P_Keythulu.cs
+++ b/Scenes/Puppets/Enemies/Keythulu/P_Keythulu.cs
@@ -27,22 +27,25 @@ public override void _Ready()
enemTween.SetLoops();
enemTween.Play();
+ const int effect1Val = 6;
+
BattleEvents = new EnemyEffect[]
{
new EnemyEffect(
this,
BattleEffectTrigger.OnBattleStart,
- 6,
+ effect1Val,
(e, eff, val) =>
{
e.BD.AddStatus(Targetting.Player, StatusEffect.MindCrush, val);
- }
+ },
+ "KEYTHULU_EFFECT1"
),
new EnemyEffect(
this,
BattleEffectTrigger.OnLoop,
3,
- (e, eff, val) =>
+ (_, _, _) =>
{
_effectSprite.Position = Vector2.Zero;
_effectSprite.Visible = true;
@@ -63,7 +66,7 @@ public override void _Ready()
this,
BattleEffectTrigger.OnDamageInstance,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
if (
StageProducer.Config.RoomType == Stages.Custom
@@ -76,7 +79,7 @@ public override void _Ready()
)
{
SteamWhisperer.PopAchievement("actTwoComp");
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.HasWon, true);
+ StageProducer.UpdatePersistantValues(StageProducer.PersistKeys.HasWon, 1);
}
}
),
diff --git a/Scenes/Puppets/Enemies/LWS/P_LWS.cs b/Scenes/Puppets/Enemies/LWS/P_LWS.cs
index 4da24ce9..25264cf8 100644
--- a/Scenes/Puppets/Enemies/LWS/P_LWS.cs
+++ b/Scenes/Puppets/Enemies/LWS/P_LWS.cs
@@ -30,7 +30,8 @@ public override void _Ready()
(e, eff, val) =>
{
e.BD.RandApplyNote(eff.Owner, InitialNote.NoteId, val);
- }
+ },
+ "LWS_EFFECT1"
),
};
}
diff --git a/Scenes/Puppets/Enemies/Parasifly/P_Parasifly.cs b/Scenes/Puppets/Enemies/Parasifly/P_Parasifly.cs
index bf27cee0..e3d42576 100644
--- a/Scenes/Puppets/Enemies/Parasifly/P_Parasifly.cs
+++ b/Scenes/Puppets/Enemies/Parasifly/P_Parasifly.cs
@@ -47,7 +47,8 @@ public override void _Ready()
return;
e.BD.AddStatus(Targetting.All, StatusEffect.Block, val);
eff.Value = 0;
- }
+ },
+ "PARASIFLY_EFFECT1"
),
};
}
diff --git a/Scenes/Puppets/Enemies/Strawman/P_Strawman.cs b/Scenes/Puppets/Enemies/Strawman/P_Strawman.cs
index cfb99b03..7e58022e 100644
--- a/Scenes/Puppets/Enemies/Strawman/P_Strawman.cs
+++ b/Scenes/Puppets/Enemies/Strawman/P_Strawman.cs
@@ -22,7 +22,7 @@ public override void _Ready()
this,
BattleEffectTrigger.OnBattleStart,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
_tutorialInstance = Toriel.AttachNewToriel(e.BD);
_tutorialInstance.IntroDialogue();
@@ -32,7 +32,7 @@ public override void _Ready()
this,
BattleEffectTrigger.OnDamageInstance,
2,
- (e, eff, val) =>
+ (e, eff, _) =>
{
if (e is not BattleDirector.Harbinger.OnDamageInstanceArgs dArgs)
return;
@@ -53,7 +53,7 @@ public override void _Ready()
this,
BattleEffectTrigger.OnLoop,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
if (e is not BattleDirector.Harbinger.LoopEventArgs lArgs)
return;
@@ -67,7 +67,7 @@ public override void _Ready()
this,
BattleEffectTrigger.NoteHit,
1,
- (e, eff, val) =>
+ (e, eff, _) =>
{
if (eff.Value == 0)
return;
@@ -83,7 +83,7 @@ public override void _Ready()
this,
BattleEffectTrigger.NotePlaced,
1,
- (e, eff, val) =>
+ (_, _, _) =>
{
_tutorialInstance.CallDeferred(nameof(_tutorialInstance.OnPlaceDialogue1));
}
@@ -92,7 +92,7 @@ public override void _Ready()
this,
BattleEffectTrigger.OnDamageInstance,
1,
- (e, eff, val) =>
+ (e, _, _) =>
{
if (e is not BattleDirector.Harbinger.OnDamageInstanceArgs dArgs)
return;
@@ -101,7 +101,10 @@ public override void _Ready()
&& dArgs.Dmg.Target.GetCurrentHealth() <= dArgs.Dmg.Damage
)
{
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.FirstTime, false);
+ StageProducer.UpdatePersistantValues(
+ StageProducer.PersistKeys.TutorialDone,
+ 1
+ );
SteamWhisperer.PopAchievement("tutorial");
}
}
diff --git a/Scenes/Puppets/Enemies/TheGWS/P_TheGWS.cs b/Scenes/Puppets/Enemies/TheGWS/P_TheGWS.cs
index b119fdbd..ee4830fd 100644
--- a/Scenes/Puppets/Enemies/TheGWS/P_TheGWS.cs
+++ b/Scenes/Puppets/Enemies/TheGWS/P_TheGWS.cs
@@ -54,7 +54,8 @@ public override void _Ready()
eff.Owner
);
eff.Value = 0;
- }
+ },
+ "GWS_EFFECT1"
),
};
}
diff --git a/Scenes/Puppets/Enemies/Turtle/P_Turtle.cs b/Scenes/Puppets/Enemies/Turtle/P_Turtle.cs
index 47ad8089..e2c992bf 100644
--- a/Scenes/Puppets/Enemies/Turtle/P_Turtle.cs
+++ b/Scenes/Puppets/Enemies/Turtle/P_Turtle.cs
@@ -26,16 +26,17 @@ public override void _Ready()
this,
BattleEffectTrigger.OnLoop,
1,
- (e, eff, val) =>
+ (e, _, val) =>
{
// take 1/4th of player's energy, and heal that amount
int quarterEnergy = (int)e.BD.NPB.GetCurrentBarValue() / 4;
e.BD.NPB.IncreaseCharge(-quarterEnergy);
- this.Heal(quarterEnergy);
+ Heal(quarterEnergy);
//gain block based on val
e.BD.AddStatus(Targetting.First, StatusEffect.Block, val);
- }
+ },
+ "TURTLE_EFFECT1"
),
};
}
diff --git a/Scenes/ShopScene/Scripts/ShopScene.cs b/Scenes/ShopScene/Scripts/ShopScene.cs
index d66c9735..672d3e7c 100644
--- a/Scenes/ShopScene/Scripts/ShopScene.cs
+++ b/Scenes/ShopScene/Scripts/ShopScene.cs
@@ -343,7 +343,7 @@ private void RemoveNote()
private bool _hasHealed;
private const int HealCost = 50;
- private int _healAmount = (StageProducer.PlayerStats.MaxHealth / 4);
+ private int HealAmount => (StageProducer.PlayerStats.MaxHealth / 4);
private void UpdateHealButton()
{
@@ -366,7 +366,7 @@ private void TryHeal()
StageProducer.PlayerStats.Money -= HealCost;
_hasHealed = true;
- _player.Heal(_healAmount);
+ _player.Heal(HealAmount);
UpdateHealButton();
UpdateMoneyLabel();
}
diff --git a/Scenes/UI/Options/HowToPlay.tscn b/Scenes/UI/Options/HowToPlay.tscn
index 04ff7993..15652baf 100644
--- a/Scenes/UI/Options/HowToPlay.tscn
+++ b/Scenes/UI/Options/HowToPlay.tscn
@@ -7,7 +7,7 @@
[ext_resource type="Texture2D" uid="uid://cr82n7aojboaw" path="res://Scenes/UI/Assets/UI_CenterFrameLight.png" id="4_4vscx"]
[ext_resource type="Texture2D" uid="uid://caw70lr5e1yiq" path="res://Classes/Notes/Assets/Note_PlayerDouble.png" id="4_m6low"]
[ext_resource type="Texture2D" uid="uid://cdf3g3174du4r" path="res://Classes/Notes/Assets/Note_PlayerHeal.png" id="5_8kiq2"]
-[ext_resource type="Texture2D" uid="uid://coav3xvksq4jy" path="res://Scenes/NoteManager/Assets/NoteWIthOutline.png" id="5_xqve7"]
+[ext_resource type="Texture2D" uid="uid://coav3xvksq4jy" path="res://Scenes/NoteManager/Assets/NoteWithOutline.png" id="5_xqve7"]
[ext_resource type="Texture2D" uid="uid://c3chrsxrulapd" path="res://Classes/Notes/Assets/Note_PlayerBasic.png" id="6_uonw3"]
[ext_resource type="Texture2D" uid="uid://dg0lmu0pip4lr" path="res://Classes/Notes/Assets/Note_PlayerVampire.png" id="7_rbdrm"]
diff --git a/Scenes/UI/Options/Scripts/HowToPlay.cs b/Scenes/UI/Options/Scripts/HowToPlay.cs
index 9fae60c5..e8a0a455 100644
--- a/Scenes/UI/Options/Scripts/HowToPlay.cs
+++ b/Scenes/UI/Options/Scripts/HowToPlay.cs
@@ -46,7 +46,7 @@ public void ReturnToPrev()
private void DoTutorial()
{
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.FirstTime, true);
+ StageProducer.UpdatePersistantValues(StageProducer.PersistKeys.TutorialDone, 0);
StageProducer.LiveInstance.TransitionStage(Stages.Map);
}
diff --git a/Scenes/UI/Options/Scripts/LanguageSelection.cs b/Scenes/UI/Options/Scripts/LanguageSelection.cs
index 5e14c224..e300ccd0 100644
--- a/Scenes/UI/Options/Scripts/LanguageSelection.cs
+++ b/Scenes/UI/Options/Scripts/LanguageSelection.cs
@@ -25,7 +25,7 @@ public void OnLanguageSelected(int index)
private void ChangeLanguage(string language)
{
TranslationServer.SetLocale(language);
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.LanguageKey, language);
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.LanguageKey, language);
}
private void PresetDropdown(string language)
diff --git a/Scenes/UI/Options/Scripts/OptionsMenu.cs b/Scenes/UI/Options/Scripts/OptionsMenu.cs
index 3e8905dd..021b28f1 100644
--- a/Scenes/UI/Options/Scripts/OptionsMenu.cs
+++ b/Scenes/UI/Options/Scripts/OptionsMenu.cs
@@ -44,8 +44,8 @@ public override void _Ready()
AudioServer.GetBusVolumeDb(AudioServer.GetBusIndex("Master"))
);
- _highContrastToggle.ButtonPressed = SaveSystem
- .GetConfigValue(SaveSystem.ConfigSettings.HighContrast)
+ _highContrastToggle.ButtonPressed = Configkeeper
+ .GetConfigValue(Configkeeper.ConfigSettings.HighContrast)
.AsBool();
_volumeSlider.DragEnded += VolumeChanged;
@@ -58,7 +58,7 @@ public override void _Ready()
_titleScreenOptions.Visible =
!StageProducer.IsInitialized
- && !SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.FirstTime).AsBool();
+ && StageProducer.GetPersistantVal(StageProducer.PersistKeys.TutorialDone) == 1;
_noteSpriteToggle.ButtonPressed = InputHandler.UseArrows;
_noteSpriteToggle.Toggled += ArrowSpritesToggled;
_verticalScrollToggle.ButtonPressed = BattleDirector.VerticalScroll;
@@ -118,7 +118,7 @@ private void VolumeChanged(bool valueChanged)
if (!valueChanged)
return;
ChangeVolume((float)_volumeSlider.Value);
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.Volume, _volumeSlider.Value);
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.Volume, _volumeSlider.Value);
}
public static void ChangeVolume(double value)
@@ -132,19 +132,19 @@ public static void ChangeVolume(double value)
private void ArrowSpritesToggled(bool value)
{
InputHandler.UseArrows = value;
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.TypeIsArrow, value);
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.TypeIsArrow, value);
}
private void VerticalScrollToggled(bool value)
{
BattleDirector.VerticalScroll = value;
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.VerticalScroll, value);
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.VerticalScroll, value);
}
private void HighContrastChanged(bool toggled)
{
StageProducer.ContrastFilter.Visible = toggled;
- SaveSystem.UpdateConfig(SaveSystem.ConfigSettings.HighContrast, toggled);
+ Configkeeper.UpdateConfig(Configkeeper.ConfigSettings.HighContrast, toggled);
}
private void OpenHowToPlay()
diff --git a/Scenes/UI/Remapping/ControlSettings.cs b/Scenes/UI/Remapping/ControlSettings.cs
index 5ae9746c..44e9f265 100644
--- a/Scenes/UI/Remapping/ControlSettings.cs
+++ b/Scenes/UI/Remapping/ControlSettings.cs
@@ -151,7 +151,7 @@ public override void _Ready()
}
_remapTabs.CurrentTab =
- SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.InputType).ToString()
+ Configkeeper.GetConfigValue(Configkeeper.ConfigSettings.InputType).ToString()
== KeyboardPrefix
? 0
: 1;
@@ -251,8 +251,8 @@ private void NoNullFocus()
///
private void ChangeInputType()
{
- SaveSystem.UpdateConfig(
- SaveSystem.ConfigSettings.InputType,
+ Configkeeper.UpdateConfig(
+ Configkeeper.ConfigSettings.InputType,
_remapTabs.CurrentTab == 0 ? KeyboardPrefix : JoyPrefix
);
_remapDescription.Text = Tr(_remapTabs.CurrentTab == 0 ? _keyboardRemap : _controllerRemap);
@@ -392,46 +392,49 @@ private void HandleRemapInput(InputEvent @event)
///
private static readonly Dictionary<
string,
- (SaveSystem.ConfigSettings keyboard, SaveSystem.ConfigSettings controller)
+ (Configkeeper.ConfigSettings keyboard, Configkeeper.ConfigSettings controller)
> ConfigMap = new()
{
{
"_arrowUp",
- (SaveSystem.ConfigSettings.InputKeyboardUp, SaveSystem.ConfigSettings.InputControllerUp)
+ (
+ Configkeeper.ConfigSettings.InputKeyboardUp,
+ Configkeeper.ConfigSettings.InputControllerUp
+ )
},
{
"_arrowDown",
(
- SaveSystem.ConfigSettings.InputKeyboardDown,
- SaveSystem.ConfigSettings.InputControllerDown
+ Configkeeper.ConfigSettings.InputKeyboardDown,
+ Configkeeper.ConfigSettings.InputControllerDown
)
},
{
"_arrowLeft",
(
- SaveSystem.ConfigSettings.InputKeyboardLeft,
- SaveSystem.ConfigSettings.InputControllerLeft
+ Configkeeper.ConfigSettings.InputKeyboardLeft,
+ Configkeeper.ConfigSettings.InputControllerLeft
)
},
{
"_arrowRight",
(
- SaveSystem.ConfigSettings.InputKeyboardRight,
- SaveSystem.ConfigSettings.InputControllerRight
+ Configkeeper.ConfigSettings.InputKeyboardRight,
+ Configkeeper.ConfigSettings.InputControllerRight
)
},
{
"_secondaryPlacement",
(
- SaveSystem.ConfigSettings.InputKeyboardSecondary,
- SaveSystem.ConfigSettings.InputControllerSecondary
+ Configkeeper.ConfigSettings.InputKeyboardSecondary,
+ Configkeeper.ConfigSettings.InputControllerSecondary
)
},
{
"_inventory",
(
- SaveSystem.ConfigSettings.InputKeyboardInventory,
- SaveSystem.ConfigSettings.InputControllerInventory
+ Configkeeper.ConfigSettings.InputKeyboardInventory,
+ Configkeeper.ConfigSettings.InputControllerInventory
)
},
};
@@ -474,7 +477,7 @@ private void SaveKeyInput(string button, InputEvent key)
return;
var config = key is InputEventKey ? configPair.keyboard : configPair.controller;
- SaveSystem.UpdateConfig(config, keycode);
+ Configkeeper.UpdateConfig(config, keycode);
}
///
diff --git a/Scenes/UI/TitleScreen/Scripts/TitleScreen.cs b/Scenes/UI/TitleScreen/Scripts/TitleScreen.cs
index b6eca91a..d666b943 100644
--- a/Scenes/UI/TitleScreen/Scripts/TitleScreen.cs
+++ b/Scenes/UI/TitleScreen/Scripts/TitleScreen.cs
@@ -30,8 +30,8 @@ public override void _UnhandledInput(InputEvent @event)
if (eventKey.Keycode == Key.Key0)
{
SteamWhisperer.ResetAll();
- SaveSystem.ClearSave();
- SaveSystem.ClearConfig();
+ Savekeeper.ClearRun();
+ Configkeeper.ClearConfig();
StageProducer.LiveInstance.InitFromCfg();
}
}
@@ -48,8 +48,8 @@ public override void _Ready()
{
if (StageProducer.LiveInstance.LastStage == Stages.Custom)
OpenCustomSelection();
- _customSelectionButton.Visible = (bool)
- SaveSystem.GetConfigValue(SaveSystem.ConfigSettings.HasWon);
+ _customSelectionButton.Visible =
+ StageProducer.GetPersistantVal(StageProducer.PersistKeys.HasWon) == 1;
}
public override void _Process(double delta)
diff --git a/Scenes/UI/TitleScreen/TitleScreenEffects.tscn b/Scenes/UI/TitleScreen/TitleScreenEffects.tscn
index 670edb8d..0c883ce0 100644
--- a/Scenes/UI/TitleScreen/TitleScreenEffects.tscn
+++ b/Scenes/UI/TitleScreen/TitleScreenEffects.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=4 format=3 uid="uid://dkaxidh7xlvfc"]
[ext_resource type="Texture2D" uid="uid://iqbqsiyjd3uq" path="res://Scenes/UI/TitleScreen/Assets/2D_Shadow_Map.webp" id="1_07rxh"]
-[ext_resource type="Texture2D" uid="uid://coav3xvksq4jy" path="res://Scenes/NoteManager/Assets/NoteWIthOutline.png" id="2_ltons"]
+[ext_resource type="Texture2D" uid="uid://coav3xvksq4jy" path="res://Scenes/NoteManager/Assets/NoteWithOutline.png" id="2_ltons"]
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_4croe"]
particle_flag_disable_z = true
diff --git a/project.godot b/project.godot
index bd90939a..06d5742c 100644
--- a/project.godot
+++ b/project.godot
@@ -24,6 +24,7 @@ buses/default_bus_layout=""
[autoload]
+Savekeeper="*res://Globals/Savekeeper.cs"
StageProducer="*res://Globals/StageProducer.cs"
TimeKeeper="*res://Globals/TimeKeeper.cs"
Scribe="*res://Globals/Scribe.cs"