Skip to content

Authoring a Component Package

mewcodex edited this page Sep 6, 2026 · 3 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: [ComponentSemanticFlags.Beneficial,
        ComponentSemanticFlags.ScalableReward,
        ComponentSemanticFlags.PowerFoundation],
    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 semantic flags include Beneficial, Negative, Restricted, EnemyDamage, PowerFoundation, ScalableReward, and SelfCardMovement. Use the constants on ComponentSemanticFlags; do not infer these facts from a Template name or localized sentence.

  • 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.

For a reusable trigger/payoff pair, keep both atoms independent and express only the edge in TriggerOwners:

var triggeredRecipe = recipe with
{
    Atoms = [afterEvent, reusablePayoff],
    TriggerOwners = [-1, 0]
};

The payoff retains its immediate RuntimeSpec and per-unit valuation. The trigger's relative cadence scales it during whole-card valuation. A compatible event-target trigger may supply the enemy for a reusable targeted payoff such as T:Poison. Do not encode “that enemy” as a second wording-specific effect.

Do not split a transaction that must capture a selected card, target, current value, or future choice before firing. Also keep nonlinear count conversions atomic when neither half has a stable standalone value—for example, Target-Vulnerable-to-Strength.

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,
    Localizations:
    [
        new ComponentLocalizationRegistration(
            "watcher/enter_calm/0",
            "进入平静。",
            "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.

For numeric or named entities, use named placeholders such as [[amount]], [[damage]], [[energy:energy]], or [[token]] in ComponentLocalizationRegistration. Every placeholder must bind to a RuntimeSpec value/text slot. LocalizedTexts remains only for API v2 source compatibility.

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