Skip to content

Releases: ing-gom/Sts2SkinManager

v0.27.1 — drag the menu anywhere + upward flip + ModKit settings bridge

Choose a tag to compare

@ing-gom ing-gom released this 30 Jul 09:11

v0.27.1 — drag the menu anywhere + upward flip + ModKit settings bridge

Added — drag the whole menu anywhere on the screen (⋮⋮ grip)

  • The overlay could only sit in one of two corners, and the anchor dropdown only swaps between them — which is no help when another mod parks its own menu bar in both. The collapsed header now starts with a ⋮⋮ grip: drag it to move the entire overlay (dropdown row + hover preview + tab panel) as one unit; double-click resets it to the anchored corner. Tooltip localized in all 16 languages (overlay_drag_tooltip).
  • The drag is a screen-space delta on top of the anchor, not a new positioning mode: every group is still anchored to its corner (Left/Right from the settings dropdown) and the same (dx, dy) is added to both horizontal offsets, which translates a right-anchored control — whose base offsets are negative — by exactly the same pixels as a left-anchored one. So one offset serves both anchors and the existing layout code is untouched apart from the + _dragOffset term.
  • Drag continues after the cursor slides off the grip. _GuiInput only fires while the pointer is inside the control, so it just starts/stops the drag; the motion itself is carried in _Input, which runs ahead of GUI routing. Without that split a fast drag "slips off" the handle and the menu stops following the mouse.
  • Persisted to its own file (overlay_layout.json, next to skin_choices.json) rather than into skin_choices.json. That file is watched by ChoicesFileWatcher and any write to it pops the restart/countdown modal — moving a menu must not look like a skin change. Saved on drag end only, not per motion frame.
  • The offset is clamped so the menu can never be lost off-screen: at least a 180 px horizontal sliver of the header stays visible, and vertically whichever element is lowest is kept on-screen. The clamp is re-applied once on attach, because a position saved at one resolution can land off-screen at another.

Added — the panel opens upward when the menu is dragged into the bottom half

  • Dragging the header below the screen's vertical midpoint would have made the expanded body grow straight off the bottom edge. Past that midpoint the accordion flips: the body is moved to be the first child and the group switches to bottom-anchored with GrowVertical.Begin, so it grows up from the header — and the dropdown row mirrors to just below the header. The header itself never moves, so the flip is invisible while collapsed.
  • Child reordering (MoveChild) is the flip's only structural side effect and lives in one place (ApplyAccordionOrder), called both on the flip transition and once after the header row and body are built — the early call from PositionAccordion short-circuits while those children don't exist yet.

Changed — settings registration moved to the shared ModKit bridge

  • ModConfigBridge was ~110 lines of hand-rolled reflection against ModConfig.ModConfigApi. It now delegates to Sts2.ModKit.Config.ModConfigBridge, which registers through either RitsuLib or ModConfig (RitsuLib preferred) and no-ops with defaults when neither is installed — so the mod still has zero hard dependency, and users with only RitsuLib now get the overlay-position dropdown too. The local class name is kept so MainFile's call site is unchanged.
  • The build moved to Sts2.ModKit.props (shared references / publicizer / mods-folder copy), and an AssemblyResolverBootstrap module initializer registers the AssemblyLoadContext.Resolving hook the ModKit consumers need. Build note: the project now imports ..\Sts2.ModKit\build\Sts2.ModKit.props, so building from a standalone clone of this repository requires the sibling Sts2.ModKit directory.

Verification

  • Release build clean and shipped to the Steam Workshop as 0.27.1 (item 3747513223). Both the drag/flip geometry and the clamp are analytic — computed from the anchors' base offsets and the viewport size rather than measured from the tree — so they hold at any resolution without a layout pass; in-game confirmation of the flip at low resolutions is still worth a look.

Install (manual)

