feat(effect): 添加效果系统模块 (#332)

实现 Buff/Debuff 效果管理和属性修改器系统:

核心功能:
- 效果容器管理效果的应用、移除和更新
- 支持持续时间类型: 永久/计时/条件
- 支持叠加规则: 刷新/叠加/独立/替换/忽略
- 效果标签系统用于分类和查询
- 效果优先级和互斥标签支持
- 效果事件系统 (应用/移除/叠加/刷新/跳动/过期)

修改器系统:
- 属性修改器支持加法/乘法/覆盖/最小值/最大值操作
- 优先级分层计算 (基础/加法/乘法/最终)
- 数值计算器自动按优先级应用修改器

蓝图节点 (12个):
- ApplyEffect: 应用效果
- RemoveEffect/RemoveEffectByTag: 移除效果
- HasEffect/HasEffectTag: 检查效果
- GetEffectStacks/GetEffectRemainingTime/GetEffectCount: 查询效果
- ClearAllEffects: 清除所有效果
- OnEffectApplied/OnEffectRemoved/OnEffectTick: 效果事件
This commit is contained in:
YHH
2025-12-25 15:17:06 +08:00
committed by GitHub
parent 275124b66c
commit 4d501ba448
15 changed files with 1917 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
/**
* @zh 修改器接口定义
* @en Modifier Interface Definitions
*/
// =============================================================================
// 修改器类型 | Modifier Types
// =============================================================================
/**
* @zh 修改器操作类型
* @en Modifier operation type
*/
export type ModifierOperation = 'add' | 'multiply' | 'override' | 'min' | 'max';
/**
* @zh 修改器优先级
* @en Modifier priority
*/
export type ModifierPriority = 'base' | 'add' | 'multiply' | 'final';
/**
* @zh 修改器接口
* @en Modifier interface
*/
export interface IModifier<T = number> {
/**
* @zh 修改器 ID
* @en Modifier ID
*/
readonly id: string;
/**
* @zh 修改器来源(效果实例 ID
* @en Modifier source (effect instance ID)
*/
readonly sourceId: string;
/**
* @zh 修改的属性名
* @en Modified attribute name
*/
readonly attribute: string;
/**
* @zh 操作类型
* @en Operation type
*/
readonly operation: ModifierOperation;
/**
* @zh 优先级
* @en Priority
*/
readonly priority: ModifierPriority;
/**
* @zh 修改值
* @en Modifier value
*/
value: T;
/**
* @zh 是否激活
* @en Whether active
*/
isActive: boolean;
}
/**
* @zh 属性值计算器
* @en Attribute value calculator
*/
export interface IAttributeCalculator<T = number> {
/**
* @zh 计算最终属性值
* @en Calculate final attribute value
*
* @param baseValue - @zh 基础值 @en Base value
* @param modifiers - @zh 修改器列表 @en Modifier list
* @returns @zh 最终值 @en Final value
*/
calculate(baseValue: T, modifiers: IModifier<T>[]): T;
}

View File

@@ -0,0 +1,304 @@
/**
* @zh 修改器容器
* @en Modifier Container
*
* @zh 管理属性修改器并计算最终值
* @en Manages attribute modifiers and calculates final values
*/
import type { IModifier, ModifierOperation, ModifierPriority, IAttributeCalculator } from './IModifier';
let modifierCounter = 0;
function generateModifierId(): string {
return `mod_${Date.now()}_${++modifierCounter}`;
}
/**
* @zh 默认数值计算器
* @en Default numeric calculator
*/
export class NumericCalculator implements IAttributeCalculator<number> {
calculate(baseValue: number, modifiers: IModifier<number>[]): number {
if (modifiers.length === 0) return baseValue;
// Sort by priority
const sorted = [...modifiers].sort((a, b) => {
const priorityOrder: Record<ModifierPriority, number> = {
base: 0,
add: 1,
multiply: 2,
final: 3
};
return priorityOrder[a.priority] - priorityOrder[b.priority];
});
let value = baseValue;
let addSum = 0;
let multiplyProduct = 1;
for (const mod of sorted) {
if (!mod.isActive) continue;
switch (mod.operation) {
case 'add':
addSum += mod.value;
break;
case 'multiply':
multiplyProduct *= mod.value;
break;
case 'override':
value = mod.value;
addSum = 0;
multiplyProduct = 1;
break;
case 'min':
value = Math.min(value, mod.value);
break;
case 'max':
value = Math.max(value, mod.value);
break;
}
}
// Apply in order: base → add → multiply
return (value + addSum) * multiplyProduct;
}
}
/**
* @zh 修改器容器
* @en Modifier container
*/
export class ModifierContainer<T = number> {
private readonly _modifiers: Map<string, IModifier<T>> = new Map();
private readonly _modifiersByAttribute: Map<string, Set<string>> = new Map();
private readonly _modifiersBySource: Map<string, Set<string>> = new Map();
private readonly _calculator: IAttributeCalculator<T>;
private readonly _baseValues: Map<string, T> = new Map();
private readonly _cachedValues: Map<string, T> = new Map();
private _isDirty = false;
/**
* @zh 创建修改器容器
* @en Create modifier container
*
* @param calculator - @zh 属性计算器 @en Attribute calculator
*/
constructor(calculator?: IAttributeCalculator<T>) {
this._calculator = calculator ?? new NumericCalculator() as unknown as IAttributeCalculator<T>;
}
/**
* @zh 设置属性基础值
* @en Set attribute base value
*/
setBaseValue(attribute: string, value: T): void {
this._baseValues.set(attribute, value);
this._invalidateAttribute(attribute);
}
/**
* @zh 获取属性基础值
* @en Get attribute base value
*/
getBaseValue(attribute: string): T | undefined {
return this._baseValues.get(attribute);
}
/**
* @zh 添加修改器
* @en Add modifier
*/
addModifier(
attribute: string,
operation: ModifierOperation,
value: T,
sourceId: string,
priority: ModifierPriority = 'add'
): IModifier<T> {
const modifier: IModifier<T> = {
id: generateModifierId(),
sourceId,
attribute,
operation,
priority,
value,
isActive: true
};
this._modifiers.set(modifier.id, modifier);
// Index by attribute
if (!this._modifiersByAttribute.has(attribute)) {
this._modifiersByAttribute.set(attribute, new Set());
}
this._modifiersByAttribute.get(attribute)!.add(modifier.id);
// Index by source
if (!this._modifiersBySource.has(sourceId)) {
this._modifiersBySource.set(sourceId, new Set());
}
this._modifiersBySource.get(sourceId)!.add(modifier.id);
this._invalidateAttribute(attribute);
return modifier;
}
/**
* @zh 移除修改器
* @en Remove modifier
*/
removeModifier(modifierId: string): boolean {
const modifier = this._modifiers.get(modifierId);
if (!modifier) return false;
this._modifiers.delete(modifierId);
this._modifiersByAttribute.get(modifier.attribute)?.delete(modifierId);
this._modifiersBySource.get(modifier.sourceId)?.delete(modifierId);
this._invalidateAttribute(modifier.attribute);
return true;
}
/**
* @zh 按来源移除修改器
* @en Remove modifiers by source
*/
removeBySource(sourceId: string): number {
const ids = this._modifiersBySource.get(sourceId);
if (!ids) return 0;
let count = 0;
for (const id of [...ids]) {
if (this.removeModifier(id)) count++;
}
this._modifiersBySource.delete(sourceId);
return count;
}
/**
* @zh 按属性移除修改器
* @en Remove modifiers by attribute
*/
removeByAttribute(attribute: string): number {
const ids = this._modifiersByAttribute.get(attribute);
if (!ids) return 0;
let count = 0;
for (const id of [...ids]) {
if (this.removeModifier(id)) count++;
}
return count;
}
/**
* @zh 获取属性的所有修改器
* @en Get all modifiers for an attribute
*/
getModifiersForAttribute(attribute: string): IModifier<T>[] {
const ids = this._modifiersByAttribute.get(attribute);
if (!ids) return [];
return [...ids].map(id => this._modifiers.get(id)!).filter(Boolean);
}
/**
* @zh 获取来源的所有修改器
* @en Get all modifiers from a source
*/
getModifiersFromSource(sourceId: string): IModifier<T>[] {
const ids = this._modifiersBySource.get(sourceId);
if (!ids) return [];
return [...ids].map(id => this._modifiers.get(id)!).filter(Boolean);
}
/**
* @zh 计算属性最终值
* @en Calculate attribute final value
*/
getValue(attribute: string): T {
// Check cache
if (!this._isDirty && this._cachedValues.has(attribute)) {
return this._cachedValues.get(attribute)!;
}
const baseValue = this._baseValues.get(attribute);
if (baseValue === undefined) {
throw new Error(`No base value set for attribute: ${attribute}`);
}
const modifiers = this.getModifiersForAttribute(attribute);
const finalValue = this._calculator.calculate(baseValue, modifiers);
this._cachedValues.set(attribute, finalValue);
return finalValue;
}
/**
* @zh 尝试获取属性最终值
* @en Try to get attribute final value
*/
tryGetValue(attribute: string, defaultValue: T): T {
try {
return this.getValue(attribute);
} catch {
return defaultValue;
}
}
/**
* @zh 检查属性是否有修改器
* @en Check if attribute has modifiers
*/
hasModifiers(attribute: string): boolean {
const ids = this._modifiersByAttribute.get(attribute);
return ids !== undefined && ids.size > 0;
}
/**
* @zh 获取所有已修改的属性
* @en Get all modified attributes
*/
getModifiedAttributes(): string[] {
return [...this._modifiersByAttribute.keys()];
}
/**
* @zh 清除所有修改器
* @en Clear all modifiers
*/
clear(): void {
this._modifiers.clear();
this._modifiersByAttribute.clear();
this._modifiersBySource.clear();
this._cachedValues.clear();
this._isDirty = true;
}
private _invalidateAttribute(attribute: string): void {
this._cachedValues.delete(attribute);
this._isDirty = true;
}
/**
* @zh 标记缓存已更新
* @en Mark cache as updated
*/
markClean(): void {
this._isDirty = false;
}
}
/**
* @zh 创建修改器容器
* @en Create modifier container
*/
export function createModifierContainer<T = number>(
calculator?: IAttributeCalculator<T>
): ModifierContainer<T> {
return new ModifierContainer(calculator);
}

View File

@@ -0,0 +1,17 @@
/**
* @zh 修改器模块
* @en Modifier Module
*/
export type {
ModifierOperation,
ModifierPriority,
IModifier,
IAttributeCalculator
} from './IModifier';
export {
NumericCalculator,
ModifierContainer,
createModifierContainer
} from './ModifierContainer';