feat(engine): mechanic effect primitives (absorb/reflect/range/block/promotion)
T3 Wave 2 batch B. Implements 5 game-mechanic primitives that seed
specs the engine's existing pipelines (T22 will wire) consume:
- absorb-damage-with-attribute: seeds {AbsorbDamageAttr, AbsorbDamageRate}.
- reflect-damage: seeds ReflectDamagePercent (0-100, validated).
- modify-movement-range: composes with T1's range-bonus by reading
existing RangeBonus (default 0) and writing existing+delta.
- block-move-type: appends a move-type filter into BlockedMoveTypes.
- override-promotion: writes the canonical PromotionOverride (mirrors
T1's promotion-override descriptor exactly).
ChessAttrMap gains AbsorbDamageAttr, AbsorbDamageRate, ReflectDamagePercent,
and BlockedMoveTypes for the new fact namespaces. Engine integration
deferred to T22; this batch is data-seeding only.
All 5 register in PRIMITIVE_REGISTRY via side-effect import. Each
primitive ships with ≥3 vitest scenarios.
This commit is contained in:
parent
d17f4bd1fd
commit
b93b4b7322
10 changed files with 470 additions and 0 deletions
|
|
@ -0,0 +1,76 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Session, type EntityId } from "@paratype/rete";
|
||||
import { ChessEngine } from "../../engine.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import { ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE } from "./absorb-damage-with-attribute.js";
|
||||
|
||||
function makeContext(session: Session, pieceId: EntityId) {
|
||||
return {
|
||||
engine: new ChessEngine(),
|
||||
session,
|
||||
pieceId,
|
||||
depth: 0,
|
||||
descriptor: {
|
||||
id: "test-descriptor",
|
||||
type: "data" as const,
|
||||
version: 1 as const,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE", () => {
|
||||
it("registers in PRIMITIVE_REGISTRY", () => {
|
||||
expect(PRIMITIVE_REGISTRY.has("absorb-damage-with-attribute")).toBe(true);
|
||||
expect(PRIMITIVE_REGISTRY.get("absorb-damage-with-attribute")).toBe(
|
||||
ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE,
|
||||
);
|
||||
});
|
||||
|
||||
it("writes both AbsorbDamageAttr and AbsorbDamageRate facts", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "pawn");
|
||||
|
||||
const params = ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.paramsSchema.parse({
|
||||
attr: "ShieldCharges",
|
||||
rate: 2,
|
||||
});
|
||||
ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.apply(makeContext(session, pieceId), params);
|
||||
|
||||
expect(session.get(pieceId, "AbsorbDamageAttr")).toBe("ShieldCharges");
|
||||
expect(session.get(pieceId, "AbsorbDamageRate")).toBe(2);
|
||||
});
|
||||
|
||||
it("rejects non-positive rate values", () => {
|
||||
expect(() => {
|
||||
ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.paramsSchema.parse({
|
||||
attr: "ShieldCharges",
|
||||
rate: 0,
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.paramsSchema.parse({
|
||||
attr: "ShieldCharges",
|
||||
rate: -1,
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("is idempotent when applying the same params twice", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "pawn");
|
||||
|
||||
const params = ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.paramsSchema.parse({
|
||||
attr: "ShieldCharges",
|
||||
rate: 3,
|
||||
});
|
||||
|
||||
ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.apply(makeContext(session, pieceId), params);
|
||||
ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE.apply(makeContext(session, pieceId), params);
|
||||
|
||||
expect(session.get(pieceId, "AbsorbDamageAttr")).toBe("ShieldCharges");
|
||||
expect(session.get(pieceId, "AbsorbDamageRate")).toBe(3);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { z } from "zod";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import type { EffectPrimitive, PrimitiveApplyContext } from "./types.js";
|
||||
|
||||
const schema = z.object({
|
||||
attr: z.string(),
|
||||
rate: z.number().int().positive(),
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof schema>;
|
||||
|
||||
const descriptor: EffectPrimitive<Params> = {
|
||||
kind: "absorb-damage-with-attribute",
|
||||
label: "Absorb Damage with Attribute",
|
||||
description:
|
||||
"Seed absorb-damage facts so damage can consume an attribute before HP.",
|
||||
paramsSchema: schema,
|
||||
apply(ctx: PrimitiveApplyContext, params: Params): void {
|
||||
ctx.session.insert(ctx.pieceId, "AbsorbDamageAttr", params.attr);
|
||||
ctx.session.insert(ctx.pieceId, "AbsorbDamageRate", params.rate);
|
||||
},
|
||||
};
|
||||
|
||||
PRIMITIVE_REGISTRY.register(descriptor);
|
||||
|
||||
export { descriptor as ABSORB_DAMAGE_WITH_ATTRIBUTE_PRIMITIVE };
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Session, type EntityId } from "@paratype/rete";
|
||||
import { ChessEngine } from "../../engine.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import { BLOCK_MOVE_TYPE_PRIMITIVE } from "./block-move-type.js";
|
||||
|
||||
function makeContext(session: Session, pieceId: EntityId) {
|
||||
return {
|
||||
engine: new ChessEngine(),
|
||||
session,
|
||||
pieceId,
|
||||
depth: 0,
|
||||
descriptor: {
|
||||
id: "test-descriptor",
|
||||
type: "data" as const,
|
||||
version: 1 as const,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("BLOCK_MOVE_TYPE_PRIMITIVE", () => {
|
||||
it("registers in PRIMITIVE_REGISTRY", () => {
|
||||
expect(PRIMITIVE_REGISTRY.has("block-move-type")).toBe(true);
|
||||
expect(PRIMITIVE_REGISTRY.get("block-move-type")).toBe(BLOCK_MOVE_TYPE_PRIMITIVE);
|
||||
});
|
||||
|
||||
it("blocks one move type", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "rook");
|
||||
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.paramsSchema.parse({ moveType: "capture" }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "BlockedMoveTypes")).toEqual(["capture"]);
|
||||
});
|
||||
|
||||
it("composes unique types across multiple applies", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "bishop");
|
||||
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.paramsSchema.parse({ moveType: "step" }),
|
||||
);
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.paramsSchema.parse({ moveType: "slide" }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "BlockedMoveTypes")).toEqual(["step", "slide"]);
|
||||
});
|
||||
|
||||
it("is idempotent when the same move type is applied repeatedly", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "queen");
|
||||
|
||||
const params = BLOCK_MOVE_TYPE_PRIMITIVE.paramsSchema.parse({ moveType: "capture" });
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.apply(makeContext(session, pieceId), params);
|
||||
BLOCK_MOVE_TYPE_PRIMITIVE.apply(makeContext(session, pieceId), params);
|
||||
|
||||
expect(session.get(pieceId, "BlockedMoveTypes")).toEqual(["capture"]);
|
||||
});
|
||||
});
|
||||
32
packages/chess/src/modifiers/primitives/block-move-type.ts
Normal file
32
packages/chess/src/modifiers/primitives/block-move-type.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { z } from "zod";
|
||||
import type { MoveType } from "../../schema.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import type { EffectPrimitive, PrimitiveApplyContext } from "./types.js";
|
||||
|
||||
const moveTypeSchema = z.enum(["capture", "step", "slide"]);
|
||||
const schema = z.object({
|
||||
moveType: moveTypeSchema,
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof schema>;
|
||||
|
||||
const descriptor: EffectPrimitive<Params> = {
|
||||
kind: "block-move-type",
|
||||
label: "Block Move Type",
|
||||
description: "Seed blocked move types for deferred move-filter integration.",
|
||||
paramsSchema: schema,
|
||||
apply(ctx: PrimitiveApplyContext, params: Params): void {
|
||||
const existingRaw = ctx.session.contains(ctx.pieceId, "BlockedMoveTypes")
|
||||
? ctx.session.get(ctx.pieceId, "BlockedMoveTypes")
|
||||
: [];
|
||||
const existing: MoveType[] = Array.isArray(existingRaw)
|
||||
? existingRaw.filter((v): v is MoveType => moveTypeSchema.safeParse(v).success)
|
||||
: [];
|
||||
const next = existing.includes(params.moveType) ? existing : [...existing, params.moveType];
|
||||
ctx.session.insert(ctx.pieceId, "BlockedMoveTypes", next);
|
||||
},
|
||||
};
|
||||
|
||||
PRIMITIVE_REGISTRY.register(descriptor);
|
||||
|
||||
export { descriptor as BLOCK_MOVE_TYPE_PRIMITIVE };
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Session, type EntityId } from "@paratype/rete";
|
||||
import { ChessEngine } from "../../engine.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import { MODIFY_MOVEMENT_RANGE_PRIMITIVE } from "./modify-movement-range.js";
|
||||
|
||||
function makeContext(session: Session, pieceId: EntityId) {
|
||||
return {
|
||||
engine: new ChessEngine(),
|
||||
session,
|
||||
pieceId,
|
||||
depth: 0,
|
||||
descriptor: {
|
||||
id: "test-descriptor",
|
||||
type: "data" as const,
|
||||
version: 1 as const,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("MODIFY_MOVEMENT_RANGE_PRIMITIVE", () => {
|
||||
it("registers in PRIMITIVE_REGISTRY", () => {
|
||||
expect(PRIMITIVE_REGISTRY.has("modify-movement-range")).toBe(true);
|
||||
expect(PRIMITIVE_REGISTRY.get("modify-movement-range")).toBe(
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE,
|
||||
);
|
||||
});
|
||||
|
||||
it("treats absent RangeBonus as 0 before applying delta", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "bishop");
|
||||
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.paramsSchema.parse({ delta: 2 }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "RangeBonus")).toBe(2);
|
||||
});
|
||||
|
||||
it("adds delta to existing RangeBonus", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "rook");
|
||||
session.insert(pieceId, "RangeBonus", 3);
|
||||
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.paramsSchema.parse({ delta: -1 }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "RangeBonus")).toBe(2);
|
||||
});
|
||||
|
||||
it("accumulates when applied multiple times", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "queen");
|
||||
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.paramsSchema.parse({ delta: 1 }),
|
||||
);
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
MODIFY_MOVEMENT_RANGE_PRIMITIVE.paramsSchema.parse({ delta: 2 }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "RangeBonus")).toBe(3);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { z } from "zod";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import type { EffectPrimitive, PrimitiveApplyContext } from "./types.js";
|
||||
|
||||
const schema = z.object({
|
||||
delta: z.number().int().min(-7).max(7),
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof schema>;
|
||||
|
||||
const descriptor: EffectPrimitive<Params> = {
|
||||
kind: "modify-movement-range",
|
||||
label: "Modify Movement Range",
|
||||
description: "Additively contributes to the existing RangeBonus attribute.",
|
||||
paramsSchema: schema,
|
||||
apply(ctx: PrimitiveApplyContext, params: Params): void {
|
||||
const existing = ctx.session.contains(ctx.pieceId, "RangeBonus")
|
||||
? ctx.session.get(ctx.pieceId, "RangeBonus")
|
||||
: 0;
|
||||
const baseline = typeof existing === "number" ? existing : 0;
|
||||
ctx.session.insert(ctx.pieceId, "RangeBonus", baseline + params.delta);
|
||||
},
|
||||
};
|
||||
|
||||
PRIMITIVE_REGISTRY.register(descriptor);
|
||||
|
||||
export { descriptor as MODIFY_MOVEMENT_RANGE_PRIMITIVE };
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Session, type EntityId } from "@paratype/rete";
|
||||
import { ChessEngine } from "../../engine.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import { OVERRIDE_PROMOTION_PRIMITIVE } from "./override-promotion.js";
|
||||
|
||||
function makeContext(session: Session, pieceId: EntityId) {
|
||||
return {
|
||||
engine: new ChessEngine(),
|
||||
session,
|
||||
pieceId,
|
||||
depth: 0,
|
||||
descriptor: {
|
||||
id: "test-descriptor",
|
||||
type: "data" as const,
|
||||
version: 1 as const,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("OVERRIDE_PROMOTION_PRIMITIVE", () => {
|
||||
it("registers in PRIMITIVE_REGISTRY", () => {
|
||||
expect(PRIMITIVE_REGISTRY.has("override-promotion")).toBe(true);
|
||||
expect(PRIMITIVE_REGISTRY.get("override-promotion")).toBe(OVERRIDE_PROMOTION_PRIMITIVE);
|
||||
});
|
||||
|
||||
it("writes PromotionOverride with the requested target", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "pawn");
|
||||
|
||||
OVERRIDE_PROMOTION_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
OVERRIDE_PROMOTION_PRIMITIVE.paramsSchema.parse({ target: "queen" }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "PromotionOverride")).toBe("queen");
|
||||
});
|
||||
|
||||
it("is last-wins when re-applied", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "pawn");
|
||||
|
||||
OVERRIDE_PROMOTION_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
OVERRIDE_PROMOTION_PRIMITIVE.paramsSchema.parse({ target: "knight" }),
|
||||
);
|
||||
OVERRIDE_PROMOTION_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
OVERRIDE_PROMOTION_PRIMITIVE.paramsSchema.parse({ target: "bishop" }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "PromotionOverride")).toBe("bishop");
|
||||
});
|
||||
|
||||
it("accepts canonical PieceType values from schema", () => {
|
||||
expect(() => OVERRIDE_PROMOTION_PRIMITIVE.paramsSchema.parse({ target: "pawn" })).not.toThrow();
|
||||
expect(() => OVERRIDE_PROMOTION_PRIMITIVE.paramsSchema.parse({ target: "king" })).not.toThrow();
|
||||
expect(() => OVERRIDE_PROMOTION_PRIMITIVE.paramsSchema.parse({ target: "queen" })).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { z } from "zod";
|
||||
import type { PieceType } from "../../schema.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import type { EffectPrimitive, PrimitiveApplyContext } from "./types.js";
|
||||
|
||||
const schema = z.object({
|
||||
target: z.enum(["pawn", "knight", "bishop", "rook", "queen", "king"]),
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof schema>;
|
||||
|
||||
const descriptor: EffectPrimitive<Params> = {
|
||||
kind: "override-promotion",
|
||||
label: "Override Promotion",
|
||||
description: "Write PromotionOverride directly to enforce a promotion target.",
|
||||
paramsSchema: schema,
|
||||
apply(ctx: PrimitiveApplyContext, params: Params): void {
|
||||
const target: PieceType = params.target;
|
||||
ctx.session.insert(ctx.pieceId, "PromotionOverride", target);
|
||||
},
|
||||
};
|
||||
|
||||
PRIMITIVE_REGISTRY.register(descriptor);
|
||||
|
||||
export { descriptor as OVERRIDE_PROMOTION_PRIMITIVE };
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { Session, type EntityId } from "@paratype/rete";
|
||||
import { ChessEngine } from "../../engine.js";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import { REFLECT_DAMAGE_PRIMITIVE } from "./reflect-damage.js";
|
||||
|
||||
function makeContext(session: Session, pieceId: EntityId) {
|
||||
return {
|
||||
engine: new ChessEngine(),
|
||||
session,
|
||||
pieceId,
|
||||
depth: 0,
|
||||
descriptor: {
|
||||
id: "test-descriptor",
|
||||
type: "data" as const,
|
||||
version: 1 as const,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("REFLECT_DAMAGE_PRIMITIVE", () => {
|
||||
it("registers in PRIMITIVE_REGISTRY", () => {
|
||||
expect(PRIMITIVE_REGISTRY.has("reflect-damage")).toBe(true);
|
||||
expect(PRIMITIVE_REGISTRY.get("reflect-damage")).toBe(REFLECT_DAMAGE_PRIMITIVE);
|
||||
});
|
||||
|
||||
it("writes ReflectDamagePercent fact", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "pawn");
|
||||
|
||||
const params = REFLECT_DAMAGE_PRIMITIVE.paramsSchema.parse({ percentage: 35 });
|
||||
REFLECT_DAMAGE_PRIMITIVE.apply(makeContext(session, pieceId), params);
|
||||
|
||||
expect(session.get(pieceId, "ReflectDamagePercent")).toBe(35);
|
||||
});
|
||||
|
||||
it("rejects percentage outside 0..100", () => {
|
||||
expect(() => REFLECT_DAMAGE_PRIMITIVE.paramsSchema.parse({ percentage: -1 })).toThrow();
|
||||
expect(() => REFLECT_DAMAGE_PRIMITIVE.paramsSchema.parse({ percentage: 101 })).toThrow();
|
||||
});
|
||||
|
||||
it("overwrites existing value on re-apply", () => {
|
||||
const session = new Session();
|
||||
const pieceId = session.nextId();
|
||||
session.insert(pieceId, "PieceType", "pawn");
|
||||
|
||||
REFLECT_DAMAGE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
REFLECT_DAMAGE_PRIMITIVE.paramsSchema.parse({ percentage: 20 }),
|
||||
);
|
||||
REFLECT_DAMAGE_PRIMITIVE.apply(
|
||||
makeContext(session, pieceId),
|
||||
REFLECT_DAMAGE_PRIMITIVE.paramsSchema.parse({ percentage: 60 }),
|
||||
);
|
||||
|
||||
expect(session.get(pieceId, "ReflectDamagePercent")).toBe(60);
|
||||
});
|
||||
});
|
||||
23
packages/chess/src/modifiers/primitives/reflect-damage.ts
Normal file
23
packages/chess/src/modifiers/primitives/reflect-damage.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { z } from "zod";
|
||||
import { PRIMITIVE_REGISTRY } from "./registry.js";
|
||||
import type { EffectPrimitive, PrimitiveApplyContext } from "./types.js";
|
||||
|
||||
const schema = z.object({
|
||||
percentage: z.number().int().min(0).max(100),
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof schema>;
|
||||
|
||||
const descriptor: EffectPrimitive<Params> = {
|
||||
kind: "reflect-damage",
|
||||
label: "Reflect Damage",
|
||||
description: "Seed reflected-damage percentage for deferred damage-pipeline wiring.",
|
||||
paramsSchema: schema,
|
||||
apply(ctx: PrimitiveApplyContext, params: Params): void {
|
||||
ctx.session.insert(ctx.pieceId, "ReflectDamagePercent", params.percentage);
|
||||
},
|
||||
};
|
||||
|
||||
PRIMITIVE_REGISTRY.register(descriptor);
|
||||
|
||||
export { descriptor as REFLECT_DAMAGE_PRIMITIVE };
|
||||
Loading…
Add table
Add a link
Reference in a new issue