Extract Sts2SkinManager/ from the attached zip into <Slay the Spire 2>/mods/. The zip now ships two DLLs — Sts2SkinManager.dll and Sts2.ModKit.dll (the shared library the settings bridge loads); keep both side by side. Steam Workshop subscribers get the same build automatically as item 3747513223.

v0.27.0 — mark inert order in the Mixed panel + wrapping tooltips + preview z-order

Choose a tag to compare

@ing-gom ing-gom released this 30 Jul 09:11

v0.27.0 — mark inert order in the Mixed panel + wrapping tooltips + preview z-order

Fixed — the ⚠ marker never reached the mods that need it

  • v0.26.0 marked card packs whose priority order is inert, but only in the Cards panel — and in practice every mod-private pack lives somewhere else. On a real install: ATA_IronClad and ATA_Silent are mixed mods (they ship a spine, so they route to the Mixed panel), and FGOCore / ArtoriaCaster are custom-character mods that are deliberately skipped from every panel. The Cards panel held only RegentCardsAnimeRework and AliceDefectCard, both shared-path — correctly unmarked. The result: the marker was unreachable on that install. The Mixed panel now carries the same marker.
  • The data existed but never reached the badge: the boot log's card_mode: comes straight from the path scan, while the badge reads DetectedSkinMod.CardOverrideMode, which v0.26.0 only populated on the Cards branch. The Character branches (which feed the Mixed panel) left it at the default.

