/** * Data-driven configuration interfaces for《影之传说》MVP. * * Every numeric default in here traces directly back to a requirement in * `.codebuddy/plan/kage_legend_mvp/requirements.md`. When you change a field, * keep the inline `req` comment in sync so QA can rebuild the traceability * matrix. * * NOTE: This module is platform-agnostic and MUST NOT depend on `cc`. */ import { PlayerColorState } from '../common/Constants'; // --------------------------------------------------------------------------- // Enemies // --------------------------------------------------------------------------- /** Enum covers all enemy types required by MVP Chapter 1 (req 6.1-6.7). */ export enum EnemyType { /** Green ninja — shuriken + sword, 2s interval (req 6.1). */ QingRen = 'qing_ren', /** Red ninja — 120px/s + smoke bomb, proactive intercept jump (req 6.2-6.3). */ ChiRen = 'chi_ren', /** Black ninja — drops magic flute scroll on castle stages (req 6.5). */ HeiRen = 'hei_ren', /** Monster priest — straight-line fireball, 3.0s interval (req 6.6). */ YaoFang = 'yao_fang', } /** Allowed damage types (req 3.7, 3.8, 10.4-10.5). */ export type AttackType = 'shuriken' | 'sword' | 'fireball' | 'smoke_bomb'; export interface IEnemyConfig { id: EnemyType; displayName: string; /** Pixel sprite size (width x height). */ size: { w: number; h: number }; /** Horizontal movement speed (px/s). 0 means stationary. */ moveSpeed: number; /** Attack interval in seconds. */ attackIntervalSec: number; /** Possible attack types. */ attacks: AttackType[]; /** Hit points (1 means dies to any successful hit). */ hp: number; /** How many enemies of this type must be killed for chapter objective (optional). */ killObjective?: number; /** Drop rules specific to this enemy type (see `IItemDropRule`). */ drops?: IItemDropRule[]; } // --------------------------------------------------------------------------- // Items // --------------------------------------------------------------------------- /** Item IDs used by MVP Chapter 1 (req 7.1-7.6, 5.1-5.6). */ export enum ItemType { /** 水晶玉 — auto-upgrades red → green → yellow (req 5.1-5.2). */ CrystalJade = 'crystal_jade', /** 点丸 — +50% attack power, 30s (req 7.3). */ DianWan = 'dian_wan', /** 术丸 — +30% move speed, 20s (req 7.3). */ ShuWan = 'shu_wan', /** 魔笛 — screen-wipe one-shot kill (req 7.4). */ MoDi = 'mo_di', /** 增丸 — +1 permanent life (req 7.5). */ ZengWan = 'zeng_wan', } export interface IItemConfig { id: ItemType; displayName: string; /** Icon asset path under `assets/resources/textures/items`. */ icon: string; /** Duration in seconds for timed buffs (0 / omitted for instant items). */ durationSec?: number; /** * Relative strength of the effect (interpretation is per item type; * see `IItemEffectApplier` in logic layer for the actual math). */ magnitude?: number; /** Lifetime in seconds after spawning on the map (req 7.2). */ lifetimeSec: number; } /** Drop rule attached to an enemy type. Evaluated on enemy death. */ export interface IItemDropRule { item: ItemType; /** Required consecutive kills of this enemy type before drop can happen. */ afterKills?: number; /** Probability 0~1 once `afterKills` condition is satisfied. */ probability: number; } // --------------------------------------------------------------------------- // Weapons // --------------------------------------------------------------------------- export enum WeaponType { Shuriken = 'shuriken', NinjaSword = 'ninja_sword', } export interface IWeaponConfig { id: WeaponType; displayName: string; /** Base attack interval (s). Yellow state may override for shuriken. */ baseIntervalSec: number; /** Upgraded (green/yellow) interval (s). */ upgradedIntervalSec?: number; /** Damage applied to standard enemies on hit. */ damage: number; /** Supports parry (req 3.7 — only sword). */ canParry: boolean; /** When long-pressed, max shots in a burst (req 3.5 — shuriken only). */ burstMax?: number; } // --------------------------------------------------------------------------- // Levels (Chapter 1 only) // --------------------------------------------------------------------------- /** Scroll direction for a level (req 8.1-8.5). */ export type ScrollDirection = 'horizontal' | 'horizontal_bi' | 'vertical'; export interface ILevelObjective { /** e.g. 'kill_yao_fang', 'reach_top', 'boss_defeated'. */ kind: 'kill_count' | 'reach_top' | 'defeat_boss'; /** For kill_count objectives — which enemy type, and how many. */ enemy?: EnemyType; /** Required count for kill_count objectives. */ count?: number; /** For defeat_boss objectives — boss ID. */ bossId?: string; } /** Reinforcement rule — enemies that jump in from screen edges during gameplay. */ export interface IReinforcementRule { /** Which enemy type to spawn as reinforcement. */ type: EnemyType; /** Minimum interval (seconds) between two reinforcement waves of this rule. */ intervalSec: number; /** How many enemies to spawn per reinforcement wave. */ count: number; /** Maximum total reinforcements of this rule per level (0 = unlimited). */ maxTotal?: number; /** Which edge(s) the enemies appear from. */ edge: 'left' | 'right' | 'both'; /** Minimum elapsed seconds before this rule becomes active. */ delaySec?: number; } export interface ILevelConfig { id: string; // e.g. '1-1' chapter: 1 | 2 | 3; displayName: string; /** One of 'forest' / 'cave' / 'castle_wall' / 'demon_castle'. */ sceneTheme: string; scrollDirection: ScrollDirection; /** Time limit in seconds (req 8.1-8.4). */ timeLimitSec: number; objective: ILevelObjective; /** Scene length in pixels (landscape 16:9 baseline, req 8.8). */ levelLengthPx: number; /** BGM bundle key under `assets/resources/audio`. */ bgm: string; /** Enemy spawn list evaluated by the LevelMgr. */ enemySpawns: Array<{ type: EnemyType; atPx: number; count?: number }>; /** Dynamic reinforcement rules — enemies jump in from screen edges. */ reinforcements?: IReinforcementRule[]; } // --------------------------------------------------------------------------- // Bosses // --------------------------------------------------------------------------- export interface IBossAttackPhase { /** HP threshold at which this phase activates (1.0, 0.66, 0.33, ...). */ hpThreshold: number; /** Human-readable mode id (e.g. 'pair_pincer', 'fireball_spread', 'clone_confuse'). */ mode: string; /** Interval between actions in this phase (s). */ actionIntervalSec: number; } export interface IBossConfig { id: string; displayName: string; hp: number; /** A non-zero value means "butterfly appearance required before damage". */ butterflyReveal: boolean; /** Ordered from highest hpThreshold to lowest. */ phases: IBossAttackPhase[]; /** Cutscene trigger (req 8.6 / 14.1): play short "princess taken" at hp<=X. */ princessCutsceneAtHpRatio?: number; } // --------------------------------------------------------------------------- // Story intro (req 19) // --------------------------------------------------------------------------- export interface IStoryPageConfig { /** 1-based page index. */ index: number; /** Texture path under `assets/resources/textures/story`. */ illustration: string; /** Pixel typewriter text to display. */ text: string; } export interface IStorySceneConfig { id: string; // e.g. 'chapter_1_intro' bgm: string; // e.g. 'bgm_story' /** Max total duration (seconds); UI should auto-advance if exceeded. */ maxDurationSec: number; pages: IStoryPageConfig[]; } // --------------------------------------------------------------------------- // Aggregate configuration table // --------------------------------------------------------------------------- export interface IChapter1ConfigBundle { enemies: IEnemyConfig[]; items: IItemConfig[]; weapons: IWeaponConfig[]; levels: ILevelConfig[]; bosses: IBossConfig[]; stories: IStorySceneConfig[]; } /** * Describes which player-state the JSON config applies to. We explicitly * leave **no room** for a 'casual' mode string so that future edits cannot * silently reintroduce the removed difficulty (decision D-4, req 13.1-13.6). */ export type DifficultyProfile = 'hardcore'; export const ONLY_DIFFICULTY: DifficultyProfile = 'hardcore'; /** Convenience map for looking up the red/green/yellow state that unlocks each move-speed bucket. */ export const COLOR_STATE_MOVE_BUCKET: Record = { [PlayerColorState.Red]: 100, [PlayerColorState.Green]: 100, [PlayerColorState.Yellow]: 150, };