# CustomSpells on Aelrynth

**Third-party mod.** CustomSpells is by **aquafir** - https://github.com/aquafir/ACE.BaseMod,
`Samples/CustomSpells`, brought in at upstream `41c2728` (2024-11-26) on 2026-09-30 at a player's
request. Licence: AGPL-3.0 (the ACE.BaseMod repository's). Upstream's `Readme.md` is here unchanged
and lists every value a spell customization can set.

**What it does.** At world open it changes spells in memory - the portal.dat SpellBase (name, mana,
range, power, duration, components...) and the world database's spell row (stat mod, damage,
boost, projectiles, transfer...) - from `CustomSpells` in Settings.json, or a spreadsheet
(`AutoloadSpreadsheet`, `/loadspells`). A customization copies a `Template` spell and writes it to
`Id` (same id = change that spell; a new id = a new spell). `Sets` replaces equipment-set bonuses.
Console `spelldump` writes every spell to Dump.xlsx; `/listset` shows an appraised item's set spells.

**The retail client** draws spell names, descriptions, icons and mana costs from its own dat: a
changed spell works but its tooltip still says retail's text, and a new spell id has no name or icon
in the client. Changing numbers of existing spells is the safe use until the client hop.

**What we changed** (see the commits after `344cb121`, which is upstream as published):
- `CustomSpells.csproj`: our `Mods/Directory.Build.props` build; ACE.Shared from NuGet 1.0.9.
- `Settings.cs`: **every default empty** (`AutoloadSpreadsheet` false, no spells, no sets). Upstream's
  defaults were live examples - Strength Self I renamed and cut to 5, Acid Volley I made bludgeon,
  the Adept's set replaced with Harm Self - applied at once on a server without a Settings.json.
- `PatchClass.cs`:
  - `HasFoci`: ACE's own check for the five schools. Upstream computed the Infused augmentations and
    then overwrote the answer with the foci-in-pack check, so every player with an Infused
    augmentation would have needed components again. Only a school ACE has no foci for (a new one)
    is answered by the mod ("always has foci").
  - `GetCreatureSkill(MagicSchool)`: ACE's own for None and the five schools; only a new school maps
    to the skill with the same number (upstream also sent None to `GetCreatureSkill(Skill.None)`).
  - **Undo**: every spell and set it touches is remembered the first time and put back on Stop, so a
    disable, hot reload or settings change leaves no stale edits (upstream kept them until a
    restart, and cleared the dat's own set objects in place). Sets are replaced, not edited.
- `Mod.cs`: a `Stop` that cannot recurse (see below).

**ACE.Shared hazards, for both third-party mods:**
- **Stack overflow on disable.** ACE.Shared's `BasicMod.Stop` calls `ModManager.DisableModByPath`
  from its catch, and disabling calls `Stop` again: one exception while stopping recursed until the
  process died (staging, 2026-09-30 14:02:41, `mod disable CustomClothingBase`). Both mods override
  `Stop` - each piece stopped on its own, failures logged, no call back into disable.
- **First load writes settings and stops.** With no Settings.json, ACE.Shared writes the defaults and
  does not start the mod (no `OnWorldOpen`). Deploy the Settings.json first, then `mod-load`.
- **Settings.json is watched**: editing it restarts the mod by itself (Stop, then Start - with the
  undo above that is a clean re-apply).

**Tested on staging 2026-09-30:** Heal Self VI set to Boost 500 / variance 0 in Settings.json - the
Healing self-test (ACE's real cast path) read 500..500; `mod disable` put it back (55..107, retail);
two disable/enable cycles of both third-party mods with no crash. Staging's list is empty again.