Added — the Mixed panel's order is judged on the body too, not just the cards

  • A mixed row's order is a single pck-mount lever governing every domain the mod ships, so the row is labelled from body and cards combined: both mod-private → ⚠ (the order does nothing at all), one of each → ◐ (it moves part of the mod), both shared → unmarked.
  • Measuring the body mattered, and overturned the obvious guess. The intuition was "the order still moves the body, just not the cards" — false for ATA: it namespaces its spine (res://ATA_IronClad/animations/characters/…) and its creature_visuals scene, so a DLL decides its body as well and the order governs neither. Verified across installed pcks: animations root (base-owned) for AncientWaifus / raye / Ryoshu, own-id root for ATA_IronClad / ATA_Silent. The path-root check also flags NecrobinderOstyVaalmonicaMod as a mod-private body.
  • Path judging is now one pass over res://{root}/{rest} shared by both domains. Re-validated against the v0.26.0 card ground truth: 0 regressions across all 9 packs.

Fixed — long tooltips ran off the screen as one line

  • Godot's stock tooltip is a PopupPanel + Label with autowrap off, so every explanation rendered as a single line — blocked_mod_toggle_tooltip is 242 characters. Autowrap is a property, not a theme item, so a Theme cannot fix it; _MakeCustomTooltip is the only hook, and it must be overridden on the concrete control. Added wrapping variants for the three control types carrying long tooltips (Label, Button, CheckBox) and applied them to all five: blocked-mod, custom-character and All Mods checkboxes, the 🧍/🃏 mixed toggles, and the new ⚠/◐ badges.
  • The wrap width is a CustomMinimumSize, not decoration: an autowrapping Label reports its full single-line width as its minimum unless constrained, and the tooltip popup sizes to that minimum — so autowrap alone would still have rendered one line.

Fixed — the hover preview rendered behind the panel

  • The dropdown row, the preview and the tab panel are all children of the character-select screen and all sat at ZIndex = 1000. Godot breaks a Z tie by tree order, and the panel is attached last, so it drew over the preview image. The preview now sits at 3100 — above the panel, and above the drag indicator, whose ZIndex = 2000 inside a row resolves to an effective 3000 through relative-Z inheritance.

Verification

  • ⚠ badge in the Mixed panel, wrapped tooltips and the preview z-order confirmed in-game by the user. Release build clean.
  • Unchanged from v0.26.0: this marks the inert case, it does not make ordering work for mod-private mods. Still awaiting the reporter's log (card_mode: / body_mode: per mod) to decide whether driving RitsuLib's registry directly is worth it.

v0.26.0 — mark card packs whose priority is inert + fresh-install order fix

Choose a tag to compare

@ing-gom ing-gom released this 30 Jul 09:11

v0.26.0 — mark card packs whose priority is inert + fresh-install order fix

Added — Cards panel now says which rows the priority order can actually control

  • A user reported that reordering card packs did nothing: four packs installed, some overlapping on the same card art, and "sometimes adjusting the priority in any order doesn't seem to affect anything". They were right, and it was not a bug in the ordering code — priority is structurally inert for a whole class of card pack, and the panel gave no hint of it.
  • Card packs get their art in front of the player in one of two ways, and only one of them can be arbitrated by load order:
    • Shared path — the pack writes to a path other packs also write to (res://images/packed/card_portraits/… owned by the base game, or the shared res://generated/assets/card_art/… tooling convention). Two packs reskinning the same card land on the same resource path, so they genuinely collide and Godot's last-mount-wins picks the winner. mod_list order — exactly what this panel controls — decides. Priority works.
    • Mod-private — the pack keeps its art under its own namespace (res://ATA_IronClad/images/card_portraits/…) and injects it with a Harmony DLL. Nothing collides: every pack's art sits at its own distinct path, and the DLL decides what CardModel.PortraitPath returns. Mount order cannot reach that decision, so priority is inert — the arrows move the row and change nothing.
  • Mod-private rows are now marked ⚠ with a greyed-out priority number and a tooltip explaining that ordering does not apply and to use the checkbox instead; packs that are half-and-half are marked ◐. Localized in all 16 languages. The boot log gains a card_mode: field per pack so a "priority does nothing" report can be triaged from the log alone.
  • Detection is by the root segment of the card-art path compared against the pack's own mod id: a pack that files its art under its own id cannot collide with anything, by construction. This reads where the mod puts its files rather than which framework it links, so it needs no per-framework knowledge.

Fixed — a freshly installed card pack silently outranked every other pack

  • CardPackApplier only rewrote mod_list entries the game had already written, skipping any pack absent from the list. But the game rebuilds mod_list at the end of ModManager.Initialize, one boot behind — and SortModList scores anything missing from mod_list at 999999999, sorting it past every listed mod, so it mounts last and wins every conflict. Meanwhile SyncCardPacks appends newly detected packs at the bottom of the panel labelled lowest priority. The UI and reality were inverted until the next restart. Such packs are now materialized into mod_list at their correct position instead of being skipped.
  • The synthesized entry carries the mod's real source (read from ModManager.Mods, falling back to the pck's location). This is load-bearing, not cosmetic: ModSettings.IsModDisabled matches on (Id, Source, IsEnabled), so an entry with the wrong source would make the row's checkbox silently do nothing.

Fixed — content mods with an embedded DLL were mis-listed as card packs

  • The base-card-namespace pattern matched MegaCrit.Sts2.Core.Models.Cards. anywhere in a pck, but that string also appears in the IL metadata of any pck embedding a DLL that references base card types — nothing to do with card visuals. SlayTheUniverse (a content mod) carried 681 such strings (…Cards.Mocks, …Cards.Abrasive+<OnPlay>d__7) and was surfaced in the Cards panel, where reordering or disabling it could only confuse. The pattern now requires the _card_art / _portrait suffix that a real asset path has, dropping it to 0 hits while keeping the genuine packs (AliceDefectCard 736, RegentCardsAnimeRework 300).

Verification

  • Classification checked against every installed pack, using the shipped regexes: shared-path for AliceDefectCard, RegentCardsAnimeRework (root generated) and AncientWaifus, Ryoshu, raye (root images); mod-private for FGOCore, ArtoriaCaster, ATA_IronClad, ATA_Silent (root = own mod id). 9/9 as designed; SlayTheUniverse correctly drops out.
  • The resolution rules were read from the decompiled sources rather than inferred: RitsuLib merges competing packs by a registration-order WriteOrder counter (per-character scope beats global regardless of order; re-registering re-issues a higher WriteOrder), and the game's SortModList applies the 999999999 rule above. Confirmed against live files that SkinManager's settings.save write is not clobbered by the game: skin_choices slot 1 (AliceDefectCard) correctly lands last in mod_list (index 48) and wins.
  • Debug + Release build clean. This release makes the inert case visible; it does not make ordering work for mod-private packs — that is not fixable from mount order. A follow-up may drive RitsuLib's registry directly for the RitsuLib-family packs; packs that patch PortraitPath with their own Harmony patch have no general fix and remain checkbox-only.

v0.25.0 — mixed-mod card toggle + macOS skin loading

Choose a tag to compare

@ing-gom ing-gom released this 30 Jul 09:11

v0.25.0 — mixed-mod card toggle + macOS skin loading

Added — "Vanilla cards / Mod cards" toggle for mixed mods (choose the body and the card art independently)

  • Mixed mods bundle a character reskin and custom card art in one pck. You could already keep the cards and revert the body (the 🧍 "Selected look / Mod look" toggle, v0.15.0). This adds the card mirror: a per-row 🃏 "Vanilla cards / Mod cards" toggle in the Mixed panel. The two toggles are independent, so a mixed mod can be shown as any combination of {mod / vanilla} body × {mod / vanilla} cards.
  • The toggle only appears for mixed mods that actually bundle card art (VanillaCardsOverlayBuilder.HasRevertibleCards); mods with none never show it.
  • Persisted as _vanilla_cards_mods in skin_choices.json; surfaced in the Applied summary as a (🃏 …) tag. Labels + tooltips localized in all 14 languages.

How it works — a Harmony prefix on CardModel.PortraitPath, not a pck overlay

  • Card art in STS2 is resolved through a code path (CardModel.PortraitPath, a virtual getter that frameworks like RitsuLib/ATA already Harmony-patch), above the resource filesystem. So a mounted base-game overlay pck can never win — mounting is invisible to the resolver. Both directions of this toggle are therefore driven by our own higher-priority (Priority.First) prefix on that getter (CardVanillaPortraitPatch), which recomputes the path and short-circuits, beating any framework prefix and the original getter.
  • Vanilla cards (revert to stock): for a flagged character's pool, force the stock atlas path res://images/atlases/card_atlas.sprites/{pool}/{entry}.tres. This reverts both direct-override skins and framework-injected (RitsuLib/ATA) card art, which a pck overlay could not touch.
  • Mod cards (surface the mod's art): a direct-override mixed skin (e.g. raye) ships its card art as a card-atlas .tres.remap, which Godot ignores for a late-mounted pack — so the card stays vanilla even though the mod is selected. But it also ships the packed portrait png as a direct .ctex byte-override at the base path (the same mechanism that makes its body work). For each mounted, non-reverted mixed mod we collect those cards (CollectPackedPortraitCardKeys) and redirect their PortraitPath to res://images/packed/card_portraits/{pool}/{entry}.png, where the mod's ctex override wins — and which is also the exact path RitsuLib uses for card injection.

Verification

  • raye selected: log confirms mod-cards (Harmony): surfacing … [ironclad/break] and vanilla-cards (Harmony): forcing stock card art [ironclad] for the two directions. raye ships exactly one custom card (break; ctex 599 KB vs base 241 KB). Debug + Release build clean. In-game visual confirmation of the mod-cards direction pending (needs break in the deck).

v0.24.0 — shipped inside this release

Fixed — skins now load on macOS (Steam Workshop skins were invisible; character skins mis-classified)

  • Steam Workshop skins were never discovered on macOS. SkinManager derived the workshop content root relative to the executable (gameDir/../../workshop/content/<appId>). That holds on Windows/Linux, where the executable sits directly in steamapps/common/<game>/, but on macOS the executable is buried in SlayTheSpire2.app/Contents/MacOS/, so climbing two levels lands inside the .app bundle — the workshop folder is never found and no workshop-subscribed skin appears in the manager. Fix: read each subscribed mod's install folder back from STS2's own ModManager.Mods, which the game resolves via the Steam API (SteamUGC.GetItemInstallInfo) as an absolute path that is correct on every OS, and climb to the shared content/<appId> root. The old relative derivation is kept only as a Windows/Linux fallback for when ModManager yields nothing (e.g. Steam not initialised).
  • Character skins were mis-classified on macOS because the base-character roster came up empty. ScanBaseCharacters read SlayTheSpire2.pck from next to the executable; inside the macOS app bundle that resource lives in Contents/Resources/ (STS2 itself probes the same ../Resources path for release_info.json). With no base roster, every character skin failed the "skin for an existing character" test and was dropped. Fix: probe Contents/Resources/SlayTheSpire2.pck as well; first hit wins.
  • Auto-restart is now cross-platform. The "apply → restart" helper only ever wrote a Windows .bat, so even a detected skin couldn't be applied on macOS/Linux. It now branches on OS: Windows keeps the batch script; macOS/Linux write a POSIX sh helper that waits for the game process to exit (by pid) and relaunches via Steam's URL handler (open on macOS, xdg-open on Linux).

Why

  • A macOS user reported that skin mods do not load. Root cause was OS-specific path handling, not the skins themselves: the game is packaged as a .app bundle on macOS, which invalidates every executable-relative path assumption made for Windows.

Note

  • All three changes are no-ops on Windows (same resolved path de-duplicated / first-candidate hit / OS-gated branch), so existing Windows behavior is unchanged. Compiles clean (Debug + Release). Pending in-game verification on macOS — developed and built on Windows; logic is grounded in the decompiled game's own macOS path handling but not yet confirmed on a real macOS install.

v0.23.0 — disable-not-reorder skin blocking + library custom-character detection

Choose a tag to compare

@ing-gom ing-gom released this 30 Jun 18:31

v0.23.0 — load-order: disable instead of reorder

SkinManager no longer reorders settings.save's mod_list. Reordering shifted the load order of content/framework mods (RitsuLib and the custom characters built on it), which reshuffled the numeric ids in the game's ModelIdSerializationCache — so an existing progress.save would mis-map and the player's progress appeared "not loaded." The reorder also never reliably stuck, so it re-armed the restart modal every boot.

  • A skin that loads before SkinManager (so the TryLoadMod intercept can't catch it) is now blocked by disabling it (is_enabled = false) instead of reordering. Disabling is order-preserving and content-safe (a managed skin defines no ModelDb entities). Skins that load after SkinManager are unaffected. Resolution is deterministic — one restart, no modal loop.
  • Hard guard: never auto-disables BaseLib, Sts2* sister mods, or any mod whose DLL defines game-entity subclasses (a content mod) — those are left loaded-but-unblocked so their save data and dependencies stay intact.

v0.22.0 — generic library-based custom-character detection

  • Custom characters built on any framework (RitsuLib, not just BaseLib) are now recognized via a framework-agnostic signal: a character-select asset for a non-base character id. Fixes RitsuLib characters (e.g. Hornet) being mis-classified as a base-character skin and DLL-blocked so they never appeared in Character Select.
  • Manifest dependencies in object form ({ "id": ..., "min_version": ... }) are now parsed, not just bare strings.
  • Fallback: every DLL-blocked mod is also surfaced in the Other Mods tab with a restore toggle, so a misclassified mod can always be switched back on.

v0.21.0 — load-order conflict auto-resolution + duplicate-list fix

Choose a tag to compare

@ing-gom ing-gom released this 30 Jul 09:11

v0.21.0 — load-order conflict auto-resolution + duplicate-list fix

Changed — load-order self-bootstrap no longer fights other "force-first" mods

  • SkinManager now only ensures it loads before the skin mods it manages, not absolute-first in the mod list. Previously it pulled itself to index 0 of settings.save's mod_list on every boot where it wasn't already first. That meant any other mod that also forces itself to the front (a core-patch or UI framework) could trigger a tug-of-war: each boot one mod reorders ahead of the other, re-arming the restart modal indefinitely.
  • The new rule (LoadOrderEnforcer.EnsureBeforeTargetsInModList) computes the earliest mod_list index among the detected character-skin mods and only moves SkinManager when it sits at or behind that target — placing it just ahead of the earliest skin mod. Unrelated front-runners are ignored: they can keep index 0 forever, because SkinManager only needs to precede the mods its TryLoadMod / LoadResourcePack prefixes manage. Since the game appends newly-installed mods to the end of the list (SortModList gives un-listed mods priority 999999999), later additions land behind us automatically — no re-trigger.

Added — deterministic auto-resolution of an unwinnable load-order war

  • The reorder target is now precise: only the character skins SkinManager actually suppresses (every detected skin minus the active pick and minus any already resolved by disable). It no longer tries to precede the skin you actually chose — order is irrelevant for a mod it doesn't block. A side benefit: with no suppressed skins, SkinManager stops forcing a reorder/restart at all.
  • One-boot conflict detection. A persisted _load_order_reordered_last_boot flag records whether we reordered. If we reordered last boot and are still behind a suppressed skin this boot, another "force-first" mod is overriding us — detected in a single restart instead of several.
  • Auto-resolve by persistent-disable instead of giving up. On detection, the blocking skin mod(s) are disabled in settings.save (is_enabled = false). Because the game's RemoveDisabledMods strips disabled mods before any mod loads, the competing mod can't re-sort itself to the front — its code never runs. The conflict ends deterministically in one restart, with no user action. The visual result is identical (a suppressed skin doesn't apply either way); the mod simply shows as disabled in the game's own mod menu. Resolved ids are tracked in _load_order_resolved_by_disable.
  • Auto-heal. If the user later picks a skin we'd disabled, it is re-enabled automatically (one restart); entries for uninstalled mods are dropped.
  • A "conflict resolved" restart prompt (load_order_conflict_title / load_order_conflict_body) across all 16 languages.

Fixed

  • Duplicate mod_list entries no longer trigger a spurious reorder. Real settings.save files can list the same mod id more than once (observed: Sts2SkinManager at both index 0 and index 59). The position lookup now uses each id's FIRST occurrence, so a stray late duplicate of ourselves is no longer read as "behind the skin mods" and does not cause a needless reorder/restart on every boot.
  • No restart prompt during headless / automated boots. RestartCountdownModal now no-ops when DisplayServer.GetName() == "headless" (or --headless is on the command line). The file mutations that warrant the restart already happened synchronously, so the next real launch picks them up — this just prevents a 10-second auto-restart from relaunching a headless process in a loop.

Verified

  • Pure load-order logic covered by an offline harness (19 cases incl. reorder index math, front-runner-ignore, duplicate lists).
  • All four runtime paths exercised end-to-end via real --headless boots against the live mod set (13 skin mods): regression (no-op), first reorder, conflict escalation (disable), and auto-heal (re-enable) — each confirmed against the resulting settings.save / skin_choices.json and logs.

v0.20.0 — Applied tab shows each mod's actual load state

Choose a tag to compare

@ing-gom ing-gom released this 30 Jul 09:11

v0.20.0 — Applied tab shows each mod's actual load state

Added — Applied tab now shows each mod's actual load state

  • A new "Actual load state" section reads the game's real ModManager state, not just the boot snapshot of settings.save. This is the authoritative answer to "which skin mod is actually active right now", and it resolves a confusing gap: a character-skin DLL that SkinManager blocks at runtime (because it isn't the picked variant) is suppressed via the TryLoadMod intercept, which never writes is_enabled — so the game's own mod menu kept showing it as enabled even though it wasn't loaded.
  • Per-mod badges make the source of each state explicit: ✅ loaded · ⛔ blocked by Skin Manager (runtime DLL-block, not the active pick) · 🚫 disabled in the mod menu (is_enabled = false) · ❌ failed to load · ⟳ added at runtime (restart to apply) · ❔ not loaded. The ⛔/🚫 split tells you why a mod isn't active. The section is included in the 📋 Copy summary for bug reports.
  • Display-only: no change to toggling, mounting, or blocking behaviour — purely restores visibility.

v0.19.0 — manage Steam Workshop–subscribed skins

Choose a tag to compare

@ing-gom ing-gom released this 19 Jun 02:38

Added — manage Steam Workshop–subscribed skins

  • SkinManager now scans Steam Workshop subscriptions, not just the local mods/ folder. Workshop subscriptions install outside mods/ — under steamapps/workshop/content/2868840/{itemId}/ — which STS2 itself loads via the Steam API (SteamUGC.GetItemInstallInfo). Until now SkinManager only walked mods/, so a skin you subscribed to on the Workshop never appeared in the panel and couldn't be previewed, toggled, or layered.
  • The Workshop root is derived relative to the game install (steamapps/common/Slay the Spire 2 → ../../workshop/content/2868840), so no Steam binding is needed — Steam guarantees workshop content lives in the same library as the game. The root is only added when the folder exists.
  • Local mods/ wins over a Workshop duplicate. When the same mod id is present in both locations, the local copy is kept — matching STS2's own loader, which disables the Workshop version of a duplicate id. Reading from Steam's managed folder is safe: SkinManager never modifies scanned mod files (mounting is in-memory via the LoadResourcePack intercept).

Install: unzip into your STS2 mods/ folder so the layout is mods/Sts2SkinManager/Sts2SkinManager.dll.

v0.18.0 — detect ClymandSaru template-replacement skins

Choose a tag to compare

@ing-gom ing-gom released this 12 Jun 00:28

Recognise and manage ClymandSaru-framework skins (e.g. jasmine_q→Ironclad, zhubao_q→Defect) as character skins. These reskin an existing character via a {modId}_config.cfg template_replacements map + a runtime DLL patch, shipping no standard animations/characters/{char}/ paths — so they were previously invisible to Skin Manager. The new ClymandSaruSkinReader reads the config as the authoritative target, which also fixes the byte-frequency suggester mis-assigning non-Ironclad skins to Ironclad (every ClymandSaru DLL embeds the literal "ironclad" default template). Creature-only reskins (e.g. reven_q→byrdpip) that target no player character are left to normal auto-mount.

See CHANGELOG.md for details.

v0.17.0 — Custom Characters tab + cosmetic-utility false-positive fix

Choose a tag to compare

@ing-gom ing-gom released this 06 Jun 02:56

Added — Custom Characters tab

A dedicated tab to enable/disable BaseLib-style custom-character mods directly from the panel. Drives the same settings.save mod-list toggle as the Other tab (Save/Discard apply; restart to take effect). Appears only when a custom character is installed.

Fixed — texture/cosmetic utilities no longer mistaken for character skins

Mods that wholesale-retexture cards/relics/powers/potions (e.g. CustomCardTextureLoaderSG) patch NCharacterSelectButton just to swap the select-screen button texture, which previously tripped the DLL-skin detector — a stale assignment could then DLL-block the utility when a real skin for that character was active.

New Signal B (CosmeticUtilityDetector): a DLL referencing ≥2 base content-model types (relic/power/potion) with no per-character identity is auto-skipped as a global utility. A HasCharacterSignal guard (concrete-patch / byte-frequency / manifest keyword) protects themed skins that also retexture content — they still name their character, so they stay managed. Stale matching assignments auto-heal to _dll_skin_skipped on boot.

Install: extract Sts2SkinManager/ into your STS2 mods/ folder.