Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Starless

Makes another character's Star-cost cards usable when they arrive through Kaleidoscope, Prismatic Gem, or any other cross-class source.

In vanilla, Stars are Regent-exclusive. Any Regent card with a Star cost that lands in another character's deck is unplayable forever - the game even has a dedicated UnplayableReason.StarCostTooHigh for it. Starless converts those cards instead of leaving them as bricks.

What it does

Applies only when a card's pool differs from its owner's character. Regent players are entirely unaffected.

Card shape Treatment
Fixed Star cost Star cost removed, Energy cost raised by ceil(stars * 2/3)
X-Star cost Filtered out of cross-class reward pools (see Limitations)
Only produces Stars Filtered out of cross-class reward pools

The ratio comes from the game's own card values, where 3 Stars is worth about 2 Energy. Comet (0 Energy / 5*) becomes a 4-Energy card; Crescent Spear (0 Energy / 1*) becomes 1 Energy.

Cards that grant Stars in addition to doing something real - ShiningStrike, SolarStrike, KnockoutBlow, GatherLight, Glow, BigBang, HiddenCache - are kept. They just lose a rider.

Build and install

Requires the .NET 9 SDK. No NuGet packages: the project references the game's own sts2.dll, GodotSharp.dll and 0Harmony.dll directly.

bash deploy.sh

Builds and copies Starless.dll + Starless.json into <game>\mods\Starless\.

The game skips loading all mods until you accept the mods warning in-game once. Until then the log reads Skipping loading mod Starless, user has not yet seen the mods warning.

Configuration

src/StarlessConfig.cs - master toggle, the star:energy ratio, the reward filter toggle, and verbose logging. Currently compile-time; wiring to ModConfig is a to-do.

Verified in-game (v0.111.0, Ironclad + Regent)

Tested by driving the real game, not simulated.

Check Result
Mod loads, initializer runs, Harmony patches bind pass, no exceptions
Comet on Ironclad (vanilla 0 Energy + 5*) shows 4 Energy, no star pip
Crescent Spear on Ironclad (vanilla 1 Energy + 1*) shows 2 Energy, no star pip
Control: same cards with mod compiled inert shows vanilla 0+5* and 1+1*
In-combat hook path (card in hand) same converted costs
Playing a converted card Energy 3/3 -> 1/3, card resolved
Unaffordable converted card (Comet at 4, with 3 Energy) correctly greyed out
Regent regression: same cards on the Regent unchanged, 0+5 and 1+1**
Kaleidoscope pickup (exercises the reward filter) ran without throwing
Foreign cards with no Star cost (Sow, Compile Driver, Tracking) untouched

Intentional: Crescent Spear counts printed Star costs

Crescent Spear reads "Deals 2 additional damage for ALL your cards that have a Star cost". Under Starless it still counts converted cross-class cards, so on a non-Regent it keeps scaling (+2 per Star card, including itself).

Measured: with only Crescent Spear present it dealt 10 (8 + 2x1); with Crescent Spear and Comet, 12 (8 + 2x2).

This is deliberate, not a leak. Crescent Spear is the only card in the game that inspects Star costs (all 606 card types were scanned for reads of CanonicalStarCost, CurrentStarCost, GetStarCostWithModifiers, HasStarCostX and CostsEnergyOrStars), and it reads CanonicalStarCost - the printed value, which nothing can modify even in vanilla. Starless changes what a card costs you to play, never what the card is, so the count is the game's own semantics. It also makes Crescent Spear a real payoff card for collecting Regent cards cross-class.

Known cosmetic wart: converted cards hide their Star pip, so the player cannot see which cards are being counted. See "Possible future work" below.

Tier 3 verification

Confirmed in-game two ways rather than by hoping a random reward appeared.

Runtime classifier audit (enable VerboseLogging, start any run):

[Starless][audit] cards with a star cost: 23
[Starless][audit] pure star generators (2): CARD.ROYAL_GAMBLE, CARD.VENERATE

The filter firing on a real Prismatic Gem reward roll as the Ironclad:

FILTERED from reward pool: CARD.ROYAL_GAMBLE (pool=REGENT_CARD_POOL, player=IRONCLAD)
FILTERED from reward pool: CARD.STARDUST     (pool=REGENT_CARD_POOL, player=IRONCLAD)
FILTERED from reward pool: CARD.VENERATE     (pool=REGENT_CARD_POOL, player=IRONCLAD)

Rewards still generated normally, so the filter composes with Prismatic Gem rather than breaking pool generation.

Possible future work: show an inert Star pip

To fix the cosmetic wart, a converted card could still render its printed Star pip greyed out. The rendering path is NCard.UpdateStarCostVisuals(PileType), which owns three private nodes: _starIcon, _starLabel and - usefully - _unplayableStarIcon, a greyed variant that already exists. A Harmony postfix could re-show the icon and write CanonicalStarCost into the label.

Difficulty: low-to-moderate, roughly 30-60 lines, no new art and no Godot scene editing. Caveats: UpdateStarCostColor / UpdateStarCostText are separate entry points that would need covering too, _unplayableStarIcon may be claimed by the game's own affordability rendering, and this is the most patch-fragile kind of change in the mod because it reaches into private Godot node fields rather than the sanctioned hook API. Worth doing only if players report confusion.

Not yet verified

  • Multiplayer, entirely.

Limitations

  • X-Star cards are filtered, not converted. On v0.111.0 that is exactly one card, Stardust. Converting it needs patches to the async SpendResources state machine and ResolveStarXValue, which is a poor risk trade for a single card. Filtering it is strictly better than shipping it broken.
  • Not yet play-tested. See the verification checklist in the plan.
  • Multiplayer untested. As with all STS2 mods, every player needs it installed at a matching load order.

Implementation notes

  • Cost conversion registers via ModHelper.SubscribeForCombatStateHooks, not the run-state variant: Hook.ModifyStarCost and Hook.ModifyEnergyCostInCombat both iterate combat hook listeners.
  • Reward filtering registers via SubscribeForRunStateHooks, because Hook.ModifyCardRewardCreationOptions iterates the run-level listeners.
  • "No star cost" is encoded as -1, not 0. Writing 0 leaves the card rendering a Star pip.
  • Those hooks only fire in combat, so DisplayPatches covers the reward, shop and deck screens. Otherwise you would pick cards based on costs that do not match what you pay.
  • Star-generator detection scans card IL for PlayerCmd.GainStars rather than using a hardcoded list, so it keeps working when cards are added.

About

Slay the Spire 2 mod: makes Regent Star-cost cards playable when other characters obtain them cross-class. C# against the game's .NET assemblies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages