namespace Aeshnidae.ResonanceAuras;
///
/// One aura's tunables. The key is fixed in code (it is what the database row and
/// the formula patch key on); everything a player sees or pays is here.
///
public class AuraSetting
{
/// Which aura this row configures - see . Not a name; do not edit.
public string Key { get; set; } = "";
/// The name players see and type. Any unambiguous prefix matches.
public string Name { get; set; } = "";
/// Highest rank a character can buy. 0 disables the aura without losing anyone's ranks.
public int MaxRanks { get; set; }
///
/// Resonance for the first rank of this aura, or 0 to use .
/// Rank n costs n times this.
///
public long RankPriceBase { get; set; }
}
public class Settings
{
public const string FileName = "Settings.json";
public bool Enabled { get; set; } = true;
///
/// Resonance for rank 1 of any aura; rank n costs n times this, so 100, 200, 300...
/// Linear on purpose: the point of an aura is small and steady, not a mastery-style
/// wall. With Resonance at 10 a quest, 100 is ten quests - a week-old character can
/// buy a first rank, and the whole table (see the Readme) is a long-term goal.
///
public long RankPriceBase { get; set; } = 100;
///
/// Thrifty Caster: chance, per rank, that a spell component which would have burned
/// survives. 0.10 x 5 ranks = half of them. Applied per component, after ACE has
/// already decided which ones burn.
///
public double ComponentSaveChancePerRank { get; set; } = 0.10;
///
/// Steady Quiver: chance, per rank, that an arrow, bolt or dart is not spent when
/// fired. 0.10 x 5 ranks = half of them. The last one in the stack always goes,
/// so "out of ammunition" stays true when it is said.
///
public double AmmoSaveChancePerRank { get; set; } = 0.10;
/// Play the augmentation burst and chime on a purchase.
public bool Effects { get; set; } = true;
///
/// The auras, in the order /aura lists them. Names and maximums are yours to
/// change; keys are not. A key missing from this list is treated as MaxRanks 0.
/// Each rank adds one to a number ACE already uses in a formula - what that number
/// does is fixed in code and described in the Readme.
///
public List Auras { get; set; } = new()
{
new() { Key = "spellduration", Name = "Lasting Enchantments", MaxRanks = 3 },
new() { Key = "itemmana", Name = "Frugal Mana", MaxRanks = 5 },
new() { Key = "imbue", Name = "Charmed Hands", MaxRanks = 3 },
new() { Key = "manastone", Name = "Generous Stones", MaxRanks = 5 },
new() { Key = "salvage", Name = "Keen Salvager", MaxRanks = 4 },
new() { Key = "carry", Name = "Strong Back", MaxRanks = 5 },
new() { Key = "regen", Name = "Deep Rest", MaxRanks = 2 },
new() { Key = "components", Name = "Thrifty Caster", MaxRanks = 5 },
new() { Key = "ammo", Name = "Steady Quiver", MaxRanks = 5 },
};
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
public static Settings Load(string modPath)
{
var path = Path.Combine(modPath, FileName);
try
{
if (File.Exists(path))
return JsonSerializer.Deserialize(File.ReadAllText(path), JsonOptions) ?? new Settings();
var defaults = new Settings();
defaults.Save(modPath);
ModManager.Log($"[{Mod.Name}] wrote default settings to {path}");
return defaults;
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] could not read {path}, using defaults: {ex.Message}", ModManager.LogLevel.Warn);
return new Settings();
}
}
public void Save(string modPath)
{
try
{
File.WriteAllText(Path.Combine(modPath, FileName), JsonSerializer.Serialize(this, JsonOptions));
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] could not save settings: {ex.Message}", ModManager.LogLevel.Error);
}
}
}