steamsheep-ts-game-engine 2.0.0 → 3.1.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.
@@ -1,5 +1,5 @@
1
- import { a as GameStore } from '../store-D0SE7zJK.js';
2
- import { A as ActionDef, G as GameState, R as RequirementDef } from '../types-BLjkeE3R.js';
1
+ import { G as GameStore } from '../store-PPh__zkF.js';
2
+ import { A as ActionDef, a as GameState, c as RequirementDef, R as RequirementCheckResult } from '../types-D-nDlnv3.js';
3
3
  import 'zustand/middleware';
4
4
  import 'zustand';
5
5
 
@@ -237,7 +237,9 @@ declare class FlowSystem {
237
237
  */
238
238
  declare class QuerySystem {
239
239
  /**
240
- * 检查是否满足所有需求条件
240
+ * 检查是否满足所有需求条件(带失败原因)
241
+ *
242
+ * ⚠️ 增强版本:返回详细的检查结果,包括失败原因
241
243
  *
242
244
  * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。
243
245
  * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。
@@ -247,6 +249,35 @@ declare class QuerySystem {
247
249
  * @template F - 标记键的联合类型
248
250
  *
249
251
  * @param state - 当前游戏状态
252
+ * @param reqs - 需求定义(可选,未定义时返回通过)
253
+ *
254
+ * @returns RequirementCheckResult 包含是否通过和失败原因
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * // 检查需求并获取失败原因
259
+ * const result = QuerySystem.checkRequirementsWithReason(state, {
260
+ * hasItems: ['key'],
261
+ * stats: { strength: { min: 5 } }
262
+ * });
263
+ *
264
+ * if (!result.passed) {
265
+ * console.log(result.reason); // 显示失败原因
266
+ * }
267
+ * ```
268
+ */
269
+ static checkRequirementsWithReason<S extends string, I extends string, F extends string, X = Record<string, unknown>>(state: GameState<S, I, F, X>, reqs?: RequirementDef<S, I, F, X>): RequirementCheckResult;
270
+ /**
271
+ * 检查是否满足所有需求条件(简化版本)
272
+ *
273
+ * 这是向后兼容的简化版本,只返回 boolean。
274
+ * 如果需要失败原因,请使用 checkRequirementsWithReason。
275
+ *
276
+ * @template S - 数值属性键的联合类型
277
+ * @template I - 物品ID的联合类型
278
+ * @template F - 标记键的联合类型
279
+ *
280
+ * @param state - 当前游戏状态
250
281
  * @param reqs - 需求定义(可选,未定义时返回true)
251
282
  *
252
283
  * @returns true表示满足所有条件,false表示至少有一个条件不满足
@@ -124,7 +124,9 @@ var EngineEvents = {
124
124
  // systems/query.ts
125
125
  var QuerySystem = class {
126
126
  /**
127
- * 检查是否满足所有需求条件
127
+ * 检查是否满足所有需求条件(带失败原因)
128
+ *
129
+ * ⚠️ 增强版本:返回详细的检查结果,包括失败原因
128
130
  *
129
131
  * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。
130
132
  * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。
@@ -134,59 +136,140 @@ var QuerySystem = class {
134
136
  * @template F - 标记键的联合类型
135
137
  *
136
138
  * @param state - 当前游戏状态
137
- * @param reqs - 需求定义(可选,未定义时返回true)
139
+ * @param reqs - 需求定义(可选,未定义时返回通过)
138
140
  *
139
- * @returns true表示满足所有条件,false表示至少有一个条件不满足
141
+ * @returns RequirementCheckResult 包含是否通过和失败原因
140
142
  *
141
143
  * @example
142
144
  * ```typescript
143
- * // 简单需求
144
- * const simple = QuerySystem.checkRequirements(state, {
145
+ * // 检查需求并获取失败原因
146
+ * const result = QuerySystem.checkRequirementsWithReason(state, {
145
147
  * hasItems: ['key'],
146
148
  * stats: { strength: { min: 5 } }
147
149
  * });
148
150
  *
149
- * // 复杂逻辑使用 custom
150
- * const complex = QuerySystem.checkRequirements(state, {
151
- * custom: (state) => {
152
- * // (有钥匙 AND 力量>=5) OR 有万能钥匙
153
- * const hasKeyAndStrength =
154
- * state.inventory.includes('key') && state.stats.strength >= 5;
155
- * const hasMasterKey = state.inventory.includes('master_key');
156
- * return hasKeyAndStrength || hasMasterKey;
157
- * }
158
- * });
151
+ * if (!result.passed) {
152
+ * console.log(result.reason); // 显示失败原因
153
+ * }
159
154
  * ```
160
155
  */
161
- static checkRequirements(state, reqs) {
162
- if (!reqs) return true;
156
+ static checkRequirementsWithReason(state, reqs) {
157
+ if (!reqs) return { passed: true };
163
158
  if (reqs.hasFlags && reqs.hasFlags.length > 0) {
164
- if (!reqs.hasFlags.every((f) => state.flags[f])) return false;
159
+ const missingFlags = reqs.hasFlags.filter((f) => !state.flags[f]);
160
+ if (missingFlags.length > 0) {
161
+ return {
162
+ passed: false,
163
+ reason: `\u9700\u8981\u6EE1\u8DB3\u6761\u4EF6\uFF1A${missingFlags.join("\u3001")}`
164
+ };
165
+ }
165
166
  }
166
167
  if (reqs.noFlags && reqs.noFlags.length > 0) {
167
- if (reqs.noFlags.some((f) => state.flags[f])) return false;
168
+ const conflictFlags = reqs.noFlags.filter((f) => state.flags[f]);
169
+ if (conflictFlags.length > 0) {
170
+ return {
171
+ passed: false,
172
+ reason: `\u4E0D\u80FD\u6EE1\u8DB3\u6761\u4EF6\uFF1A${conflictFlags.join("\u3001")}`
173
+ };
174
+ }
168
175
  }
169
176
  if (reqs.hasItems && reqs.hasItems.length > 0) {
170
- if (!reqs.hasItems.every((i) => state.inventory.includes(i)))
171
- return false;
177
+ const missingItems = reqs.hasItems.filter((i) => !state.inventory.includes(i));
178
+ if (missingItems.length > 0) {
179
+ return {
180
+ passed: false,
181
+ reason: `\u7F3A\u5C11\u5FC5\u9700\u7269\u54C1\uFF1A${missingItems.join("\u3001")}`
182
+ };
183
+ }
172
184
  }
173
185
  if (reqs.noItems && reqs.noItems.length > 0) {
174
- if (reqs.noItems.some((i) => state.inventory.includes(i))) return false;
186
+ const conflictItems = reqs.noItems.filter((i) => state.inventory.includes(i));
187
+ if (conflictItems.length > 0) {
188
+ return {
189
+ passed: false,
190
+ reason: `\u4E0D\u80FD\u643A\u5E26\u7269\u54C1\uFF1A${conflictItems.join("\u3001")}`
191
+ };
192
+ }
175
193
  }
176
194
  if (reqs.stats) {
177
195
  for (const [key, condition] of Object.entries(reqs.stats)) {
178
196
  const currentVal = state.stats[key] || 0;
179
197
  const cond = condition;
180
198
  if (typeof cond === "number") {
181
- if (currentVal < cond) return false;
199
+ if (currentVal < cond) {
200
+ return {
201
+ passed: false,
202
+ reason: `${key} \u4E0D\u8DB3\uFF08\u9700\u8981 ${cond}\uFF0C\u5F53\u524D ${currentVal}\uFF09`
203
+ };
204
+ }
182
205
  } else {
183
- if (cond.min !== void 0 && currentVal < cond.min) return false;
184
- if (cond.max !== void 0 && currentVal > cond.max) return false;
206
+ if (cond.min !== void 0 && currentVal < cond.min) {
207
+ return {
208
+ passed: false,
209
+ reason: `${key} \u8FC7\u4F4E\uFF08\u9700\u8981\u81F3\u5C11 ${cond.min}\uFF0C\u5F53\u524D ${currentVal}\uFF09`
210
+ };
211
+ }
212
+ if (cond.max !== void 0 && currentVal > cond.max) {
213
+ return {
214
+ passed: false,
215
+ reason: `${key} \u8FC7\u9AD8\uFF08\u9700\u8981\u6700\u591A ${cond.max}\uFF0C\u5F53\u524D ${currentVal}\uFF09`
216
+ };
217
+ }
185
218
  }
186
219
  }
187
220
  }
188
- if (reqs.custom && !reqs.custom(state)) return false;
189
- return true;
221
+ if (reqs.custom) {
222
+ const customResult = reqs.custom(state);
223
+ if (typeof customResult === "object") {
224
+ if (!customResult.passed) {
225
+ return customResult;
226
+ }
227
+ } else if (!customResult) {
228
+ return {
229
+ passed: false,
230
+ reason: "\u4E0D\u6EE1\u8DB3\u81EA\u5B9A\u4E49\u6761\u4EF6"
231
+ };
232
+ }
233
+ }
234
+ return { passed: true };
235
+ }
236
+ /**
237
+ * 检查是否满足所有需求条件(简化版本)
238
+ *
239
+ * 这是向后兼容的简化版本,只返回 boolean。
240
+ * 如果需要失败原因,请使用 checkRequirementsWithReason。
241
+ *
242
+ * @template S - 数值属性键的联合类型
243
+ * @template I - 物品ID的联合类型
244
+ * @template F - 标记键的联合类型
245
+ *
246
+ * @param state - 当前游戏状态
247
+ * @param reqs - 需求定义(可选,未定义时返回true)
248
+ *
249
+ * @returns true表示满足所有条件,false表示至少有一个条件不满足
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * // 简单需求
254
+ * const simple = QuerySystem.checkRequirements(state, {
255
+ * hasItems: ['key'],
256
+ * stats: { strength: { min: 5 } }
257
+ * });
258
+ *
259
+ * // 复杂逻辑使用 custom
260
+ * const complex = QuerySystem.checkRequirements(state, {
261
+ * custom: (state) => {
262
+ * // (有钥匙 AND 力量>=5) OR 有万能钥匙
263
+ * const hasKeyAndStrength =
264
+ * state.inventory.includes('key') && state.stats.strength >= 5;
265
+ * const hasMasterKey = state.inventory.includes('master_key');
266
+ * return hasKeyAndStrength || hasMasterKey;
267
+ * }
268
+ * });
269
+ * ```
270
+ */
271
+ static checkRequirements(state, reqs) {
272
+ return this.checkRequirementsWithReason(state, reqs).passed;
190
273
  }
191
274
  /**
192
275
  * 检查是否有足够资源支付成本
@@ -288,8 +371,12 @@ var FlowSystem = class {
288
371
  */
289
372
  static executeAction(store, action) {
290
373
  const state = store;
291
- if (!QuerySystem.checkRequirements(state, action.requirements)) {
292
- console.warn(`\u6761\u4EF6\u4E0D\u6EE1\u8DB3: ${action.id}`);
374
+ const reqCheck = QuerySystem.checkRequirementsWithReason(state, action.requirements);
375
+ if (!reqCheck.passed) {
376
+ console.warn(`\u6761\u4EF6\u4E0D\u6EE1\u8DB3: ${action.id}`, reqCheck.reason);
377
+ if (reqCheck.reason) {
378
+ store.addLog(reqCheck.reason, "warn");
379
+ }
293
380
  return false;
294
381
  }
295
382
  if (!QuerySystem.canAfford(state, action.costs)) {
@@ -305,21 +392,88 @@ var FlowSystem = class {
305
392
  }
306
393
  const { effects } = action;
307
394
  if (effects) {
308
- if (effects.statsChange) {
309
- store.setStats(effects.statsChange);
310
- }
311
- effects.itemsAdd?.forEach((i) => store.addItem(i));
312
- effects.itemsRemove?.forEach((i) => store.removeItem(i));
313
- if (effects.flagsSet) {
314
- Object.entries(effects.flagsSet).forEach(([f, v]) => {
315
- store.setFlag(f, v);
316
- });
317
- }
318
- if (effects.teleport) {
319
- store.teleport(effects.teleport);
320
- }
321
- if (effects.triggerEvent) {
322
- gameEvents.emit(EngineEvents.CUSTOM, { id: effects.triggerEvent });
395
+ const applyEffectDef = (effectDef) => {
396
+ if (effectDef.statsChange) {
397
+ const statsToChange = typeof effectDef.statsChange === "function" ? effectDef.statsChange(store) : effectDef.statsChange;
398
+ store.setStats(statsToChange);
399
+ }
400
+ effectDef.itemsAdd?.forEach((i) => store.addItem(i));
401
+ effectDef.itemsRemove?.forEach((i) => store.removeItem(i));
402
+ if (effectDef.flagsSet) {
403
+ const flagsToSet = typeof effectDef.flagsSet === "function" ? effectDef.flagsSet(store) : effectDef.flagsSet;
404
+ Object.entries(flagsToSet).forEach(([f, v]) => {
405
+ store.setFlag(f, v);
406
+ });
407
+ }
408
+ if (effectDef.flagsBatch) {
409
+ const batchOp = typeof effectDef.flagsBatch === "function" ? effectDef.flagsBatch(store) : effectDef.flagsBatch;
410
+ if (batchOp) {
411
+ if (batchOp.clear) {
412
+ const allFlags = Object.keys(store.flags);
413
+ let flagsToClear = [];
414
+ if (batchOp.clear instanceof RegExp) {
415
+ flagsToClear = allFlags.filter((f) => batchOp.clear instanceof RegExp && batchOp.clear.test(f));
416
+ } else if (typeof batchOp.clear === "string") {
417
+ flagsToClear = allFlags.filter((f) => f.startsWith(batchOp.clear));
418
+ } else if (Array.isArray(batchOp.clear)) {
419
+ flagsToClear = batchOp.clear;
420
+ }
421
+ flagsToClear.forEach((f) => {
422
+ store.setFlag(f, false);
423
+ });
424
+ }
425
+ if (batchOp.set) {
426
+ Object.entries(batchOp.set).forEach(([f, v]) => {
427
+ store.setFlag(f, v);
428
+ });
429
+ }
430
+ }
431
+ }
432
+ if (effectDef.teleport) {
433
+ store.teleport(effectDef.teleport);
434
+ }
435
+ if (effectDef.timeAdvance !== void 0) {
436
+ const daysToAdvance = typeof effectDef.timeAdvance === "function" ? effectDef.timeAdvance(store) : effectDef.timeAdvance;
437
+ if (daysToAdvance > 0) {
438
+ const previousDay = store.world.day;
439
+ store.advanceTime(daysToAdvance);
440
+ if (effectDef.onTimeAdvance) {
441
+ const actions = {
442
+ updateStat: store.updateStat,
443
+ setStats: store.setStats,
444
+ setExtra: store.setExtra,
445
+ addItem: store.addItem,
446
+ removeItem: store.removeItem,
447
+ setFlag: store.setFlag,
448
+ addLog: store.addLog,
449
+ showToast: store.showToast,
450
+ showModal: store.showModal,
451
+ advanceTime: store.advanceTime,
452
+ teleport: store.teleport,
453
+ reset: store.reset,
454
+ saveSnapshot: store.saveSnapshot,
455
+ undo: store.undo
456
+ };
457
+ for (let i = 1; i <= daysToAdvance; i++) {
458
+ const currentDay = previousDay + i;
459
+ const dayBefore = currentDay - 1;
460
+ const currentState = store;
461
+ effectDef.onTimeAdvance(currentDay, dayBefore, currentState, actions);
462
+ }
463
+ }
464
+ }
465
+ }
466
+ if (effectDef.triggerEvent) {
467
+ gameEvents.emit(EngineEvents.CUSTOM, { id: effectDef.triggerEvent });
468
+ }
469
+ };
470
+ applyEffectDef(effects);
471
+ if (effects.conditionalEffects) {
472
+ const currentState = store;
473
+ const conditionalEffect = effects.conditionalEffects(currentState);
474
+ if (conditionalEffect) {
475
+ applyEffectDef(conditionalEffect);
476
+ }
323
477
  }
324
478
  if (effects.custom) {
325
479
  const draft = { ...state.extra };
@@ -330,8 +484,81 @@ var FlowSystem = class {
330
484
  store.setExtra(draft);
331
485
  }
332
486
  }
487
+ if (effects.customFull) {
488
+ const originalState = { ...state };
489
+ const currentState = store;
490
+ const draft = {
491
+ stats: { ...currentState.stats },
492
+ inventory: [...currentState.inventory],
493
+ flags: { ...currentState.flags },
494
+ world: { ...currentState.world },
495
+ extra: { ...currentState.extra }
496
+ };
497
+ effects.customFull(draft, originalState);
498
+ const statsChanges = {};
499
+ for (const key in draft.stats) {
500
+ const oldVal = currentState.stats[key] || 0;
501
+ const newVal = draft.stats[key];
502
+ const delta = newVal - oldVal;
503
+ if (delta !== 0) {
504
+ statsChanges[key] = delta;
505
+ }
506
+ }
507
+ if (Object.keys(statsChanges).length > 0) {
508
+ store.setStats(statsChanges);
509
+ }
510
+ const currentItems = new Set(currentState.inventory);
511
+ const newItems = new Set(draft.inventory);
512
+ currentState.inventory.forEach((item) => {
513
+ if (!newItems.has(item)) {
514
+ store.removeItem(item);
515
+ }
516
+ });
517
+ draft.inventory.forEach((item) => {
518
+ if (!currentItems.has(item)) {
519
+ store.addItem(item);
520
+ }
521
+ });
522
+ for (const key in draft.flags) {
523
+ const oldVal = currentState.flags[key];
524
+ const newVal = draft.flags[key];
525
+ if (oldVal !== newVal) {
526
+ store.setFlag(key, newVal);
527
+ }
528
+ }
529
+ if (draft.world.currentLocationId !== currentState.world.currentLocationId) {
530
+ store.teleport(draft.world.currentLocationId);
531
+ }
532
+ if (draft.world.day !== currentState.world.day) {
533
+ const dayDelta = draft.world.day - currentState.world.day;
534
+ store.advanceTime(dayDelta);
535
+ }
536
+ store.setExtra(draft.extra);
537
+ }
538
+ }
539
+ if (action.afterEffects) {
540
+ const originalState = { ...state };
541
+ const currentState = store;
542
+ const actions = {
543
+ updateStat: store.updateStat,
544
+ setStats: store.setStats,
545
+ setExtra: store.setExtra,
546
+ addItem: store.addItem,
547
+ removeItem: store.removeItem,
548
+ setFlag: store.setFlag,
549
+ addLog: store.addLog,
550
+ showToast: store.showToast,
551
+ showModal: store.showModal,
552
+ advanceTime: store.advanceTime,
553
+ teleport: store.teleport,
554
+ reset: store.reset,
555
+ saveSnapshot: store.saveSnapshot,
556
+ undo: store.undo
557
+ };
558
+ action.afterEffects(currentState, originalState, actions);
333
559
  }
334
- const resultText = typeof action.resultText === "function" ? action.resultText(state) : action.resultText;
560
+ const finalState = store;
561
+ const resultText = typeof action.resultText === "function" ? action.resultText(finalState, state) : action.resultText;
335
562
  store.addLog(resultText, "result");
336
563
  gameEvents.emit(EngineEvents.ACTION_EXECUTED, { actionId: action.id });
337
564
  return true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../systems/events.ts","../../systems/query.ts","../../core/messages.ts","../../systems/flow.ts"],"names":[],"mappings":";;;;;;;AA6BO,IAAM,WAAN,MAAe;AAAA,EAAf,WAAA,GAAA;AAOL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,aAAA,CAAA,IAAA,EAAQ,WAAA,sBAAyC,GAAA,EAAI,CAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBrD,EAAA,CAAM,OAAe,QAAA,EAAmC;AACtD,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,EAAO,EAAE,CAAA;AAAA,IAC9B;AACA,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,CAAG,KAAK,QAAoB,CAAA;AAGpD,IAAA,OAAO,MAAM,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,QAAQ,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,GAAA,CAAO,OAAe,QAAA,EAAuB;AAC3C,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC1C,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA;AAAA,QACb,KAAA;AAAA,QACA,SAAA,CAAU,MAAA,CAAO,CAAC,EAAA,KAAO,OAAQ,QAAqB;AAAA,OACxD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,IAAA,CAAQ,OAAe,IAAA,EAAU;AAC/B,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC1C,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,EAAA,KAAO;AACxB,QAAA,IAAI;AACF,UAAA,EAAA,CAAG,IAAI,CAAA;AAAA,QACT,SAAS,KAAA,EAAO;AACd,UAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,6BAAA,EAAgC,KAAK,CAAA,EAAA,CAAA,EAAM,KAAK,CAAA;AAAA,QAChE;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,KAAA,GAAQ;AACN,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACvB;AACF;AAeO,IAAM,UAAA,GAAa,IAAI,QAAA;AAgBvB,IAAM,YAAA,GAAe;AAAA;AAAA,EAE1B,WAAA,EAAa,oBAAA;AAAA;AAAA,EAEb,QAAA,EAAU,iBAAA;AAAA;AAAA,EAEV,WAAA,EAAa,oBAAA;AAAA;AAAA,EAEb,WAAA,EAAa,oBAAA;AAAA;AAAA,EAEb,eAAA,EAAiB,oBAAA;AAAA;AAAA,EAEjB,SAAA,EAAW,kBAAA;AAAA;AAAA,EAEX,YAAA,EAAc,qBAAA;AAAA;AAAA,EAEd,MAAA,EAAQ;AACV;;;ACtIO,IAAM,cAAN,MAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoCvB,OAAO,iBAAA,CAML,KAAA,EACA,IAAA,EACS;AAET,IAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAQlB,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,KAAM,KAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,OAAO,KAAA;AAAA,IAC1D;AAMA,IAAA,IAAI,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC3C,MAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,MAAM,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,OAAO,KAAA;AAAA,IACvD;AAQA,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,CAAC,MAAM,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS,CAAC,CAAC,CAAA;AACzD,QAAA,OAAO,KAAA;AAAA,IACX;AAMA,IAAA,IAAI,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC3C,MAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS,CAAC,CAAC,CAAA,EAAG,OAAO,KAAA;AAAA,IACpE;AAUA,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,KAAA,MAAW,CAAC,KAAK,SAAS,CAAA,IAAK,OAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,EAAG;AAEzD,QAAA,MAAM,UAAA,GAAa,KAAA,CAAM,KAAA,CAAM,GAAQ,CAAA,IAAK,CAAA;AAC5C,QAAA,MAAM,IAAA,GAAO,SAAA;AAEb,QAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAK5B,UAAA,IAAI,UAAA,GAAa,MAAM,OAAO,KAAA;AAAA,QAChC,CAAA,MAAO;AAKL,UAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,UAAA,GAAa,IAAA,CAAK,KAAK,OAAO,KAAA;AAC5D,UAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,UAAA,GAAa,IAAA,CAAK,KAAK,OAAO,KAAA;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAQA,IAAA,IAAI,KAAK,MAAA,IAAU,CAAC,KAAK,MAAA,CAAO,KAAK,GAAG,OAAO,KAAA;AAG/C,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CA,OAAO,SAAA,CAML,KAAA,EACA,KAAA,EACS;AAET,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAMnB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/C,MAAA,MAAM,UAAA,GAAa,KAAA,CAAM,KAAA,CAAM,GAAQ,CAAA,IAAK,CAAA;AAC5C,MAAA,IAAI,UAAA,GAAc,MAAiB,OAAO,KAAA;AAAA,IAC5C;AAGA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;AClOO,IAAM,cAAA,GAAiB;AAAA;AAAA,EAE5B,oBAAA,EAAsB,0BAOxB,CAAA;;;ACgCO,IAAM,aAAN,MAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CtB,OAAO,aAAA,CAML,KAAA,EACA,MAAA,EACS;AAET,IAAA,MAAM,KAAA,GAAQ,KAAA;AAwBd,IAAA,IAAI,CAAC,WAAA,CAAY,iBAAA,CAAkB,KAAA,EAAO,MAAA,CAAO,YAAY,CAAA,EAAG;AAC9D,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,gCAAA,EAAU,MAAA,CAAO,EAAE,CAAA,CAAE,CAAA;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAOA,IAAA,IAAI,CAAC,WAAA,CAAY,SAAA,CAAU,KAAA,EAAO,MAAA,CAAO,KAAK,CAAA,EAAG;AAC/C,MAAA,KAAA,CAAM,MAAA,CAAO,cAAA,CAAe,oBAAA,EAAsB,MAAM,CAAA;AACxD,MAAA,OAAO,KAAA;AAAA,IACT;AAUA,IAAA,IAAI,OAAO,KAAA,EAAO;AAChB,MAAA,MAAM,gBAA4C,EAAC;AACnD,MAAA,KAAA,MAAW,CAAC,KAAK,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA,EAAG;AACrD,QAAA,aAAA,CAAc,GAAQ,IAAI,CAAE,GAAA;AAAA,MAC9B;AACA,MAAA,KAAA,CAAM,SAAS,aAAa,CAAA;AAAA,IAC9B;AAIA,IAAA,MAAM,EAAE,SAAQ,GAAI,MAAA;AACpB,IAAA,IAAI,OAAA,EAAS;AAMX,MAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,QAAA,KAAA,CAAM,QAAA,CAAS,QAAQ,WAAW,CAAA;AAAA,MACpC;AAOA,MAAA,OAAA,CAAQ,UAAU,OAAA,CAAQ,CAAC,MAAM,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAC,CAAA;AACjD,MAAA,OAAA,CAAQ,aAAa,OAAA,CAAQ,CAAC,MAAM,KAAA,CAAM,UAAA,CAAW,CAAC,CAAC,CAAA;AAOvD,MAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,QAAA,MAAA,CAAO,OAAA,CAAQ,QAAQ,QAAQ,CAAA,CAAE,QAAQ,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM;AACnD,UAAA,KAAA,CAAM,OAAA,CAAQ,GAAQ,CAAY,CAAA;AAAA,QACpC,CAAC,CAAA;AAAA,MACH;AAOA,MAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,QAAA,KAAA,CAAM,QAAA,CAAS,QAAQ,QAAQ,CAAA;AAAA,MACjC;AAUA,MAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,QAAA,UAAA,CAAW,KAAK,YAAA,CAAa,MAAA,EAAQ,EAAE,EAAA,EAAI,OAAA,CAAQ,cAAc,CAAA;AAAA,MACnE;AAQA,MAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,QAAA,MAAM,KAAA,GAAQ,EAAE,GAAG,KAAA,CAAM,KAAA,EAAM;AAC/B,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,KAAA,EAAO,KAAK,CAAA;AAC1C,QAAA,IAAI,MAAA,EAAQ;AAEV,UAAA,KAAA,CAAM,SAAS,EAAE,GAAG,KAAA,EAAO,GAAG,QAAQ,CAAA;AAAA,QACxC,CAAA,MAAO;AAEL,UAAA,KAAA,CAAM,SAAS,KAAK,CAAA;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAUA,IAAA,MAAM,UAAA,GACJ,OAAO,MAAA,CAAO,UAAA,KAAe,aACzB,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,GACvB,MAAA,CAAO,UAAA;AAEb,IAAA,KAAA,CAAM,MAAA,CAAO,YAAY,QAAQ,CAAA;AAuBjC,IAAA,UAAA,CAAW,KAAK,YAAA,CAAa,eAAA,EAAiB,EAAE,QAAA,EAAU,MAAA,CAAO,IAAI,CAAA;AAGrE,IAAA,OAAO,IAAA;AAAA,EACT;AACF","file":"index.js","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"]}
1
+ {"version":3,"sources":["../../systems/events.ts","../../systems/query.ts","../../core/messages.ts","../../systems/flow.ts"],"names":[],"mappings":";;;;;;;AA6BO,IAAM,WAAN,MAAe;AAAA,EAAf,WAAA,GAAA;AAOL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,aAAA,CAAA,IAAA,EAAQ,WAAA,sBAAyC,GAAA,EAAI,CAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBrD,EAAA,CAAM,OAAe,QAAA,EAAmC;AACtD,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,EAAO,EAAE,CAAA;AAAA,IAC9B;AACA,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,CAAG,KAAK,QAAoB,CAAA;AAGpD,IAAA,OAAO,MAAM,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,QAAQ,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,GAAA,CAAO,OAAe,QAAA,EAAuB;AAC3C,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC1C,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA;AAAA,QACb,KAAA;AAAA,QACA,SAAA,CAAU,MAAA,CAAO,CAAC,EAAA,KAAO,OAAQ,QAAqB;AAAA,OACxD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,IAAA,CAAQ,OAAe,IAAA,EAAU;AAC/B,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC1C,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,EAAA,KAAO;AACxB,QAAA,IAAI;AACF,UAAA,EAAA,CAAG,IAAI,CAAA;AAAA,QACT,SAAS,KAAA,EAAO;AACd,UAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,6BAAA,EAAgC,KAAK,CAAA,EAAA,CAAA,EAAM,KAAK,CAAA;AAAA,QAChE;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,KAAA,GAAQ;AACN,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACvB;AACF;AAeO,IAAM,UAAA,GAAa,IAAI,QAAA;AAgBvB,IAAM,YAAA,GAAe;AAAA;AAAA,EAE1B,WAAA,EAAa,oBAAA;AAAA;AAAA,EAEb,QAAA,EAAU,iBAAA;AAAA;AAAA,EAEV,WAAA,EAAa,oBAAA;AAAA;AAAA,EAEb,WAAA,EAAa,oBAAA;AAAA;AAAA,EAEb,eAAA,EAAiB,oBAAA;AAAA;AAAA,EAEjB,SAAA,EAAW,kBAAA;AAAA;AAAA,EAEX,YAAA,EAAc,qBAAA;AAAA;AAAA,EAEd,MAAA,EAAQ;AACV;;;ACtIO,IAAM,cAAN,MAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BvB,OAAO,2BAAA,CAML,KAAA,EACA,IAAA,EACwB;AAExB,IAAA,IAAI,CAAC,IAAA,EAAM,OAAO,EAAE,QAAQ,IAAA,EAAK;AAQjC,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,CAAC,MAAM,CAAC,KAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CAAA;AAChE,MAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,KAAA;AAAA,UACR,MAAA,EAAQ,CAAA,0CAAA,EAAU,YAAA,CAAa,IAAA,CAAK,QAAG,CAAC,CAAA;AAAA,SAC1C;AAAA,MACF;AAAA,IACF;AAMA,IAAA,IAAI,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC3C,MAAA,MAAM,aAAA,GAAgB,KAAK,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,KAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CAAA;AAC/D,MAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,KAAA;AAAA,UACR,MAAA,EAAQ,CAAA,0CAAA,EAAU,aAAA,CAAc,IAAA,CAAK,QAAG,CAAC,CAAA;AAAA,SAC3C;AAAA,MACF;AAAA,IACF;AAQA,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS,CAAC,CAAC,CAAA;AAC7E,MAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,KAAA;AAAA,UACR,MAAA,EAAQ,CAAA,0CAAA,EAAU,YAAA,CAAa,IAAA,CAAK,QAAG,CAAC,CAAA;AAAA,SAC1C;AAAA,MACF;AAAA,IACF;AAMA,IAAA,IAAI,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC3C,MAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,CAAC,MAAM,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS,CAAC,CAAC,CAAA;AAC5E,MAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,KAAA;AAAA,UACR,MAAA,EAAQ,CAAA,0CAAA,EAAU,aAAA,CAAc,IAAA,CAAK,QAAG,CAAC,CAAA;AAAA,SAC3C;AAAA,MACF;AAAA,IACF;AAUA,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,KAAA,MAAW,CAAC,KAAK,SAAS,CAAA,IAAK,OAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,EAAG;AAEzD,QAAA,MAAM,UAAA,GAAa,KAAA,CAAM,KAAA,CAAM,GAAQ,CAAA,IAAK,CAAA;AAC5C,QAAA,MAAM,IAAA,GAAO,SAAA;AAEb,QAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAK5B,UAAA,IAAI,aAAa,IAAA,EAAM;AACrB,YAAA,OAAO;AAAA,cACL,MAAA,EAAQ,KAAA;AAAA,cACR,QAAQ,CAAA,EAAG,GAAG,CAAA,gCAAA,EAAU,IAAI,sBAAO,UAAU,CAAA,MAAA;AAAA,aAC/C;AAAA,UACF;AAAA,QACF,CAAA,MAAO;AAKL,UAAA,IAAI,IAAA,CAAK,GAAA,KAAQ,MAAA,IAAa,UAAA,GAAa,KAAK,GAAA,EAAK;AACnD,YAAA,OAAO;AAAA,cACL,MAAA,EAAQ,KAAA;AAAA,cACR,QAAQ,CAAA,EAAG,GAAG,+CAAY,IAAA,CAAK,GAAG,sBAAO,UAAU,CAAA,MAAA;AAAA,aACrD;AAAA,UACF;AACA,UAAA,IAAI,IAAA,CAAK,GAAA,KAAQ,MAAA,IAAa,UAAA,GAAa,KAAK,GAAA,EAAK;AACnD,YAAA,OAAO;AAAA,cACL,MAAA,EAAQ,KAAA;AAAA,cACR,QAAQ,CAAA,EAAG,GAAG,+CAAY,IAAA,CAAK,GAAG,sBAAO,UAAU,CAAA,MAAA;AAAA,aACrD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAQA,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAGtC,MAAA,IAAI,OAAO,iBAAiB,QAAA,EAAU;AACpC,QAAA,IAAI,CAAC,aAAa,MAAA,EAAQ;AACxB,UAAA,OAAO,YAAA;AAAA,QACT;AAAA,MACF,CAAA,MAAA,IAES,CAAC,YAAA,EAAc;AACtB,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,KAAA;AAAA,UACR,MAAA,EAAQ;AAAA,SACV;AAAA,MACF;AAAA,IACF;AAGA,IAAA,OAAO,EAAE,QAAQ,IAAA,EAAK;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqCA,OAAO,iBAAA,CAML,KAAA,EACA,IAAA,EACS;AACT,IAAA,OAAO,IAAA,CAAK,2BAAA,CAA4B,KAAA,EAAO,IAAI,CAAA,CAAE,MAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CA,OAAO,SAAA,CAML,KAAA,EACA,KAAA,EACS;AAET,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAMnB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/C,MAAA,MAAM,UAAA,GAAa,KAAA,CAAM,KAAA,CAAM,GAAQ,CAAA,IAAK,CAAA;AAC5C,MAAA,IAAI,UAAA,GAAc,MAAiB,OAAO,KAAA;AAAA,IAC5C;AAGA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;AClUO,IAAM,cAAA,GAAiB;AAAA;AAAA,EAE5B,oBAAA,EAAsB,0BAOxB,CAAA;;;ACgCO,IAAM,aAAN,MAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CtB,OAAO,aAAA,CAML,KAAA,EACA,MAAA,EACS;AAET,IAAA,MAAM,KAAA,GAAQ,KAAA;AA0Bd,IAAA,MAAM,QAAA,GAAW,WAAA,CAAY,2BAAA,CAA4B,KAAA,EAAO,OAAO,YAAY,CAAA;AACnF,IAAA,IAAI,CAAC,SAAS,MAAA,EAAQ;AACpB,MAAA,OAAA,CAAQ,KAAK,CAAA,gCAAA,EAAU,MAAA,CAAO,EAAE,CAAA,CAAA,EAAI,SAAS,MAAM,CAAA;AAGnD,MAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,QAAA,KAAA,CAAM,MAAA,CAAO,QAAA,CAAS,MAAA,EAAQ,MAAM,CAAA;AAAA,MACtC;AAEA,MAAA,OAAO,KAAA;AAAA,IACT;AAOA,IAAA,IAAI,CAAC,WAAA,CAAY,SAAA,CAAU,KAAA,EAAO,MAAA,CAAO,KAAK,CAAA,EAAG;AAC/C,MAAA,KAAA,CAAM,MAAA,CAAO,cAAA,CAAe,oBAAA,EAAsB,MAAM,CAAA;AACxD,MAAA,OAAO,KAAA;AAAA,IACT;AAUA,IAAA,IAAI,OAAO,KAAA,EAAO;AAChB,MAAA,MAAM,gBAA4C,EAAC;AACnD,MAAA,KAAA,MAAW,CAAC,KAAK,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA,EAAG;AACrD,QAAA,aAAA,CAAc,GAAQ,IAAI,CAAE,GAAA;AAAA,MAC9B;AACA,MAAA,KAAA,CAAM,SAAS,aAAa,CAAA;AAAA,IAC9B;AA+BA,IAAA,MAAM,EAAE,SAAQ,GAAI,MAAA;AACpB,IAAA,IAAI,OAAA,EAAS;AAOX,MAAA,MAAM,cAAA,GAAiB,CAAC,SAAA,KAA2F;AAEjH,QAAA,IAAI,UAAU,WAAA,EAAa;AACzB,UAAA,MAAM,aAAA,GAAgB,OAAO,SAAA,CAAU,WAAA,KAAgB,aACnD,SAAA,CAAU,WAAA,CAAY,KAAK,CAAA,GAC3B,SAAA,CAAU,WAAA;AAEd,UAAA,KAAA,CAAM,SAAS,aAAa,CAAA;AAAA,QAC9B;AAGA,QAAA,SAAA,CAAU,UAAU,OAAA,CAAQ,CAAC,MAAS,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAC,CAAA;AAGtD,QAAA,SAAA,CAAU,aAAa,OAAA,CAAQ,CAAC,MAAS,KAAA,CAAM,UAAA,CAAW,CAAC,CAAC,CAAA;AAG5D,QAAA,IAAI,UAAU,QAAA,EAAU;AACtB,UAAA,MAAM,UAAA,GAAa,OAAO,SAAA,CAAU,QAAA,KAAa,aAC7C,SAAA,CAAU,QAAA,CAAS,KAAK,CAAA,GACxB,SAAA,CAAU,QAAA;AAEd,UAAA,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM;AAC7C,YAAA,KAAA,CAAM,OAAA,CAAQ,GAAQ,CAAY,CAAA;AAAA,UACpC,CAAC,CAAA;AAAA,QACH;AAGA,QAAA,IAAI,UAAU,UAAA,EAAY;AACxB,UAAA,MAAM,OAAA,GAAU,OAAO,SAAA,CAAU,UAAA,KAAe,aAC5C,SAAA,CAAU,UAAA,CAAW,KAAK,CAAA,GAC1B,SAAA,CAAU,UAAA;AAEd,UAAA,IAAI,OAAA,EAAS;AAEX,YAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,cAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AACxC,cAAA,IAAI,eAAyB,EAAC;AAE9B,cAAA,IAAI,OAAA,CAAQ,iBAAiB,MAAA,EAAQ;AAEnC,gBAAA,YAAA,GAAe,QAAA,CAAS,MAAA,CAAO,CAAA,CAAA,KAAK,OAAA,CAAQ,KAAA,YAAiB,UAAU,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,cAC9F,CAAA,MAAA,IAAW,OAAO,OAAA,CAAQ,KAAA,KAAU,QAAA,EAAU;AAE5C,gBAAA,YAAA,GAAe,SAAS,MAAA,CAAO,CAAA,CAAA,KAAK,EAAE,UAAA,CAAW,OAAA,CAAQ,KAAe,CAAC,CAAA;AAAA,cAC3E,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,OAAA,CAAQ,KAAK,CAAA,EAAG;AAEvC,gBAAA,YAAA,GAAe,OAAA,CAAQ,KAAA;AAAA,cACzB;AAGA,cAAA,YAAA,CAAa,QAAQ,CAAA,CAAA,KAAK;AACxB,gBAAA,KAAA,CAAM,OAAA,CAAQ,GAAQ,KAAK,CAAA;AAAA,cAC7B,CAAC,CAAA;AAAA,YACH;AAGA,YAAA,IAAI,QAAQ,GAAA,EAAK;AACf,cAAA,MAAA,CAAO,OAAA,CAAQ,QAAQ,GAAG,CAAA,CAAE,QAAQ,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM;AAC9C,gBAAA,KAAA,CAAM,OAAA,CAAQ,GAAQ,CAAY,CAAA;AAAA,cACpC,CAAC,CAAA;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAGA,QAAA,IAAI,UAAU,QAAA,EAAU;AACtB,UAAA,KAAA,CAAM,QAAA,CAAS,UAAU,QAAQ,CAAA;AAAA,QACnC;AAGA,QAAA,IAAI,SAAA,CAAU,gBAAgB,MAAA,EAAW;AACvC,UAAA,MAAM,aAAA,GAAgB,OAAO,SAAA,CAAU,WAAA,KAAgB,aACnD,SAAA,CAAU,WAAA,CAAY,KAAK,CAAA,GAC3B,SAAA,CAAU,WAAA;AAEd,UAAA,IAAI,gBAAgB,CAAA,EAAG;AACrB,YAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,GAAA;AAGhC,YAAA,KAAA,CAAM,YAAY,aAAa,CAAA;AAG/B,YAAA,IAAI,UAAU,aAAA,EAAe;AAC3B,cAAA,MAAM,OAAA,GAAU;AAAA,gBACd,YAAY,KAAA,CAAM,UAAA;AAAA,gBAClB,UAAU,KAAA,CAAM,QAAA;AAAA,gBAChB,UAAU,KAAA,CAAM,QAAA;AAAA,gBAChB,SAAS,KAAA,CAAM,OAAA;AAAA,gBACf,YAAY,KAAA,CAAM,UAAA;AAAA,gBAClB,SAAS,KAAA,CAAM,OAAA;AAAA,gBACf,QAAQ,KAAA,CAAM,MAAA;AAAA,gBACd,WAAW,KAAA,CAAM,SAAA;AAAA,gBACjB,WAAW,KAAA,CAAM,SAAA;AAAA,gBACjB,aAAa,KAAA,CAAM,WAAA;AAAA,gBACnB,UAAU,KAAA,CAAM,QAAA;AAAA,gBAChB,OAAO,KAAA,CAAM,KAAA;AAAA,gBACb,cAAc,KAAA,CAAM,YAAA;AAAA,gBACpB,MAAM,KAAA,CAAM;AAAA,eACd;AAGA,cAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,aAAA,EAAe,CAAA,EAAA,EAAK;AACvC,gBAAA,MAAM,aAAa,WAAA,GAAc,CAAA;AACjC,gBAAA,MAAM,YAAY,UAAA,GAAa,CAAA;AAG/B,gBAAA,MAAM,YAAA,GAAe,KAAA;AAErB,gBAAA,SAAA,CAAU,aAAA,CAAc,UAAA,EAAY,SAAA,EAAW,YAAA,EAAc,OAAO,CAAA;AAAA,cACtE;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,QAAA,IAAI,UAAU,YAAA,EAAc;AAC1B,UAAA,UAAA,CAAW,KAAK,YAAA,CAAa,MAAA,EAAQ,EAAE,EAAA,EAAI,SAAA,CAAU,cAAc,CAAA;AAAA,QACrE;AAAA,MACF,CAAA;AAQA,MAAA,cAAA,CAAe,OAAO,CAAA;AAatB,MAAA,IAAI,QAAQ,kBAAA,EAAoB;AAE9B,QAAA,MAAM,YAAA,GAAe,KAAA;AAGrB,QAAA,MAAM,iBAAA,GAAoB,OAAA,CAAQ,kBAAA,CAAmB,YAAY,CAAA;AAGjE,QAAA,IAAI,iBAAA,EAAmB;AACrB,UAAA,cAAA,CAAe,iBAAiB,CAAA;AAAA,QAClC;AAAA,MACF;AAaA,MAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,QAAA,MAAM,KAAA,GAAQ,EAAE,GAAG,KAAA,CAAM,KAAA,EAAM;AAC/B,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,KAAA,EAAO,KAAK,CAAA;AAC1C,QAAA,IAAI,MAAA,EAAQ;AAEV,UAAA,KAAA,CAAM,SAAS,EAAE,GAAG,KAAA,EAAO,GAAG,QAAQ,CAAA;AAAA,QACxC,CAAA,MAAO;AAEL,UAAA,KAAA,CAAM,SAAS,KAAK,CAAA;AAAA,QACtB;AAAA,MACF;AAqBA,MAAA,IAAI,QAAQ,UAAA,EAAY;AAEtB,QAAA,MAAM,aAAA,GAAgB,EAAE,GAAG,KAAA,EAAM;AAGjC,QAAA,MAAM,YAAA,GAAe,KAAA;AAGrB,QAAA,MAAM,KAAA,GAAQ;AAAA,UACZ,KAAA,EAAO,EAAE,GAAG,YAAA,CAAa,KAAA,EAAM;AAAA,UAC/B,SAAA,EAAW,CAAC,GAAG,YAAA,CAAa,SAAS,CAAA;AAAA,UACrC,KAAA,EAAO,EAAE,GAAG,YAAA,CAAa,KAAA,EAAM;AAAA,UAC/B,KAAA,EAAO,EAAE,GAAG,YAAA,CAAa,KAAA,EAAM;AAAA,UAC/B,KAAA,EAAO,EAAE,GAAG,YAAA,CAAa,KAAA;AAAM,SACjC;AAGA,QAAA,OAAA,CAAQ,UAAA,CAAW,OAAO,aAAa,CAAA;AAIvC,QAAA,MAAM,eAA2C,EAAC;AAClD,QAAA,KAAA,MAAW,GAAA,IAAO,MAAM,KAAA,EAAO;AAC7B,UAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,GAAQ,CAAA,IAAK,CAAA;AAC/C,UAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,GAAQ,CAAA;AACnC,UAAA,MAAM,QAAQ,MAAA,GAAS,MAAA;AACvB,UAAA,IAAI,UAAU,CAAA,EAAG;AACf,YAAA,YAAA,CAAa,GAAQ,CAAA,GAAI,KAAA;AAAA,UAC3B;AAAA,QACF;AACA,QAAA,IAAI,MAAA,CAAO,IAAA,CAAK,YAAY,CAAA,CAAE,SAAS,CAAA,EAAG;AACxC,UAAA,KAAA,CAAM,SAAS,YAAY,CAAA;AAAA,QAC7B;AAIA,QAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,YAAA,CAAa,SAAS,CAAA;AACnD,QAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,KAAA,CAAM,SAAS,CAAA;AAGxC,QAAA,YAAA,CAAa,SAAA,CAAU,QAAQ,CAAA,IAAA,KAAQ;AACrC,UAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,IAAI,CAAA,EAAG;AACvB,YAAA,KAAA,CAAM,WAAW,IAAI,CAAA;AAAA,UACvB;AAAA,QACF,CAAC,CAAA;AAGD,QAAA,KAAA,CAAM,SAAA,CAAU,QAAQ,CAAA,IAAA,KAAQ;AAC9B,UAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,EAAG;AAC3B,YAAA,KAAA,CAAM,QAAQ,IAAI,CAAA;AAAA,UACpB;AAAA,QACF,CAAC,CAAA;AAGD,QAAA,KAAA,MAAW,GAAA,IAAO,MAAM,KAAA,EAAO;AAC7B,UAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,GAAQ,CAAA;AAC1C,UAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,GAAQ,CAAA;AACnC,UAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,YAAA,KAAA,CAAM,OAAA,CAAQ,KAAU,MAAM,CAAA;AAAA,UAChC;AAAA,QACF;AAGA,QAAA,IAAI,KAAA,CAAM,KAAA,CAAM,iBAAA,KAAsB,YAAA,CAAa,MAAM,iBAAA,EAAmB;AAC1E,UAAA,KAAA,CAAM,QAAA,CAAS,KAAA,CAAM,KAAA,CAAM,iBAAiB,CAAA;AAAA,QAC9C;AACA,QAAA,IAAI,KAAA,CAAM,KAAA,CAAM,GAAA,KAAQ,YAAA,CAAa,MAAM,GAAA,EAAK;AAC9C,UAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,GAAA,GAAM,aAAa,KAAA,CAAM,GAAA;AACtD,UAAA,KAAA,CAAM,YAAY,QAAQ,CAAA;AAAA,QAC5B;AAIA,QAAA,KAAA,CAAM,QAAA,CAAS,MAAM,KAAK,CAAA;AAAA,MAC5B;AAAA,IACF;AAyBA,IAAA,IAAI,OAAO,YAAA,EAAc;AAEvB,MAAA,MAAM,aAAA,GAAgB,EAAE,GAAG,KAAA,EAAM;AAGjC,MAAA,MAAM,YAAA,GAAe,KAAA;AAGrB,MAAA,MAAM,OAAA,GAAU;AAAA,QACd,YAAY,KAAA,CAAM,UAAA;AAAA,QAClB,UAAU,KAAA,CAAM,QAAA;AAAA,QAChB,UAAU,KAAA,CAAM,QAAA;AAAA,QAChB,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,YAAY,KAAA,CAAM,UAAA;AAAA,QAClB,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,aAAa,KAAA,CAAM,WAAA;AAAA,QACnB,UAAU,KAAA,CAAM,QAAA;AAAA,QAChB,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,cAAc,KAAA,CAAM,YAAA;AAAA,QACpB,MAAM,KAAA,CAAM;AAAA,OACd;AAGA,MAAA,MAAA,CAAO,YAAA,CAAa,YAAA,EAAc,aAAA,EAAe,OAAO,CAAA;AAAA,IAC1D;AA6BA,IAAA,MAAM,UAAA,GAAa,KAAA;AACnB,IAAA,MAAM,UAAA,GACJ,OAAO,MAAA,CAAO,UAAA,KAAe,UAAA,GACzB,OAAO,UAAA,CAAW,UAAA,EAAY,KAAK,CAAA,GACnC,MAAA,CAAO,UAAA;AAEb,IAAA,KAAA,CAAM,MAAA,CAAO,YAAY,QAAQ,CAAA;AAuBjC,IAAA,UAAA,CAAW,KAAK,YAAA,CAAa,eAAA,EAAiB,EAAE,QAAA,EAAU,MAAA,CAAO,IAAI,CAAA;AAGrE,IAAA,OAAO,IAAA;AAAA,EACT;AACF","file":"index.js","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, RequirementCheckResult } 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 * \n * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。\n * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。\n * \n * @template S - 数值属性键的联合类型\n * @template I - 物品ID的联合类型\n * @template F - 标记键的联合类型\n * \n * @param state - 当前游戏状态\n * @param reqs - 需求定义(可选,未定义时返回通过)\n * \n * @returns RequirementCheckResult 包含是否通过和失败原因\n * \n * @example\n * ```typescript\n * // 检查需求并获取失败原因\n * const result = QuerySystem.checkRequirementsWithReason(state, {\n * hasItems: ['key'],\n * stats: { strength: { min: 5 } }\n * });\n * \n * if (!result.passed) {\n * console.log(result.reason); // 显示失败原因\n * }\n * ```\n */\n static checkRequirementsWithReason<\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 ): RequirementCheckResult {\n // 如果没有定义需求,默认通过\n if (!reqs) return { passed: true };\n\n // ========== 1. 标记检查 ==========\n\n /**\n * 检查必须为true的标记\n * 使用every确保所有指定的标记都为true(AND逻辑)\n */\n if (reqs.hasFlags && reqs.hasFlags.length > 0) {\n const missingFlags = reqs.hasFlags.filter((f) => !state.flags[f]);\n if (missingFlags.length > 0) {\n return {\n passed: false,\n reason: `需要满足条件:${missingFlags.join('、')}`\n };\n }\n }\n\n /**\n * 检查必须为false的标记\n * 使用some检查是否有任何标记为true(OR逻辑)\n */\n if (reqs.noFlags && reqs.noFlags.length > 0) {\n const conflictFlags = reqs.noFlags.filter((f) => state.flags[f]);\n if (conflictFlags.length > 0) {\n return {\n passed: false,\n reason: `不能满足条件:${conflictFlags.join('、')}`\n };\n }\n }\n\n // ========== 2. 物品检查 ==========\n\n /**\n * 检查必须拥有的物品\n * 使用every确保库存中包含所有指定的物品(AND逻辑)\n */\n if (reqs.hasItems && reqs.hasItems.length > 0) {\n const missingItems = reqs.hasItems.filter((i) => !state.inventory.includes(i));\n if (missingItems.length > 0) {\n return {\n passed: false,\n reason: `缺少必需物品:${missingItems.join('、')}`\n };\n }\n }\n\n /**\n * 检查不能拥有的物品\n * 使用some检查库存中是否包含任何禁止的物品(OR逻辑)\n */\n if (reqs.noItems && reqs.noItems.length > 0) {\n const conflictItems = reqs.noItems.filter((i) => state.inventory.includes(i));\n if (conflictItems.length > 0) {\n return {\n passed: false,\n reason: `不能携带物品:${conflictItems.join('、')}`\n };\n }\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) {\n return {\n passed: false,\n reason: `${key} 不足(需要 ${cond},当前 ${currentVal})`\n };\n }\n } else {\n /**\n * 对象模式:支持区间检查\n * 例如:{ hp: { min: 10, max: 50 } } 表示hp在10-50之间\n */\n if (cond.min !== undefined && currentVal < cond.min) {\n return {\n passed: false,\n reason: `${key} 过低(需要至少 ${cond.min},当前 ${currentVal})`\n };\n }\n if (cond.max !== undefined && currentVal > cond.max) {\n return {\n passed: false,\n reason: `${key} 过高(需要最多 ${cond.max},当前 ${currentVal})`\n };\n }\n }\n }\n }\n\n // ========== 4. 自定义函数检查 ==========\n\n /**\n * 执行自定义验证函数\n * 支持返回 boolean 或 RequirementCheckResult\n */\n if (reqs.custom) {\n const customResult = reqs.custom(state);\n \n // 如果返回的是对象(RequirementCheckResult)\n if (typeof customResult === 'object') {\n if (!customResult.passed) {\n return customResult;\n }\n } \n // 如果返回的是 boolean\n else if (!customResult) {\n return {\n passed: false,\n reason: '不满足自定义条件'\n };\n }\n }\n\n // 所有检查都通过\n return { passed: true };\n }\n\n /**\n * 检查是否满足所有需求条件(简化版本)\n * \n * 这是向后兼容的简化版本,只返回 boolean。\n * 如果需要失败原因,请使用 checkRequirementsWithReason。\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 return this.checkRequirementsWithReason(state, reqs).passed;\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, EffectDef } 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 * 使用增强版本的检查方法,可以获取失败原因并显示给玩家\n */\n const reqCheck = QuerySystem.checkRequirementsWithReason(state, action.requirements);\n if (!reqCheck.passed) {\n console.warn(`条件不满足: ${action.id}`, reqCheck.reason);\n \n // 如果有失败原因,显示给玩家\n if (reqCheck.reason) {\n store.addLog(reqCheck.reason, \"warn\");\n }\n \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 /**\n * 效果执行顺序说明:\n * \n * 【阶段 1:静态效果】\n * 1. statsChange - 数值属性变更\n * 2. itemsAdd - 添加物品\n * 3. itemsRemove - 移除物品\n * 4. flagsSet - 设置标记\n * 5. flagsBatch - 批量标记操作\n * 6. teleport - 传送\n * 7. timeAdvance + onTimeAdvance - 时间推进及其钩子\n * 8. triggerEvent - 触发自定义事件\n * \n * 【阶段 2:条件效果】\n * 9. conditionalEffects - 根据阶段1后的状态决定额外效果\n * \n * 【阶段 3:自定义效果】\n * 10. custom - 修改 extra 扩展数据\n * 11. customFull - 修改完整游戏状态\n * \n * 【阶段 4:后置钩子】(在 effects 之外)\n * 12. afterEffects - 基于最终状态执行副作用\n * \n * 【阶段 5:结果文本】(在 effects 之外)\n * 13. resultText - 生成反馈文本\n */\n\n const { effects } = action;\n if (effects) {\n /**\n * 辅助函数:应用单个效果定义\n * \n * 将效果应用逻辑提取为函数,以便复用于静态效果和条件效果。\n * 此函数按固定顺序执行各种效果类型。\n */\n const applyEffectDef = (effectDef: Omit<EffectDef<S, I, F, X>, 'conditionalEffects' | 'custom' | 'customFull'>) => {\n // ===== 效果类型 1:数值属性变更 =====\n if (effectDef.statsChange) {\n const statsToChange = typeof effectDef.statsChange === 'function'\n ? effectDef.statsChange(store)\n : effectDef.statsChange;\n \n store.setStats(statsToChange);\n }\n\n // ===== 效果类型 2:添加物品 =====\n effectDef.itemsAdd?.forEach((i: I) => store.addItem(i));\n \n // ===== 效果类型 3:移除物品 =====\n effectDef.itemsRemove?.forEach((i: I) => store.removeItem(i));\n\n // ===== 效果类型 4:设置标记 =====\n if (effectDef.flagsSet) {\n const flagsToSet = typeof effectDef.flagsSet === 'function'\n ? effectDef.flagsSet(store)\n : effectDef.flagsSet;\n \n Object.entries(flagsToSet).forEach(([f, v]) => {\n store.setFlag(f as F, v as boolean);\n });\n }\n\n // ===== 效果类型 5:批量标记操作 =====\n if (effectDef.flagsBatch) {\n const batchOp = typeof effectDef.flagsBatch === 'function'\n ? effectDef.flagsBatch(store)\n : effectDef.flagsBatch;\n \n if (batchOp) {\n // 步骤1:清除标记\n if (batchOp.clear) {\n const allFlags = Object.keys(store.flags) as F[];\n let flagsToClear: string[] = [];\n\n if (batchOp.clear instanceof RegExp) {\n // 正则表达式匹配\n flagsToClear = allFlags.filter(f => batchOp.clear instanceof RegExp && batchOp.clear.test(f));\n } else if (typeof batchOp.clear === 'string') {\n // 前缀字符串匹配\n flagsToClear = allFlags.filter(f => f.startsWith(batchOp.clear as string));\n } else if (Array.isArray(batchOp.clear)) {\n // 标记列表\n flagsToClear = batchOp.clear;\n }\n\n // 清除匹配的标记(设置为 false)\n flagsToClear.forEach(f => {\n store.setFlag(f as F, false);\n });\n }\n\n // 步骤2:设置标记\n if (batchOp.set) {\n Object.entries(batchOp.set).forEach(([f, v]) => {\n store.setFlag(f as F, v as boolean);\n });\n }\n }\n }\n\n // ===== 效果类型 6:传送 =====\n if (effectDef.teleport) {\n store.teleport(effectDef.teleport);\n }\n\n // ===== 效果类型 7:时间推进 + onTimeAdvance 钩子 =====\n if (effectDef.timeAdvance !== undefined) {\n const daysToAdvance = typeof effectDef.timeAdvance === 'function'\n ? effectDef.timeAdvance(store)\n : effectDef.timeAdvance;\n \n if (daysToAdvance > 0) {\n const previousDay = store.world.day;\n \n // 推进时间\n store.advanceTime(daysToAdvance);\n \n // 如果定义了 onTimeAdvance 钩子,为每一天调用一次\n if (effectDef.onTimeAdvance) {\n const actions = {\n updateStat: store.updateStat,\n setStats: store.setStats,\n setExtra: store.setExtra,\n addItem: store.addItem,\n removeItem: store.removeItem,\n setFlag: store.setFlag,\n addLog: store.addLog,\n showToast: store.showToast,\n showModal: store.showModal,\n advanceTime: store.advanceTime,\n teleport: store.teleport,\n reset: store.reset,\n saveSnapshot: store.saveSnapshot,\n undo: store.undo,\n };\n \n // 为每一天分别调用钩子\n for (let i = 1; i <= daysToAdvance; i++) {\n const currentDay = previousDay + i;\n const dayBefore = currentDay - 1;\n \n // 获取当前最新状态\n const currentState = store;\n \n effectDef.onTimeAdvance(currentDay, dayBefore, currentState, actions);\n }\n }\n }\n }\n\n // ===== 效果类型 8:触发自定义事件 =====\n if (effectDef.triggerEvent) {\n gameEvents.emit(EngineEvents.CUSTOM, { id: effectDef.triggerEvent });\n }\n };\n\n /**\n * 【阶段 1:静态效果】\n * \n * 首先应用所有静态定义的效果。\n * 这些效果按固定顺序执行(见 applyEffectDef 函数内部)。\n */\n applyEffectDef(effects);\n\n /**\n * 【阶段 2:条件效果】\n * \n * 根据当前游戏状态(包含阶段1的修改)动态决定要应用的效果。\n * conditionalEffects 返回的效果会按阶段1的顺序再次执行。\n * \n * 使用场景:\n * - 基于修改后的属性值决定额外效果\n * - 实现\"如果...则...\"的逻辑\n * - 根据不同条件产生不同效果\n */\n if (effects.conditionalEffects) {\n // 获取当前最新状态(包含静态效果的修改)\n const currentState = store;\n \n // 执行条件函数,获取要应用的效果\n const conditionalEffect = effects.conditionalEffects(currentState);\n \n // 如果返回了效果定义,应用它\n if (conditionalEffect) {\n applyEffectDef(conditionalEffect);\n }\n }\n\n /**\n * 【阶段 3.1:自定义效果 - 修改 extra 字段】\n * \n * 在所有静态效果和条件效果之后执行。\n * 用于修改扩展数据(extra 字段)。\n * \n * 使用场景:\n * - 更新游戏特定的自定义数据\n * - 记录时间戳、计数器等元数据\n * - 存储复杂的状态信息\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 * 【阶段 3.2:完整自定义效果 - 修改完整游戏状态】\n * \n * ⚠️ 最强大的效果类型,在所有其他效果之后执行\n * \n * 允许修改完整的游戏状态(stats、inventory、flags、world、extra)。\n * 可以覆盖或调整之前所有效果的结果。\n * \n * 使用场景:\n * - 实现复杂的连锁反应\n * - 基于最终状态进行调整\n * - 需要同时修改多个状态字段的复杂逻辑\n * - 实现\"护盾吸收伤害\"等覆盖机制\n * \n * 注意:\n * - 这是最后的修改机会(除了 afterEffects)\n * - 可以访问原始状态和当前状态\n * - 批量应用所有修改以提高性能\n */\n if (effects.customFull) {\n // 保存原始状态(只读,供函数参考)\n const originalState = { ...state };\n \n // 获取当前最新状态(包含之前效果的修改)\n const currentState = store;\n \n // 创建可修改的状态副本(不包含 logs 和方法)\n const draft = {\n stats: { ...currentState.stats },\n inventory: [...currentState.inventory],\n flags: { ...currentState.flags },\n world: { ...currentState.world },\n extra: { ...currentState.extra },\n };\n \n // 执行自定义函数,允许修改 draft\n effects.customFull(draft, originalState);\n \n // 批量应用所有修改\n // 使用 setStats 而不是直接赋值,以保持增量更新的语义\n const statsChanges: Partial<Record<S, number>> = {};\n for (const key in draft.stats) {\n const oldVal = currentState.stats[key as S] || 0;\n const newVal = draft.stats[key as S];\n const delta = newVal - oldVal;\n if (delta !== 0) {\n statsChanges[key as S] = delta;\n }\n }\n if (Object.keys(statsChanges).length > 0) {\n store.setStats(statsChanges);\n }\n \n // 应用物品变更\n // 计算需要添加和移除的物品\n const currentItems = new Set(currentState.inventory);\n const newItems = new Set(draft.inventory);\n \n // 移除不在新清单中的物品\n currentState.inventory.forEach(item => {\n if (!newItems.has(item)) {\n store.removeItem(item);\n }\n });\n \n // 添加新清单中的新物品\n draft.inventory.forEach(item => {\n if (!currentItems.has(item)) {\n store.addItem(item);\n }\n });\n \n // 应用标记变更\n for (const key in draft.flags) {\n const oldVal = currentState.flags[key as F];\n const newVal = draft.flags[key as F];\n if (oldVal !== newVal) {\n store.setFlag(key as F, newVal);\n }\n }\n \n // 应用世界状态变更\n if (draft.world.currentLocationId !== currentState.world.currentLocationId) {\n store.teleport(draft.world.currentLocationId);\n }\n if (draft.world.day !== currentState.world.day) {\n const dayDelta = draft.world.day - currentState.world.day;\n store.advanceTime(dayDelta);\n }\n // 注意:time 字段目前没有专门的更新方法,如果需要可以扩展\n \n // 应用扩展数据变更\n store.setExtra(draft.extra);\n }\n }\n\n // ========== 步骤4:执行后置效果钩子 ==========\n\n /**\n * 【阶段 4:后置钩子】\n * \n * 在所有效果(阶段1-3)执行完成后调用。\n * 这是最后一个可以修改状态的机会。\n * \n * 与 customFull 的区别:\n * - customFull: 在 effects 内部,用于修改效果本身\n * - afterEffects: 在 effects 外部,用于基于最终状态执行副作用\n * \n * 使用场景:\n * - 检查成就解锁\n * - 触发连锁事件\n * - 清理过期数据\n * - 自动保存快照\n * - 发送通知\n * \n * 职责分离:\n * - afterEffects: 执行副作用(修改状态、触发事件等)\n * - resultText: 生成反馈文本(纯函数,不修改状态)\n */\n if (action.afterEffects) {\n // 保存原始状态用于对比\n const originalState = { ...state };\n \n // 获取当前最新状态(包含所有效果的修改)\n const currentState = store;\n \n // 提取操作方法(不包含状态数据)\n const actions = {\n updateStat: store.updateStat,\n setStats: store.setStats,\n setExtra: store.setExtra,\n addItem: store.addItem,\n removeItem: store.removeItem,\n setFlag: store.setFlag,\n addLog: store.addLog,\n showToast: store.showToast,\n showModal: store.showModal,\n advanceTime: store.advanceTime,\n teleport: store.teleport,\n reset: store.reset,\n saveSnapshot: store.saveSnapshot,\n undo: store.undo,\n };\n \n // 执行后置效果钩子\n action.afterEffects(currentState, originalState, actions);\n }\n\n // ========== 步骤5:生成结果文本并记录日志 ==========\n\n /**\n * 【阶段 5:结果文本】\n * \n * 在所有效果和钩子执行完成后生成反馈文本。\n * \n * resultText 可以是静态字符串或动态函数。\n * 动态函数可以访问最终状态(包含所有修改)和原始状态,\n * 因此可以比较前后变化,生成准确的反馈信息。\n * \n * ⚠️ 增强功能:函数现在接收两个参数\n * - 第一个参数:最终状态(所有效果执行后)\n * - 第二个参数:原始状态(动作执行前)\n * \n * 这允许你比较状态变化,而无需通过 afterEffects + extra 传递信息。\n * \n * 职责分离:\n * - resultText 只负责生成文本,不应修改状态\n * - 所有状态修改应在 effects 或 afterEffects 中完成\n * \n * 使用场景:\n * - 根据最终状态生成个性化反馈\n * - 显示属性变化的具体数值\n * - 比较前后状态,检测是否触发了特殊效果\n * - 提示玩家达成的成就或触发的事件\n */\n const finalState = store; // 获取最终状态\n const resultText =\n typeof action.resultText === \"function\"\n ? action.resultText(finalState, state) // 传递最终状态和原始状态\n : action.resultText;\n\n store.addLog(resultText, \"result\");\n\n // ========== 步骤6:发射动作完成事件 ==========\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"]}