Skip to content

Authoring a Component Package

mewcodex edited this page Sep 2, 2026 · 2 revisions

Authoring a Component Package

English | 简体中文

This page builds the smallest useful external catalog. A production integration should derive its recipes from a manually reviewed decomposition of the character's native cards.

1. Define a structured component

using ChaosCardGenerator;

var enterCalmSpec = new OperationRuntimeSpec(
    OperationRuntimeSpec.CurrentSchemaVersion,
    Opcode: "watcher_stance",
    Variant: "enter_calm",
    Target: "self",
    SourceZone: "none",
    DestinationZone: "none",
    CardFilter: "any",
    Flags: ["api_scalable_reward", "api_power_foundation"],
    Values: [new RuntimeValueSlot("amount", 1)]);

var enterCalm = new ComponentAtom(
    Template: "watcher:enter_calm",
    Scope: OperationScope.NonTargeted,
    ChineseText: "进入平静。",
    RequiresSingleTarget: false,
    CardReference: CardReferenceRequirement.None)
{
    SemanticId = "watcher/enter_calm/0",
    RuntimeSpec = enterCalmSpec
};

Opcode, Variant, flags, value-slot IDs, condition IDs, and trigger IDs must be lowercase ASCII identifiers. Template, profile, package, and semantic IDs must be stable non-empty ASCII strings. Never encode runtime decisions in ChineseText or EnglishText.

Useful API flags:

  • api_scalable_reward: numeric generation may scale this positive effect.
  • api_power_foundation: the effect may establish a valid Power card.
  • api_negative: classify the operation as a downside when custom pricing is handled elsewhere.

2. Build native recipes and a catalog

var recipe = new IroncladCardRecipe(
    Id: "WatcherCalmExample",
    ChineseTitle: "静心",
    Cost: 1,
    Type: GeneratedCardType.Skill,
    Target: TargetMode.Other,
    OriginalRarity: GeneratedRarity.Common,
    Tags: [],
    Atoms: [enterCalm],
    TriggerOwners: [-1],
    EnglishTitle: "Centering");

var catalog = new ImmutableComponentCatalog(
    GeneratedCharacter.Regent,
    [recipe]);

TriggerOwners[i] is -1 for a top-level operation or the zero-based index of the trigger that owns operation i. Every recipe atom must also exist in the component catalog. Semantic IDs must be unique.

3. Create and register the generation profile

const string profileId = "my_watcher:autoanthony";
var request = new ComponentProfileRequest(
    profileId,
    GeneratedCharacter.Regent, // balance/legality archetype only
    unlockComponentRoles: false);

var profile = new ComponentGenerationProfile(
    id: profileId + ":normal",
    character: GeneratedCharacter.Regent,
    unlockComponentRoles: false,
    shellCatalog: catalog,
    componentCatalog: catalog,
    nameCatalog: catalog,
    occurrenceFactory: () => ComponentApi.CreateNativeOccurrencePolicy(catalog),
    valuePolicy: ComponentApi.DefaultValuePolicy);

ComponentPackageApi.Register(new ComponentPackageRegistration(
    PackageId: "my_watcher:autoanthony:components",
    Request: request,
    Profile: profile,
    LocalizedTexts:
    [
        new ComponentLocalizedText(
            "watcher:enter_calm",
            "进入平静。",
            "Enter Calm.")
    ],
    Valuations:
    [
        new ComponentValuationRegistration(
            "watcher_stance",
            "enter_calm",
            new EnterCalmValuation())
    ]));

shellCatalog controls source shell distributions, componentCatalog controls selectable mechanics, and nameCatalog controls source names. They may be different catalogs. The occurrence factory must return a fresh, pool-scoped policy instance.

4. Price the effect

sealed class EnterCalmValuation : IComponentValuation
{
    public int Estimate(ComponentValuationContext context) => 500;
}

Values are hundredths of one point of ordinary single-target damage: 500 means five damage-equivalent. Use context.Value("slot") or FirstExplicitFixedValue() for rolled values. See API Reference for downside pricing and multiplicity metadata.

5. Generate cards

var generator = new RandomCardGenerator(request, stableIntegerSeed, balancedValues: true);
GeneratedCard card = generator.Generate(GeneratedRarity.Common);

The owning mod chooses seed derivation and rarity counts. Use a stable hash such as SHA-256; never use string.GetHashCode() for multiplayer or persisted seeds.

Clone this wiki locally