refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
24
packages/rendering/effect/module.json
Normal file
24
packages/rendering/effect/module.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"id": "effect",
|
||||
"name": "@esengine/effect",
|
||||
"globalKey": "effect",
|
||||
"displayName": "Effect System",
|
||||
"description": "效果系统,支持持续时间、叠加规则和属性修改器 | Effect system with duration, stacking rules, and attribute modifiers",
|
||||
"version": "1.0.0",
|
||||
"category": "Gameplay",
|
||||
"icon": "Sparkles",
|
||||
"tags": ["effect", "buff", "debuff", "modifier", "status"],
|
||||
"isCore": false,
|
||||
"defaultEnabled": true,
|
||||
"isEngineModule": true,
|
||||
"canContainContent": false,
|
||||
"platforms": ["web", "desktop"],
|
||||
"dependencies": ["core"],
|
||||
"exports": {
|
||||
"components": ["EffectContainer"],
|
||||
"systems": ["EffectSystem"]
|
||||
},
|
||||
"requiresWasm": false,
|
||||
"outputPath": "dist/index.js",
|
||||
"pluginExport": "EffectPlugin"
|
||||
}
|
||||
40
packages/rendering/effect/package.json
Normal file
40
packages/rendering/effect/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@esengine/effect",
|
||||
"version": "1.0.0",
|
||||
"description": "Effect system for ECS Framework / ECS 框架的效果系统",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"module.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"build:watch": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esengine/ecs-framework": "workspace:*",
|
||||
"@esengine/blueprint": "workspace:*",
|
||||
"@esengine/build-config": "workspace:*",
|
||||
"@types/node": "^20.19.17",
|
||||
"rimraf": "^5.0.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
443
packages/rendering/effect/src/core/EffectContainer.ts
Normal file
443
packages/rendering/effect/src/core/EffectContainer.ts
Normal file
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* @zh 效果容器
|
||||
* @en Effect Container
|
||||
*
|
||||
* @zh 管理单个实体上的所有效果
|
||||
* @en Manages all effects on a single entity
|
||||
*/
|
||||
|
||||
import type {
|
||||
IEffectInstance,
|
||||
IEffectDefinition,
|
||||
IEffectEvent,
|
||||
EffectEventListener,
|
||||
EffectEventType,
|
||||
IEffectHandler
|
||||
} from './IEffect';
|
||||
|
||||
let instanceCounter = 0;
|
||||
|
||||
function generateInstanceId(): string {
|
||||
return `effect_${Date.now()}_${++instanceCounter}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 效果容器
|
||||
* @en Effect container
|
||||
*/
|
||||
export class EffectContainer<TTarget = unknown> {
|
||||
private readonly _effects: Map<string, IEffectInstance> = new Map();
|
||||
private readonly _effectsByType: Map<string, Set<string>> = new Map();
|
||||
private readonly _effectsByTag: Map<string, Set<string>> = new Map();
|
||||
private readonly _handlers: Map<string, IEffectHandler<TTarget>> = new Map();
|
||||
private readonly _listeners: Map<EffectEventType, Set<EffectEventListener>> = new Map();
|
||||
private readonly _target: TTarget;
|
||||
private readonly _targetId: string;
|
||||
|
||||
/**
|
||||
* @zh 创建效果容器
|
||||
* @en Create effect container
|
||||
*
|
||||
* @param target - @zh 目标对象 @en Target object
|
||||
* @param targetId - @zh 目标 ID @en Target ID
|
||||
*/
|
||||
constructor(target: TTarget, targetId: string) {
|
||||
this._target = target;
|
||||
this._targetId = targetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取目标对象
|
||||
* @en Get target object
|
||||
*/
|
||||
get target(): TTarget {
|
||||
return this._target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取目标 ID
|
||||
* @en Get target ID
|
||||
*/
|
||||
get targetId(): string {
|
||||
return this._targetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取效果数量
|
||||
* @en Get effect count
|
||||
*/
|
||||
get count(): number {
|
||||
return this._effects.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 注册效果处理器
|
||||
* @en Register effect handler
|
||||
*/
|
||||
registerHandler(typeId: string, handler: IEffectHandler<TTarget>): void {
|
||||
this._handlers.set(typeId, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 注销效果处理器
|
||||
* @en Unregister effect handler
|
||||
*/
|
||||
unregisterHandler(typeId: string): void {
|
||||
this._handlers.delete(typeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 添加事件监听器
|
||||
* @en Add event listener
|
||||
*/
|
||||
addEventListener(type: EffectEventType, listener: EffectEventListener): void {
|
||||
if (!this._listeners.has(type)) {
|
||||
this._listeners.set(type, new Set());
|
||||
}
|
||||
this._listeners.get(type)!.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 移除事件监听器
|
||||
* @en Remove event listener
|
||||
*/
|
||||
removeEventListener(type: EffectEventType, listener: EffectEventListener): void {
|
||||
this._listeners.get(type)?.delete(listener);
|
||||
}
|
||||
|
||||
private _emitEvent(type: EffectEventType, effect: IEffectInstance, data?: Record<string, unknown>): void {
|
||||
const event: IEffectEvent = {
|
||||
type,
|
||||
effect,
|
||||
targetId: this._targetId,
|
||||
timestamp: Date.now(),
|
||||
data
|
||||
};
|
||||
|
||||
this._listeners.get(type)?.forEach(listener => listener(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 应用效果
|
||||
* @en Apply effect
|
||||
*
|
||||
* @param definition - @zh 效果定义 @en Effect definition
|
||||
* @param sourceId - @zh 来源 ID @en Source ID
|
||||
* @param initialData - @zh 初始数据 @en Initial data
|
||||
* @returns @zh 效果实例或 null @en Effect instance or null
|
||||
*/
|
||||
apply(
|
||||
definition: IEffectDefinition,
|
||||
sourceId?: string,
|
||||
initialData?: Record<string, unknown>
|
||||
): IEffectInstance | null {
|
||||
// Handle exclusive tags - remove conflicting effects
|
||||
if (definition.exclusiveTags) {
|
||||
for (const tag of definition.exclusiveTags) {
|
||||
this.removeByTag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing effect of same type
|
||||
const existingIds = this._effectsByType.get(definition.typeId);
|
||||
if (existingIds && existingIds.size > 0) {
|
||||
const existingId = existingIds.values().next().value as string;
|
||||
const existing = this._effects.get(existingId);
|
||||
|
||||
if (existing) {
|
||||
return this._handleStacking(existing, definition);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new effect instance
|
||||
const instance = this._createInstance(definition, sourceId, initialData);
|
||||
|
||||
// Add to collections
|
||||
this._effects.set(instance.instanceId, instance);
|
||||
|
||||
if (!this._effectsByType.has(definition.typeId)) {
|
||||
this._effectsByType.set(definition.typeId, new Set());
|
||||
}
|
||||
this._effectsByType.get(definition.typeId)!.add(instance.instanceId);
|
||||
|
||||
for (const tag of definition.tags) {
|
||||
if (!this._effectsByTag.has(tag)) {
|
||||
this._effectsByTag.set(tag, new Set());
|
||||
}
|
||||
this._effectsByTag.get(tag)!.add(instance.instanceId);
|
||||
}
|
||||
|
||||
// Call handler
|
||||
const handler = this._handlers.get(definition.typeId);
|
||||
handler?.onApply?.(instance, this._target);
|
||||
|
||||
// Emit event
|
||||
this._emitEvent('applied', instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private _createInstance(
|
||||
definition: IEffectDefinition,
|
||||
sourceId?: string,
|
||||
initialData?: Record<string, unknown>
|
||||
): IEffectInstance {
|
||||
const duration = definition.duration;
|
||||
let remainingTime = Infinity;
|
||||
|
||||
if (duration.type === 'timed' && duration.duration !== undefined) {
|
||||
remainingTime = duration.duration;
|
||||
}
|
||||
|
||||
return {
|
||||
instanceId: generateInstanceId(),
|
||||
definition,
|
||||
sourceId,
|
||||
stacks: 1,
|
||||
remainingTime,
|
||||
nextTickTime: definition.tickInterval ?? 0,
|
||||
data: { ...initialData },
|
||||
isActive: true,
|
||||
appliedAt: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
private _handleStacking(existing: IEffectInstance, definition: IEffectDefinition): IEffectInstance | null {
|
||||
const rule = definition.stacking.rule;
|
||||
|
||||
switch (rule) {
|
||||
case 'refresh':
|
||||
// Reset duration
|
||||
if (definition.duration.type === 'timed' && definition.duration.duration !== undefined) {
|
||||
existing.remainingTime = definition.duration.duration;
|
||||
}
|
||||
this._handlers.get(definition.typeId)?.onRefresh?.(existing, this._target);
|
||||
this._emitEvent('refreshed', existing);
|
||||
return existing;
|
||||
|
||||
case 'stack':
|
||||
// Add stacks
|
||||
const maxStacks = definition.stacking.maxStacks ?? Infinity;
|
||||
if (existing.stacks < maxStacks) {
|
||||
existing.stacks++;
|
||||
this._handlers.get(definition.typeId)?.onStack?.(existing, this._target, existing.stacks);
|
||||
this._emitEvent('stacked', existing, { stacks: existing.stacks });
|
||||
}
|
||||
// Also refresh duration
|
||||
if (definition.duration.type === 'timed' && definition.duration.duration !== undefined) {
|
||||
existing.remainingTime = definition.duration.duration;
|
||||
}
|
||||
return existing;
|
||||
|
||||
case 'replace':
|
||||
// Remove existing and apply new
|
||||
this.remove(existing.instanceId);
|
||||
return null; // Will be created as new
|
||||
|
||||
case 'ignore':
|
||||
// Do nothing
|
||||
return existing;
|
||||
|
||||
case 'independent':
|
||||
default:
|
||||
// Allow multiple instances - will create new one
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 移除效果
|
||||
* @en Remove effect
|
||||
*
|
||||
* @param instanceId - @zh 实例 ID @en Instance ID
|
||||
*/
|
||||
remove(instanceId: string): boolean {
|
||||
const effect = this._effects.get(instanceId);
|
||||
if (!effect) return false;
|
||||
|
||||
effect.isActive = false;
|
||||
|
||||
// Call handler
|
||||
const handler = this._handlers.get(effect.definition.typeId);
|
||||
handler?.onRemove?.(effect, this._target);
|
||||
|
||||
// Remove from collections
|
||||
this._effects.delete(instanceId);
|
||||
this._effectsByType.get(effect.definition.typeId)?.delete(instanceId);
|
||||
|
||||
for (const tag of effect.definition.tags) {
|
||||
this._effectsByTag.get(tag)?.delete(instanceId);
|
||||
}
|
||||
|
||||
// Emit event
|
||||
this._emitEvent('removed', effect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 按类型移除效果
|
||||
* @en Remove effects by type
|
||||
*/
|
||||
removeByType(typeId: string): number {
|
||||
const ids = this._effectsByType.get(typeId);
|
||||
if (!ids) return 0;
|
||||
|
||||
let count = 0;
|
||||
for (const id of [...ids]) {
|
||||
if (this.remove(id)) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 按标签移除效果
|
||||
* @en Remove effects by tag
|
||||
*/
|
||||
removeByTag(tag: string): number {
|
||||
const ids = this._effectsByTag.get(tag);
|
||||
if (!ids) return 0;
|
||||
|
||||
let count = 0;
|
||||
for (const id of [...ids]) {
|
||||
if (this.remove(id)) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 移除所有效果
|
||||
* @en Remove all effects
|
||||
*/
|
||||
removeAll(): void {
|
||||
for (const id of [...this._effects.keys()]) {
|
||||
this.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取效果实例
|
||||
* @en Get effect instance
|
||||
*/
|
||||
get(instanceId: string): IEffectInstance | undefined {
|
||||
return this._effects.get(instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 按类型获取效果
|
||||
* @en Get effects by type
|
||||
*/
|
||||
getByType(typeId: string): IEffectInstance[] {
|
||||
const ids = this._effectsByType.get(typeId);
|
||||
if (!ids) return [];
|
||||
return [...ids].map(id => this._effects.get(id)!).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 按标签获取效果
|
||||
* @en Get effects by tag
|
||||
*/
|
||||
getByTag(tag: string): IEffectInstance[] {
|
||||
const ids = this._effectsByTag.get(tag);
|
||||
if (!ids) return [];
|
||||
return [...ids].map(id => this._effects.get(id)!).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查是否有指定类型的效果
|
||||
* @en Check if has effect of specified type
|
||||
*/
|
||||
hasType(typeId: string): boolean {
|
||||
const ids = this._effectsByType.get(typeId);
|
||||
return ids !== undefined && ids.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查是否有指定标签的效果
|
||||
* @en Check if has effect with specified tag
|
||||
*/
|
||||
hasTag(tag: string): boolean {
|
||||
const ids = this._effectsByTag.get(tag);
|
||||
return ids !== undefined && ids.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取指定类型的叠加层数
|
||||
* @en Get stack count for specified type
|
||||
*/
|
||||
getStacks(typeId: string): number {
|
||||
const effects = this.getByType(typeId);
|
||||
return effects.reduce((sum, e) => sum + e.stacks, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取所有效果
|
||||
* @en Get all effects
|
||||
*/
|
||||
getAll(): IEffectInstance[] {
|
||||
return [...this._effects.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 更新效果(每帧调用)
|
||||
* @en Update effects (called every frame)
|
||||
*
|
||||
* @param deltaTime - @zh 帧时间(秒)@en Delta time in seconds
|
||||
*/
|
||||
update(deltaTime: number): void {
|
||||
const toRemove: string[] = [];
|
||||
|
||||
for (const effect of this._effects.values()) {
|
||||
if (!effect.isActive) continue;
|
||||
|
||||
const definition = effect.definition;
|
||||
const handler = this._handlers.get(definition.typeId);
|
||||
|
||||
// Update remaining time
|
||||
if (definition.duration.type === 'timed') {
|
||||
effect.remainingTime -= deltaTime;
|
||||
if (effect.remainingTime <= 0) {
|
||||
this._emitEvent('expired', effect);
|
||||
toRemove.push(effect.instanceId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check conditional duration
|
||||
if (definition.duration.type === 'conditional') {
|
||||
const condition = definition.duration.condition;
|
||||
if (condition && !condition()) {
|
||||
this._emitEvent('expired', effect);
|
||||
toRemove.push(effect.instanceId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle periodic tick
|
||||
if (definition.tickInterval && definition.tickInterval > 0) {
|
||||
effect.nextTickTime -= deltaTime;
|
||||
if (effect.nextTickTime <= 0) {
|
||||
handler?.onTick?.(effect, this._target, deltaTime);
|
||||
this._emitEvent('ticked', effect);
|
||||
effect.nextTickTime = definition.tickInterval;
|
||||
}
|
||||
}
|
||||
|
||||
// Call update handler
|
||||
handler?.onUpdate?.(effect, this._target, deltaTime);
|
||||
}
|
||||
|
||||
// Remove expired effects
|
||||
for (const id of toRemove) {
|
||||
this.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 创建效果容器
|
||||
* @en Create effect container
|
||||
*/
|
||||
export function createEffectContainer<TTarget>(target: TTarget, targetId: string): EffectContainer<TTarget> {
|
||||
return new EffectContainer(target, targetId);
|
||||
}
|
||||
306
packages/rendering/effect/src/core/IEffect.ts
Normal file
306
packages/rendering/effect/src/core/IEffect.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* @zh 效果接口定义
|
||||
* @en Effect Interface Definitions
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// 持续时间类型 | Duration Types
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 持续时间类型
|
||||
* @en Duration type
|
||||
*/
|
||||
export type DurationType = 'permanent' | 'timed' | 'conditional';
|
||||
|
||||
/**
|
||||
* @zh 持续时间配置
|
||||
* @en Duration configuration
|
||||
*/
|
||||
export interface IEffectDuration {
|
||||
/**
|
||||
* @zh 持续时间类型
|
||||
* @en Duration type
|
||||
*/
|
||||
readonly type: DurationType;
|
||||
|
||||
/**
|
||||
* @zh 持续时间(秒),仅 timed 类型有效
|
||||
* @en Duration in seconds, only valid for timed type
|
||||
*/
|
||||
readonly duration?: number;
|
||||
|
||||
/**
|
||||
* @zh 剩余时间(秒)
|
||||
* @en Remaining time in seconds
|
||||
*/
|
||||
remainingTime?: number;
|
||||
|
||||
/**
|
||||
* @zh 条件检查函数,仅 conditional 类型有效
|
||||
* @en Condition check function, only valid for conditional type
|
||||
*/
|
||||
readonly condition?: () => boolean;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 叠加规则 | Stacking Rules
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 叠加规则类型
|
||||
* @en Stacking rule type
|
||||
*/
|
||||
export type StackingRule = 'refresh' | 'stack' | 'independent' | 'replace' | 'ignore';
|
||||
|
||||
/**
|
||||
* @zh 叠加配置
|
||||
* @en Stacking configuration
|
||||
*/
|
||||
export interface IStackingConfig {
|
||||
/**
|
||||
* @zh 叠加规则
|
||||
* @en Stacking rule
|
||||
*/
|
||||
readonly rule: StackingRule;
|
||||
|
||||
/**
|
||||
* @zh 最大叠加层数(stack 规则)
|
||||
* @en Maximum stack count (for stack rule)
|
||||
*/
|
||||
readonly maxStacks?: number;
|
||||
|
||||
/**
|
||||
* @zh 每层效果强度倍率(stack 规则)
|
||||
* @en Effect intensity multiplier per stack (for stack rule)
|
||||
*/
|
||||
readonly stackMultiplier?: number;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 效果接口 | Effect Interface
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 效果定义
|
||||
* @en Effect definition
|
||||
*/
|
||||
export interface IEffectDefinition {
|
||||
/**
|
||||
* @zh 效果类型 ID
|
||||
* @en Effect type ID
|
||||
*/
|
||||
readonly typeId: string;
|
||||
|
||||
/**
|
||||
* @zh 显示名称
|
||||
* @en Display name
|
||||
*/
|
||||
readonly displayName: string;
|
||||
|
||||
/**
|
||||
* @zh 描述
|
||||
* @en Description
|
||||
*/
|
||||
readonly description?: string;
|
||||
|
||||
/**
|
||||
* @zh 图标
|
||||
* @en Icon
|
||||
*/
|
||||
readonly icon?: string;
|
||||
|
||||
/**
|
||||
* @zh 标签(用于分组、互斥、增强)
|
||||
* @en Tags (for grouping, exclusion, enhancement)
|
||||
*/
|
||||
readonly tags: readonly string[];
|
||||
|
||||
/**
|
||||
* @zh 持续时间配置
|
||||
* @en Duration configuration
|
||||
*/
|
||||
readonly duration: IEffectDuration;
|
||||
|
||||
/**
|
||||
* @zh 叠加配置
|
||||
* @en Stacking configuration
|
||||
*/
|
||||
readonly stacking: IStackingConfig;
|
||||
|
||||
/**
|
||||
* @zh 周期性触发间隔(秒),0 表示不周期触发
|
||||
* @en Periodic trigger interval in seconds, 0 means no periodic trigger
|
||||
*/
|
||||
readonly tickInterval?: number;
|
||||
|
||||
/**
|
||||
* @zh 互斥标签(拥有这些标签的效果会被移除)
|
||||
* @en Exclusive tags (effects with these tags will be removed)
|
||||
*/
|
||||
readonly exclusiveTags?: readonly string[];
|
||||
|
||||
/**
|
||||
* @zh 效果优先级(用于处理顺序)
|
||||
* @en Effect priority (for processing order)
|
||||
*/
|
||||
readonly priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 效果实例
|
||||
* @en Effect instance
|
||||
*/
|
||||
export interface IEffectInstance {
|
||||
/**
|
||||
* @zh 实例唯一 ID
|
||||
* @en Instance unique ID
|
||||
*/
|
||||
readonly instanceId: string;
|
||||
|
||||
/**
|
||||
* @zh 效果定义
|
||||
* @en Effect definition
|
||||
*/
|
||||
readonly definition: IEffectDefinition;
|
||||
|
||||
/**
|
||||
* @zh 效果来源(施加者 ID)
|
||||
* @en Effect source (applier ID)
|
||||
*/
|
||||
readonly sourceId?: string;
|
||||
|
||||
/**
|
||||
* @zh 当前叠加层数
|
||||
* @en Current stack count
|
||||
*/
|
||||
stacks: number;
|
||||
|
||||
/**
|
||||
* @zh 剩余时间(秒)
|
||||
* @en Remaining time in seconds
|
||||
*/
|
||||
remainingTime: number;
|
||||
|
||||
/**
|
||||
* @zh 下次触发时间(秒)
|
||||
* @en Next tick time in seconds
|
||||
*/
|
||||
nextTickTime: number;
|
||||
|
||||
/**
|
||||
* @zh 效果数据
|
||||
* @en Effect data
|
||||
*/
|
||||
data: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* @zh 效果是否激活
|
||||
* @en Whether the effect is active
|
||||
*/
|
||||
isActive: boolean;
|
||||
|
||||
/**
|
||||
* @zh 应用时间戳
|
||||
* @en Application timestamp
|
||||
*/
|
||||
readonly appliedAt: number;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 效果事件 | Effect Events
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 效果事件类型
|
||||
* @en Effect event type
|
||||
*/
|
||||
export type EffectEventType = 'applied' | 'removed' | 'stacked' | 'refreshed' | 'ticked' | 'expired';
|
||||
|
||||
/**
|
||||
* @zh 效果事件
|
||||
* @en Effect event
|
||||
*/
|
||||
export interface IEffectEvent {
|
||||
/**
|
||||
* @zh 事件类型
|
||||
* @en Event type
|
||||
*/
|
||||
readonly type: EffectEventType;
|
||||
|
||||
/**
|
||||
* @zh 效果实例
|
||||
* @en Effect instance
|
||||
*/
|
||||
readonly effect: IEffectInstance;
|
||||
|
||||
/**
|
||||
* @zh 目标实体 ID
|
||||
* @en Target entity ID
|
||||
*/
|
||||
readonly targetId: string;
|
||||
|
||||
/**
|
||||
* @zh 事件时间戳
|
||||
* @en Event timestamp
|
||||
*/
|
||||
readonly timestamp: number;
|
||||
|
||||
/**
|
||||
* @zh 额外数据
|
||||
* @en Extra data
|
||||
*/
|
||||
readonly data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 效果事件监听器
|
||||
* @en Effect event listener
|
||||
*/
|
||||
export type EffectEventListener = (event: IEffectEvent) => void;
|
||||
|
||||
// =============================================================================
|
||||
// 效果处理器 | Effect Handler
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 效果处理器接口
|
||||
* @en Effect handler interface
|
||||
*/
|
||||
export interface IEffectHandler<TTarget = unknown> {
|
||||
/**
|
||||
* @zh 效果应用时调用
|
||||
* @en Called when effect is applied
|
||||
*/
|
||||
onApply?(effect: IEffectInstance, target: TTarget): void;
|
||||
|
||||
/**
|
||||
* @zh 效果移除时调用
|
||||
* @en Called when effect is removed
|
||||
*/
|
||||
onRemove?(effect: IEffectInstance, target: TTarget): void;
|
||||
|
||||
/**
|
||||
* @zh 效果叠加时调用
|
||||
* @en Called when effect is stacked
|
||||
*/
|
||||
onStack?(effect: IEffectInstance, target: TTarget, newStacks: number): void;
|
||||
|
||||
/**
|
||||
* @zh 效果刷新时调用
|
||||
* @en Called when effect is refreshed
|
||||
*/
|
||||
onRefresh?(effect: IEffectInstance, target: TTarget): void;
|
||||
|
||||
/**
|
||||
* @zh 效果周期触发时调用
|
||||
* @en Called on periodic tick
|
||||
*/
|
||||
onTick?(effect: IEffectInstance, target: TTarget, deltaTime: number): void;
|
||||
|
||||
/**
|
||||
* @zh 效果更新时调用(每帧)
|
||||
* @en Called on update (every frame)
|
||||
*/
|
||||
onUpdate?(effect: IEffectInstance, target: TTarget, deltaTime: number): void;
|
||||
}
|
||||
19
packages/rendering/effect/src/core/index.ts
Normal file
19
packages/rendering/effect/src/core/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @zh 效果核心模块
|
||||
* @en Effect Core Module
|
||||
*/
|
||||
|
||||
export type {
|
||||
DurationType,
|
||||
IEffectDuration,
|
||||
StackingRule,
|
||||
IStackingConfig,
|
||||
IEffectDefinition,
|
||||
IEffectInstance,
|
||||
EffectEventType,
|
||||
IEffectEvent,
|
||||
EffectEventListener,
|
||||
IEffectHandler
|
||||
} from './IEffect';
|
||||
|
||||
export { EffectContainer, createEffectContainer } from './EffectContainer';
|
||||
80
packages/rendering/effect/src/index.ts
Normal file
80
packages/rendering/effect/src/index.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @esengine/effect
|
||||
*
|
||||
* @zh 效果系统
|
||||
* @en Effect System
|
||||
*
|
||||
* @zh 提供 Buff/Debuff 效果管理和属性修改器
|
||||
* @en Provides Buff/Debuff effect management and attribute modifiers
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// Core | 核心
|
||||
// =============================================================================
|
||||
|
||||
export type {
|
||||
DurationType,
|
||||
IEffectDuration,
|
||||
StackingRule,
|
||||
IStackingConfig,
|
||||
IEffectDefinition,
|
||||
IEffectInstance,
|
||||
EffectEventType,
|
||||
IEffectEvent,
|
||||
EffectEventListener,
|
||||
IEffectHandler
|
||||
} from './core';
|
||||
|
||||
export { EffectContainer, createEffectContainer } from './core';
|
||||
|
||||
// =============================================================================
|
||||
// Modifiers | 修改器
|
||||
// =============================================================================
|
||||
|
||||
export type {
|
||||
ModifierOperation,
|
||||
ModifierPriority,
|
||||
IModifier,
|
||||
IAttributeCalculator
|
||||
} from './modifiers';
|
||||
|
||||
export {
|
||||
NumericCalculator,
|
||||
ModifierContainer,
|
||||
createModifierContainer
|
||||
} from './modifiers';
|
||||
|
||||
// =============================================================================
|
||||
// Blueprint Nodes | 蓝图节点
|
||||
// =============================================================================
|
||||
|
||||
export {
|
||||
// Templates
|
||||
ApplyEffectTemplate,
|
||||
RemoveEffectTemplate,
|
||||
RemoveEffectByTagTemplate,
|
||||
HasEffectTemplate,
|
||||
HasEffectTagTemplate,
|
||||
GetEffectStacksTemplate,
|
||||
GetEffectRemainingTimeTemplate,
|
||||
GetEffectCountTemplate,
|
||||
ClearAllEffectsTemplate,
|
||||
OnEffectAppliedTemplate,
|
||||
OnEffectRemovedTemplate,
|
||||
OnEffectTickTemplate,
|
||||
// Executors
|
||||
ApplyEffectExecutor,
|
||||
RemoveEffectExecutor,
|
||||
RemoveEffectByTagExecutor,
|
||||
HasEffectExecutor,
|
||||
HasEffectTagExecutor,
|
||||
GetEffectStacksExecutor,
|
||||
GetEffectRemainingTimeExecutor,
|
||||
GetEffectCountExecutor,
|
||||
ClearAllEffectsExecutor,
|
||||
OnEffectAppliedExecutor,
|
||||
OnEffectRemovedExecutor,
|
||||
OnEffectTickExecutor,
|
||||
// Collection
|
||||
EffectNodeDefinitions
|
||||
} from './nodes';
|
||||
84
packages/rendering/effect/src/modifiers/IModifier.ts
Normal file
84
packages/rendering/effect/src/modifiers/IModifier.ts
Normal 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;
|
||||
}
|
||||
304
packages/rendering/effect/src/modifiers/ModifierContainer.ts
Normal file
304
packages/rendering/effect/src/modifiers/ModifierContainer.ts
Normal 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);
|
||||
}
|
||||
17
packages/rendering/effect/src/modifiers/index.ts
Normal file
17
packages/rendering/effect/src/modifiers/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @zh 修改器模块
|
||||
* @en Modifier Module
|
||||
*/
|
||||
|
||||
export type {
|
||||
ModifierOperation,
|
||||
ModifierPriority,
|
||||
IModifier,
|
||||
IAttributeCalculator
|
||||
} from './IModifier';
|
||||
|
||||
export {
|
||||
NumericCalculator,
|
||||
ModifierContainer,
|
||||
createModifierContainer
|
||||
} from './ModifierContainer';
|
||||
489
packages/rendering/effect/src/nodes/EffectNodes.ts
Normal file
489
packages/rendering/effect/src/nodes/EffectNodes.ts
Normal file
@@ -0,0 +1,489 @@
|
||||
/**
|
||||
* @zh 效果系统蓝图节点
|
||||
* @en Effect System Blueprint Nodes
|
||||
*/
|
||||
|
||||
import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint';
|
||||
import type { EffectContainer } from '../core/EffectContainer';
|
||||
import type { IEffectDefinition, IEffectInstance, EffectEventType } from '../core/IEffect';
|
||||
|
||||
// =============================================================================
|
||||
// 执行上下文接口 | Execution Context Interface
|
||||
// =============================================================================
|
||||
|
||||
interface EffectContext {
|
||||
entity: {
|
||||
getComponent<T>(type: new (...args: unknown[]) => T): T | null;
|
||||
};
|
||||
getEffectContainer(): EffectContainer | null;
|
||||
evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown;
|
||||
setOutputs(nodeId: string, outputs: Record<string, unknown>): void;
|
||||
triggerOutput(nodeId: string, pinName: string): void;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ApplyEffect 节点 | ApplyEffect Node
|
||||
// =============================================================================
|
||||
|
||||
export const ApplyEffectTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ApplyEffect',
|
||||
title: 'Apply Effect',
|
||||
category: 'custom',
|
||||
description: 'Apply an effect to target / 对目标应用效果',
|
||||
keywords: ['effect', 'buff', 'debuff', 'apply', 'status'],
|
||||
menuPath: ['Effect', 'Apply Effect'],
|
||||
inputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' },
|
||||
{ name: 'duration', displayName: 'Duration', type: 'float' },
|
||||
{ name: 'sourceId', displayName: 'Source ID', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'success', displayName: 'Success', type: 'bool' },
|
||||
{ name: 'instanceId', displayName: 'Instance ID', type: 'string' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class ApplyEffectExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const effectTypeId = ctx.evaluateInput(node.id, 'effectTypeId', '') as string;
|
||||
const duration = ctx.evaluateInput(node.id, 'duration', 0) as number;
|
||||
const sourceId = ctx.evaluateInput(node.id, 'sourceId', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !effectTypeId) {
|
||||
return {
|
||||
outputs: { success: false, instanceId: '' },
|
||||
nextExec: 'exec'
|
||||
};
|
||||
}
|
||||
|
||||
// Create a basic effect definition
|
||||
const definition: IEffectDefinition = {
|
||||
typeId: effectTypeId,
|
||||
displayName: effectTypeId,
|
||||
tags: [],
|
||||
duration: duration > 0
|
||||
? { type: 'timed', duration, remainingTime: duration }
|
||||
: { type: 'permanent' },
|
||||
stacking: { rule: 'refresh' }
|
||||
};
|
||||
|
||||
const instance = container.apply(definition, sourceId || undefined);
|
||||
|
||||
return {
|
||||
outputs: {
|
||||
success: instance !== null,
|
||||
instanceId: instance?.instanceId ?? ''
|
||||
},
|
||||
nextExec: 'exec'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RemoveEffect 节点 | RemoveEffect Node
|
||||
// =============================================================================
|
||||
|
||||
export const RemoveEffectTemplate: BlueprintNodeTemplate = {
|
||||
type: 'RemoveEffect',
|
||||
title: 'Remove Effect',
|
||||
category: 'custom',
|
||||
description: 'Remove effect from target / 从目标移除效果',
|
||||
keywords: ['effect', 'remove', 'clear', 'dispel'],
|
||||
menuPath: ['Effect', 'Remove Effect'],
|
||||
inputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'removed', displayName: 'Removed', type: 'int' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class RemoveEffectExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const effectTypeId = ctx.evaluateInput(node.id, 'effectTypeId', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !effectTypeId) {
|
||||
return { outputs: { removed: 0 }, nextExec: 'exec' };
|
||||
}
|
||||
|
||||
const removed = container.removeByType(effectTypeId);
|
||||
|
||||
return { outputs: { removed }, nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RemoveEffectByTag 节点 | RemoveEffectByTag Node
|
||||
// =============================================================================
|
||||
|
||||
export const RemoveEffectByTagTemplate: BlueprintNodeTemplate = {
|
||||
type: 'RemoveEffectByTag',
|
||||
title: 'Remove Effect By Tag',
|
||||
category: 'custom',
|
||||
description: 'Remove effects with specific tag / 移除带有指定标签的效果',
|
||||
keywords: ['effect', 'remove', 'tag', 'dispel'],
|
||||
menuPath: ['Effect', 'Remove Effect By Tag'],
|
||||
inputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'tag', displayName: 'Tag', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'removed', displayName: 'Removed', type: 'int' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class RemoveEffectByTagExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const tag = ctx.evaluateInput(node.id, 'tag', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !tag) {
|
||||
return { outputs: { removed: 0 }, nextExec: 'exec' };
|
||||
}
|
||||
|
||||
const removed = container.removeByTag(tag);
|
||||
|
||||
return { outputs: { removed }, nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HasEffect 节点 | HasEffect Node
|
||||
// =============================================================================
|
||||
|
||||
export const HasEffectTemplate: BlueprintNodeTemplate = {
|
||||
type: 'HasEffect',
|
||||
title: 'Has Effect',
|
||||
category: 'custom',
|
||||
description: 'Check if target has effect / 检查目标是否有效果',
|
||||
keywords: ['effect', 'check', 'has', 'status'],
|
||||
menuPath: ['Effect', 'Has Effect'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'hasEffect', displayName: 'Has Effect', type: 'bool' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class HasEffectExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const effectTypeId = ctx.evaluateInput(node.id, 'effectTypeId', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !effectTypeId) {
|
||||
return { outputs: { hasEffect: false } };
|
||||
}
|
||||
|
||||
const hasEffect = container.hasType(effectTypeId);
|
||||
|
||||
return { outputs: { hasEffect } };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HasEffectTag 节点 | HasEffectTag Node
|
||||
// =============================================================================
|
||||
|
||||
export const HasEffectTagTemplate: BlueprintNodeTemplate = {
|
||||
type: 'HasEffectTag',
|
||||
title: 'Has Effect Tag',
|
||||
category: 'custom',
|
||||
description: 'Check if target has effect with tag / 检查目标是否有带标签的效果',
|
||||
keywords: ['effect', 'check', 'tag', 'status'],
|
||||
menuPath: ['Effect', 'Has Effect Tag'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'tag', displayName: 'Tag', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'hasTag', displayName: 'Has Tag', type: 'bool' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class HasEffectTagExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const tag = ctx.evaluateInput(node.id, 'tag', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !tag) {
|
||||
return { outputs: { hasTag: false } };
|
||||
}
|
||||
|
||||
const hasTag = container.hasTag(tag);
|
||||
|
||||
return { outputs: { hasTag } };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GetEffectStacks 节点 | GetEffectStacks Node
|
||||
// =============================================================================
|
||||
|
||||
export const GetEffectStacksTemplate: BlueprintNodeTemplate = {
|
||||
type: 'GetEffectStacks',
|
||||
title: 'Get Effect Stacks',
|
||||
category: 'custom',
|
||||
description: 'Get effect stack count / 获取效果叠加层数',
|
||||
keywords: ['effect', 'stacks', 'count', 'layers'],
|
||||
menuPath: ['Effect', 'Get Effect Stacks'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'stacks', displayName: 'Stacks', type: 'int' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class GetEffectStacksExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const effectTypeId = ctx.evaluateInput(node.id, 'effectTypeId', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !effectTypeId) {
|
||||
return { outputs: { stacks: 0 } };
|
||||
}
|
||||
|
||||
const stacks = container.getStacks(effectTypeId);
|
||||
|
||||
return { outputs: { stacks } };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GetEffectRemainingTime 节点 | GetEffectRemainingTime Node
|
||||
// =============================================================================
|
||||
|
||||
export const GetEffectRemainingTimeTemplate: BlueprintNodeTemplate = {
|
||||
type: 'GetEffectRemainingTime',
|
||||
title: 'Get Effect Remaining Time',
|
||||
category: 'custom',
|
||||
description: 'Get remaining time of effect / 获取效果剩余时间',
|
||||
keywords: ['effect', 'time', 'remaining', 'duration'],
|
||||
menuPath: ['Effect', 'Get Effect Remaining Time'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'remainingTime', displayName: 'Remaining Time', type: 'float' },
|
||||
{ name: 'hasEffect', displayName: 'Has Effect', type: 'bool' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class GetEffectRemainingTimeExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const effectTypeId = ctx.evaluateInput(node.id, 'effectTypeId', '') as string;
|
||||
|
||||
const container = ctx.getEffectContainer();
|
||||
if (!container || !effectTypeId) {
|
||||
return { outputs: { remainingTime: 0, hasEffect: false } };
|
||||
}
|
||||
|
||||
const effects = container.getByType(effectTypeId);
|
||||
if (effects.length === 0) {
|
||||
return { outputs: { remainingTime: 0, hasEffect: false } };
|
||||
}
|
||||
|
||||
// Return max remaining time among all instances
|
||||
const remainingTime = Math.max(...effects.map(e => e.remainingTime));
|
||||
|
||||
return { outputs: { remainingTime, hasEffect: true } };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GetEffectCount 节点 | GetEffectCount Node
|
||||
// =============================================================================
|
||||
|
||||
export const GetEffectCountTemplate: BlueprintNodeTemplate = {
|
||||
type: 'GetEffectCount',
|
||||
title: 'Get Effect Count',
|
||||
category: 'custom',
|
||||
description: 'Get total effect count / 获取效果总数',
|
||||
keywords: ['effect', 'count', 'total'],
|
||||
menuPath: ['Effect', 'Get Effect Count'],
|
||||
isPure: true,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ name: 'count', displayName: 'Count', type: 'int' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class GetEffectCountExecutor implements INodeExecutor {
|
||||
execute(_node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const container = ctx.getEffectContainer();
|
||||
|
||||
return { outputs: { count: container?.count ?? 0 } };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ClearAllEffects 节点 | ClearAllEffects Node
|
||||
// =============================================================================
|
||||
|
||||
export const ClearAllEffectsTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ClearAllEffects',
|
||||
title: 'Clear All Effects',
|
||||
category: 'custom',
|
||||
description: 'Remove all effects from target / 移除目标所有效果',
|
||||
keywords: ['effect', 'clear', 'remove', 'all'],
|
||||
menuPath: ['Effect', 'Clear All Effects'],
|
||||
inputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' }
|
||||
],
|
||||
color: '#e91e63'
|
||||
};
|
||||
|
||||
export class ClearAllEffectsExecutor implements INodeExecutor {
|
||||
execute(_node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as EffectContext;
|
||||
const container = ctx.getEffectContainer();
|
||||
container?.removeAll();
|
||||
|
||||
return { nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// OnEffectApplied 事件节点 | OnEffectApplied Event Node
|
||||
// =============================================================================
|
||||
|
||||
export const OnEffectAppliedTemplate: BlueprintNodeTemplate = {
|
||||
type: 'OnEffectApplied',
|
||||
title: 'On Effect Applied',
|
||||
category: 'event',
|
||||
description: 'Triggered when effect is applied / 效果应用时触发',
|
||||
keywords: ['effect', 'event', 'applied', 'add'],
|
||||
menuPath: ['Effect', 'Events', 'On Effect Applied'],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' },
|
||||
{ name: 'instanceId', displayName: 'Instance ID', type: 'string' },
|
||||
{ name: 'stacks', displayName: 'Stacks', type: 'int' }
|
||||
],
|
||||
color: '#ff5722'
|
||||
};
|
||||
|
||||
export class OnEffectAppliedExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
return { nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// OnEffectRemoved 事件节点 | OnEffectRemoved Event Node
|
||||
// =============================================================================
|
||||
|
||||
export const OnEffectRemovedTemplate: BlueprintNodeTemplate = {
|
||||
type: 'OnEffectRemoved',
|
||||
title: 'On Effect Removed',
|
||||
category: 'event',
|
||||
description: 'Triggered when effect is removed / 效果移除时触发',
|
||||
keywords: ['effect', 'event', 'removed', 'expire'],
|
||||
menuPath: ['Effect', 'Events', 'On Effect Removed'],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' },
|
||||
{ name: 'instanceId', displayName: 'Instance ID', type: 'string' }
|
||||
],
|
||||
color: '#ff5722'
|
||||
};
|
||||
|
||||
export class OnEffectRemovedExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
return { nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// OnEffectTick 事件节点 | OnEffectTick Event Node
|
||||
// =============================================================================
|
||||
|
||||
export const OnEffectTickTemplate: BlueprintNodeTemplate = {
|
||||
type: 'OnEffectTick',
|
||||
title: 'On Effect Tick',
|
||||
category: 'event',
|
||||
description: 'Triggered on effect periodic tick / 效果周期触发时调用',
|
||||
keywords: ['effect', 'event', 'tick', 'periodic'],
|
||||
menuPath: ['Effect', 'Events', 'On Effect Tick'],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ name: 'exec', displayName: '', type: 'exec' },
|
||||
{ name: 'effectTypeId', displayName: 'Effect Type', type: 'string' },
|
||||
{ name: 'instanceId', displayName: 'Instance ID', type: 'string' },
|
||||
{ name: 'stacks', displayName: 'Stacks', type: 'int' }
|
||||
],
|
||||
color: '#ff5722'
|
||||
};
|
||||
|
||||
export class OnEffectTickExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
return { nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 节点定义集合 | Node Definition Collection
|
||||
// =============================================================================
|
||||
|
||||
export const EffectNodeDefinitions = {
|
||||
templates: [
|
||||
ApplyEffectTemplate,
|
||||
RemoveEffectTemplate,
|
||||
RemoveEffectByTagTemplate,
|
||||
HasEffectTemplate,
|
||||
HasEffectTagTemplate,
|
||||
GetEffectStacksTemplate,
|
||||
GetEffectRemainingTimeTemplate,
|
||||
GetEffectCountTemplate,
|
||||
ClearAllEffectsTemplate,
|
||||
OnEffectAppliedTemplate,
|
||||
OnEffectRemovedTemplate,
|
||||
OnEffectTickTemplate
|
||||
],
|
||||
executors: new Map<string, INodeExecutor>([
|
||||
['ApplyEffect', new ApplyEffectExecutor()],
|
||||
['RemoveEffect', new RemoveEffectExecutor()],
|
||||
['RemoveEffectByTag', new RemoveEffectByTagExecutor()],
|
||||
['HasEffect', new HasEffectExecutor()],
|
||||
['HasEffectTag', new HasEffectTagExecutor()],
|
||||
['GetEffectStacks', new GetEffectStacksExecutor()],
|
||||
['GetEffectRemainingTime', new GetEffectRemainingTimeExecutor()],
|
||||
['GetEffectCount', new GetEffectCountExecutor()],
|
||||
['ClearAllEffects', new ClearAllEffectsExecutor()],
|
||||
['OnEffectApplied', new OnEffectAppliedExecutor()],
|
||||
['OnEffectRemoved', new OnEffectRemovedExecutor()],
|
||||
['OnEffectTick', new OnEffectTickExecutor()]
|
||||
])
|
||||
};
|
||||
35
packages/rendering/effect/src/nodes/index.ts
Normal file
35
packages/rendering/effect/src/nodes/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @zh 效果蓝图节点模块
|
||||
* @en Effect Blueprint Nodes Module
|
||||
*/
|
||||
|
||||
export {
|
||||
// Templates
|
||||
ApplyEffectTemplate,
|
||||
RemoveEffectTemplate,
|
||||
RemoveEffectByTagTemplate,
|
||||
HasEffectTemplate,
|
||||
HasEffectTagTemplate,
|
||||
GetEffectStacksTemplate,
|
||||
GetEffectRemainingTimeTemplate,
|
||||
GetEffectCountTemplate,
|
||||
ClearAllEffectsTemplate,
|
||||
OnEffectAppliedTemplate,
|
||||
OnEffectRemovedTemplate,
|
||||
OnEffectTickTemplate,
|
||||
// Executors
|
||||
ApplyEffectExecutor,
|
||||
RemoveEffectExecutor,
|
||||
RemoveEffectByTagExecutor,
|
||||
HasEffectExecutor,
|
||||
HasEffectTagExecutor,
|
||||
GetEffectStacksExecutor,
|
||||
GetEffectRemainingTimeExecutor,
|
||||
GetEffectCountExecutor,
|
||||
ClearAllEffectsExecutor,
|
||||
OnEffectAppliedExecutor,
|
||||
OnEffectRemovedExecutor,
|
||||
OnEffectTickExecutor,
|
||||
// Collection
|
||||
EffectNodeDefinitions
|
||||
} from './EffectNodes';
|
||||
22
packages/rendering/effect/tsconfig.build.json
Normal file
22
packages/rendering/effect/tsconfig.build.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
23
packages/rendering/effect/tsconfig.json
Normal file
23
packages/rendering/effect/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../framework/core"
|
||||
},
|
||||
{
|
||||
"path": "../../framework/blueprint"
|
||||
}
|
||||
]
|
||||
}
|
||||
12
packages/rendering/effect/tsup.config.ts
Normal file
12
packages/rendering/effect/tsup.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
tsconfig: 'tsconfig.build.json'
|
||||
});
|
||||
Reference in New Issue
Block a user