From 1bcdcc3677016dcc0cf325766330e3803dfe5ab9 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 9 Oct 2026 20:09:51 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=89=B9=E6=AE=8A=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E5=AE=9A=E4=B9=89=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/enemy/__test__/enemy.test.ts | 4 +- .../src/enemy/__test__/saveLoad.test.ts | 4 +- .../src/enemy/__test__/special.test.ts | 8 +- packages-user/data-base/src/enemy/special.ts | 52 +- packages-user/data-base/src/enemy/types.ts | 7 + .../__test__/enemyCombination.test.ts | 8 +- .../data-state/__test__/mapScenario.perf.ts | 15 +- packages-user/data-state/src/core.ts | 193 +++---- .../src/enemy/__test__/aura.test.ts | 4 +- .../src/enemy/__test__/mapDamage.test.ts | 8 +- .../src/enemy/__test__/special.test.ts | 8 +- packages-user/data-state/src/enemy/aura.ts | 8 +- .../data-state/src/enemy/mapDamage.ts | 6 +- packages-user/data-state/src/enemy/special.ts | 474 +++++++++--------- 14 files changed, 439 insertions(+), 360 deletions(-) diff --git a/packages-user/data-base/src/enemy/__test__/enemy.test.ts b/packages-user/data-base/src/enemy/__test__/enemy.test.ts index 7c08510..f9c2660 100644 --- a/packages-user/data-base/src/enemy/__test__/enemy.test.ts +++ b/packages-user/data-base/src/enemy/__test__/enemy.test.ts @@ -74,8 +74,8 @@ function createEnemy( */ function createSpecial(code: number, value: number): ISpecial { return new modules.CommonSerializableSpecial(code, value, { - getSpecialName: () => `special-${code}`, - getDescription: () => `description-${code}`, + name: () => `special-${code}`, + desc: () => `description-${code}`, fromLegacyEnemy: () => value }); } diff --git a/packages-user/data-base/src/enemy/__test__/saveLoad.test.ts b/packages-user/data-base/src/enemy/__test__/saveLoad.test.ts index 84ae5dc..ba2bed0 100644 --- a/packages-user/data-base/src/enemy/__test__/saveLoad.test.ts +++ b/packages-user/data-base/src/enemy/__test__/saveLoad.test.ts @@ -78,8 +78,8 @@ function createEnemy( /** 构造一个可序列化的数值特殊属性 */ function createSpecial(code: number, value: number): ISpecial { return new modules.CommonSerializableSpecial(code, value, { - getSpecialName: () => `special-${code}`, - getDescription: () => `description-${code}`, + name: () => `special-${code}`, + desc: () => `description-${code}`, fromLegacyEnemy: () => value }); } diff --git a/packages-user/data-base/src/enemy/__test__/special.test.ts b/packages-user/data-base/src/enemy/__test__/special.test.ts index ffbc42f..c81ea93 100644 --- a/packages-user/data-base/src/enemy/__test__/special.test.ts +++ b/packages-user/data-base/src/enemy/__test__/special.test.ts @@ -46,8 +46,8 @@ afterAll(() => { */ function makeCommonConfig(): ICommonSpecialConfig { return { - getSpecialName: special => `连击${special.getValue()}`, - getDescription: special => `怪物每回合攻击${special.getValue()}次。`, + name: special => `连击${special.getValue()}`, + desc: special => `怪物每回合攻击${special.getValue()}次。`, fromLegacyEnemy: () => 0 }; } @@ -57,8 +57,8 @@ function makeCommonConfig(): ICommonSpecialConfig { */ function makeNoneConfig(): ICommonSpecialConfig { return { - getSpecialName: () => '先攻', - getDescription: () => '怪物首先攻击。', + name: () => '先攻', + desc: () => '怪物首先攻击。', fromLegacyEnemy: () => undefined }; } diff --git a/packages-user/data-base/src/enemy/special.ts b/packages-user/data-base/src/enemy/special.ts index 78a9f23..20ec999 100644 --- a/packages-user/data-base/src/enemy/special.ts +++ b/packages-user/data-base/src/enemy/special.ts @@ -2,13 +2,13 @@ import { isEqual } from 'lodash-es'; import { SaveCompression } from '@user/data-common'; import { ISpecial, SpecialCreation } from './types'; -// TODO: 颜色参数 - export interface ICommonSpecialConfig { /** 获取特殊属性的名称 */ - getSpecialName: (special: ISpecial) => string; + name: string | ((special: ISpecial) => string); /** 获取特殊属性的描述 */ - getDescription: (special: ISpecial) => string; + desc: string | ((special: ISpecial) => string); + /** 获取特殊属性的名称显示颜色 */ + color: string | ((special: ISpecial) => string); } export class CommonSerializableSpecial implements ISpecial { @@ -26,12 +26,30 @@ export class CommonSerializableSpecial implements ISpecial { return this.value; } + /** + * 获取配置数据的值,是字面量则直接返回,是函数则返回其调用结果 + * @param config 配置源数据 + */ + private getConfigValue( + config: string | ((special: ISpecial) => string) + ) { + if (typeof config === 'string') { + return config; + } else { + return config(this); + } + } + getSpecialName(): string { - return this.config.getSpecialName(this); + return this.getConfigValue(this.config.name); } getDescription(): string { - return this.config.getDescription(this); + return this.getConfigValue(this.config.desc); + } + + getNameColor(): string { + return this.getConfigValue(this.config.color); } clone(): ISpecial { @@ -72,12 +90,30 @@ export class NonePropertySpecial implements ISpecial { return void 0; } + /** + * 获取配置数据的值,是字面量则直接返回,是函数则返回其调用结果 + * @param config 配置源数据 + */ + private getConfigValue( + config: string | ((special: ISpecial) => string) + ) { + if (typeof config === 'string') { + return config; + } else { + return config(this); + } + } + getSpecialName(): string { - return this.config.getSpecialName(this); + return this.getConfigValue(this.config.name); } getDescription(): string { - return this.config.getDescription(this); + return this.getConfigValue(this.config.desc); + } + + getNameColor(): string { + return this.getConfigValue(this.config.color); } clone(): ISpecial { diff --git a/packages-user/data-base/src/enemy/types.ts b/packages-user/data-base/src/enemy/types.ts index 08e5df1..1ca16ab 100644 --- a/packages-user/data-base/src/enemy/types.ts +++ b/packages-user/data-base/src/enemy/types.ts @@ -55,6 +55,13 @@ export interface ISpecial extends ISaveableContent { */ getDescription(): string; + // TODO: 后续需要改进渲染端的文字渲染,颜色使用内嵌方式写入到字符串里面,而不是单独指定 + + /** + * 获取此特殊属性的名称颜色 + */ + getNameColor(): string; + /** * 深拷贝此特殊属性 */ diff --git a/packages-user/data-state/__test__/enemyCombination.test.ts b/packages-user/data-state/__test__/enemyCombination.test.ts index 5a7d4ef..c5f6ab2 100644 --- a/packages-user/data-state/__test__/enemyCombination.test.ts +++ b/packages-user/data-state/__test__/enemyCombination.test.ts @@ -22,7 +22,7 @@ import { CoreState } from '../src/core'; import { MainDamageCalculator } from '../src/enemy/calculator'; import { CommonAuraConverter, GuardAuraConverter } from '../src/enemy/aura'; import { MainEnemyFinalEffect } from '../src/enemy/final'; -import { type IHaloValue, type IVampireValue } from '../src/enemy/special'; +import { type ISpecial23Value, type IVampireValue } from '../src/enemy/special'; /** 按 08-08 约定的 CoreState 装配创建一个独立状态实例 */ function createCoreState(): CoreState { @@ -203,7 +203,7 @@ function createPipelineSpecials(haloAtkBuff: number): ISpecial[] { createSpecial(11, { vampire: 10, add: true }), createSpecial(17, undefined), createSpecial(22, 7), - createSpecial(25, { + createSpecial(25, { haloRange: 0, haloSquare: false, hpBuff: 0, @@ -255,7 +255,7 @@ describe('enemy combination stage 1 - component baselines', () => { code: 25, attrs: { atk: 20 }, specials: [ - createSpecial(25, { + createSpecial(25, { haloRange: 0, haloSquare: false, hpBuff: 0, @@ -489,7 +489,7 @@ describe('enemy combination stage 2 - multi-special and system pipelines', () => id: 'query-target', code: 25, specials: [ - createSpecial(25, { + createSpecial(25, { haloRange: 0, haloSquare: false, hpBuff: 0, diff --git a/packages-user/data-state/__test__/mapScenario.perf.ts b/packages-user/data-state/__test__/mapScenario.perf.ts index 80019db..023b4cd 100644 --- a/packages-user/data-state/__test__/mapScenario.perf.ts +++ b/packages-user/data-state/__test__/mapScenario.perf.ts @@ -15,7 +15,10 @@ import { } from '@user/data-base'; import { type IDamageSystem, type IMapDamage } from '@user/data-system'; import { CoreState } from '../src/core'; -import { type IHaloValue, type IZoneValue } from '../src/enemy/special'; +import { + type ISpecial23Value, + type ISpecial13Value +} from '../src/enemy/special'; import floorDataset from './fixtures/floors.json'; /** 按 08-08 约定的 CoreState 装配创建一个独立状态实例 */ @@ -153,7 +156,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [ attrs: { hp: 120, atk: 12, def: 3, money: 5, exp: 7, point: 1 }, specials: [ // haloRange 为 0 时 25 会落到 FullRange,即同楼层全体光环 - createSpecial(25, { + createSpecial(25, { haloRange: 0, haloSquare: false, hpBuff: 0, @@ -168,7 +171,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [ attrs: { hp: 90, atk: 9, def: 2, money: 4, exp: 6, point: 1 }, specials: [ // 十字范围光环,让光环拓扑随怪物坐标变化而非全图连通 - createSpecial(25, { + createSpecial(25, { haloRange: 3, haloSquare: false, hpBuff: 20, @@ -183,7 +186,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [ attrs: { hp: 150, atk: 15, def: 5, money: 8, exp: 12, point: 2 }, specials: [ // 九宫格范围光环,与十字范围共用同一转换器但走不同 range 实现 - createSpecial(25, { + createSpecial(25, { haloRange: 2, haloSquare: true, hpBuff: 0, @@ -207,7 +210,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [ code: 104, attrs: { hp: 60, atk: 6, def: 1, money: 2, exp: 3, point: 1 }, specials: [ - createSpecial(15, { + createSpecial(15, { zone: 5, zoneSquare: false, range: 2 @@ -219,7 +222,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [ code: 105, attrs: { hp: 70, atk: 7, def: 2, money: 3, exp: 4, point: 1 }, specials: [ - createSpecial(15, { + createSpecial(15, { zone: 8, zoneSquare: true, range: 1 diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts index 2df9c3e..bd07594 100644 --- a/packages-user/data-state/src/core.ts +++ b/packages-user/data-state/src/core.ts @@ -54,10 +54,10 @@ import { MainEnemyFinalEffect, MainMapDamageConverter, MainMapDamageReducer, - registerSpecials, + createSpecials, MainEnemyComparer } from './enemy'; -import { HERO_DEFAULT_ATTRIBUTE, TILE_HEIGHT, TILE_WIDTH } from './shared'; +import { HERO_DEFAULT_ATTRIBUTE } from './shared'; import { DefaultHeroMoveTopImpl, DefaultPassPredicateImpl } from './hero'; import { createEventRegistrations } from './event'; import { @@ -109,120 +109,145 @@ export class CoreState implements ICoreState { > = new Map(); constructor(config: Readonly) { - //#region L0 初始化 + //#region L0 定义 - // 朝向 this.roleFace = new RoleFaceBinder(); this.faceManager = new FaceManager(); + this.tileStore = new TileStore(); + this.itemStore = new ItemStore(this.tileStore); + this.mapStore = new MapStore(); + this.eventStore = new GameEventStore(); + + this.initializeL0(); + + //#endregion + + //#region L1 定义 + + this.flags = new FlagSystem(); + this.maps = new MapState(this); + const dir8 = this.faceManager.get(FaceGroup.Dir8)!; + const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE); + this.hero = new HeroState(this, dir8, heroAttribute); + this.enemyManager = new EnemyManager(this.tileStore); + + this.initializeL1(); + + //#endregion + + //#region L2 定义 + + this.enemyContext = new EnemyContext(this); + const events = createEventRegistrations(); + this.eventSystem = new GameEventSystem(this, events); + this.replaySystem = new ReplaySystem(); + this.pathfinding = new PathfindingSystem(this); + + this.initializeL2(); + + //#endregion + + //#region L3 定义 + + this.loadManager = new LoadManager(); + this.loader = new MotaDataLoader(this.loadManager, config.loadStarter); + + this.initializeL3(config); + + //#endregion + } + + //#region 初始化方法 + + /** + * 初始化数据端 L0 + */ + private initializeL0() { const dir4 = new Dir4FaceHandler(); const dir8 = new Dir8FaceHandler(); this.faceManager.register(FaceGroup.Dir4, dir4); this.faceManager.registerById('dir4', dir4); this.faceManager.register(FaceGroup.Dir8, dir8); this.faceManager.registerById('dir8', dir8); + } - // 图块 - this.tileStore = new TileStore(); - // 道具 - this.itemStore = new ItemStore(this.tileStore); - // 地图 - this.mapStore = new MapStore(); - // 游戏事件 - this.eventStore = new GameEventStore(); - - //#endregion - - //#region L1 初始化 - - // Flag 系统 - this.flags = new FlagSystem(); - - // 地图 - this.maps = new MapState(this); - - // 勇士 - const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE); - const heroState = new HeroState(this, dir8, heroAttribute); - this.hero = heroState; - - // 怪物管理器 + /** + * 初始化数据端 L1 + */ + private initializeL1() { const comparer = new MainEnemyComparer(); - const enemyManager = new EnemyManager(this.tileStore); - enemyManager.attachEnemyComparer(comparer); - registerSpecials(enemyManager); - this.enemyManager = enemyManager; + this.enemyManager.attachEnemyComparer(comparer); - //#endregion + const specials = createSpecials(this); + for (const [code, cons] of specials) { + this.enemyManager.registerSpecial(code, cons); + } + } - //#region L2 初始化 - - // 怪物上下文 - const enemyContext = new EnemyContext(this); - const damageSystem = new DamageSystem(enemyContext); - const mapDamage = new MapDamage(enemyContext); - damageSystem.useCalculator(new MainDamageCalculator()); - mapDamage.useReducer(new MainMapDamageReducer()); - mapDamage.useConverter(new MainMapDamageConverter()); - enemyContext.attachDamageSystem(damageSystem); - enemyContext.attachMapDamage(mapDamage); - enemyContext.registerAuraConverter(new CommonAuraConverter()); - enemyContext.registerAuraConverter(new GuardAuraConverter()); - enemyContext.registerFinalEffect(new MainEnemyFinalEffect()); - enemyContext.resize(TILE_WIDTH, TILE_HEIGHT); - enemyContext.bindHero(heroAttribute); - this.enemyContext = enemyContext; - - // 游戏事件系统 - const eventSystem = new GameEventSystem( - this, - createEventRegistrations() - ); - this.eventSystem = eventSystem; - - // 录像系统 - this.replaySystem = new ReplaySystem(); - - // 寻路系统 - this.pathfinding = new PathfindingSystem(this); - this.pathfinding.useMover(this.hero.location.mover); - // 初始状态下勇士不在任何楼层,切换楼层后再具体设置 - this.pathfinding.finder.useMapLayer(null); + /** + * 初始化数据端 L2 + */ + private initializeL2() { const predicate = new DefaultPassPredicateImpl(this); this.pathfinding.finder.usePassPredicate(predicate); + // 初始状态下勇士不在任何楼层,切换楼层后再具体设置 + this.pathfinding.finder.useMapLayer(null); + this.pathfinding.useMover(this.hero.location.mover); - //#endregion + this.initializeEnemy(); + } - //#region L3 初始化 + private initializeL3(config: Readonly) { + // 勇士顶层初始化 + const heroMoveTopImpl = new DefaultHeroMoveTopImpl(this); + this.hero.location.mover.useTopImplementation(heroMoveTopImpl); // 加载 - this.loadManager = new LoadManager(); - this.loader = new MotaDataLoader(this.loadManager, config.loadStarter); this.loader.addHook(new DefaultDataLoaderHook()); this.loader.addCoreConfig('core', config.coreURL); - // 存档内容 + this.initializeSave(); + this.initializeReplay(); + } + + /** + * 初始化怪物信息 + */ + private initializeEnemy() { + // 初始化怪物上下文 + const ctx = this.enemyContext; + const damageSystem = new DamageSystem(ctx); + const mapDamage = new MapDamage(ctx); + damageSystem.useCalculator(new MainDamageCalculator()); + mapDamage.useReducer(new MainMapDamageReducer()); + mapDamage.useConverter(new MainMapDamageConverter()); + ctx.attachDamageSystem(damageSystem); + ctx.attachMapDamage(mapDamage); + ctx.bindHero(this.hero.getAttribute()); + + // 注册光环转换器 + ctx.registerAuraConverter(new CommonAuraConverter()); + ctx.registerAuraConverter(new GuardAuraConverter()); + + // 注册怪物最终效果 + ctx.registerFinalEffect(new MainEnemyFinalEffect()); + } + + /** + * 初始化存档内容 + */ + private initializeSave() { this.addSaveableContent('@system/hero', this.hero); this.addSaveableContent('@system/flags', this.flags); this.addSaveableContent('@system/maps', this.maps); this.addSaveableContent('@system/enemy', this.enemyManager); this.addSaveableContent('@system/replay', this.replaySystem); - - // 勇士顶层初始化 - const heroMoveTopImpl = new DefaultHeroMoveTopImpl(this); - this.hero.location.mover.useTopImplementation(heroMoveTopImpl); - - // 录像系统初始化注册 - this.registerReplayCommands(); - - //#endregion } - //#region 私有方法 - /** * 注册全部录像指令 */ - private registerReplayCommands() { + private initializeReplay() { const replay = this.replaySystem; const up = new ReplayMove(this, FaceDirection.Up); diff --git a/packages-user/data-state/src/enemy/__test__/aura.test.ts b/packages-user/data-state/src/enemy/__test__/aura.test.ts index eccc9d2..6c8670f 100644 --- a/packages-user/data-state/src/enemy/__test__/aura.test.ts +++ b/packages-user/data-state/src/enemy/__test__/aura.test.ts @@ -13,7 +13,7 @@ import { type IReadonlyEnemy, type ISpecial } from '@user/data-base'; -import { type IHaloValue } from '../special'; +import { type ISpecial23Value } from '../special'; vi.hoisted(() => { vi.stubGlobal('main', { replayChecking: true }); @@ -180,7 +180,7 @@ function createWritableHandler( * 构造一个完整的光环数值 * @param overrides 覆盖的光环字段 */ -function createHalo(overrides: Partial = {}): IHaloValue { +function createHalo(overrides: Partial = {}): ISpecial23Value { return { haloRange: 0, haloSquare: false, diff --git a/packages-user/data-state/src/enemy/__test__/mapDamage.test.ts b/packages-user/data-state/src/enemy/__test__/mapDamage.test.ts index f5d7860..23d2852 100644 --- a/packages-user/data-state/src/enemy/__test__/mapDamage.test.ts +++ b/packages-user/data-state/src/enemy/__test__/mapDamage.test.ts @@ -13,7 +13,7 @@ import { type IReadonlyHeroAttribute, type ISpecial } from '@user/data-base'; -import { type IZoneValue } from '../special'; +import { type ISpecial13Value } from '../special'; const testGlobals = vi.hoisted(() => { vi.stubGlobal('main', { replayChecking: true }); @@ -215,7 +215,7 @@ function createInfo( describe('ZoneDamageView', () => { // 验证十字领域使用曼哈顿范围参数并输出领域伤害 it('builds a manhattan range and zone damage for a cross zone', () => { - const special = createSpecial(15, { + const special = createSpecial(15, { zone: 7, zoneSquare: false, range: 1 @@ -235,7 +235,7 @@ describe('ZoneDamageView', () => { // 验证九宫格领域使用矩形范围参数并输出领域伤害 it('builds a rect range and zone damage for a square zone', () => { - const special = createSpecial(15, { + const special = createSpecial(15, { zone: 4, zoneSquare: true, range: 2 @@ -255,7 +255,7 @@ describe('ZoneDamageView', () => { // 验证领域范围判定命中范围内的坐标并忽略范围外坐标 it('checks coordinates against the zone range', () => { - const special = createSpecial(15, { + const special = createSpecial(15, { zone: 7, zoneSquare: false, range: 1 diff --git a/packages-user/data-state/src/enemy/__test__/special.test.ts b/packages-user/data-state/src/enemy/__test__/special.test.ts index 0879f1b..66f8aae 100644 --- a/packages-user/data-state/src/enemy/__test__/special.test.ts +++ b/packages-user/data-state/src/enemy/__test__/special.test.ts @@ -1,7 +1,7 @@ // 测试怪物特殊属性注册:代码 0-27 全量注册、守卫默认值、名称与描述生成 import { beforeAll, describe, expect, it, vi } from 'vitest'; import { type ISpecial } from '@user/data-base'; -import { type IHaloValue } from '../special'; +import { type ISpecial23Value } from '../special'; vi.hoisted(() => { vi.stubGlobal('main', { replayChecking: true }); @@ -30,7 +30,7 @@ vi.hoisted(() => { }); interface TestModules { - registerSpecials: typeof import('../special').registerSpecials; + registerSpecials: typeof import('../special').createSpecials; } let modules: TestModules; @@ -39,7 +39,7 @@ beforeAll(async () => { vi.stubGlobal('main', { replayChecking: true }); vi.stubGlobal('location', { origin: 'http://localhost' }); const specialModule = await import('../special'); - modules = { registerSpecials: specialModule.registerSpecials }; + modules = { registerSpecials: specialModule.createSpecials }; }); type SpecialCreation = (enemy: unknown) => ISpecial; @@ -146,7 +146,7 @@ describe('registerSpecials', () => { expect(halo.getDescription()).toContain('同楼层所有怪物'); expect(halo.getDescription()).toContain('线性叠加。'); - const value: IHaloValue = { + const value: ISpecial23Value = { haloRange: 2, haloSquare: true, hpBuff: 10, diff --git a/packages-user/data-state/src/enemy/aura.ts b/packages-user/data-state/src/enemy/aura.ts index 6f7856a..a6f66a8 100644 --- a/packages-user/data-state/src/enemy/aura.ts +++ b/packages-user/data-state/src/enemy/aura.ts @@ -17,7 +17,7 @@ import { IReadonlyEnemyHandler } from '@user/data-system'; import { IReadonlyEnemy, ISpecial } from '@user/data-base'; -import { IHaloValue } from './special'; +import { ISpecial23Value } from './special'; import { IHeroAttr, IEnemyAttr } from '@user/data-common'; const FULL_RANGE = new FullRange(); @@ -35,7 +35,7 @@ export class CommonAuraConverter implements IAuraConverter< } convert( - special: ISpecial, + special: ISpecial, handler: IReadonlyEnemyHandler ): CommonAura { return new CommonAura(handler.enemy, special, handler.locator); @@ -45,7 +45,7 @@ export class CommonAuraConverter implements IAuraConverter< export class CommonAura implements IEnemyAuraView< IEnemyAttr, IRectRangeParam | IManhattanRangeParam | void, - IHaloValue + ISpecial23Value > { readonly priority: number = 25; readonly couldApplyBase: boolean = true; @@ -54,7 +54,7 @@ export class CommonAura implements IEnemyAuraView< constructor( readonly enemy: IReadonlyEnemy, - readonly special: ISpecial, + readonly special: ISpecial, readonly locator: ITileLocator ) { this.range = this.createRange(); diff --git a/packages-user/data-state/src/enemy/mapDamage.ts b/packages-user/data-state/src/enemy/mapDamage.ts index 6b5a152..447626a 100644 --- a/packages-user/data-state/src/enemy/mapDamage.ts +++ b/packages-user/data-state/src/enemy/mapDamage.ts @@ -24,7 +24,7 @@ import { IReadonlyHeroAttribute, IReadonlyEnemy } from '@user/data-base'; -import { IZoneValue } from './special'; +import { ISpecial13Value } from './special'; import { MapDamageType } from './types'; import { IHeroAttr, IEnemyAttr } from '@user/data-common'; import { FaceGroup } from '@user/data-common'; @@ -87,7 +87,7 @@ export class ZoneDamageView extends BaseMapDamageView< constructor( context: IEnemyContext, private readonly locator: Readonly, - private readonly special: Readonly> + private readonly special: Readonly> ) { super(context); } @@ -296,7 +296,7 @@ export class MainMapDamageConverter implements IMapDamageConverter< const views: IMapDamageView[] = []; const { enemy, locator, hero } = handler; - const zone = enemy.getSpecial(15); + const zone = enemy.getSpecial(15); if (zone) { views.push(new ZoneDamageView(context, locator, zone)); } diff --git a/packages-user/data-state/src/enemy/special.ts b/packages-user/data-state/src/enemy/special.ts index 3b38376..9ec580f 100644 --- a/packages-user/data-state/src/enemy/special.ts +++ b/packages-user/data-state/src/enemy/special.ts @@ -1,43 +1,63 @@ import { defineCommonSerializableSpecial, defineNonePropertySpecial, - IEnemyManager + SpecialCreation } from '@user/data-base'; import { IEnemyAttr } from '@user/data-common'; +import { IStateSystem } from '@user/data-system'; +// 重要!!!下面这一行的注释不要删 //#region 复合属性值类型 -export interface IVampireValue { +// 对象参数名称必须命名为 ISpecial{code}Value,接口前必须添加如下格式的注释,否则不会被编辑器识别 +// /** {code} - {name} */ + +/** 9 - 吸血 */ +export interface ISpecial9Value { + /** 吸血比例 */ vampire: number; + /** 是否加到自身生命值上 */ add: boolean; } -export interface IZoneValue { +/** 13 - 领域 */ +export interface ISpecial13Value { + /** 领域伤害值 */ zone: number; + /** 矩形领域(true)还是十字形领域(false),当范围大于 1 时,十字型领域采用曼哈顿距离计算 */ zoneSquare: boolean; + /** 领域范围值 */ range: number; } -export interface IDegradationValue { +/** 21 - 退化 */ +export interface ISpecial21Value { + /** 攻击下降值 */ atkValue: number; + /** 防御下降值 */ defValue: number; } -export interface IHaloValue { +export interface ISpecial23Value { + /** 光环范围值 */ haloRange: number; + /** 光环是矩形范围(true)还是十字型范围(false),当范围大于 1 时,十字型范围采用曼哈顿距离计算 */ haloSquare: boolean; + /** 生命值增加比例 */ hpBuff: number; + /** 攻击力增加比例 */ atkBuff: number; + /** 防御力增加比例 */ defBuff: number; } +// 重要!!!下面这一行的注释不要删 //#endregion // TODO: 注释需要更新 /** * 注册所有怪物特殊属性到 enemyManager * - * * 属性实现位置一览('./'表示当前文件夹 '../'表示上一级文件夹): * 1. 调参类属性 | 仅影响战斗过程的属性:./damage.ts calDamageWithTurn 函数 * 2. 地图伤害:./damage.ts DamageEnemy.calMapDamage 方法 @@ -47,318 +67,306 @@ export interface IHaloValue { * 6. 衰弱效果:../state/hero.ts getHeroStatusOf 方法 * 7. 重生属性:还在脚本编辑的 changingFloor */ -export function registerSpecials(manager: IEnemyManager): void { - manager.setAttributeDefaults('guard', new Set()); +export function createSpecials( + state: IStateSystem +): ReadonlyMap> { + const hero = state.hero; + + const map = new Map>(); + + // 注意,不需要参数的特殊属性,如先攻、魔攻、坚固这些纯机制, + // 不需要一个数值来描述的特殊属性,使用 defineNonePropertySpecial 定义。 + // 需要参数的特殊属性,如连击、领域、破甲这些需要一个或多个数值来描述的特殊属性, + // 使用 defineCommonSerializableSpecial 定义。 + // 特殊属性的参数可以填写数值、字符串,也可以填写对象,参考吸血、领域、退化、光环的写法。 + + // 注意,每个属性定义前必须添加注释 // {code} - {name},这会作为编辑器识别的匹配模式。 + + // 重要!!!下面这一行的注释不要删 + //#region 属性定义 // 0 - 空 - manager.registerSpecial( + map.set( 0, defineNonePropertySpecial(0, { - getSpecialName: () => '空', - getDescription: () => '空', - fromLegacyEnemy: () => {} + name: '空', + desc: '空', + color: '#FFF' }) ); // 1 - 先攻 - manager.registerSpecial( + map.set( 1, defineNonePropertySpecial(1, { - getSpecialName: () => '先攻', - getDescription: () => '怪物首先攻击。', - fromLegacyEnemy: () => {} + name: '先攻', + desc: '怪物首先攻击。', + color: '#FC3' }) ); // 2 - 魔攻 - manager.registerSpecial( + map.set( 2, defineNonePropertySpecial(2, { - getSpecialName: () => '魔攻', - getDescription: () => '怪物攻击无视勇士的防御。', - fromLegacyEnemy: () => {} + name: '魔攻', + desc: '怪物攻击无视勇士的防御。', + color: '#BBB0FF' }) ); // 3 - 坚固 - manager.registerSpecial( + map.set( 3, defineNonePropertySpecial(3, { - getSpecialName: () => '坚固', - getDescription: () => '怪物防御不小于勇士攻击-1。', - fromLegacyEnemy: () => {} + name: '坚固', + desc: '怪物防御不小于勇士攻击-1。', + color: '#C0B088' }) ); - // 4 - 2连击 - manager.registerSpecial( + // 4 - n连击 + map.set( 4, - defineNonePropertySpecial(4, { - getSpecialName: () => '2连击', - getDescription: () => '怪物每回合攻击2次。', - fromLegacyEnemy: () => {} + defineCommonSerializableSpecial(4, 2, { + name: special => `${special.value}连击`, + desc: special => `怪物每回合攻击${special.value}次。`, + color: '#FE7' }) ); - // 5 - 3连击 - manager.registerSpecial( + // 5 - 破甲 + map.set( 5, - defineNonePropertySpecial(5, { - getSpecialName: () => '3连击', - getDescription: () => '怪物每回合攻击3次。', - fromLegacyEnemy: () => {} + defineCommonSerializableSpecial(5, 0, { + name: '破甲', + desc: special => + `战斗前,附加角色防御的${special.value}%作为伤害。`, + color: '#88C0FF' }) ); - // 6 - n连击 - manager.registerSpecial( + // 6 - 反击 + map.set( 6, - defineCommonSerializableSpecial(6, 4, { - getSpecialName: special => `${special.value}连击`, - getDescription: special => `怪物每回合攻击${special.value}次。`, - fromLegacyEnemy: enemy => enemy.n ?? 4 + defineCommonSerializableSpecial(6, 0, { + name: '反击', + desc: special => + `战斗时,怪物每回合附加角色攻击的${special.value}%作为伤害,无视角色防御。`, + color: '#FA4' }) ); - // 7 - 破甲 - manager.registerSpecial( + // 7 - 净化 + map.set( 7, defineCommonSerializableSpecial(7, 0, { - getSpecialName: () => '破甲', - getDescription: special => - `战斗前,附加角色防御的${special.value}%作为伤害。`, - fromLegacyEnemy: enemy => enemy.breakArmor ?? 0 - }) - ); - - // 8 - 反击 - manager.registerSpecial( - 8, - defineCommonSerializableSpecial(8, 0, { - getSpecialName: () => '反击', - getDescription: special => - `战斗时,怪物每回合附加角色攻击的${special.value}%作为伤害,无视角色防御。`, - fromLegacyEnemy: enemy => enemy.counterAttack ?? 0 - }) - ); - - // 9 - 净化 - manager.registerSpecial( - 9, - defineCommonSerializableSpecial(9, 0, { - getSpecialName: () => '净化', - getDescription: special => + name: '净化', + desc: special => `战斗前,怪物附加角色护盾的${special.value}倍作为伤害。`, - fromLegacyEnemy: enemy => enemy.purify ?? 0 + color: '#80EED6' }) ); - // 10 - 模仿 - manager.registerSpecial( - 10, - defineNonePropertySpecial(10, { - getSpecialName: () => '模仿', - getDescription: () => '怪物的攻防与勇士相同。', - fromLegacyEnemy: () => {} + // 8 - 模仿 + map.set( + 8, + defineNonePropertySpecial(8, { + name: '模仿', + desc: '怪物的攻防与勇士相同。', + color: '#B0C0DD' }) ); - // 11 - 吸血 - manager.registerSpecial( - 11, - defineCommonSerializableSpecial( - 11, + // 9 - 吸血 + map.set( + 9, + defineCommonSerializableSpecial( + 9, { vampire: 0, add: false }, { - getSpecialName: () => '吸血', - getDescription: special => { + name: '吸血', + desc: special => { const { vampire, add } = special.value; + const attr = hero.getAttribute(); return ( `战斗前,怪物首先吸取角色的${vampire}%生命` + - // @ts-expect-error 需要重构 - `(约${Math.floor((vampire / 100) * getHeroStatusOn('hp'))}点)作为伤害` + + `(约${Math.floor((vampire / 100) * attr.getFinalAttribute('hp'))}点)作为伤害` + (add ? `,并把伤害数值加到自身生命上。` : `。`) ); }, - fromLegacyEnemy: enemy => ({ - vampire: enemy.vampire ?? 0, - add: enemy.add ?? false - }) + color: '#DD4448' } ) ); - // 12 - 中毒 - manager.registerSpecial( - 12, - defineNonePropertySpecial(12, { - getSpecialName: () => '中毒', - getDescription: () => - `战斗后,角色陷入中毒状态,每一步损失生命${core.values.poisonDamage}点。`, - fromLegacyEnemy: () => {} + // 10 - 中毒 + map.set( + 10, + defineCommonSerializableSpecial(10, 0, { + name: '中毒', + desc: special => + `战斗后,角色陷入中毒状态,每一步损失生命${special.value}点,多次触发时,${state.flags.getFieldValueDefaults('poisonAdd', false) ? '与已有中毒值叠加' : '取最高值'}。`, + color: '#9E8' }) ); - // 13 - 衰弱 - manager.registerSpecial( - 13, - defineNonePropertySpecial(13, { - getSpecialName: () => '衰弱', - getDescription: () => { - const weak = core.values.weakValue; + // 11 - 衰弱 + map.set( + 11, + defineCommonSerializableSpecial(11, 0, { + name: '衰弱', + desc: special => { + const weak = special.value; + const suffix = ',多次触发时取最高值。'; if (weak < 1) { - return `战斗后,角色陷入衰弱状态,攻防暂时下降${Math.floor(weak * 100)}%`; + return ( + `战斗后,角色陷入衰弱状态,攻防暂时下降${Math.floor(weak * 100)}%` + + suffix + ); } else { - return `战斗后,角色陷入衰弱状态,攻防暂时下降${weak}点`; + return ( + `战斗后,角色陷入衰弱状态,攻防暂时下降${weak}点` + + suffix + ); } }, - fromLegacyEnemy: () => {} + color: '#F0BBCC' }) ); - // 14 - 诅咒 - manager.registerSpecial( - 14, - defineNonePropertySpecial(14, { - getSpecialName: () => '诅咒', - getDescription: () => - '战斗后,角色陷入诅咒状态,战斗无法获得金币和经验。', - fromLegacyEnemy: () => {} + // 12 - 诅咒 + map.set( + 12, + defineNonePropertySpecial(12, { + name: '诅咒', + desc: '战斗后,角色陷入诅咒状态,战斗无法获得金币和经验。', + color: '#BBEEF0' }) ); - // 15 - 领域 - manager.registerSpecial( - 15, - defineCommonSerializableSpecial( - 15, + // 13 - 领域 + map.set( + 13, + defineCommonSerializableSpecial( + 13, { zone: 0, zoneSquare: false, range: 1 }, { - getSpecialName: () => '领域', - getDescription: special => { + name: '领域', + desc: special => { const { zone, zoneSquare, range } = special.value; return `经过怪物周围${zoneSquare ? '九宫格' : '十字'}范围内${range}格时自动减生命${zone}点。`; }, - fromLegacyEnemy: enemy => ({ - zone: enemy.zone ?? 0, - zoneSquare: enemy.zoneSquare ?? false, - range: enemy.range ?? 1 - }) + color: '#C677DD' } ) ); - // 16 - 夹击 - manager.registerSpecial( - 16, - defineNonePropertySpecial(16, { - getSpecialName: () => '夹击', - getDescription: () => - '经过两只相同的怪物中间,角色生命值变成一半。', - fromLegacyEnemy: () => {} + // 14 - 夹击 + map.set( + 14, + defineNonePropertySpecial(14, { + name: '夹击', + desc: '经过两只相同的怪物中间,角色生命值变成一半。', + color: '#B9E' }) ); - // 17 - 仇恨 - manager.registerSpecial( + // 15 - 仇恨 + map.set( + 15, + defineNonePropertySpecial(15, { + name: '仇恨', + desc: `战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得${state.flags.getFieldValueDefaults('hatred', 1)}点仇恨值)。`, + color: '#B0B666' + }) + ); + + // 16 - 阻击 + map.set( + 16, + defineCommonSerializableSpecial(16, 0, { + name: '阻击', + desc: special => + `经过怪物十字范围内时怪物后退一格,同时对勇士造成${special.value}点伤害。`, + color: '#8888E6' + }) + ); + + // 17 - 自爆 + map.set( 17, defineNonePropertySpecial(17, { - getSpecialName: () => '仇恨', - getDescription: () => - `战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得${core.values.hatred}点仇恨值)。`, - fromLegacyEnemy: () => {} + name: '自爆', + desc: '战斗后角色的生命值变成1。', + color: '#F66' }) ); - // 18 - 阻击 - manager.registerSpecial( + // 18 - 无敌 + map.set( 18, - defineCommonSerializableSpecial(18, 0, { - getSpecialName: () => '阻击', - getDescription: special => - `经过怪物十字范围内时怪物后退一格,同时对勇士造成${special.value}点伤害。`, - fromLegacyEnemy: enemy => enemy.repulse ?? 0 + defineNonePropertySpecial(18, { + name: '无敌', + desc: '角色无法打败怪物,除非拥有十字架。', + color: '#AAA' }) ); - // 19 - 自爆 - manager.registerSpecial( + // 19 - 退化 + map.set( 19, - defineNonePropertySpecial(19, { - getSpecialName: () => '自爆', - getDescription: () => '战斗后角色的生命值变成1。', - fromLegacyEnemy: () => {} - }) - ); - - // 20 - 无敌 - manager.registerSpecial( - 20, - defineNonePropertySpecial(20, { - getSpecialName: () => '无敌', - getDescription: () => '角色无法打败怪物,除非拥有十字架。', - fromLegacyEnemy: () => {} - }) - ); - - // 21 - 退化 - manager.registerSpecial( - 21, - defineCommonSerializableSpecial( - 21, + defineCommonSerializableSpecial( + 19, { atkValue: 0, defValue: 0 }, { - getSpecialName: () => '退化', - getDescription: special => { + name: '退化', + desc: special => { const { atkValue, defValue } = special.value; return `战斗后角色永久下降${atkValue}点攻击和${defValue}点防御。`; }, - fromLegacyEnemy: enemy => ({ - atkValue: enemy.atkValue ?? 0, - defValue: enemy.defValue ?? 0 - }) + color: '#FFF' } ) ); - // 22 - 固伤 - manager.registerSpecial( + // 20 - 固伤 + map.set( + 20, + defineCommonSerializableSpecial(20, 0, { + name: '固伤', + desc: special => + `战斗前,怪物对角色造成${special.value}点固定伤害,未开启负伤时无视角色护盾。`, + color: '#F97' + }) + ); + + // 21 - 重生 + map.set( + 21, + defineNonePropertySpecial(21, { + name: '重生', + desc: '怪物被击败后,角色转换楼层则怪物将再次出现。', + color: '#A0E0FF' + }) + ); + + // 22 - 激光 + map.set( 22, defineCommonSerializableSpecial(22, 0, { - getSpecialName: () => '固伤', - getDescription: special => - `战斗前,怪物对角色造成${special.value}点固定伤害,未开启负伤时无视角色护盾。`, - fromLegacyEnemy: enemy => enemy.damage ?? 0 - }) - ); - - // 23 - 重生 - manager.registerSpecial( - 23, - defineNonePropertySpecial(23, { - getSpecialName: () => '重生', - getDescription: () => - '怪物被击败后,角色转换楼层则怪物将再次出现。', - fromLegacyEnemy: () => {} - }) - ); - - // 24 - 激光 - manager.registerSpecial( - 24, - defineCommonSerializableSpecial(24, 0, { - getSpecialName: () => '激光', - getDescription: special => + name: '激光', + desc: special => `经过怪物同行或同列时自动减生命${special.value}点。`, - fromLegacyEnemy: enemy => enemy.laser ?? 0 + color: '#D0A0DD' }) ); - // 25 - 光环 - manager.registerSpecial( - 25, - defineCommonSerializableSpecial( - 25, + // 23 - 光环 + map.set( + 23, + defineCommonSerializableSpecial( + 23, { haloRange: 0, haloSquare: false, @@ -367,8 +375,8 @@ export function registerSpecials(manager: IEnemyManager): void { defBuff: 0 }, { - getSpecialName: () => '光环', - getDescription: special => { + name: '光环', + desc: special => { const { haloRange, haloSquare, hpBuff, atkBuff, defBuff } = special.value; let str = ''; @@ -394,35 +402,35 @@ export function registerSpecials(manager: IEnemyManager): void { str += `,线性叠加。`; return str; }, - fromLegacyEnemy: enemy => ({ - haloRange: enemy.haloRange ?? 0, - haloSquare: enemy.haloSquare ?? false, - hpBuff: enemy.hpBuff ?? 0, - atkBuff: enemy.atkBuff ?? 0, - defBuff: enemy.defBuff ?? 0 - }) + color: '#E6E099' } ) ); - // 26 - 支援 - manager.registerSpecial( - 26, - defineNonePropertySpecial(26, { - getSpecialName: () => '支援', - getDescription: () => - '当周围一圈的怪物受到攻击时将上前支援,并组成小队战斗。', - fromLegacyEnemy: () => {} + // 24 - 支援 + map.set( + 24, + defineNonePropertySpecial(24, { + name: '支援', + desc: '当周围一圈的怪物受到攻击时将上前支援,并组成小队战斗。', + color: '#77C0B6' }) ); - // 27 - 捕捉 - manager.registerSpecial( - 27, + // 25 - 捕捉 + map.set( + 25, defineNonePropertySpecial(27, { - getSpecialName: () => '捕捉', - getDescription: () => '当走到怪物十字范围内时会进行强制战斗。', - fromLegacyEnemy: () => {} + name: '捕捉', + desc: '当走到怪物十字范围内时会进行强制战斗。', + color: '#C0DDBB' }) ); + + // TODO: 26 - 特殊光环 + + // 重要!!!下面这一行的注释不要删 + //#endregion + + return map; }