feat: 特殊光环描述

This commit is contained in:
unanmed 2026-10-10 17:42:34 +08:00
parent a2dd4ace72
commit 77a8adc05d
11 changed files with 110 additions and 53 deletions

View File

@ -13,10 +13,8 @@ import { isNil } from 'lodash-es';
export class EnemyManager<TEnemy> implements IEnemyManager<TEnemy> {
/** 特殊属性注册表,code -> 创建函数 */
private readonly specialRegistry: Map<
number,
SpecialCreation<any, TEnemy>
> = new Map();
private readonly specialRegistry: Map<number, SpecialCreation<any>> =
new Map();
/** 怪物模板表,code -> IEnemy */
private readonly prefabByCode: Map<number, IEnemy<TEnemy>> = new Map();
/** 复用映射,reusedCode -> sourceCode */
@ -32,10 +30,14 @@ export class EnemyManager<TEnemy> implements IEnemyManager<TEnemy> {
constructor(private readonly tileStore: ITileStore) {}
registerSpecial(code: number, cons: SpecialCreation<any, TEnemy>): void {
registerSpecial(code: number, cons: SpecialCreation<any>): void {
this.specialRegistry.set(code, cons);
}
getSpecial(code: number): SpecialCreation<any> | null {
return this.specialRegistry.get(code) ?? null;
}
/**
* 根据怪物的图块数字或 id 获取其模板
* @param token 怪物的图块数字或 id

View File

@ -133,18 +133,18 @@ export class NonePropertySpecial implements ISpecial<void> {
}
}
export function defineCommonSerializableSpecial<T, TAttr = any>(
export function defineCommonSerializableSpecial<T>(
code: number,
value: T,
config: ICommonSpecialConfig<T>
): SpecialCreation<T, TAttr> {
): SpecialCreation<T> {
return () =>
new CommonSerializableSpecial(code, structuredClone(value), config);
}
export function defineNonePropertySpecial<TAttr = any>(
export function defineNonePropertySpecial(
code: number,
config: ICommonSpecialConfig<void>
): SpecialCreation<void, TAttr> {
): SpecialCreation<void> {
return () => new NonePropertySpecial(code, config);
}

View File

@ -161,7 +161,7 @@ export interface IEnemy<TAttr>
//#region 怪物管理器
export type SpecialCreation<T, TAttr> = (enemy: IEnemy<TAttr>) => ISpecial<T>;
export type SpecialCreation<T> = () => ISpecial<T>;
export interface IEnemyManager<TAttr> extends ISaveableContent<
IEnemyManagerSaveState<TAttr>
@ -171,7 +171,13 @@ export interface IEnemyManager<TAttr> extends ISaveableContent<
* @param code 特殊属性代码
* @param cons 特殊属性创建函数
*/
registerSpecial(code: number, cons: SpecialCreation<any, TAttr>): void;
registerSpecial(code: number, cons: SpecialCreation<any>): void;
/**
* 获取一个特殊属性的注册函数
* @param code 特殊属性代码
*/
getSpecial(code: number): SpecialCreation<any> | null;
/**
* 创建怪物对象,如果对应数字的怪物不存在则会返回 `null`

View File

@ -204,8 +204,8 @@ function createPipelineSpecials(haloAtkBuff: number): ISpecial<any>[] {
createSpecial<void>(17, undefined),
createSpecial<number>(22, 7),
createSpecial<ISpecial23Value>(25, {
haloRange: 0,
haloSquare: false,
range: 0,
square: false,
hpBuff: 0,
atkBuff: haloAtkBuff,
defBuff: 0
@ -256,8 +256,8 @@ describe('enemy combination stage 1 - component baselines', () => {
attrs: { atk: 20 },
specials: [
createSpecial<ISpecial23Value>(25, {
haloRange: 0,
haloSquare: false,
range: 0,
square: false,
hpBuff: 0,
atkBuff: 50,
defBuff: 0
@ -490,8 +490,8 @@ describe('enemy combination stage 2 - multi-special and system pipelines', () =>
code: 25,
specials: [
createSpecial<ISpecial23Value>(25, {
haloRange: 0,
haloSquare: false,
range: 0,
square: false,
hpBuff: 0,
atkBuff: 50,
defBuff: 0

View File

@ -157,8 +157,8 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [
specials: [
// haloRange 为 0 时 25 会落到 FullRange,即同楼层全体光环
createSpecial<ISpecial23Value>(25, {
haloRange: 0,
haloSquare: false,
range: 0,
square: false,
hpBuff: 0,
atkBuff: 30,
defBuff: 0
@ -172,8 +172,8 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [
specials: [
// 十字范围光环,让光环拓扑随怪物坐标变化而非全图连通
createSpecial<ISpecial23Value>(25, {
haloRange: 3,
haloSquare: false,
range: 3,
square: false,
hpBuff: 20,
atkBuff: 0,
defBuff: 0
@ -187,8 +187,8 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [
specials: [
// 九宫格范围光环,与十字范围共用同一转换器但走不同 range 实现
createSpecial<ISpecial23Value>(25, {
haloRange: 2,
haloSquare: true,
range: 2,
square: true,
hpBuff: 0,
atkBuff: 0,
defBuff: 25
@ -212,7 +212,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [
specials: [
createSpecial<ISpecial13Value>(15, {
zone: 5,
zoneSquare: false,
square: false,
range: 2
})
]
@ -224,7 +224,7 @@ const MONSTER_PORTFOLIO: readonly IMonsterPrefab[] = [
specials: [
createSpecial<ISpecial13Value>(15, {
zone: 8,
zoneSquare: true,
square: true,
range: 1
}),
createSpecial<void>(2, undefined)

View File

@ -182,8 +182,8 @@ function createWritableHandler(
*/
function createHalo(overrides: Partial<ISpecial23Value> = {}): ISpecial23Value {
return {
haloRange: 0,
haloSquare: false,
range: 0,
square: false,
hpBuff: 0,
atkBuff: 0,
defBuff: 0,
@ -227,7 +227,7 @@ describe('CommonAura range', () => {
const { enemy } = createEnemy();
const aura = new modules.CommonAura(
enemy,
createSpecial(25, createHalo({ haloRange: 0 })),
createSpecial(25, createHalo({ range: 0 })),
{ x: 3, y: 4 }
);
@ -240,7 +240,7 @@ describe('CommonAura range', () => {
const { enemy } = createEnemy();
const aura = new modules.CommonAura(
enemy,
createSpecial(25, createHalo({ haloRange: 2, haloSquare: true })),
createSpecial(25, createHalo({ range: 2, square: true })),
{ x: 3, y: 4 }
);
@ -253,7 +253,7 @@ describe('CommonAura range', () => {
const { enemy } = createEnemy();
const aura = new modules.CommonAura(
enemy,
createSpecial(25, createHalo({ haloRange: 2, haloSquare: false })),
createSpecial(25, createHalo({ range: 2, square: false })),
{ x: 3, y: 4 }
);

View File

@ -217,7 +217,7 @@ describe('ZoneDamageView', () => {
it('builds a manhattan range and zone damage for a cross zone', () => {
const special = createSpecial<ISpecial13Value>(15, {
zone: 7,
zoneSquare: false,
square: false,
range: 1
});
const view = new modules.ZoneDamageView(
@ -237,7 +237,7 @@ describe('ZoneDamageView', () => {
it('builds a rect range and zone damage for a square zone', () => {
const special = createSpecial<ISpecial13Value>(15, {
zone: 4,
zoneSquare: true,
square: true,
range: 2
});
const view = new modules.ZoneDamageView(
@ -257,7 +257,7 @@ describe('ZoneDamageView', () => {
it('checks coordinates against the zone range', () => {
const special = createSpecial<ISpecial13Value>(15, {
zone: 7,
zoneSquare: false,
square: false,
range: 1
});
const view = new modules.ZoneDamageView(

View File

@ -147,8 +147,8 @@ describe('registerSpecials', () => {
expect(halo.getDescription()).toContain('线性叠加。');
const value: ISpecial23Value = {
haloRange: 2,
haloSquare: true,
range: 2,
square: true,
hpBuff: 10,
atkBuff: 0,
defBuff: 0

View File

@ -63,7 +63,7 @@ export class CommonAura implements IEnemyAuraView<
private createRange(): IRange<
IRectRangeParam | IManhattanRangeParam | void
> {
const { haloRange, haloSquare } = this.special.value;
const { range: haloRange, square: haloSquare } = this.special.value;
if (haloRange <= 0) {
return FULL_RANGE;
}
@ -71,7 +71,7 @@ export class CommonAura implements IEnemyAuraView<
}
getRangeParam(): IRectRangeParam | IManhattanRangeParam | void {
const { haloRange, haloSquare } = this.special.value;
const { range: haloRange, square: haloSquare } = this.special.value;
if (haloRange <= 0) {
return undefined;
}

View File

@ -93,11 +93,11 @@ export class ZoneDamageView extends BaseMapDamageView<
}
getRange(): IRange<IRectRangeParam | IManhattanRangeParam> {
return this.special.value.zoneSquare ? RECT_RANGE : MANHATTAN_RANGE;
return this.special.value.square ? RECT_RANGE : MANHATTAN_RANGE;
}
getRangeParam(): IRectRangeParam | IManhattanRangeParam {
if (this.special.value.zoneSquare) {
if (this.special.value.square) {
return {
h: this.special.value.range * 2 + 1,
w: this.special.value.range * 2 + 1,

View File

@ -3,7 +3,6 @@ import {
defineNonePropertySpecial,
SpecialCreation
} from '@user/data-base';
import { IEnemyAttr } from '@user/data-common';
import { IStateSystem } from '@user/data-system';
// 重要!!!下面这一行的注释不要删
@ -25,7 +24,7 @@ export interface ISpecial13Value {
/** 领域伤害值 */
zone: number;
/** 矩形领域(true)还是十字形领域(false),当范围大于 1 时,十字型领域采用曼哈顿距离计算 */
zoneSquare: boolean;
square: boolean;
/** 领域范围值 */
range: number;
}
@ -38,11 +37,12 @@ export interface ISpecial21Value {
defValue: number;
}
/** 23 - 光环 */
export interface ISpecial23Value {
/** 光环范围值 */
haloRange: number;
range: number;
/** 光环是矩形范围(true)还是十字型范围(false),当范围大于 1 时,十字型范围采用曼哈顿距离计算 */
haloSquare: boolean;
square: boolean;
/** 生命值增加比例 */
hpBuff: number;
/** 攻击力增加比例 */
@ -51,6 +51,18 @@ export interface ISpecial23Value {
defBuff: number;
}
/** 26 - 特殊光环 */
export interface ISpecial26Value {
/** 光环范围值 */
range: number;
/** 光环是矩形范围(true)还是十字型范围(false),当范围大于 1 时,十字型范围采用曼哈顿距离计算 */
square: boolean;
/** 当怪物受到多个特殊光环加成时,是乘算叠加(true),还是加算叠加(false) */
mulAdd: boolean;
/** 增加的特殊属性列表 */
specials: [number, any][];
}
// 重要!!!下面这一行的注释不要删
//#endregion
@ -69,10 +81,10 @@ export interface ISpecial23Value {
*/
export function createSpecials(
state: IStateSystem
): ReadonlyMap<number, SpecialCreation<any, IEnemyAttr>> {
): ReadonlyMap<number, SpecialCreation<any>> {
const hero = state.hero;
const map = new Map<number, SpecialCreation<any, IEnemyAttr>>();
const map = new Map<number, SpecialCreation<any>>();
// 注意,不需要参数的特殊属性,如先攻、魔攻、坚固这些纯机制,
// 不需要一个数值来描述的特殊属性,使用 defineNonePropertySpecial 定义。
@ -250,11 +262,11 @@ export function createSpecials(
13,
defineCommonSerializableSpecial<ISpecial13Value>(
13,
{ zone: 0, zoneSquare: false, range: 1 },
{ zone: 0, square: false, range: 1 },
{
name: '领域',
desc: special => {
const { zone, zoneSquare, range } = special.value;
const { zone, square: zoneSquare, range } = special.value;
return `经过怪物周围${zoneSquare ? '九宫格' : '十字'}范围内${range}格时自动减生命${zone}点。`;
},
color: '#C677DD'
@ -368,8 +380,8 @@ export function createSpecials(
defineCommonSerializableSpecial<ISpecial23Value>(
23,
{
haloRange: 0,
haloSquare: false,
range: 0,
square: false,
hpBuff: 0,
atkBuff: 0,
defBuff: 0
@ -377,8 +389,13 @@ export function createSpecials(
{
name: '光环',
desc: special => {
const { haloRange, haloSquare, hpBuff, atkBuff, defBuff } =
special.value;
const {
range: haloRange,
square: haloSquare,
hpBuff,
atkBuff,
defBuff
} = special.value;
let str = '';
if (haloRange > 0) {
if (haloSquare) {
@ -420,14 +437,46 @@ export function createSpecials(
// 25 - 捕捉
map.set(
25,
defineNonePropertySpecial(27, {
defineNonePropertySpecial(25, {
name: '捕捉',
desc: '当走到怪物十字范围内时会进行强制战斗。',
color: '#C0DDBB'
})
);
// TODO: 26 - 特殊光环
// 26 - 特殊光环
map.set(
26,
defineCommonSerializableSpecial<ISpecial26Value>(
26,
{
range: 0,
square: false,
mulAdd: false,
specials: []
},
{
name: '特殊光环',
desc: special => {
const { range, square, specials, mulAdd } = special.value;
const prefix = `在怪物周围${square ? '正方形' : '十字'} ${range} 格范围内的怪物获得如下特殊属性,${mulAdd ? '乘算' : '加算'}叠加:\n`;
const list: string[] = [];
for (const [code, param] of specials) {
const special = state.enemyManager.getSpecial(code)?.();
if (!special) continue;
special.setValue(param);
const name = special.getSpecialName();
const color = special.getNameColor();
const desc = special.getDescription();
const item = ` ${list.length + 1}. \\r[${color}]${name}\\r: ${desc}`;
list.push(item);
}
return prefix + list.join('\n');
},
color: '#e945ff'
}
)
);
// 重要!!!下面这一行的注释不要删
//#endregion