### Install STS2.RitsuLib Package
Source: https://sts2-ritsulib.ritsukage.com/guide/getting-started
Add the package reference to your mod project.
```xml
```
--------------------------------
### Create I18N instance with CreateModLocalization and example zhs.json (Chinese)
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Chinese version of the I18N creation example. The JSON file shows the expected format for the Chinese localization file (zhs.json).
```csharp
var i18n = RitsuLibFramework.CreateModLocalization(
modId: "MyMod",
instanceName: "settings",
pckFolders: ["res://MyMod/localization/settings"]);
var label = i18n.Get("settings.enabled", "启用");
```
```json
{
"settings.enabled": "启用"
}
```
--------------------------------
### Register a Secondary Resource
Source: https://sts2-ritsulib.ritsukage.com/guide/secondary-resources
Use RitsuLibFramework.GetSecondaryResourceRegistry to declare a new combat resource. This example demonstrates setting up a resource with a maximum amount, turn-start reset policy, and custom icons.
```csharp
var resources = RitsuLibFramework.GetSecondaryResourceRegistry("MyMod");
var charge = resources.Register("charge", new SecondaryResourceDefinition(
defaultAmount: 0,
baseMaxAmount: 3,
turnStartPolicy: SecondaryResourceTurnStartPolicy.ResetToMax,
persistencePolicy: SecondaryResourcePersistencePolicy.Combat,
smallIconPath: "res://MyMod/assets/ui/charge_small.png",
largeIconPath: "res://MyMod/assets/ui/charge_large.png")
{
ClampToMaxAmount = true,
});
```
--------------------------------
### Architect dialogue example
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Example JSON for Architect dialogue with a mod character. Uses `THE_ARCHITECT.talk..*` keys. Optional `-attack` values are `None`, `Player`, `Architect`, or `Both`; optional `-visit` keys can override the visit index; explicit `-startattack` and `-endattack` keys set `StartAttackers` and `EndAttackers` respectively.
```json
{
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.0-0.char": "The exit is yours, but the cost is mine.",
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.0-1.ancient": "Then pay precisely.",
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.0-attack": "Both"
}
```
--------------------------------
### Architect dialogue with startattack/endattack/visit
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Example JSON for Architect dialogue with explicit `-startattack`, `-endattack`, and `-visit` keys. `-startattack` sets `StartAttackers`, `-endattack` sets `EndAttackers`, and `-visit` overrides the visit index.
```json
{
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.1-startattack": "Player",
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.1-endattack": "Both",
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.1-visit": "3"
}
```
--------------------------------
### Manage Looping Audio Handles
Source: https://sts2-ritsulib.ritsukage.com/guide/fmod-and-audio
Use PlayLoop to start looping audio and retain the returned handle to adjust parameters or stop playback later.
```csharp
var loop = GameAudioService.Shared.PlayLoop(
AudioSource.Event("event:/MyMod/ambience/engine"),
new AudioPlaybackOptions
{
Routing = new AudioRoutingOptions(Channel: "my_mod_ambience"),
Scope = AudioLifecycleScope.Run,
});
loop?.TrySetParameter("danger", 0.5f);
loop?.TryStop();
```
--------------------------------
### Mod ancient dialogue example
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Example JSON for a mod ancient talking to a mod character. Uses `MY_MOD_ANCIENT_MIRROR` and `MY_MOD_CHARACTER_SEER` entries, with a repeating dialogue using `r` after the line index. `ModAncientEventTemplate` reads `{ancient}.talk.firstVisitEver.*` for the first visit ever, `{ancient}.talk..*` for a specific character, and `{ancient}.talk.ANY.*` for character-agnostic fallback.
```json
{
"MY_MOD_ANCIENT_MIRROR.talk.MY_MOD_CHARACTER_SEER.0-0.ancient": "You brought a future with you.",
"MY_MOD_ANCIENT_MIRROR.talk.MY_MOD_CHARACTER_SEER.0-0.next": "Continue",
"MY_MOD_ANCIENT_MIRROR.talk.MY_MOD_CHARACTER_SEER.0-1.char": "Then I should spend it carefully.",
"MY_MOD_ANCIENT_MIRROR.talk.ANY.0-0r.ancient": "Again, a face in the glass.",
"MY_MOD_ANCIENT_MIRROR.talk.ANY.0-1r.char": "Again, a choice."
}
```
--------------------------------
### HarmonyAsyncIl.RedirectAwaitedCalls example
Source: https://sts2-ritsulib.ritsukage.com/guide/patching-guide
Use for async state-machine transpilers. RedirectAwaitedCalls only redirects calls directly awaited by the state machine and requires the replacement to preserve stack shape and exact awaitable return type. The example validates the rewrite with RequireSucceeded, RequireExactly(1), and InstructionsChecked.
```csharp
var rewriter = HarmonyIlRewriter.From(instructions);
var report = HarmonyAsyncIl.RedirectAwaitedCalls(
rewriter,
"Redirect awaited OnPlay",
originalOnPlayMethod,
wrapperMethod,
code => code.Any(instruction => HarmonyIl.IsCallTo(instruction, wrapperMethod)));
report.RequireSucceeded();
report.RequireExactly(1);
return rewriter.InstructionsChecked("Redirect awaited OnPlay");
```
--------------------------------
### Architect dialogue example (Chinese)
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Example JSON for Architect dialogue with a mod character (Chinese version). Uses `THE_ARCHITECT.talk..*` keys. Optional `-attack` values are `None`, `Player`, `Architect`, or `Both`; optional `-visit` keys can override the visit index; explicit `-startattack` and `-endattack` keys set `StartAttackers` and `EndAttackers` respectively.
```json
{
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.0-0.char": "出口属于你,但代价由我承担。",
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.0-1.ancient": "那就精确地支付。",
"THE_ARCHITECT.talk.MY_MOD_CHARACTER_SEER.0-attack": "Both"
}
```
--------------------------------
### Create I18N instance with CreateModLocalization and example eng.json
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Creates an I18N instance for key-value JSON files outside the game's model tables. The JSON file shows the expected format for the fallback language (eng.json).
```csharp
var i18n = RitsuLibFramework.CreateModLocalization(
modId: "MyMod",
instanceName: "settings",
pckFolders: ["res://MyMod/localization/settings"]);
var label = i18n.Get("settings.enabled", "Enabled");
```
```json
{
"settings.enabled": "Enabled"
}
```
--------------------------------
### Mod ancient dialogue example (Chinese)
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Example JSON for a mod ancient talking to a mod character (Chinese version). Uses `MY_MOD_ANCIENT_MIRROR` and `MY_MOD_CHARACTER_SEER` entries, with a repeating dialogue using `r`. `ModAncientEventTemplate` reads `{ancient}.talk.firstVisitEver.*` for the first visit ever, `{ancient}.talk..*` for a specific character, and `{ancient}.talk.ANY.*` for character-agnostic fallback.
```json
{
"MY_MOD_ANCIENT_MIRROR.talk.MY_MOD_CHARACTER_SEER.0-0.ancient": "你带来了一份未来。",
"MY_MOD_ANCIENT_MIRROR.talk.MY_MOD_CHARACTER_SEER.0-0.next": "继续",
"MY_MOD_ANCIENT_MIRROR.talk.MY_MOD_CHARACTER_SEER.0-1.char": "那我应该谨慎地使用它。",
"MY_MOD_ANCIENT_MIRROR.talk.ANY.0-0r.ancient": "又一次,镜中映出一张脸。",
"MY_MOD_ANCIENT_MIRROR.talk.ANY.0-1r.char": "又一次,一个选择。"
}
```
--------------------------------
### Get content and keyword registries and register a card and keyword
Source: https://sts2-ritsulib.ritsukage.com/guide/content-packs-and-registries
Shows how to obtain the content and keyword registries for a mod ID and use them to register a card and a keyword. The keyword registration uses RegisterCardKeywordOwnedByLocNamespace.
```csharp
var content = RitsuLibFramework.GetContentRegistry("MyMod");
content.RegisterCard();
var keywords = RitsuLibFramework.GetKeywordRegistry("MyMod");
keywords.RegisterCardKeywordOwnedByLocNamespace("bleeding");
```
--------------------------------
### 添加共享 Tooltip 到动态变量(中文示例)
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Chinese version of the shared tooltip example. The JSON keys provide localized title and description text for the tooltip. Use this when the game UI is in Chinese.
```csharp
ModCardVars.Int("heat", Heat)
.WithSharedTooltip("MY_MOD_HEAT", "res://MyMod/images/ui/heat.png");
```
```json
{
"MY_MOD_HEAT.title": "热量",
"MY_MOD_HEAT.description": "部分卡牌会根据当前热量改变效果。"
}
```
--------------------------------
### Event key construction with ModEventTemplate (C# and JSON)
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Shows the C# helper methods for building localization keys and a JSON example for event entry MY_MOD_EVENT_QUIET_DOOR. The keys combine the event entry, page name, and option name. Keep page and option names stable after release because they are part of the player's localization and event-choice history.
```csharp
protected string ModOptionKey(string pageName, string optionName);
protected string InitialOptionKey(string optionName);
protected LocString PageDescription(string pageName);
```
```json
{
"MY_MOD_EVENT_QUIET_DOOR.pages.INITIAL.description": "A quiet door waits in the wall.",
"MY_MOD_EVENT_QUIET_DOOR.pages.INITIAL.options.OPEN": "[Open] Step through.",
"MY_MOD_EVENT_QUIET_DOOR.pages.DONE.description": "The room is quiet again."
}
```
--------------------------------
### Example entry ID: MY_MOD_CARD_MY_STRIKE
Source: https://sts2-ritsulib.ritsukage.com/guide/content-authoring-toolkit
Example of the entry ID for a card named MyStrike in mod MyMod. The entry is used by saves, model IDs, localization keys, asset defaults, unlock rules, and cross-mod references, and should be treated as stable after release.
```text
MY_MOD_CARD_MY_STRIKE
```
--------------------------------
### Register FMOD Banks and GUID Mappings
Source: https://sts2-ritsulib.ritsukage.com/guide/fmod-and-audio
Register banks and GUID mappings before attempting to use their associated events.
```csharp
FmodStudioDeferredBankRegistration.RegisterBank("res://MyMod/audio/MyMod.bank");
FmodStudioDeferredBankRegistration.RegisterStudioGuidMappings("res://MyMod/audio/guid_map.json");
```
--------------------------------
### Successful response JSON
Source: https://sts2-ritsulib.ritsukage.com/guide/telemetry-backend
Successful response example. Return HTTP 200 or 202 with this JSON body.
```json
{
"ok": true,
"accepted": 2,
"rejected": 0,
"request_id": "optional-log-correlation-id"
}
```
--------------------------------
### Ancient dialogue with .sfx FMOD event
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Example JSON showing how to attach an FMOD event to a dialogue line by appending `.sfx` to the line key. The value is the FMOD event path.
```json
{
"MY_MOD_ANCIENT_MIRROR.talk.ANY.0-0r.ancient.sfx": "event:/sfx/ui/enchant_simple"
}
```
--------------------------------
### Register Card, Relic, and Character with Attributes
Source: https://sts2-ritsulib.ritsukage.com/guide/content-authoring-toolkit
Shows attribute registration on concrete model types. Abstract classes are skipped. The example registers a card, a relic, and a character using the corresponding attributes.
```csharp
[RegisterCard(typeof(MyCardPool))]
public sealed class MyStrike
: ModCardTemplate(1, CardType.Attack, CardRarity.Common, TargetType.SingleEnemy)
{
}
[RegisterRelic(typeof(MyRelicPool))]
public sealed class MyStarterRelic : ModRelicTemplate
{
}
[RegisterCharacter]
public sealed class MyCharacter
: ModCharacterTemplate
{
}
```
--------------------------------
### Chinese LocString JSON with DynamicVars placeholder
Source: https://sts2-ritsulib.ritsukage.com/guide/loc-string-placeholder-resolution
Chinese localization example for the same card, keeping the {damage} placeholder in the description.
```json
{
"MY_MOD_CARD_MY_STRIKE.title": "精准打击",
"MY_MOD_CARD_MY_STRIKE.description": "造成 {damage} 点伤害。"
}
```
--------------------------------
### Set lobby values before run start
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Writes lobby values before a run starts via the slot's Lobby accessor. Lobby values are committed into the run snapshot when the new run begins. If SyncLobbyOnChange is enabled, Set and Modify also push the local contribution in multiplayer.
```csharp
public static void SetLobbyChallenge(StartRunLobby lobby, string challengeId)
{
ChallengeData.Lobby.Modify(lobby, data =>
{
data.ChallengeId = challengeId;
});
}
public static void SetLocalLoadout(StartRunLobby lobby, string loadoutId)
{
PlayerData.Lobby.Modify(lobby, lobby.NetService.NetId, data =>
{
data.LoadoutId = loadoutId;
});
}
```
--------------------------------
### LocString JSON with DynamicVars placeholder
Source: https://sts2-ritsulib.ritsukage.com/guide/loc-string-placeholder-resolution
Example of a game localization JSON entry using the LocString pipeline. The description keeps the {damage} placeholder for a DynamicVars value.
```json
{
"MY_MOD_CARD_MY_STRIKE.title": "Measured Strike",
"MY_MOD_CARD_MY_STRIKE.description": "Deal {damage} damage."
}
```
--------------------------------
### Read and Write Data Store Settings
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Use this snippet to read and write settings in a data store. Get returns the live object, Modify mutates it, and Save() persists changes explicitly unless another layer calls Save() for you.
```csharp
var store = RitsuLibFramework.GetDataStore("MyMod");
var settings = store.Get("settings");
store.Modify("settings", data =>
{
data.Volume = 60;
});
store.Save("settings");
```
--------------------------------
### HarmonyIlRewriter TryInsertAfterFirst with pattern
Source: https://sts2-ritsulib.ritsukage.com/guide/patching-guide
Use for transpilers that must edit IL. The example inserts a call to overrideMethod after the first occurrence of a pattern, then validates the rewrite with report expectations and InstructionsChecked.
```csharp
var rewriter = HarmonyIlRewriter.From(instructions);
var pattern = HarmonyIlPattern.Sequence(
HarmonyIl.IsLdstr("prefix"),
HarmonyIl.IsLdloc(),
HarmonyIl.IsCall(concatMethod),
HarmonyIl.IsStloc());
var report = rewriter.TryInsertAfterFirst(
"MyPatch insert override",
pattern,
[
HarmonyIl.Ldarg(0),
HarmonyIl.Call(overrideMethod),
],
code => code.Any(instruction => HarmonyIl.IsCallTo(instruction, overrideMethod)));
report.RequireSucceeded();
if (report.Applied > 0)
report.RequireExactly(1);
return rewriter.InstructionsChecked("MyPatch insert override");
```
--------------------------------
### Batch payload JSON example (ritsulib.telemetry.batch.v1)
Source: https://sts2-ritsulib.ritsukage.com/guide/telemetry-backend
Shows the batch payload structure with schema, applicant_id, and events. The same JSON appears in both English and Chinese sections; backends should index properties first and store the full payload as JSON/blob. The payload may contain base_payload, private_contributions, shared_contributions, and applicant_payload; private contributions come from the applicant's own mod, shared contributions from another mod only after explicit consent.
```json
{
"schema": "ritsulib.telemetry.batch.v1",
"applicant_id": "author.some-mod",
"events": [
{
"schema": "ritsulib.telemetry.v1",
"applicantId": "author.some-mod",
"eventName": "exception",
"requestId": "diagnostics",
"category": "Diagnostics",
"timestampUtc": "2026-05-19T00:00:00Z",
"properties": {
"anonymous_install_id": "stable-anonymous-id",
"session_id": "process-session-id",
"ritsulib_version": "0.0.0",
"applicant_id": "author.some-mod",
"owner_mod_id": "author.some-mod",
"payload_kind": "exception",
"exception_type": "System.Exception"
},
"payload": {
"applicant_payload": {
"exception": {
"type": "System.Exception",
"message": "example",
"stack_trace": "..."
}
}
}
}
]
}
```
```json
{
"schema": "ritsulib.telemetry.batch.v1",
"applicant_id": "author.some-mod",
"events": [
{
"schema": "ritsulib.telemetry.v1",
"applicantId": "author.some-mod",
"eventName": "exception",
"requestId": "diagnostics",
"category": "Diagnostics",
"timestampUtc": "2026-05-19T00:00:00Z",
"properties": {
"anonymous_install_id": "stable-anonymous-id",
"session_id": "process-session-id",
"ritsulib_version": "0.0.0",
"applicant_id": "author.some-mod",
"owner_mod_id": "author.some-mod",
"payload_kind": "exception",
"exception_type": "System.Exception"
},
"payload": {
"applicant_payload": {
"exception": {
"type": "System.Exception",
"message": "example",
"stack_trace": "..."
}
}
}
}
]
}
```
--------------------------------
### Error response JSON
Source: https://sts2-ritsulib.ritsukage.com/guide/telemetry-backend
Error response example. Use a stable machine-readable error string.
```json
{
"error": "invalid_schema",
"message": "schema must be ritsulib.telemetry.batch.v1"
}
```
--------------------------------
### 事件 Key 构造示例 (C# 与 JSON)
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
Chinese version of the event key example. Uses the same C# helper methods and the same event entry MY_MOD_EVENT_QUIET_DOOR, but with Chinese localization values. Page and option names must remain stable after release because they are part of the localization contract and may affect event-choice records.
```csharp
protected string ModOptionKey(string pageName, string optionName);
protected string InitialOptionKey(string optionName);
protected LocString PageDescription(string pageName);
```
```json
{
"MY_MOD_EVENT_QUIET_DOOR.pages.INITIAL.description": "墙上有一扇安静的门。",
"MY_MOD_EVENT_QUIET_DOOR.pages.INITIAL.options.OPEN": "[打开] 走进去。",
"MY_MOD_EVENT_QUIET_DOOR.pages.DONE.description": "房间再次安静下来。"
}
```
--------------------------------
### Literal Single Energy Icon with energyIcons(1) (Chinese)
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Chinese localization of the literal single energy icon example. Uses the same vanilla extra argument energyIcons(1) to render a fixed single energy icon following the card pool color.
```json
{
"MY_CARD.description": "下一张牌耗能变为0{energyPrefix:energyIcons(1)}。"
}
```
--------------------------------
### Computed Energy, Star, and Power Icon Counts with Localization (Chinese)
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Chinese localization of the same computed icon variables example. The C# block is identical to the English version; the JSON block uses the Chinese description text with the same formatters.
```csharp
public override DynamicVarSet DynamicVars => new()
{
ModCardVars.ComputedEnergy("EnergyGain", 1, card => ResolveEnergyGain(card)),
ModCardVars.ComputedStars("StarGain", 1, card => ResolveStarGain(card)),
ModCardVars.ComputedPower("StrengthPower", 2, card => ResolveStrength(card)),
};
```
```json
{
"MY_CARD.description": "获得{EnergyGain:energyIcons()}。\n获得{StarGain:starIcons()}。\n获得{StrengthPower:diff()}点力量。"
}
```
--------------------------------
### Override AssetProfile with VisualCueSetBuilder
Source: https://sts2-ritsulib.ritsukage.com/guide/creature-visuals-and-animation
Use this override to define a VisualCueSet with the builder, allowing a still frame to hold briefly (e.g., 0.5 seconds for "attack") before the state machine returns to idle. The example defines "idle" and "attack" cues with a duration for the attack cue.
```csharp
public override CharacterAssetProfile AssetProfile => new()
{
VisualCues = VisualCueSetBuilder.Create()
.Single("idle", "res://MyMod/images/character/idle.png")
.Single("attack", "res://MyMod/images/character/attack.png", 0.5f)
.Build()
};
```
--------------------------------
### Override AssetProfile with VisualCueSet dictionary
Source: https://sts2-ritsulib.ritsukage.com/guide/creature-visuals-and-animation
Use this override to define a VisualCueSet with a dictionary mapping cue names to texture paths. This is suitable for non-Spine characters that can be described by named stills or frame sequences. The example maps "idle" and "hit" cues to PNG textures.
```csharp
public override CharacterAssetProfile AssetProfile => new()
{
VisualCues = new VisualCueSet(
texturePathByCue: new Dictionary
{
["idle"] = "res://MyMod/images/character/idle.png",
["hit"] = "res://MyMod/images/character/hit.png",
})
};
```
--------------------------------
### Implement ICustomTypeTextCard.GetTypeModifiers
Source: https://sts2-ritsulib.ritsukage.com/guide/content-authoring-toolkit
Implement ICustomTypeTextCard on a card to modify its own type plaque. Returns a collection of LocString entries; the example adds a localized string keyed to "my_mod_ui" and "card_type.cursed". RitsuLib discovers this implementation through the normal run/combat hook-listener order.
```csharp
IEnumerable ICustomTypeTextCard.GetTypeModifiers()
{
return
[
new LocString("my_mod_ui", "card_type.cursed"),
];
}
```
--------------------------------
### Register starter content using a content pack
Source: https://sts2-ritsulib.ritsukage.com/guide/character-and-unlock-scaffolding
Use a content pack for a centralized way to review and register starter lists, cards, and relics.
```csharp
RitsuLibFramework.CreateContentPack("MyMod")
.Character(c => c
.AddStartingRelic(1, order: 0)
.AddStartingCard(4, order: 10)
.AddStartingCard(4, order: 20))
.Card()
.Card()
.Relic()
.Apply();
```
--------------------------------
### Register Starter Card with Order
Source: https://sts2-ritsulib.ritsukage.com/guide/content-authoring-toolkit
Registers a starter card with an explicit Order. Starter lists are resolved by Order, then by registration order. Lower Order values run earlier within the same phase.
```csharp
[RegisterCard(typeof(MyCardPool))]
[RegisterCharacterStarterCard(typeof(MyCharacter), 4, Order = 10)]
public sealed class MyStrike : ModCardTemplate(1, CardType.Attack, CardRarity.Common, TargetType.SingleEnemy)
{
}
```
--------------------------------
### Register character and starter content with attributes
Source: https://sts2-ritsulib.ritsukage.com/guide/character-and-unlock-scaffolding
Register the character, starter cards, and starter relics using attributes. Ensure character names and pronouns are defined in characters.json.
```csharp
[RegisterCharacter]
public sealed class MyCharacter
: ModCharacterTemplate
{
}
[RegisterCard(typeof(MyCardPool))]
[RegisterCharacterStarterCard(typeof(MyCharacter), 4, Order = 10)]
public sealed class MyStrike : ModCardTemplate(1, CardType.Attack, CardRarity.Common, TargetType.SingleEnemy)
{
}
[RegisterRelic(typeof(MyRelicPool))]
[RegisterCharacterStarterRelic(typeof(MyCharacter), Order = 0)]
public sealed class MyStarterRelic : ModRelicTemplate
{
}
```
--------------------------------
### Batch Registration with Content Pack
Source: https://sts2-ritsulib.ritsukage.com/guide/getting-started
Use RitsuLibFramework.CreateContentPack to register multiple content types in a single batch.
```csharp
RitsuLibFramework.CreateContentPack("MyMod")
.Card()
.Relic()
.Apply();
```
--------------------------------
### Create and apply a content pack with RitsuLibFramework.CreateContentPack
Source: https://sts2-ritsulib.ritsukage.com/guide/content-authoring-toolkit
Use a content pack when a batch is more readable than scattered attributes. Registers cards, relics, potions, powers, act events, and a keyword owned by a localization namespace. Do not register the same model through both attributes and a content pack unless you intentionally accept duplicate handling being skipped; pick one source of truth per content family.
```csharp
RitsuLibFramework.CreateContentPack("MyMod")
.Card()
.Relic()
.Potion()
.Power()
.ActEvent()
.CardKeywordOwnedByLocNamespace("bleeding")
.Apply();
```
--------------------------------
### Register Data with BeginModDataRegistration
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Register a settings class inside BeginModDataRegistration so initialization happens after the batch is complete. Use classes rather than primitive values so you can add fields later without changing the storage slot.
```csharp
public sealed class MySettings
{
public bool Enabled { get; set; } = true;
public int Volume { get; set; } = 80;
}
using (RitsuLibFramework.BeginModDataRegistration("MyMod"))
{
var store = RitsuLibFramework.GetDataStore("MyMod");
store.Register(
key: "settings",
fileName: "settings.json",
scope: SaveScope.Global,
defaultFactory: () => new MySettings(),
autoCreateIfMissing: true);
}
```
--------------------------------
### SetupCustomCombatAnimationStateMachine with ModAnimStateMachines.Standard
Source: https://sts2-ritsulib.ritsukage.com/guide/creature-visuals-and-animation
Use when explicit animation state transitions are needed. Return null to let the normal vanilla animation path run. Merchant and rest-site state machines receive the same combat trigger names (Idle, Dead, Hit, Attack, Cast, Relaxed, Revive); world animation names are normalized before reaching the state machine.
```csharp
protected override ModAnimStateMachine? SetupCustomCombatAnimationStateMachine(
Node visualsRoot,
CharacterModel character)
{
return ModAnimStateMachines.Standard(
CompositeBackendFactory.FromNode(visualsRoot));
}
```
--------------------------------
### Initialize RitsuLib in Mod Entry
Source: https://sts2-ritsulib.ritsukage.com/guide/getting-started
Register your assembly for RitsuLib discovery using the ModInitializer attribute. EnsureGodotScriptsRegistered is only required if your mod uses C# scripts attached to .tscn scenes.
```csharp
using System.Reflection;
using MegaCrit.Sts2.Core.Logging;
using MegaCrit.Sts2.Core.Modding;
using STS2RitsuLib;
using STS2RitsuLib.Interop;
using STS2RitsuLib.Patching.Core;
[ModInitializer(nameof(Initialize))]
public static class MyModEntry
{
public const string ModId = "MyMod";
public static Logger Logger { get; private set; } = null!;
public static void Initialize()
{
var assembly = Assembly.GetExecutingAssembly();
Logger = RitsuLibFramework.CreateLogger(ModId);
ModTypeDiscoveryHub.RegisterModAssembly(ModId, assembly);
RitsuLibFramework.EnsureGodotScriptsRegistered(assembly, Logger);
var patcher = RitsuLibFramework.CreatePatcher(ModId, "main");
patcher.RegisterPatches();
RitsuLibFramework.ApplyRequiredPatcher(patcher, DisableMod);
}
private static void DisableMod()
{
// Mark your own mod disabled when a required patch cannot apply.
}
}
```
--------------------------------
### Generate Initial Options with InitialOptionKey
Source: https://sts2-ritsulib.ritsukage.com/guide/custom-events
Defines initial event options using a namespaced key. Keep page and option names stable after release to maintain the localization contract.
```csharp
protected override IReadOnlyList GenerateInitialOptions()
{
return
[
new EventOption(this, OpenDoor, InitialOptionKey("OPEN"))
];
}
```
--------------------------------
### Open log viewer from dev console
Source: https://sts2-ritsulib.ritsukage.com/guide/debug-log-viewer
Execute this command in the dev console to open the current session's log viewer. It is also available from the settings page.
```text
openlogviewer
```
--------------------------------
### Register Story and Epoch with Content Pack
Source: https://sts2-ritsulib.ritsukage.com/guide/timeline-and-unlocks
Registers the story and epoch using a content pack. Chains Story, Epoch, StoryEpoch, and ModEpochAutoTimelineSlotAfterColumn methods, then calls Apply().
```csharp
RitsuLibFramework.CreateContentPack("MyMod")
.Story()
.Epoch()
.StoryEpoch()
.ModEpochAutoTimelineSlotAfterColumn(EpochEra.Act1)
.Apply();
```
--------------------------------
### Implement Repeatable Payments
Source: https://sts2-ritsulib.ritsukage.com/guide/secondary-resources
Use SpendExtra to buy as many full stacks as possible for a repeatable payment.
```csharp
card.SecondaryResourceUses()
.Require("seven_stars", stars.Id, 7)
.SpendExtra(
"seven_stars_bonus",
stars.Id,
perStackAmount: 2,
maxStacks: null);
```
--------------------------------
### Cached Access with CreateCache
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Use this snippet to create a reusable accessor that invalidates itself after profile changes or data reloads. Do not keep a Get result as a long-lived cache because profile reloads may replace the root object.
```csharp
private static ModDataStoreCache Settings =
RitsuLibFramework.GetDataStore("MyMod").CreateCache("settings");
var settings = Settings.Value;
```
--------------------------------
### Implement IPatchMethod patch class with Harmony Postfix
Source: https://sts2-ritsulib.ritsukage.com/guide/patching-guide
Implements IPatchMethod for a strongly typed patch class. Declares PatchId, Description, IsCritical, targets, and a Harmony Postfix. Use new ModPatchTarget(type, methodName, parameterTypes) for overload disambiguation and ignoreIfMissing: true only for optional compatibility targets.
```csharp
public sealed class MyCombatPatch : IPatchMethod
{
public static string PatchId => "my_mod_combat_patch";
public static string Description => "Adjust combat start behavior";
public static bool IsCritical => true;
public static ModPatchTarget[] GetTargets() =>
[
new(typeof(CombatRoom), "OnEnter"),
];
public static void Postfix(CombatRoom __instance)
{
// Harmony postfix body.
}
}
```
--------------------------------
### Declare and use SavedAttachedState
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Use SavedAttachedState only for model objects that already pass through the game's SavedProperties serialization. The example declares a static readonly state with a default value of 0 and assigns a value to a card instance.
```csharp
private static readonly SavedAttachedState BonusDamage =
new("bonus_damage", () => 0);
BonusDamage[card] = 3;
```
--------------------------------
### Play One-Shot Audio with GameAudioService
Source: https://sts2-ritsulib.ritsukage.com/guide/fmod-and-audio
Use GameAudioService.Shared.PlayOneShot to trigger audio events with specific volume, parameters, and lifecycle scope.
```csharp
using STS2RitsuLib.Audio;
GameAudioService.Shared.PlayOneShot(
AudioSource.Event("event:/MyMod/ui/click"),
new AudioPlaybackOptions
{
Volume = 0.8f,
Parameters = FmodParameterMap.Set(("intensity", 1f)),
Scope = AudioLifecycleScope.Room,
});
```
--------------------------------
### Registering content with manifest arrays
Source: https://sts2-ritsulib.ritsukage.com/guide/content-packs-and-registries
Use manifest arrays when content is generated or split across multiple files. This approach allows for organized registration of cards, relics, and other content types.
```csharp
IContentRegistrationEntry[] content =
[
new CardRegistrationEntry(),
new RelicRegistrationEntry(),
];
RitsuLibFramework.CreateContentPack("MyMod")
.ContentManifest(content)
.KeywordManifest(keywordEntries)
.PackManifest(timelineAndUnlockEntries)
.Apply();
```
--------------------------------
### Register and use RunSavedData slots with lobby sync and preparing event
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Register run-scoped saved data slots early and keep keys stable. Use RunSavedData for a shared value and PlayerRunSavedData for per-player values keyed by net id. Access lobby values via the Lobby accessor; with SyncLobbyOnChange enabled, Set and Modify also push local contributions in multiplayer. Subscribe to RunSavedDataPreparingEvent to fill defaults before the new run snapshot is exported.
```csharp
using MegaCrit.Sts2.Core.Runs;
using MegaCrit.Sts2.Core.Multiplayer.Game.Lobby;
using STS2RitsuLib.RunData;
public sealed class ChallengeRunState
{
public string? ChallengeId { get; set; }
public int ElitesKilled { get; set; }
}
public sealed class PlayerRunState
{
public string? LoadoutId { get; set; }
}
private static RunSavedData ChallengeData = null!;
private static PlayerRunSavedData PlayerData = null!;
using (RitsuLibFramework.BeginModDataRegistration("MyMod"))
{
var runStore = RitsuLibFramework.GetRunSavedDataStore("MyMod");
ChallengeData = runStore.Register(
key: "challenge",
defaultFactory: () => new ChallengeRunState(),
options: new RunSavedDataOptions
{
WritePolicy = RunSavedDataWritePolicy.WhenNonDefault,
SyncLobbyOnChange = true,
});
PlayerData = runStore.RegisterPerPlayer(
key: "player",
defaultFactory: () => new PlayerRunState(),
options: new RunSavedDataOptions
{
SyncLobbyOnChange = true,
});
}
```
```csharp
public static void RecordEliteKilled(RunState runState)
{
ChallengeData.Modify(runState, data =>
{
data.ElitesKilled++;
});
}
public static string? GetPlayerLoadout(RunState runState, ulong netId)
{
return PlayerData.TryGet(runState, netId, out var data)
? data.LoadoutId
: null;
}
```
```csharp
public static void SetLobbyChallenge(StartRunLobby lobby, string challengeId)
{
ChallengeData.Lobby.Modify(lobby, data =>
{
data.ChallengeId = challengeId;
});
}
public static void SetLocalLoadout(StartRunLobby lobby, string loadoutId)
{
PlayerData.Lobby.Modify(lobby, lobby.NetService.NetId, data =>
{
data.LoadoutId = loadoutId;
});
}
```
```csharp
RitsuLibFramework.SubscribeLifecycle(evt =>
{
ChallengeData.Modify(evt.RunState, data =>
{
data.ChallengeId ??= "standard";
});
});
```
--------------------------------
### Register a mod update check with Steam Workshop skip using SkipModUpdateCheckWhenLoadedFromSteamWorkshop
Source: https://sts2-ritsulib.ritsukage.com/guide/update-checks
Use for mods also distributed through Steam Workshop. Wrap the options with SkipModUpdateCheckWhenLoadedFromSteamWorkshop and pass the mod's Workshop item id. This keeps local/manual installs on the external update source, while that exact Workshop item is left to Steam's Workshop update flow.
```csharp
RitsuLibFramework.RegisterModUpdateCheck(
RitsuLibFramework.SkipModUpdateCheckWhenLoadedFromSteamWorkshop(new()
{
ModId = MyModConst.ModId,
DisplayName = MyModConst.Name,
CurrentVersion = MyModConst.Version,
ManifestUri = new("https://example.com/my-mod/update.json"),
ReleasePageUri = new("https://example.com/my-mod/releases"),
}, typeof(MyModPlugin).Assembly, 1234567890));
```
--------------------------------
### Register Trash Heap Content with Content-Pack Builder
Source: https://sts2-ritsulib.ritsukage.com/guide/custom-events
Use the RitsuLibFramework content-pack builder to register your custom cards and relics to the Trash Heap.
```csharp
RitsuLibFramework.CreateContentPack("MyMod")
.TrashHeapCard()
.TrashHeapRelic()
.Apply();
```
--------------------------------
### Subscribe and Dispose CombatStartingEvent subscription
Source: https://sts2-ritsulib.ritsukage.com/guide/lifecycle-events
Use this C# snippet to subscribe to a lifecycle event and dispose the subscription within the handler when the handler is temporary. The handler calls PrepareForCombat with the run state and then disposes the subscription.
```csharp
RitsuLibFramework.SubscribeLifecycle((evt, sub) =>
{
PrepareForCombat(evt.RunState);
sub.Dispose();
});
```
--------------------------------
### Define CharacterAssetProfile
Source: https://sts2-ritsulib.ritsukage.com/guide/asset-profiles-and-fallbacks
Configures character assets by grouping them into Scenes, UI, and Audio categories. Use explicit res:// paths for mod-specific assets.
```csharp
public override CharacterAssetProfile AssetProfile => new()
{
Scenes = new()
{
VisualsPath = "res://MyMod/scenes/characters/my_character_visuals.tscn",
EnergyCounterPath = "res://MyMod/scenes/ui/my_energy_counter.tscn",
},
Ui = new()
{
CharacterSelectIconPath = "res://MyMod/images/character/select_icon.png",
MapMarkerPath = "res://MyMod/images/character/map_marker.png",
},
Audio = new()
{
AttackSfx = "event:/MyMod/character_attack",
},
};
```
--------------------------------
### ComputedDynamicVar with preview factory
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Use preview logic when card preview, target preview, or upgrade preview should show a value different from the current live card value. The preview factory is passed as the fourth argument to ComputedDynamicVar.
```csharp
ModCardVars.Computed(
"damage",
Damage,
(card, target) => ResolveDamage(card, target),
(card, mode, target, runGlobalHooks) => ResolvePreviewDamage(card, mode, target));
```
--------------------------------
### Resolve Shortfalls with Backup Sources
Source: https://sts2-ritsulib.ritsukage.com/guide/secondary-resources
Use a resolver in RequireAllowingShortfall to cover missing resources from an alternative source.
```csharp
card.SecondaryResourceUses()
.RequireAllowingShortfall(
"seven_stars",
stars.Id,
7,
resolveShortfall: ctx =>
{
var backup = SecondaryResourceCmd.Get(ctx.Player, backupStars.Id);
if (backup < ctx.Shortfall)
return SecondaryResourceShortfallResolution.None;
return SecondaryResourceShortfallResolution.Cover(
ctx.Shortfall,
async commit =>
{
await SecondaryResourceCmd.Spend(
commit.Player,
backupStars.Id,
commit.CoveredShortfall,
commit.Card,
commit.Source);
});
},
onShortfall: async ctx =>
{
// Runs only for any remaining amount not covered by the replacement payment.
await ApplyShortfallPenalty(ctx.Card, ctx.Shortfall);
});
```
--------------------------------
### 本地化卡牌关键词标题和描述(含能量图标语法)
Source: https://sts2-ritsulib.ritsukage.com/guide/localization-and-keywords
中文版:卡牌关键词文本写入 card_keywords。描述可以使用跟随当前角色的标准能量图标语法 {energyPrefix:energyIcons(1)}。关键词悬浮提示不绑定具体卡牌实例,因此不能使用 {Damage}、{Block} 等卡牌动态变量。
```json
{
"MY_MOD_KEYWORD_BLEEDING.title": "流血",
"MY_MOD_KEYWORD_BLEEDING.description": "回合结束时失去生命。"
}
```
```json
{
"MY_MOD_KEYWORD_BLEEDING.description": "下一张牌的耗能为 0{energyPrefix:energyIcons(1)}。"
}
```
--------------------------------
### Context factory for ComputedDamage with static ctx
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Use the context overload when one value depends on several pieces of card, run, combat, preview, or target state. The same ComputedDynamicVarContext is used for live values and previews. Prefer static factories because dynamic variables are cloned with their cards and a static factory cannot accidentally retain the card instance from which the variable was created.
```csharp
public override DynamicVarSet DynamicVars => new()
{
ModCardVars.Int("Heat", Heat),
ModCardVars.ComputedDamage(
"Damage",
static ctx =>
{
var heat = ctx.GetCardIntOrDefault("Heat");
var targetBonus = ctx.HasTarget && ctx.IsInCombat
? ResolveTargetBonus(ctx.CombatState, ctx.Target)
: 0m;
return ctx.BaseValue + heat + targetBonus + (ctx.IsUpgraded ? 3m : 0m);
},
baseValue: 6),
};
```
--------------------------------
### Computed Energy, Star, and Power Icon Counts with Localization (English)
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Defines computed icon variables for energy, stars, and power in a card's DynamicVars, then uses them in the English localization with the corresponding icon formatters. ComputedPower keeps the typed PowerVar shape and does not run power-amount hooks by default; use ComputedPowerAmountGiven when the computed amount should follow the vanilla preview hook path.
```csharp
public override DynamicVarSet DynamicVars => new()
{
ModCardVars.ComputedEnergy("EnergyGain", 1, card => ResolveEnergyGain(card)),
ModCardVars.ComputedStars("StarGain", 1, card => ResolveStarGain(card)),
ModCardVars.ComputedPower("StrengthPower", 2, card => ResolveStrength(card)),
};
```
```json
{
"MY_CARD.description": "Gain {EnergyGain:energyIcons()}.\nGain {StarGain:starIcons()}.\nGain {StrengthPower:diff()} Strength."
}
```
--------------------------------
### Register Combat UI with Feedback
Source: https://sts2-ritsulib.ritsukage.com/guide/secondary-resources
Registers a custom combat UI node and defines update and change handlers to provide visual feedback when resource amounts change.
```csharp
resources.RegisterCombatUi(
"charge_feedback",
parent => new MyChargeFeedbackNode(),
update: ctx => ctx.Node.Refresh(ctx.Player, ctx.VisibleDefinitions),
changed: ctx =>
{
if (ctx.Definition.Id == charge.Id && ctx.Delta > 0)
ctx.Node.PlayGainFeedback(ctx.Delta, ctx.Source);
});
```
--------------------------------
### Create custom card pile with CardPileOwned and ExtraHand style
Source: https://sts2-ritsulib.ritsukage.com/guide/shell-theme
Creates a custom card pile with an ExtraHand style. Use LayoutResolver for per-card transforms and OnCardVisualCreated/OnCardArrived for custom animations. FlightTargetPositionResolver and FlightStartPositionResolver customize vanilla flight endpoints. When an extra-hand card is manually played, RitsuLib bridges it through the backend vanilla hand; canceled targeting or queued actions restore the card to its source pile. Use stable local stems and add generated pile id .title, .description, and .empty entries to static_hover_tips.json.
```csharp
RitsuLibFramework.CreateContentPack("MyMod")
.CardPileOwned("archive", new ModCardPileSpec
{
Style = ModCardPileUiStyle.ExtraHand,
Anchor = ModCardPileAnchor.AtCenter(new Vector2(260f, 520f)),
CardShouldBeVisible = true,
ExtraHand = new ModCardPileExtraHandSpec
{
Direction = ModExtraHandLayoutDirection.Vertical,
Spacing = 86f,
CardScale = Vector2.One * 0.55f,
HoverScale = Vector2.One,
ShowPlayableGlow = true,
AllowCardPlay = true,
},
})
.Apply();
```
--------------------------------
### Define Optional Resource Uses
Source: https://sts2-ritsulib.ritsukage.com/guide/secondary-resources
Use SpendIfAvailable to define optional payments that do not block card play.
```csharp
card.SecondaryResourceUses()
.SpendIfAvailable("bonus_charge", charge.Id, 2);
```
--------------------------------
### ComputedDamage with preview base factory
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Pass a preview base factory when the preview base amount differs from the live amount. The result still goes through normal damage or block hooks.
```csharp
ModCardVars.ComputedDamage(
"damage",
6,
(card, target) => ResolveDamage(card, target),
(card, mode, target, runGlobalHooks) => ResolvePreviewDamageBase(card, mode, target));
```
--------------------------------
### Declare Runtime Dependency (Legacy String Form)
Source: https://sts2-ritsulib.ritsukage.com/guide/getting-started
Use this legacy string form for older game API branches where dependency objects may fail.
```json
{
"dependencies": [
"STS2-RitsuLib"
]
}
```
--------------------------------
### Subscribe to GameReadyEvent with RitsuLibFramework.SubscribeLifecycle
Source: https://sts2-ritsulib.ritsukage.com/guide/lifecycle-events
Use this C# snippet to subscribe to a lifecycle event when a mod needs game timing but does not need to own a Harmony patch. The handler receives the event and logs the game name. Dispose the returned subscription when the handler is temporary.
```csharp
var subscription = RitsuLibFramework.SubscribeLifecycle(evt =>
{
Logger.Info(
$"Game ready: {evt.Game.Name}");
});
```
--------------------------------
### Create ModSettingsValueBinding and ProjectedModSettingsValueBinding
Source: https://sts2-ritsulib.ritsukage.com/guide/mod-settings
Creates a settings binding for a mod data store and a projected binding for a sub-value. The projected binding wraps the parent binding to edit the 'volume' field.
```csharp
var settingsBinding = new ModSettingsValueBinding(
"MyMod", "settings", SaveScope.Global, s => s, (_, value) => value);
var volumeBinding = new ProjectedModSettingsValueBinding(
settingsBinding,
"volume",
s => s.Volume,
(s, value) => { s.Volume = value; return s; });
```
--------------------------------
### Read dynamic variables safely with GetIntOrDefault, HasPositiveValue, GetRequired, EvaluateValueOrDefault
Source: https://sts2-ritsulib.ritsukage.com/guide/card-dynamic-var-toolkit
Shows safe extension helpers for reading dynamic variables from another card. Use GetIntOrDefault for optional integers, HasPositiveValue for boolean checks, GetRequired when absence is a content error, and EvaluateValueOrDefault to accept fixed or computed variables. The ...OrDefault and Try... helpers do not throw for missing keys; required helpers throw descriptive exceptions.
```csharp
var amount = card.DynamicVars.GetIntOrDefault("damage");
var hasHeat = card.DynamicVars.HasPositiveValue("heat");
var damageVar = card.DynamicVars.GetRequired("Damage");
var displayedValue = card.DynamicVars.EvaluateValueOrDefault("ComputedDamage", target: target);
```
--------------------------------
### Register Data Store with Migrations
Source: https://sts2-ritsulib.ritsukage.com/guide/persistence-guide
Use this snippet to register a data store with migration support before publishing a breaking data shape. Keep fileName and key stable after release; change the schema version when the JSON shape changes in a way old files cannot deserialize directly.
```csharp
store.Register(
"settings",
"settings.json",
SaveScope.Global,
defaultFactory: () => new MySettings(),
migrationConfig: new ModDataMigrationConfig(
currentDataVersion: 2,
minimumSupportedDataVersion: 1),
migrations:
[
new SettingsV1ToV2Migration(),
]);
```
--------------------------------
### Show basic toast notifications
Source: https://sts2-ritsulib.ritsukage.com/guide/shell-theme
Use RitsuToastService to display info, warning, or error messages that do not interrupt gameplay.
```csharp
RitsuToastService.ShowInfo("Settings saved.", "My Mod");
RitsuToastService.ShowWarning("Optional bank failed to load.", "My Mod");
RitsuToastService.ShowError("Required setup failed.", "My Mod");
```