mirror of
https://github.com/motajs/template.git
synced 2026-10-11 15:10:17 +08:00
refactor: 特殊属性定义方式
This commit is contained in:
parent
46bcafcfbb
commit
1bcdcc3677
@ -74,8 +74,8 @@ function createEnemy(
|
||||
*/
|
||||
function createSpecial(code: number, value: number): ISpecial<number> {
|
||||
return new modules.CommonSerializableSpecial<number>(code, value, {
|
||||
getSpecialName: () => `special-${code}`,
|
||||
getDescription: () => `description-${code}`,
|
||||
name: () => `special-${code}`,
|
||||
desc: () => `description-${code}`,
|
||||
fromLegacyEnemy: () => value
|
||||
});
|
||||
}
|
||||
|
||||
@ -78,8 +78,8 @@ function createEnemy(
|
||||
/** 构造一个可序列化的数值特殊属性 */
|
||||
function createSpecial(code: number, value: number): ISpecial<number> {
|
||||
return new modules.CommonSerializableSpecial<number>(code, value, {
|
||||
getSpecialName: () => `special-${code}`,
|
||||
getDescription: () => `description-${code}`,
|
||||
name: () => `special-${code}`,
|
||||
desc: () => `description-${code}`,
|
||||
fromLegacyEnemy: () => value
|
||||
});
|
||||
}
|
||||
|
||||
@ -46,8 +46,8 @@ afterAll(() => {
|
||||
*/
|
||||
function makeCommonConfig(): ICommonSpecialConfig<number> {
|
||||
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<number> {
|
||||
*/
|
||||
function makeNoneConfig(): ICommonSpecialConfig<void> {
|
||||
return {
|
||||
getSpecialName: () => '先攻',
|
||||
getDescription: () => '怪物首先攻击。',
|
||||
name: () => '先攻',
|
||||
desc: () => '怪物首先攻击。',
|
||||
fromLegacyEnemy: () => undefined
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,13 +2,13 @@ import { isEqual } from 'lodash-es';
|
||||
import { SaveCompression } from '@user/data-common';
|
||||
import { ISpecial, SpecialCreation } from './types';
|
||||
|
||||
// TODO: 颜色参数
|
||||
|
||||
export interface ICommonSpecialConfig<T> {
|
||||
/** 获取特殊属性的名称 */
|
||||
getSpecialName: (special: ISpecial<T>) => string;
|
||||
name: string | ((special: ISpecial<T>) => string);
|
||||
/** 获取特殊属性的描述 */
|
||||
getDescription: (special: ISpecial<T>) => string;
|
||||
desc: string | ((special: ISpecial<T>) => string);
|
||||
/** 获取特殊属性的名称显示颜色 */
|
||||
color: string | ((special: ISpecial<T>) => string);
|
||||
}
|
||||
|
||||
export class CommonSerializableSpecial<T> implements ISpecial<T> {
|
||||
@ -26,12 +26,30 @@ export class CommonSerializableSpecial<T> implements ISpecial<T> {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置数据的值,是字面量则直接返回,是函数则返回其调用结果
|
||||
* @param config 配置源数据
|
||||
*/
|
||||
private getConfigValue(
|
||||
config: string | ((special: ISpecial<T>) => 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<T> {
|
||||
@ -72,12 +90,30 @@ export class NonePropertySpecial implements ISpecial<void> {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置数据的值,是字面量则直接返回,是函数则返回其调用结果
|
||||
* @param config 配置源数据
|
||||
*/
|
||||
private getConfigValue(
|
||||
config: string | ((special: ISpecial<void>) => 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<void> {
|
||||
|
||||
@ -55,6 +55,13 @@ export interface ISpecial<T = void> extends ISaveableContent<T> {
|
||||
*/
|
||||
getDescription(): string;
|
||||
|
||||
// TODO: 后续需要改进渲染端的文字渲染,颜色使用内嵌方式写入到字符串里面,而不是单独指定
|
||||
|
||||
/**
|
||||
* 获取此特殊属性的名称颜色
|
||||
*/
|
||||
getNameColor(): string;
|
||||
|
||||
/**
|
||||
* 深拷贝此特殊属性
|
||||
*/
|
||||
|
||||
@ -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<any>[] {
|
||||
createSpecial<IVampireValue>(11, { vampire: 10, add: true }),
|
||||
createSpecial<void>(17, undefined),
|
||||
createSpecial<number>(22, 7),
|
||||
createSpecial<IHaloValue>(25, {
|
||||
createSpecial<ISpecial23Value>(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<IHaloValue>(25, {
|
||||
createSpecial<ISpecial23Value>(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<IHaloValue>(25, {
|
||||
createSpecial<ISpecial23Value>(25, {
|
||||
haloRange: 0,
|
||||
haloSquare: false,
|
||||
hpBuff: 0,
|
||||
|
||||
@ -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<IHaloValue>(25, {
|
||||
createSpecial<ISpecial23Value>(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<IHaloValue>(25, {
|
||||
createSpecial<ISpecial23Value>(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<IHaloValue>(25, {
|
||||
createSpecial<ISpecial23Value>(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<IZoneValue>(15, {
|
||||
createSpecial<ISpecial13Value>(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<IZoneValue>(15, {
|
||||
createSpecial<ISpecial13Value>(15, {
|
||||
zone: 8,
|
||||
zoneSquare: true,
|
||||
range: 1
|
||||
|
||||
@ -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<ICoreStateConfig>) {
|
||||
//#region L0 初始化
|
||||
//#region L0 定义
|
||||
|
||||
// 朝向
|
||||
this.roleFace = new RoleFaceBinder();
|
||||
this.faceManager = new FaceManager();
|
||||
this.tileStore = new TileStore();
|
||||
this.itemStore = new ItemStore<IHeroAttr>(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<IEnemyAttr>(this.tileStore);
|
||||
|
||||
this.initializeL1();
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region L2 定义
|
||||
|
||||
this.enemyContext = new EnemyContext<IEnemyAttr, IHeroAttr>(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<IHeroAttr>(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<IEnemyAttr>(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<IEnemyAttr, IHeroAttr>(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<ICoreStateConfig>) {
|
||||
// 勇士顶层初始化
|
||||
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);
|
||||
|
||||
@ -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> = {}): IHaloValue {
|
||||
function createHalo(overrides: Partial<ISpecial23Value> = {}): ISpecial23Value {
|
||||
return {
|
||||
haloRange: 0,
|
||||
haloSquare: false,
|
||||
|
||||
@ -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<IZoneValue>(15, {
|
||||
const special = createSpecial<ISpecial13Value>(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<IZoneValue>(15, {
|
||||
const special = createSpecial<ISpecial13Value>(15, {
|
||||
zone: 4,
|
||||
zoneSquare: true,
|
||||
range: 2
|
||||
@ -255,7 +255,7 @@ describe('ZoneDamageView', () => {
|
||||
|
||||
// 验证领域范围判定命中范围内的坐标并忽略范围外坐标
|
||||
it('checks coordinates against the zone range', () => {
|
||||
const special = createSpecial<IZoneValue>(15, {
|
||||
const special = createSpecial<ISpecial13Value>(15, {
|
||||
zone: 7,
|
||||
zoneSquare: false,
|
||||
range: 1
|
||||
|
||||
@ -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<any>;
|
||||
@ -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,
|
||||
|
||||
@ -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<IHaloValue>,
|
||||
special: ISpecial<ISpecial23Value>,
|
||||
handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>
|
||||
): 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<IEnemyAttr>,
|
||||
readonly special: ISpecial<IHaloValue>,
|
||||
readonly special: ISpecial<ISpecial23Value>,
|
||||
readonly locator: ITileLocator
|
||||
) {
|
||||
this.range = this.createRange();
|
||||
|
||||
@ -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<IEnemyAttr, IHeroAttr>,
|
||||
private readonly locator: Readonly<ITileLocator>,
|
||||
private readonly special: Readonly<ISpecial<IZoneValue>>
|
||||
private readonly special: Readonly<ISpecial<ISpecial13Value>>
|
||||
) {
|
||||
super(context);
|
||||
}
|
||||
@ -296,7 +296,7 @@ export class MainMapDamageConverter implements IMapDamageConverter<
|
||||
const views: IMapDamageView<any>[] = [];
|
||||
const { enemy, locator, hero } = handler;
|
||||
|
||||
const zone = enemy.getSpecial<IZoneValue>(15);
|
||||
const zone = enemy.getSpecial<ISpecial13Value>(15);
|
||||
if (zone) {
|
||||
views.push(new ZoneDamageView(context, locator, zone));
|
||||
}
|
||||
|
||||
@ -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<IEnemyAttr>): void {
|
||||
manager.setAttributeDefaults('guard', new Set());
|
||||
export function createSpecials(
|
||||
state: IStateSystem
|
||||
): ReadonlyMap<number, SpecialCreation<any, IEnemyAttr>> {
|
||||
const hero = state.hero;
|
||||
|
||||
const map = new Map<number, SpecialCreation<any, IEnemyAttr>>();
|
||||
|
||||
// 注意,不需要参数的特殊属性,如先攻、魔攻、坚固这些纯机制,
|
||||
// 不需要一个数值来描述的特殊属性,使用 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<IVampireValue>(
|
||||
11,
|
||||
// 9 - 吸血
|
||||
map.set(
|
||||
9,
|
||||
defineCommonSerializableSpecial<ISpecial9Value>(
|
||||
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<IZoneValue>(
|
||||
15,
|
||||
// 13 - 领域
|
||||
map.set(
|
||||
13,
|
||||
defineCommonSerializableSpecial<ISpecial13Value>(
|
||||
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<IDegradationValue>(
|
||||
21,
|
||||
defineCommonSerializableSpecial<ISpecial21Value>(
|
||||
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<IHaloValue>(
|
||||
25,
|
||||
// 23 - 光环
|
||||
map.set(
|
||||
23,
|
||||
defineCommonSerializableSpecial<ISpecial23Value>(
|
||||
23,
|
||||
{
|
||||
haloRange: 0,
|
||||
haloSquare: false,
|
||||
@ -367,8 +375,8 @@ export function registerSpecials(manager: IEnemyManager<IEnemyAttr>): 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<IEnemyAttr>): 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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user