steamsheep-ts-game-engine 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,341 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
4
+
5
+ // systems/events.ts
6
+ var EventBus = class {
7
+ constructor() {
8
+ /**
9
+ * 事件监听器的内部存储
10
+ * 将事件名映射到监听器回调函数数组
11
+ * 使用 Listener[](默认为 Listener<unknown>[])以允许同一事件名有不同类型的监听器
12
+ * 类型安全在订阅/发射时强制执行
13
+ */
14
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
15
+ }
16
+ /**
17
+ * 订阅事件
18
+ *
19
+ * @template T - 事件载荷的预期类型
20
+ * @param event - 要订阅的事件名称
21
+ * @param callback - 事件触发时调用的监听器函数
22
+ * @returns 取消订阅的函数
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const handler = (data: { count: number }) => console.log(data.count);
27
+ * const unsubscribe = eventBus.on('update', handler);
28
+ * // 稍后取消订阅
29
+ * unsubscribe();
30
+ * ```
31
+ */
32
+ on(event, callback) {
33
+ if (!this.listeners.has(event)) {
34
+ this.listeners.set(event, []);
35
+ }
36
+ this.listeners.get(event).push(callback);
37
+ return () => this.off(event, callback);
38
+ }
39
+ /**
40
+ * 取消订阅事件
41
+ *
42
+ * @template T - 事件载荷的预期类型
43
+ * @param event - 要取消订阅的事件名称
44
+ * @param callback - 要移除的监听器函数
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const handler = (data: number) => console.log(data);
49
+ * eventBus.on('count', handler);
50
+ * eventBus.off('count', handler); // 移除监听器
51
+ * ```
52
+ */
53
+ off(event, callback) {
54
+ const callbacks = this.listeners.get(event);
55
+ if (callbacks) {
56
+ this.listeners.set(
57
+ event,
58
+ callbacks.filter((cb) => cb !== callback)
59
+ );
60
+ }
61
+ }
62
+ /**
63
+ * 发射事件并携带可选的类型化载荷数据
64
+ * 该事件的所有已注册监听器都将被调用并传入数据
65
+ * 单个监听器中的错误会被捕获并记录,不会影响其他监听器
66
+ *
67
+ * @template T - 事件载荷的类型
68
+ * @param event - 要发射的事件名称
69
+ * @param data - 传递给监听器的可选载荷数据
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * eventBus.emit<{ userId: string }>('login', { userId: '123' });
74
+ * eventBus.emit('logout'); // 无载荷
75
+ * ```
76
+ */
77
+ emit(event, data) {
78
+ const callbacks = this.listeners.get(event);
79
+ if (callbacks) {
80
+ callbacks.forEach((cb) => {
81
+ try {
82
+ cb(data);
83
+ } catch (error) {
84
+ console.error(`Error in event listener for "${event}":`, error);
85
+ }
86
+ });
87
+ }
88
+ }
89
+ /**
90
+ * 清空所有事件的所有监听器
91
+ * 用于清理或重置事件系统
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * eventBus.clear(); // 所有监听器被移除
96
+ * ```
97
+ */
98
+ clear() {
99
+ this.listeners.clear();
100
+ }
101
+ };
102
+ var gameEvents = new EventBus();
103
+ var EngineEvents = {
104
+ /** 当角色属性变化时触发 */
105
+ STAT_CHANGE: "engine:stat_change",
106
+ /** 当物品添加到背包时触发 */
107
+ ITEM_ADD: "engine:item_add",
108
+ /** 当物品从背包移除时触发 */
109
+ ITEM_REMOVE: "engine:item_remove",
110
+ /** 当游戏标志变化时触发 */
111
+ FLAG_CHANGE: "engine:flag_change",
112
+ /** 当动作执行时触发 */
113
+ ACTION_EXECUTED: "engine:action_exec",
114
+ /** 当游戏时间推进时触发 */
115
+ TIME_PASS: "engine:time_pass",
116
+ /** 当需要显示瞬时通知时触发(Toast/Modal,不持久化) */
117
+ NOTIFICATION: "engine:notification",
118
+ /** 用于自定义触发事件 */
119
+ CUSTOM: "engine:custom_trigger"
120
+ };
121
+
122
+ // systems/query.ts
123
+ var QuerySystem = class {
124
+ /**
125
+ * 检查是否满足所有需求条件
126
+ *
127
+ * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。
128
+ * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。
129
+ *
130
+ * @template S - 数值属性键的联合类型
131
+ * @template I - 物品ID的联合类型
132
+ * @template F - 标记键的联合类型
133
+ *
134
+ * @param state - 当前游戏状态
135
+ * @param reqs - 需求定义(可选,未定义时返回true)
136
+ *
137
+ * @returns true表示满足所有条件,false表示至少有一个条件不满足
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // 简单需求
142
+ * const simple = QuerySystem.checkRequirements(state, {
143
+ * hasItems: ['key'],
144
+ * stats: { strength: { min: 5 } }
145
+ * });
146
+ *
147
+ * // 复杂逻辑使用 custom
148
+ * const complex = QuerySystem.checkRequirements(state, {
149
+ * custom: (state) => {
150
+ * // (有钥匙 AND 力量>=5) OR 有万能钥匙
151
+ * const hasKeyAndStrength =
152
+ * state.inventory.includes('key') && state.stats.strength >= 5;
153
+ * const hasMasterKey = state.inventory.includes('master_key');
154
+ * return hasKeyAndStrength || hasMasterKey;
155
+ * }
156
+ * });
157
+ * ```
158
+ */
159
+ static checkRequirements(state, reqs) {
160
+ if (!reqs) return true;
161
+ if (reqs.hasFlags && reqs.hasFlags.length > 0) {
162
+ if (!reqs.hasFlags.every((f) => state.flags[f])) return false;
163
+ }
164
+ if (reqs.noFlags && reqs.noFlags.length > 0) {
165
+ if (reqs.noFlags.some((f) => state.flags[f])) return false;
166
+ }
167
+ if (reqs.hasItems && reqs.hasItems.length > 0) {
168
+ if (!reqs.hasItems.every((i) => state.inventory.includes(i)))
169
+ return false;
170
+ }
171
+ if (reqs.noItems && reqs.noItems.length > 0) {
172
+ if (reqs.noItems.some((i) => state.inventory.includes(i))) return false;
173
+ }
174
+ if (reqs.stats) {
175
+ for (const [key, condition] of Object.entries(reqs.stats)) {
176
+ const currentVal = state.stats[key] || 0;
177
+ const cond = condition;
178
+ if (typeof cond === "number") {
179
+ if (currentVal < cond) return false;
180
+ } else {
181
+ if (cond.min !== void 0 && currentVal < cond.min) return false;
182
+ if (cond.max !== void 0 && currentVal > cond.max) return false;
183
+ }
184
+ }
185
+ }
186
+ if (reqs.custom && !reqs.custom(state)) return false;
187
+ return true;
188
+ }
189
+ /**
190
+ * 检查是否有足够资源支付成本
191
+ *
192
+ * 验证玩家的数值属性是否足够支付指定的成本。
193
+ * 这是一个简化的检查,只验证数值是否足够,不实际扣除。
194
+ *
195
+ * 与checkRequirements的区别:
196
+ * - canAfford只检查数值属性
197
+ * - checkRequirements检查完整的需求(标记、物品、数值、自定义)
198
+ * - canAfford用于成本检查,checkRequirements用于前置条件检查
199
+ *
200
+ * @template S - 数值属性键的联合类型
201
+ * @template I - 物品ID的联合类型
202
+ * @template F - 标记键的联合类型
203
+ *
204
+ * @param state - 当前游戏状态
205
+ * @param costs - 成本定义(可选,未定义时返回true)
206
+ *
207
+ * @returns true表示有足够资源,false表示资源不足
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * // 检查是否有足够金币购买物品
212
+ * const canBuy = QuerySystem.canAfford(state, { gold: 100 });
213
+ *
214
+ * // 检查是否有足够资源施放技能
215
+ * const canCast = QuerySystem.canAfford(state, {
216
+ * mp: 30,
217
+ * stamina: 10
218
+ * });
219
+ *
220
+ * // 在动作执行前使用
221
+ * if (QuerySystem.canAfford(state, action.costs)) {
222
+ * // 执行动作并扣除成本
223
+ * executeAction(action);
224
+ * } else {
225
+ * addLog('资源不足!', 'error');
226
+ * }
227
+ * ```
228
+ */
229
+ static canAfford(state, costs) {
230
+ if (!costs) return true;
231
+ for (const [key, cost] of Object.entries(costs)) {
232
+ const currentVal = state.stats[key] || 0;
233
+ if (currentVal < cost) return false;
234
+ }
235
+ return true;
236
+ }
237
+ };
238
+
239
+ // core/messages.ts
240
+ var SystemMessages = {
241
+ /** 资源不足提示 */
242
+ NOT_ENOUGH_RESOURCES: "sys:not_enough_resources"};
243
+
244
+ // systems/flow.ts
245
+ var FlowSystem = class {
246
+ /**
247
+ * 执行一个游戏动作
248
+ *
249
+ * 这是核心方法,负责完整的动作执行流程。
250
+ * 执行过程是原子性的:要么全部成功,要么全部失败。
251
+ *
252
+ * 执行步骤:
253
+ * 1. 最终验证:检查需求和成本(防止UI滞后)
254
+ * 2. 扣除成本:消耗资源(如体力、金币)
255
+ * 3. 应用效果:修改状态(属性、物品、标记等)
256
+ * 4. 记录日志:生成反馈信息
257
+ *
258
+ * @template S - 数值属性键的联合类型
259
+ * @template I - 物品ID的联合类型
260
+ * @template F - 标记键的联合类型
261
+ *
262
+ * @param store - Zustand Store 实例(包含状态和方法)
263
+ * @param action - 要执行的动作定义
264
+ *
265
+ * @returns true表示执行成功,false表示执行失败
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * // 定义一个攻击动作
270
+ * const attackAction: ActionDef<Stats, Items, Flags> = {
271
+ * id: 'attack_goblin',
272
+ * label: '攻击哥布林',
273
+ * costs: { stamina: 10 },
274
+ * requirements: { hasItems: ['weapon'] },
275
+ * effects: {
276
+ * statsChange: { exp: 5 },
277
+ * flagsSet: { goblin_defeated: true }
278
+ * },
279
+ * resultText: '你击败了哥布林!'
280
+ * };
281
+ *
282
+ * // 执行动作
283
+ * const store = useGameStore.getState();
284
+ * const success = FlowSystem.executeAction(store, attackAction);
285
+ * ```
286
+ */
287
+ static executeAction(store, action) {
288
+ const state = store;
289
+ if (!QuerySystem.checkRequirements(state, action.requirements)) {
290
+ console.warn(`\u6761\u4EF6\u4E0D\u6EE1\u8DB3: ${action.id}`);
291
+ return false;
292
+ }
293
+ if (!QuerySystem.canAfford(state, action.costs)) {
294
+ store.addLog(SystemMessages.NOT_ENOUGH_RESOURCES, "warn");
295
+ return false;
296
+ }
297
+ if (action.costs) {
298
+ const costsToDeduct = {};
299
+ for (const [key, val] of Object.entries(action.costs)) {
300
+ costsToDeduct[key] = -val;
301
+ }
302
+ store.setStats(costsToDeduct);
303
+ }
304
+ const { effects } = action;
305
+ if (effects) {
306
+ if (effects.statsChange) {
307
+ store.setStats(effects.statsChange);
308
+ }
309
+ effects.itemsAdd?.forEach((i) => store.addItem(i));
310
+ effects.itemsRemove?.forEach((i) => store.removeItem(i));
311
+ if (effects.flagsSet) {
312
+ Object.entries(effects.flagsSet).forEach(([f, v]) => {
313
+ store.setFlag(f, v);
314
+ });
315
+ }
316
+ if (effects.teleport) {
317
+ store.teleport(effects.teleport);
318
+ }
319
+ if (effects.triggerEvent) {
320
+ gameEvents.emit(EngineEvents.CUSTOM, { id: effects.triggerEvent });
321
+ }
322
+ if (effects.custom) {
323
+ const draft = { ...state.extra };
324
+ const result = effects.custom(draft, state);
325
+ if (result) {
326
+ store.setExtra({ ...draft, ...result });
327
+ } else {
328
+ store.setExtra(draft);
329
+ }
330
+ }
331
+ }
332
+ const resultText = typeof action.resultText === "function" ? action.resultText(state) : action.resultText;
333
+ store.addLog(resultText, "result");
334
+ gameEvents.emit(EngineEvents.ACTION_EXECUTED, { actionId: action.id });
335
+ return true;
336
+ }
337
+ };
338
+
339
+ export { EngineEvents, EventBus, FlowSystem, QuerySystem, gameEvents };
340
+ //# sourceMappingURL=index.mjs.map
341
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../systems/events.ts","../../systems/query.ts","../../core/messages.ts","../../systems/flow.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// src/engine/systems/events.ts\n\n/**\n * 类型安全的事件监听器回调函数\n * 使用 `unknown` 作为默认类型以强制类型安全\n * @template T - 监听器期望接收的数据类型\n */\ntype Listener<T = unknown> = (data: T) => void;\n\n/**\n * EventBus - 类型安全的发布-订阅事件系统\n *\n * 提供了一个集中式机制,让组件通过事件进行通信而无需紧密耦合。\n * 支持泛型类型参数以实现类型安全的载荷数据。\n *\n * @example\n * ```typescript\n * // 订阅事件并保证类型安全\n * const unsubscribe = eventBus.on<{ count: number }>('update', (data) => {\n * console.log(data.count); // TypeScript 知道 data 有 count 属性\n * });\n *\n * // 发射事件\n * eventBus.emit('update', { count: 42 });\n *\n * // 取消订阅\n * unsubscribe();\n * ```\n */\nexport class EventBus {\n /**\n * 事件监听器的内部存储\n * 将事件名映射到监听器回调函数数组\n * 使用 Listener[](默认为 Listener<unknown>[])以允许同一事件名有不同类型的监听器\n * 类型安全在订阅/发射时强制执行\n */\n private listeners: Map<string, Listener[]> = new Map();\n\n /**\n * 订阅事件\n *\n * @template T - 事件载荷的预期类型\n * @param event - 要订阅的事件名称\n * @param callback - 事件触发时调用的监听器函数\n * @returns 取消订阅的函数\n *\n * @example\n * ```typescript\n * const handler = (data: { count: number }) => console.log(data.count);\n * const unsubscribe = eventBus.on('update', handler);\n * // 稍后取消订阅\n * unsubscribe();\n * ```\n */\n on<T>(event: string, callback: Listener<T>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n this.listeners.get(event)!.push(callback as Listener);\n\n // 返回 unsubscribe 函数\n return () => this.off(event, callback);\n }\n\n /**\n * 取消订阅事件\n *\n * @template T - 事件载荷的预期类型\n * @param event - 要取消订阅的事件名称\n * @param callback - 要移除的监听器函数\n *\n * @example\n * ```typescript\n * const handler = (data: number) => console.log(data);\n * eventBus.on('count', handler);\n * eventBus.off('count', handler); // 移除监听器\n * ```\n */\n off<T>(event: string, callback: Listener<T>) {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n this.listeners.set(\n event,\n callbacks.filter((cb) => cb !== (callback as Listener))\n );\n }\n }\n\n /**\n * 发射事件并携带可选的类型化载荷数据\n * 该事件的所有已注册监听器都将被调用并传入数据\n * 单个监听器中的错误会被捕获并记录,不会影响其他监听器\n *\n * @template T - 事件载荷的类型\n * @param event - 要发射的事件名称\n * @param data - 传递给监听器的可选载荷数据\n *\n * @example\n * ```typescript\n * eventBus.emit<{ userId: string }>('login', { userId: '123' });\n * eventBus.emit('logout'); // 无载荷\n * ```\n */\n emit<T>(event: string, data?: T) {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((cb) => {\n try {\n cb(data);\n } catch (error) {\n console.error(`Error in event listener for \"${event}\":`, error);\n }\n });\n }\n }\n\n /**\n * 清空所有事件的所有监听器\n * 用于清理或重置事件系统\n *\n * @example\n * ```typescript\n * eventBus.clear(); // 所有监听器被移除\n * ```\n */\n clear() {\n this.listeners.clear();\n }\n}\n\n/**\n * 全局单例 EventBus 实例,用于游戏范围内的事件通信\n * 在整个应用中使用此实例以保持一致的事件处理\n *\n * @example\n * ```typescript\n * import { gameEvents, EngineEvents } from './events';\n *\n * gameEvents.on(EngineEvents.STAT_CHANGE, (data) => {\n * console.log(`属性 ${data.stat} 变化了 ${data.delta}`);\n * });\n * ```\n */\nexport const gameEvents = new EventBus();\n\n/**\n * 预定义的系统事件名称常量,用于常见的游戏引擎事件\n * 使用这些常量而不是字符串字面量,以保证类型安全和一致性\n *\n * 事件载荷类型:\n * - STAT_CHANGE: { stat: string, delta: number, current: number }\n * - ITEM_ADD: { item: string }\n * - ITEM_REMOVE: { item: string }\n * - FLAG_CHANGE: { flag: string, value: boolean }\n * - ACTION_EXECUTED: { actionId: string }\n * - TIME_PASS: { day: number, time: number }\n * - NOTIFICATION: { text: string, type: string, notificationType: string, timestamp: number }\n * - CUSTOM: { id: string } (对应 effects.triggerEvent)\n */\nexport const EngineEvents = {\n /** 当角色属性变化时触发 */\n STAT_CHANGE: \"engine:stat_change\",\n /** 当物品添加到背包时触发 */\n ITEM_ADD: \"engine:item_add\",\n /** 当物品从背包移除时触发 */\n ITEM_REMOVE: \"engine:item_remove\",\n /** 当游戏标志变化时触发 */\n FLAG_CHANGE: \"engine:flag_change\",\n /** 当动作执行时触发 */\n ACTION_EXECUTED: \"engine:action_exec\",\n /** 当游戏时间推进时触发 */\n TIME_PASS: \"engine:time_pass\",\n /** 当需要显示瞬时通知时触发(Toast/Modal,不持久化) */\n NOTIFICATION: \"engine:notification\",\n /** 用于自定义触发事件 */\n CUSTOM: \"engine:custom_trigger\",\n} as const;\n","// src/engine/systems/query.ts\n// ============================================================================\n// 查询系统 - 游戏状态查询和条件检查\n// ============================================================================\n//\n// 这个文件提供了游戏状态的查询和验证功能。\n// 主要用于:\n// - 检查动作执行的前置条件(需求检查)\n// - 验证玩家是否有足够资源支付成本\n// - 提供统一的条件判断逻辑\n//\n// 设计模式:\n// - 使用静态方法类,无需实例化\n// - 纯函数设计,不修改状态\n// - 支持复杂的条件组合(AND逻辑)\n//\n// ============================================================================\n\nimport type { GameState, RequirementDef } from \"../core/types\";\n\n/**\n * 查询系统类 - 提供游戏状态查询和条件检查功能\n * \n * 这是一个工具类,所有方法都是静态的,不需要实例化。\n * 主要用于在执行游戏动作前验证各种条件。\n * \n * 核心功能:\n * - checkRequirements: 检查复杂的需求条件\n * - canAfford: 检查是否有足够资源支付成本\n * \n * @example\n * ```typescript\n * // 检查是否满足开门的需求\n * const canOpenDoor = QuerySystem.checkRequirements(gameState, {\n * hasItems: ['key'],\n * stats: { strength: { min: 5 } }\n * });\n * \n * // 检查是否能支付购买成本\n * const canBuy = QuerySystem.canAfford(gameState, { gold: 100 });\n * ```\n */\nexport class QuerySystem {\n /**\n * 检查是否满足所有需求条件\n * \n * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。\n * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。\n * \n * @template S - 数值属性键的联合类型\n * @template I - 物品ID的联合类型\n * @template F - 标记键的联合类型\n * \n * @param state - 当前游戏状态\n * @param reqs - 需求定义(可选,未定义时返回true)\n * \n * @returns true表示满足所有条件,false表示至少有一个条件不满足\n * \n * @example\n * ```typescript\n * // 简单需求\n * const simple = QuerySystem.checkRequirements(state, {\n * hasItems: ['key'],\n * stats: { strength: { min: 5 } }\n * });\n * \n * // 复杂逻辑使用 custom\n * const complex = QuerySystem.checkRequirements(state, {\n * custom: (state) => {\n * // (有钥匙 AND 力量>=5) OR 有万能钥匙\n * const hasKeyAndStrength = \n * state.inventory.includes('key') && state.stats.strength >= 5;\n * const hasMasterKey = state.inventory.includes('master_key');\n * return hasKeyAndStrength || hasMasterKey;\n * }\n * });\n * ```\n */\n static checkRequirements<\n S extends string,\n I extends string,\n F extends string,\n X = Record<string, unknown>\n >(\n state: GameState<S, I, F, X>,\n reqs?: RequirementDef<S, I, F, X>\n ): boolean {\n // 如果没有定义需求,默认通过\n if (!reqs) return true;\n\n // ========== 1. 标记检查 ==========\n\n /**\n * 检查必须为true的标记\n * 使用every确保所有指定的标记都为true(AND逻辑)\n */\n if (reqs.hasFlags && reqs.hasFlags.length > 0) {\n if (!reqs.hasFlags.every((f) => state.flags[f])) return false;\n }\n\n /**\n * 检查必须为false的标记\n * 使用some检查是否有任何标记为true(OR逻辑)\n */\n if (reqs.noFlags && reqs.noFlags.length > 0) {\n if (reqs.noFlags.some((f) => state.flags[f])) return false;\n }\n\n // ========== 2. 物品检查 ==========\n\n /**\n * 检查必须拥有的物品\n * 使用every确保库存中包含所有指定的物品(AND逻辑)\n */\n if (reqs.hasItems && reqs.hasItems.length > 0) {\n if (!reqs.hasItems.every((i) => state.inventory.includes(i)))\n return false;\n }\n\n /**\n * 检查不能拥有的物品\n * 使用some检查库存中是否包含任何禁止的物品(OR逻辑)\n */\n if (reqs.noItems && reqs.noItems.length > 0) {\n if (reqs.noItems.some((i) => state.inventory.includes(i))) return false;\n }\n\n // ========== 3. 数值属性检查 ==========\n\n /**\n * 检查数值属性是否满足条件\n * 支持两种模式:\n * - 数字模式:表示最小值要求\n * - 对象模式:支持min和max范围检查\n */\n if (reqs.stats) {\n for (const [key, condition] of Object.entries(reqs.stats)) {\n // 获取当前属性值,如果不存在则默认为0\n const currentVal = state.stats[key as S] || 0;\n const cond = condition as number | { min?: number; max?: number };\n\n if (typeof cond === \"number\") {\n /**\n * 简写模式:数字表示最小值\n * 例如:{ hp: 10 } 表示hp至少为10\n */\n if (currentVal < cond) return false;\n } else {\n /**\n * 对象模式:支持区间检查\n * 例如:{ hp: { min: 10, max: 50 } } 表示hp在10-50之间\n */\n if (cond.min !== undefined && currentVal < cond.min) return false;\n if (cond.max !== undefined && currentVal > cond.max) return false;\n }\n }\n }\n\n // ========== 4. 自定义函数检查 ==========\n\n /**\n * 执行自定义验证函数\n * 用于处理内置检查无法覆盖的复杂逻辑\n */\n if (reqs.custom && !reqs.custom(state)) return false;\n\n // 所有检查都通过\n return true;\n }\n\n /**\n * 检查是否有足够资源支付成本\n * \n * 验证玩家的数值属性是否足够支付指定的成本。\n * 这是一个简化的检查,只验证数值是否足够,不实际扣除。\n * \n * 与checkRequirements的区别:\n * - canAfford只检查数值属性\n * - checkRequirements检查完整的需求(标记、物品、数值、自定义)\n * - canAfford用于成本检查,checkRequirements用于前置条件检查\n * \n * @template S - 数值属性键的联合类型\n * @template I - 物品ID的联合类型\n * @template F - 标记键的联合类型\n * \n * @param state - 当前游戏状态\n * @param costs - 成本定义(可选,未定义时返回true)\n * \n * @returns true表示有足够资源,false表示资源不足\n * \n * @example\n * ```typescript\n * // 检查是否有足够金币购买物品\n * const canBuy = QuerySystem.canAfford(state, { gold: 100 });\n * \n * // 检查是否有足够资源施放技能\n * const canCast = QuerySystem.canAfford(state, {\n * mp: 30,\n * stamina: 10\n * });\n * \n * // 在动作执行前使用\n * if (QuerySystem.canAfford(state, action.costs)) {\n * // 执行动作并扣除成本\n * executeAction(action);\n * } else {\n * addLog('资源不足!', 'error');\n * }\n * ```\n */\n static canAfford<\n S extends string,\n I extends string,\n F extends string,\n X = Record<string, unknown>\n >(\n state: GameState<S, I, F, X>,\n costs?: Partial<Record<S, number>>\n ): boolean {\n // 如果没有定义成本,默认通过\n if (!costs) return true;\n\n /**\n * 遍历所有成本项,检查每个属性是否足够\n * 只要有一个属性不足,就返回false\n */\n for (const [key, cost] of Object.entries(costs)) {\n const currentVal = state.stats[key as S] || 0;\n if (currentVal < (cost as number)) return false;\n }\n\n // 所有资源都足够\n return true;\n }\n}\n","// src/engine/core/messages.ts\n\n/**\n * 系统消息常量\n * \n * 使用特殊的标识符(sys: 前缀),让 UI 层知道这需要翻译。\n * 这些常量用于系统级别的提示消息,应该在 UI 层进行本地化处理。\n */\nexport const SystemMessages = {\n /** 资源不足提示 */\n NOT_ENOUGH_RESOURCES: 'sys:not_enough_resources',\n /** 条件不满足提示 */\n REQUIREMENT_NOT_MET: 'sys:requirement_not_met',\n /** 操作成功提示 */\n ACTION_SUCCESS: 'sys:action_success',\n /** 时间回溯成功提示 */\n UNDO_SUCCESS: 'sys:undo_success',\n} as const;","// src/engine/systems/flow.ts\n// ============================================================================\n// 流程系统 - 游戏动作执行引擎\n// ============================================================================\n//\n// 这个文件负责执行游戏动作的完整流程,包括:\n// - 条件验证:检查是否满足执行条件\n// - 成本扣除:扣除执行动作所需的资源\n// - 效果应用:应用动作产生的所有效果\n// - 日志记录:记录动作执行结果\n//\n// 设计原则:\n// - 原子性:动作要么完全执行,要么完全不执行\n// - 安全性:执行前进行最终检查,防止非法操作\n// - 可追溯:所有操作都记录日志\n//\n// ============================================================================\n\nimport type { GameStore } from \"../state/store\";\nimport type { ActionDef } from \"../core/types\";\nimport { QuerySystem } from \"./query\";\nimport { SystemMessages } from '../core/messages';\nimport { gameEvents, EngineEvents } from './events';\n\n/**\n * 流程系统类 - 负责游戏动作的执行流程\n *\n * 这是一个工具类,所有方法都是静态的,不需要实例化。\n * 主要功能是协调动作执行的各个步骤,确保游戏逻辑的正确性。\n *\n * 执行流程:\n * 1. 验证条件:检查需求和成本\n * 2. 扣除成本:消耗资源\n * 3. 应用效果:修改游戏状态\n * 4. 记录日志:反馈给玩家\n *\n * @example\n * ```typescript\n * // 在游戏中执行动作\n * const success = FlowSystem.executeAction(\n * useGameStore.getState(),\n * attackAction\n * );\n *\n * if (success) {\n * console.log('动作执行成功');\n * }\n * ```\n */\nexport class FlowSystem {\n /**\n * 执行一个游戏动作\n *\n * 这是核心方法,负责完整的动作执行流程。\n * 执行过程是原子性的:要么全部成功,要么全部失败。\n *\n * 执行步骤:\n * 1. 最终验证:检查需求和成本(防止UI滞后)\n * 2. 扣除成本:消耗资源(如体力、金币)\n * 3. 应用效果:修改状态(属性、物品、标记等)\n * 4. 记录日志:生成反馈信息\n *\n * @template S - 数值属性键的联合类型\n * @template I - 物品ID的联合类型\n * @template F - 标记键的联合类型\n *\n * @param store - Zustand Store 实例(包含状态和方法)\n * @param action - 要执行的动作定义\n *\n * @returns true表示执行成功,false表示执行失败\n *\n * @example\n * ```typescript\n * // 定义一个攻击动作\n * const attackAction: ActionDef<Stats, Items, Flags> = {\n * id: 'attack_goblin',\n * label: '攻击哥布林',\n * costs: { stamina: 10 },\n * requirements: { hasItems: ['weapon'] },\n * effects: {\n * statsChange: { exp: 5 },\n * flagsSet: { goblin_defeated: true }\n * },\n * resultText: '你击败了哥布林!'\n * };\n *\n * // 执行动作\n * const store = useGameStore.getState();\n * const success = FlowSystem.executeAction(store, attackAction);\n * ```\n */\n static executeAction<\n S extends string,\n I extends string,\n F extends string,\n X = Record<string, unknown>\n >(\n store: GameStore<S, I, F, X>,\n action: ActionDef<S, I, F, X>\n ): boolean {\n // Store 本身包含 State 数据(Zustand 的设计)\n const state = store;\n\n // ========== 步骤0:保存快照(可选) ==========\n\n /**\n * 在动作执行前保存状态快照\n * \n * 这允许玩家在动作执行后撤销操作。\n * 可以根据游戏设计决定是否启用此功能。\n * \n * 建议:对于重要或不可逆的动作启用快照\n */\n // store.saveSnapshot(); // 取消注释以启用自动快照\n\n // ========== 步骤1:最终验证 ==========\n\n /**\n * 检查需求条件\n *\n * 即使UI已经检查过,这里也要再次检查,因为:\n * - UI状态可能滞后\n * - 多个动作可能同时触发\n * - 防止作弊或bug\n */\n if (!QuerySystem.checkRequirements(state, action.requirements)) {\n console.warn(`条件不满足: ${action.id}`);\n return false;\n }\n\n /**\n * 检查成本\n *\n * 确保玩家有足够的资源支付成本\n */\n if (!QuerySystem.canAfford(state, action.costs)) {\n store.addLog(SystemMessages.NOT_ENOUGH_RESOURCES, \"warn\");\n return false;\n }\n\n // ========== 步骤2:扣除成本 ==========\n\n /**\n * 扣除执行动作所需的成本\n *\n * 将成本转换为负数,然后应用到状态上。\n * 例如:{ stamina: 10 } 转换为 { stamina: -10 }\n */\n if (action.costs) {\n const costsToDeduct: Partial<Record<S, number>> = {};\n for (const [key, val] of Object.entries(action.costs)) {\n costsToDeduct[key as S] = -(val as number);\n }\n store.setStats(costsToDeduct);\n }\n\n // ========== 步骤3:应用效果 ==========\n\n const { effects } = action;\n if (effects) {\n /**\n * 3.1 应用数值属性变更\n *\n * 修改玩家的属性值(如生命值、经验值等)\n */\n if (effects.statsChange) {\n store.setStats(effects.statsChange);\n }\n\n /**\n * 3.2 应用物品变更\n *\n * 添加或移除物品\n */\n effects.itemsAdd?.forEach((i) => store.addItem(i));\n effects.itemsRemove?.forEach((i) => store.removeItem(i));\n\n /**\n * 3.3 应用标记变更\n *\n * 设置游戏标记(如任务进度、解锁状态等)\n */\n if (effects.flagsSet) {\n Object.entries(effects.flagsSet).forEach(([f, v]) => {\n store.setFlag(f as F, v as boolean);\n });\n }\n\n /**\n * 3.4 应用传送效果\n *\n * 如果动作包含传送,将玩家传送到指定地点\n */\n if (effects.teleport) {\n store.teleport(effects.teleport);\n }\n\n /**\n * 3.4.5 触发自定义事件\n * \n * 如果动作定义了 triggerEvent,发射自定义事件。\n * 这允许游戏逻辑监听特定事件并做出响应。\n * \n * 例如:触发剧情事件、播放音效、显示特效等\n */\n if (effects.triggerEvent) {\n gameEvents.emit(EngineEvents.CUSTOM, { id: effects.triggerEvent });\n }\n\n /**\n * 3.5 应用自定义效果(修改 extra 字段)\n *\n * 如果动作包含自定义效果函数,执行它来修改扩展数据。\n * 函数可以直接修改 draft 或返回部分更新。\n */\n if (effects.custom) {\n const draft = { ...state.extra };\n const result = effects.custom(draft, state);\n if (result) {\n // 如果返回了部分更新,合并到 draft\n store.setExtra({ ...draft, ...result });\n } else {\n // 如果没有返回值,使用修改后的 draft\n store.setExtra(draft);\n }\n }\n }\n\n // ========== 步骤4:记录日志 ==========\n\n /**\n * 生成结果文本并记录到日志\n *\n * resultText 可以是静态字符串或动态函数。\n * 动态函数可以根据当前状态生成个性化的反馈。\n */\n const resultText =\n typeof action.resultText === \"function\"\n ? action.resultText(state)\n : action.resultText;\n\n store.addLog(resultText, \"result\");\n\n // ========== 步骤5:发射动作完成事件 ==========\n\n /**\n * 发射 ACTION_EXECUTED 事件\n * \n * 通知系统动作已成功执行。\n * UI 层或其他系统可以监听此事件来:\n * - 播放音效\n * - 显示动画\n * - 更新成就系统\n * - 触发连锁反应\n * \n * @example\n * ```typescript\n * // 在 UI 组件中监听动作执行\n * gameEvents.on(EngineEvents.ACTION_EXECUTED, (data) => {\n * console.log(`动作 ${data.actionId} 已执行`);\n * playSound('action_success');\n * });\n * ```\n */\n gameEvents.emit(EngineEvents.ACTION_EXECUTED, { actionId: action.id });\n\n // 执行成功\n return true;\n }\n}\n"]}