steamsheep-ts-game-engine 3.1.1 → 3.1.2

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.
package/dist/index.js CHANGED
@@ -201,159 +201,6 @@ function getStateDiff(oldState, newState) {
201
201
  return diff;
202
202
  }
203
203
 
204
- // systems/events.ts
205
- var EventBus = class {
206
- constructor() {
207
- /**
208
- * 事件监听器的内部存储
209
- * 将事件名映射到监听器回调函数数组
210
- * 使用 Listener[](默认为 Listener<unknown>[])以允许同一事件名有不同类型的监听器
211
- * 类型安全在订阅/发射时强制执行
212
- */
213
- __publicField(this, "listeners", /* @__PURE__ */ new Map());
214
- }
215
- /**
216
- * 订阅事件
217
- *
218
- * @template T - 事件载荷的预期类型
219
- * @param event - 要订阅的事件名称
220
- * @param callback - 事件触发时调用的监听器函数
221
- * @returns 取消订阅的函数
222
- *
223
- * @example
224
- * ```typescript
225
- * const handler = (data: { count: number }) => console.log(data.count);
226
- * const unsubscribe = eventBus.on('update', handler);
227
- * // 稍后取消订阅
228
- * unsubscribe();
229
- * ```
230
- */
231
- on(event, callback) {
232
- if (!this.listeners.has(event)) {
233
- this.listeners.set(event, []);
234
- }
235
- this.listeners.get(event).push(callback);
236
- return () => this.off(event, callback);
237
- }
238
- /**
239
- * 取消订阅事件
240
- *
241
- * @template T - 事件载荷的预期类型
242
- * @param event - 要取消订阅的事件名称
243
- * @param callback - 要移除的监听器函数
244
- *
245
- * @example
246
- * ```typescript
247
- * const handler = (data: number) => console.log(data);
248
- * eventBus.on('count', handler);
249
- * eventBus.off('count', handler); // 移除监听器
250
- * ```
251
- */
252
- off(event, callback) {
253
- const callbacks = this.listeners.get(event);
254
- if (callbacks) {
255
- this.listeners.set(
256
- event,
257
- callbacks.filter((cb) => cb !== callback)
258
- );
259
- }
260
- }
261
- /**
262
- * 发射事件并携带可选的类型化载荷数据
263
- * 该事件的所有已注册监听器都将被调用并传入数据
264
- * 单个监听器中的错误会被捕获并记录,不会影响其他监听器
265
- *
266
- * @template T - 事件载荷的类型
267
- * @param event - 要发射的事件名称
268
- * @param data - 传递给监听器的可选载荷数据
269
- *
270
- * @example
271
- * ```typescript
272
- * eventBus.emit<{ userId: string }>('login', { userId: '123' });
273
- * eventBus.emit('logout'); // 无载荷
274
- * ```
275
- */
276
- emit(event, data) {
277
- const callbacks = this.listeners.get(event);
278
- if (callbacks) {
279
- callbacks.forEach((cb) => {
280
- try {
281
- cb(data);
282
- } catch (error) {
283
- console.error(`Error in event listener for "${event}":`, error);
284
- }
285
- });
286
- }
287
- }
288
- /**
289
- * 清空所有事件的所有监听器
290
- * 用于清理或重置事件系统
291
- *
292
- * @example
293
- * ```typescript
294
- * eventBus.clear(); // 所有监听器被移除
295
- * ```
296
- */
297
- clear() {
298
- this.listeners.clear();
299
- }
300
- };
301
- var GLOBAL_KEY = "__STEAMSHEEP_GAME_EVENTS__";
302
- function getGlobalEventBus() {
303
- const globalObj = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof self !== "undefined" ? self : {};
304
- if (!globalObj[GLOBAL_KEY]) {
305
- globalObj[GLOBAL_KEY] = new EventBus();
306
- }
307
- return globalObj[GLOBAL_KEY];
308
- }
309
- var gameEvents = getGlobalEventBus();
310
- var EngineEvents = {
311
- /** 当角色属性变化时触发 */
312
- STAT_CHANGE: "engine:stat_change",
313
- /** 当物品添加到背包时触发 */
314
- ITEM_ADD: "engine:item_add",
315
- /** 当物品从背包移除时触发 */
316
- ITEM_REMOVE: "engine:item_remove",
317
- /** 当游戏标志变化时触发 */
318
- FLAG_CHANGE: "engine:flag_change",
319
- /** 当动作执行时触发 */
320
- ACTION_EXECUTED: "engine:action_exec",
321
- /** 当游戏时间推进时触发 */
322
- TIME_PASS: "engine:time_pass",
323
- /** 当需要显示瞬时通知时触发(Toast/Modal,不持久化) */
324
- NOTIFICATION: "engine:notification",
325
- /** 用于自定义触发事件 */
326
- CUSTOM: "engine:custom_trigger"
327
- };
328
- function isEventBusSingleton() {
329
- const GLOBAL_KEY2 = "__STEAMSHEEP_GAME_EVENTS__";
330
- const globalObj = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof self !== "undefined" ? self : {};
331
- return globalObj[GLOBAL_KEY2] === gameEvents;
332
- }
333
- function getEventListenerCounts() {
334
- const counts = {};
335
- const listeners = gameEvents.listeners;
336
- listeners.forEach((callbacks, event) => {
337
- counts[event] = callbacks.length;
338
- });
339
- return counts;
340
- }
341
- function debugEventSystem() {
342
- console.log("\n========== EventBus \u8C03\u8BD5\u4FE1\u606F ==========");
343
- console.log("\u5355\u4F8B\u72B6\u6001:", isEventBusSingleton() ? "\u2713 \u6B63\u5E38" : "\u2717 \u5F02\u5E38\uFF08\u591A\u4E2A\u5B9E\u4F8B\uFF09");
344
- const counts = getEventListenerCounts();
345
- const eventCount = Object.keys(counts).length;
346
- if (eventCount === 0) {
347
- console.log("\u5DF2\u6CE8\u518C\u7684\u4E8B\u4EF6: \u65E0");
348
- } else {
349
- console.log("\u5DF2\u6CE8\u518C\u7684\u4E8B\u4EF6:");
350
- Object.entries(counts).forEach(([event, count]) => {
351
- console.log(` - ${event}: ${count} \u4E2A\u76D1\u542C\u5668`);
352
- });
353
- }
354
- console.log("=====================================\n");
355
- }
356
-
357
204
  // state/history.ts
358
205
  var HistoryManager = class {
359
206
  /**
@@ -665,346 +512,205 @@ var HistoryManager = class {
665
512
  }
666
513
  };
667
514
 
668
- // state/store.ts
669
- var createGameEngineStore = (initialState, persistName = "generic-rpg-save") => {
670
- const history = new HistoryManager(
671
- DEFAULT_CONFIG.MAX_HISTORY_SNAPSHOTS
672
- );
673
- return zustand.create()(
674
- middleware.persist(
675
- (set, get2) => ({
676
- // 展开初始状态,包含所有游戏数据
677
- ...initialState,
678
- // ========== 数值属性操作实现 ==========
679
- /**
680
- * 实现:更新单个数值属性
681
- * 使用增量更新,保持其他属性不变
682
- * 发射 STAT_CHANGE 事件通知监听器
683
- */
684
- updateStat: (stat, delta) => set((state) => {
685
- const currentVal = state.stats[stat] || 0;
686
- const newVal = currentVal + delta;
687
- gameEvents.emit(EngineEvents.STAT_CHANGE, {
688
- stat,
689
- delta,
690
- current: newVal
691
- });
692
- return {
693
- stats: {
694
- ...state.stats,
695
- [stat]: newVal
696
- }
697
- };
698
- }),
699
- /**
700
- * 实现:批量更新多个数值属性
701
- * 遍历所有变化并应用到新的stats对象
702
- */
703
- setStats: (changes) => set((state) => {
704
- const newStats = { ...state.stats };
705
- Object.entries(changes).forEach(([k, v]) => {
706
- newStats[k] = (newStats[k] || 0) + v;
707
- });
708
- return { stats: newStats };
709
- }),
710
- // ========== 扩展数据操作实现 ==========
711
- /**
712
- * 实现:更新扩展数据
713
- *
714
- * 支持两种更新方式:
715
- * 1. 直接传入部分更新对象
716
- * 2. 传入更新函数,接收当前值返回部分更新
717
- *
718
- * @example
719
- * ```typescript
720
- * // 方式1:直接更新
721
- * setExtra({ reputation: 100 });
722
- *
723
- * // 方式2:基于当前值更新
724
- * setExtra((prev) => ({ reputation: prev.reputation + 10 }));
725
- * ```
726
- */
727
- setExtra: (updater) => set((state) => {
728
- const update = typeof updater === "function" ? updater(state.extra) : updater;
729
- return {
730
- extra: { ...state.extra, ...update }
731
- };
732
- }),
733
- // ========== 物品操作实现 ==========
734
- /**
735
- * 实现:添加物品到库存
736
- * 创建新数组,保持不可变性
737
- * 发射 ITEM_ADD 事件通知监听器
738
- */
739
- addItem: (item) => set((state) => {
740
- gameEvents.emit(EngineEvents.ITEM_ADD, { item });
741
- return {
742
- inventory: [...state.inventory, item]
743
- };
744
- }),
745
- /**
746
- * 实现:从库存移除物品
747
- * 使用filter移除第一个匹配的物品
748
- * 发射 ITEM_REMOVE 事件通知监听器
749
- */
750
- removeItem: (item) => set((state) => {
751
- gameEvents.emit(EngineEvents.ITEM_REMOVE, { item });
752
- return {
753
- inventory: state.inventory.filter((i) => i !== item)
754
- };
755
- }),
756
- // ========== 标记操作实现 ==========
757
- /**
758
- * 实现:设置布尔标记
759
- * 创建新的flags对象,更新指定标记
760
- * 发射 FLAG_CHANGE 事件通知监听器
761
- */
762
- setFlag: (flag, value) => set((state) => {
763
- gameEvents.emit(EngineEvents.FLAG_CHANGE, { flag, value });
764
- return {
765
- flags: { ...state.flags, [flag]: value }
766
- };
767
- }),
768
- // ========== 系统操作实现 ==========
769
- /**
770
- * 实现:添加日志条目
771
- *
772
- * 创建新日志并添加到日志数组开头。
773
- * 使用 MAX_LOG_ENTRIES 限制日志数量,防止内存泄漏。
774
- * 日志ID使用时间戳+随机数确保唯一性。
775
- *
776
- * ⚠️ 注意:日志会被持久化到 localStorage
777
- */
778
- addLog: (text, type = "info") => set((state) => {
779
- const newLog = {
780
- id: generateId(),
781
- text,
782
- type,
783
- timestamp: state.world.day
784
- };
785
- return {
786
- logs: [newLog, ...state.logs].slice(0, DEFAULT_CONFIG.MAX_LOG_ENTRIES)
787
- };
788
- }),
789
- /**
790
- * 实现:显示飘字通知
791
- *
792
- * 发射 NOTIFICATION 事件,UI 层监听后显示飘字效果。
793
- *
794
- * ⚠️ 注意:通知不会被持久化,仅在当前会话中显示
795
- */
796
- showToast: (text, type = "info") => {
797
- const notification = {
798
- text,
799
- type,
800
- notificationType: "toast",
801
- timestamp: Date.now()
802
- };
803
- gameEvents.emit(EngineEvents.NOTIFICATION, notification);
804
- },
805
- /**
806
- * 实现:显示弹窗通知
807
- *
808
- * 发射 NOTIFICATION 事件,UI 层监听后显示模态弹窗。
809
- *
810
- * ⚠️ 注意:通知不会被持久化,仅在当前会话中显示
811
- */
812
- showModal: (text, type = "info") => {
813
- const notification = {
814
- text,
815
- type,
816
- notificationType: "modal",
817
- timestamp: Date.now()
818
- };
819
- gameEvents.emit(EngineEvents.NOTIFICATION, notification);
820
- },
821
- /**
822
- * 实现:推进游戏时间
823
- * 增加world.day的值
824
- */
825
- advanceTime: (amount = 1) => set((state) => ({
826
- world: { ...state.world, day: state.world.day + amount }
827
- })),
828
- /**
829
- * 实现:传送到指定地点
830
- * 更新world.currentLocationId
831
- */
832
- teleport: (locationId) => set((state) => ({
833
- world: { ...state.world, currentLocationId: locationId }
834
- })),
835
- // ========== 存档操作实现 ==========
836
- /**
837
- * 实现:重置游戏状态
838
- * 恢复到初始状态,清空日志
839
- */
840
- reset: () => set({ ...initialState, logs: [] }),
841
- // ========== 历史记录操作实现 ==========
842
- /**
843
- * 实现:保存当前状态快照(匿名快照)
844
- *
845
- * 保存除日志外的所有状态数据到历史管理器。
846
- * 日志通常不需要回退,因此被排除以节省内存。
847
- */
848
- saveSnapshot: (description) => {
849
- const currentState = get2();
850
- history.push(currentState, description);
851
- },
852
- /**
853
- * 实现:保存命名快照
854
- *
855
- * 保存可以通过名称访问的快照。
856
- * 命名快照不受数量限制,适用于重要的游戏节点。
857
- */
858
- saveNamedSnapshot: (name, description) => {
859
- const currentState = get2();
860
- history.pushNamed(currentState, name, description);
861
- },
862
- /**
863
- * 实现:撤销到上一个状态
864
- *
865
- * 从历史管理器中恢复上一个匿名快照。
866
- * 恢复时保留当前的日志,并添加系统提示。
867
- *
868
- * @returns 是否成功撤销
869
- */
870
- undo: () => {
871
- const prev = history.pop();
872
- if (prev) {
873
- set({
874
- stats: prev.stats,
875
- inventory: prev.inventory,
876
- flags: prev.flags,
877
- world: prev.world,
878
- extra: prev.extra
879
- // logs 保留当前的,不回退日志
880
- });
881
- get2().addLog(SystemMessages.UNDO_SUCCESS, "info");
882
- return true;
883
- }
884
- return false;
885
- },
886
- /**
887
- * 实现:恢复到命名快照
888
- *
889
- * 通过名称恢复到指定的快照。
890
- * 快照不会被删除,可以多次恢复。
891
- *
892
- * @returns 是否成功恢复
893
- */
894
- restoreSnapshot: (name) => {
895
- const saved = history.restoreNamed(name);
896
- if (saved) {
897
- set({
898
- stats: saved.stats,
899
- inventory: saved.inventory,
900
- flags: saved.flags,
901
- world: saved.world,
902
- extra: saved.extra
903
- // logs 保留当前的,不回退日志
904
- });
905
- get2().addLog(`\u5DF2\u6062\u590D\u5230\u5FEB\u7167\uFF1A${name}`, "info");
906
- return true;
907
- }
908
- return false;
909
- },
910
- /**
911
- * 实现:删除命名快照
912
- *
913
- * 从历史记录中删除指定名称的快照。
914
- *
915
- * @returns 是否成功删除
916
- */
917
- deleteSnapshot: (name) => {
918
- return history.deleteNamed(name);
919
- },
920
- /**
921
- * 实现:列出所有快照
922
- *
923
- * 返回所有快照的元数据列表。
924
- */
925
- listSnapshots: () => {
926
- return history.listSnapshots();
927
- },
928
- /**
929
- * 实现:检查命名快照是否存在
930
- *
931
- * @returns 是否存在指定名称的快照
932
- */
933
- hasSnapshot: (name) => {
934
- return history.hasNamed(name);
935
- }
936
- }),
937
- {
938
- // 持久化配置
939
- name: persistName,
940
- // localStorage中的键名
941
- storage: middleware.createJSONStorage(() => localStorage),
942
- // 使用localStorage存储
943
- /**
944
- * 部分持久化 - 只保存数据字段,排除方法
945
- *
946
- * 这很重要,因为方法不能被序列化到JSON。
947
- * 我们只持久化游戏状态数据,方法会在store重新创建时自动添加。
948
- */
949
- partialize: (state) => ({
950
- stats: state.stats,
951
- inventory: state.inventory,
952
- flags: state.flags,
953
- world: state.world,
954
- logs: state.logs,
955
- extra: state.extra
956
- })
957
- }
958
- )
959
- );
960
- };
961
-
962
- // systems/query.ts
963
- var QuerySystem = class {
515
+ // systems/events.ts
516
+ var EventBus = class {
517
+ constructor() {
518
+ /**
519
+ * 事件监听器的内部存储
520
+ * 将事件名映射到监听器回调函数数组
521
+ * 使用 Listener[](默认为 Listener<unknown>[])以允许同一事件名有不同类型的监听器
522
+ * 类型安全在订阅/发射时强制执行
523
+ */
524
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
525
+ }
964
526
  /**
965
- * 检查是否满足所有需求条件(带失败原因)
966
- *
967
- * ⚠️ 增强版本:返回详细的检查结果,包括失败原因
968
- *
969
- * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。
970
- * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。
971
- *
972
- * @template S - 数值属性键的联合类型
973
- * @template I - 物品ID的联合类型
974
- * @template F - 标记键的联合类型
975
- *
976
- * @param state - 当前游戏状态
977
- * @param reqs - 需求定义(可选,未定义时返回通过)
978
- *
979
- * @returns RequirementCheckResult 包含是否通过和失败原因
980
- *
527
+ * 订阅事件
528
+ *
529
+ * @template T - 事件载荷的预期类型
530
+ * @param event - 要订阅的事件名称
531
+ * @param callback - 事件触发时调用的监听器函数
532
+ * @returns 取消订阅的函数
533
+ *
981
534
  * @example
982
535
  * ```typescript
983
- * // 检查需求并获取失败原因
984
- * const result = QuerySystem.checkRequirementsWithReason(state, {
985
- * hasItems: ['key'],
986
- * stats: { strength: { min: 5 } }
987
- * });
988
- *
989
- * if (!result.passed) {
990
- * console.log(result.reason); // 显示失败原因
991
- * }
536
+ * const handler = (data: { count: number }) => console.log(data.count);
537
+ * const unsubscribe = eventBus.on('update', handler);
538
+ * // 稍后取消订阅
539
+ * unsubscribe();
992
540
  * ```
993
541
  */
994
- static checkRequirementsWithReason(state, reqs) {
995
- if (!reqs) return { passed: true };
996
- if (reqs.hasFlags && reqs.hasFlags.length > 0) {
997
- const missingFlags = reqs.hasFlags.filter((f) => !state.flags[f]);
998
- if (missingFlags.length > 0) {
999
- return {
1000
- passed: false,
1001
- reason: `\u9700\u8981\u6EE1\u8DB3\u6761\u4EF6\uFF1A${missingFlags.join("\u3001")}`
1002
- };
1003
- }
542
+ on(event, callback) {
543
+ if (!this.listeners.has(event)) {
544
+ this.listeners.set(event, []);
1004
545
  }
1005
- if (reqs.noFlags && reqs.noFlags.length > 0) {
1006
- const conflictFlags = reqs.noFlags.filter((f) => state.flags[f]);
1007
- if (conflictFlags.length > 0) {
546
+ this.listeners.get(event).push(callback);
547
+ return () => this.off(event, callback);
548
+ }
549
+ /**
550
+ * 取消订阅事件
551
+ *
552
+ * @template T - 事件载荷的预期类型
553
+ * @param event - 要取消订阅的事件名称
554
+ * @param callback - 要移除的监听器函数
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * const handler = (data: number) => console.log(data);
559
+ * eventBus.on('count', handler);
560
+ * eventBus.off('count', handler); // 移除监听器
561
+ * ```
562
+ */
563
+ off(event, callback) {
564
+ const callbacks = this.listeners.get(event);
565
+ if (callbacks) {
566
+ this.listeners.set(
567
+ event,
568
+ callbacks.filter((cb) => cb !== callback)
569
+ );
570
+ }
571
+ }
572
+ /**
573
+ * 发射事件并携带可选的类型化载荷数据
574
+ * 该事件的所有已注册监听器都将被调用并传入数据
575
+ * 单个监听器中的错误会被捕获并记录,不会影响其他监听器
576
+ *
577
+ * @template T - 事件载荷的类型
578
+ * @param event - 要发射的事件名称
579
+ * @param data - 传递给监听器的可选载荷数据
580
+ *
581
+ * @example
582
+ * ```typescript
583
+ * eventBus.emit<{ userId: string }>('login', { userId: '123' });
584
+ * eventBus.emit('logout'); // 无载荷
585
+ * ```
586
+ */
587
+ emit(event, data) {
588
+ const callbacks = this.listeners.get(event);
589
+ if (callbacks) {
590
+ callbacks.forEach((cb) => {
591
+ try {
592
+ cb(data);
593
+ } catch (error) {
594
+ console.error(`Error in event listener for "${event}":`, error);
595
+ }
596
+ });
597
+ }
598
+ }
599
+ /**
600
+ * 清空所有事件的所有监听器
601
+ * 用于清理或重置事件系统
602
+ *
603
+ * @example
604
+ * ```typescript
605
+ * eventBus.clear(); // 所有监听器被移除
606
+ * ```
607
+ */
608
+ clear() {
609
+ this.listeners.clear();
610
+ }
611
+ };
612
+ var GLOBAL_KEY = "__STEAMSHEEP_GAME_EVENTS__";
613
+ function getGlobalEventBus() {
614
+ const globalObj = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof self !== "undefined" ? self : {};
615
+ if (!globalObj[GLOBAL_KEY]) {
616
+ globalObj[GLOBAL_KEY] = new EventBus();
617
+ }
618
+ return globalObj[GLOBAL_KEY];
619
+ }
620
+ var gameEvents = getGlobalEventBus();
621
+ var EngineEvents = {
622
+ /** 当角色属性变化时触发 */
623
+ STAT_CHANGE: "engine:stat_change",
624
+ /** 当物品添加到背包时触发 */
625
+ ITEM_ADD: "engine:item_add",
626
+ /** 当物品从背包移除时触发 */
627
+ ITEM_REMOVE: "engine:item_remove",
628
+ /** 当游戏标志变化时触发 */
629
+ FLAG_CHANGE: "engine:flag_change",
630
+ /** 当动作执行时触发 */
631
+ ACTION_EXECUTED: "engine:action_exec",
632
+ /** 当游戏时间推进时触发 */
633
+ TIME_PASS: "engine:time_pass",
634
+ /** 当需要显示瞬时通知时触发(Toast/Modal,不持久化) */
635
+ NOTIFICATION: "engine:notification",
636
+ /** 用于自定义触发事件 */
637
+ CUSTOM: "engine:custom_trigger"
638
+ };
639
+ function isEventBusSingleton() {
640
+ const GLOBAL_KEY2 = "__STEAMSHEEP_GAME_EVENTS__";
641
+ const globalObj = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof self !== "undefined" ? self : {};
642
+ return globalObj[GLOBAL_KEY2] === gameEvents;
643
+ }
644
+ function getEventListenerCounts() {
645
+ const counts = {};
646
+ const listeners = gameEvents.listeners;
647
+ listeners.forEach((callbacks, event) => {
648
+ counts[event] = callbacks.length;
649
+ });
650
+ return counts;
651
+ }
652
+ function debugEventSystem() {
653
+ console.log("\n========== EventBus \u8C03\u8BD5\u4FE1\u606F ==========");
654
+ console.log("\u5355\u4F8B\u72B6\u6001:", isEventBusSingleton() ? "\u2713 \u6B63\u5E38" : "\u2717 \u5F02\u5E38\uFF08\u591A\u4E2A\u5B9E\u4F8B\uFF09");
655
+ const counts = getEventListenerCounts();
656
+ const eventCount = Object.keys(counts).length;
657
+ if (eventCount === 0) {
658
+ console.log("\u5DF2\u6CE8\u518C\u7684\u4E8B\u4EF6: \u65E0");
659
+ } else {
660
+ console.log("\u5DF2\u6CE8\u518C\u7684\u4E8B\u4EF6:");
661
+ Object.entries(counts).forEach(([event, count]) => {
662
+ console.log(` - ${event}: ${count} \u4E2A\u76D1\u542C\u5668`);
663
+ });
664
+ }
665
+ console.log("=====================================\n");
666
+ }
667
+
668
+ // systems/query.ts
669
+ var QuerySystem = class {
670
+ /**
671
+ * 检查是否满足所有需求条件(带失败原因)
672
+ *
673
+ * ⚠️ 增强版本:返回详细的检查结果,包括失败原因
674
+ *
675
+ * 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。
676
+ * 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。
677
+ *
678
+ * @template S - 数值属性键的联合类型
679
+ * @template I - 物品ID的联合类型
680
+ * @template F - 标记键的联合类型
681
+ *
682
+ * @param state - 当前游戏状态
683
+ * @param reqs - 需求定义(可选,未定义时返回通过)
684
+ *
685
+ * @returns RequirementCheckResult 包含是否通过和失败原因
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * // 检查需求并获取失败原因
690
+ * const result = QuerySystem.checkRequirementsWithReason(state, {
691
+ * hasItems: ['key'],
692
+ * stats: { strength: { min: 5 } }
693
+ * });
694
+ *
695
+ * if (!result.passed) {
696
+ * console.log(result.reason); // 显示失败原因
697
+ * }
698
+ * ```
699
+ */
700
+ static checkRequirementsWithReason(state, reqs) {
701
+ if (!reqs) return { passed: true };
702
+ if (reqs.hasFlags && reqs.hasFlags.length > 0) {
703
+ const missingFlags = reqs.hasFlags.filter((f) => !state.flags[f]);
704
+ if (missingFlags.length > 0) {
705
+ return {
706
+ passed: false,
707
+ reason: `\u9700\u8981\u6EE1\u8DB3\u6761\u4EF6\uFF1A${missingFlags.join("\u3001")}`
708
+ };
709
+ }
710
+ }
711
+ if (reqs.noFlags && reqs.noFlags.length > 0) {
712
+ const conflictFlags = reqs.noFlags.filter((f) => state.flags[f]);
713
+ if (conflictFlags.length > 0) {
1008
714
  return {
1009
715
  passed: false,
1010
716
  reason: `\u4E0D\u80FD\u6EE1\u8DB3\u6761\u4EF6\uFF1A${conflictFlags.join("\u3001")}`
@@ -1397,6 +1103,300 @@ var FlowSystem = class {
1397
1103
  return true;
1398
1104
  }
1399
1105
  };
1106
+
1107
+ // state/store.ts
1108
+ var createGameEngineStore = (initialState, persistName = "generic-rpg-save") => {
1109
+ const history = new HistoryManager(
1110
+ DEFAULT_CONFIG.MAX_HISTORY_SNAPSHOTS
1111
+ );
1112
+ return zustand.create()(
1113
+ middleware.persist(
1114
+ (set, get2) => ({
1115
+ // 展开初始状态,包含所有游戏数据
1116
+ ...initialState,
1117
+ // ========== 数值属性操作实现 ==========
1118
+ /**
1119
+ * 实现:更新单个数值属性
1120
+ * 使用增量更新,保持其他属性不变
1121
+ * 发射 STAT_CHANGE 事件通知监听器
1122
+ */
1123
+ updateStat: (stat, delta) => set((state) => {
1124
+ const currentVal = state.stats[stat] || 0;
1125
+ const newVal = currentVal + delta;
1126
+ gameEvents.emit(EngineEvents.STAT_CHANGE, {
1127
+ stat,
1128
+ delta,
1129
+ current: newVal
1130
+ });
1131
+ return {
1132
+ stats: {
1133
+ ...state.stats,
1134
+ [stat]: newVal
1135
+ }
1136
+ };
1137
+ }),
1138
+ /**
1139
+ * 实现:批量更新多个数值属性
1140
+ * 遍历所有变化并应用到新的stats对象
1141
+ */
1142
+ setStats: (changes) => set((state) => {
1143
+ const newStats = { ...state.stats };
1144
+ Object.entries(changes).forEach(([k, v]) => {
1145
+ newStats[k] = (newStats[k] || 0) + v;
1146
+ });
1147
+ return { stats: newStats };
1148
+ }),
1149
+ // ========== 扩展数据操作实现 ==========
1150
+ /**
1151
+ * 实现:更新扩展数据
1152
+ *
1153
+ * 支持两种更新方式:
1154
+ * 1. 直接传入部分更新对象
1155
+ * 2. 传入更新函数,接收当前值返回部分更新
1156
+ *
1157
+ * @example
1158
+ * ```typescript
1159
+ * // 方式1:直接更新
1160
+ * setExtra({ reputation: 100 });
1161
+ *
1162
+ * // 方式2:基于当前值更新
1163
+ * setExtra((prev) => ({ reputation: prev.reputation + 10 }));
1164
+ * ```
1165
+ */
1166
+ setExtra: (updater) => set((state) => {
1167
+ const update = typeof updater === "function" ? updater(state.extra) : updater;
1168
+ return {
1169
+ extra: { ...state.extra, ...update }
1170
+ };
1171
+ }),
1172
+ // ========== 物品操作实现 ==========
1173
+ /**
1174
+ * 实现:添加物品到库存
1175
+ * 创建新数组,保持不可变性
1176
+ * 发射 ITEM_ADD 事件通知监听器
1177
+ */
1178
+ addItem: (item) => set((state) => {
1179
+ gameEvents.emit(EngineEvents.ITEM_ADD, { item });
1180
+ return {
1181
+ inventory: [...state.inventory, item]
1182
+ };
1183
+ }),
1184
+ /**
1185
+ * 实现:从库存移除物品
1186
+ * 使用filter移除第一个匹配的物品
1187
+ * 发射 ITEM_REMOVE 事件通知监听器
1188
+ */
1189
+ removeItem: (item) => set((state) => {
1190
+ gameEvents.emit(EngineEvents.ITEM_REMOVE, { item });
1191
+ return {
1192
+ inventory: state.inventory.filter((i) => i !== item)
1193
+ };
1194
+ }),
1195
+ // ========== 标记操作实现 ==========
1196
+ /**
1197
+ * 实现:设置布尔标记
1198
+ * 创建新的flags对象,更新指定标记
1199
+ * 发射 FLAG_CHANGE 事件通知监听器
1200
+ */
1201
+ setFlag: (flag, value) => set((state) => {
1202
+ gameEvents.emit(EngineEvents.FLAG_CHANGE, { flag, value });
1203
+ return {
1204
+ flags: { ...state.flags, [flag]: value }
1205
+ };
1206
+ }),
1207
+ // ========== 系统操作实现 ==========
1208
+ /**
1209
+ * 实现:添加日志条目
1210
+ *
1211
+ * 创建新日志并添加到日志数组开头。
1212
+ * 使用 MAX_LOG_ENTRIES 限制日志数量,防止内存泄漏。
1213
+ * 日志ID使用时间戳+随机数确保唯一性。
1214
+ *
1215
+ * ⚠️ 注意:日志会被持久化到 localStorage
1216
+ */
1217
+ addLog: (text, type = "info") => set((state) => {
1218
+ const newLog = {
1219
+ id: generateId(),
1220
+ text,
1221
+ type,
1222
+ timestamp: state.world.day
1223
+ };
1224
+ return {
1225
+ logs: [newLog, ...state.logs].slice(0, DEFAULT_CONFIG.MAX_LOG_ENTRIES)
1226
+ };
1227
+ }),
1228
+ /**
1229
+ * 实现:显示飘字通知
1230
+ *
1231
+ * 发射 NOTIFICATION 事件,UI 层监听后显示飘字效果。
1232
+ *
1233
+ * ⚠️ 注意:通知不会被持久化,仅在当前会话中显示
1234
+ */
1235
+ showToast: (text, type = "info") => {
1236
+ const notification = {
1237
+ text,
1238
+ type,
1239
+ notificationType: "toast",
1240
+ timestamp: Date.now()
1241
+ };
1242
+ gameEvents.emit(EngineEvents.NOTIFICATION, notification);
1243
+ },
1244
+ /**
1245
+ * 实现:显示弹窗通知
1246
+ *
1247
+ * 发射 NOTIFICATION 事件,UI 层监听后显示模态弹窗。
1248
+ *
1249
+ * ⚠️ 注意:通知不会被持久化,仅在当前会话中显示
1250
+ */
1251
+ showModal: (text, type = "info") => {
1252
+ const notification = {
1253
+ text,
1254
+ type,
1255
+ notificationType: "modal",
1256
+ timestamp: Date.now()
1257
+ };
1258
+ gameEvents.emit(EngineEvents.NOTIFICATION, notification);
1259
+ },
1260
+ /**
1261
+ * 实现:推进游戏时间
1262
+ * 增加world.day的值
1263
+ */
1264
+ advanceTime: (amount = 1) => set((state) => ({
1265
+ world: { ...state.world, day: state.world.day + amount }
1266
+ })),
1267
+ /**
1268
+ * 实现:传送到指定地点
1269
+ * 更新world.currentLocationId
1270
+ */
1271
+ teleport: (locationId) => set((state) => ({
1272
+ world: { ...state.world, currentLocationId: locationId }
1273
+ })),
1274
+ // ========== 存档操作实现 ==========
1275
+ /**
1276
+ * 实现:重置游戏状态
1277
+ * 恢复到初始状态,清空日志
1278
+ */
1279
+ reset: () => set({ ...initialState, logs: [] }),
1280
+ // ========== 历史记录操作实现 ==========
1281
+ /**
1282
+ * 实现:保存当前状态快照(匿名快照)
1283
+ *
1284
+ * 保存除日志外的所有状态数据到历史管理器。
1285
+ * 日志通常不需要回退,因此被排除以节省内存。
1286
+ */
1287
+ saveSnapshot: (description) => {
1288
+ const currentState = get2();
1289
+ history.push(currentState, description);
1290
+ },
1291
+ /**
1292
+ * 实现:保存命名快照
1293
+ *
1294
+ * 保存可以通过名称访问的快照。
1295
+ * 命名快照不受数量限制,适用于重要的游戏节点。
1296
+ */
1297
+ saveNamedSnapshot: (name, description) => {
1298
+ const currentState = get2();
1299
+ history.pushNamed(currentState, name, description);
1300
+ },
1301
+ /**
1302
+ * 实现:撤销到上一个状态
1303
+ *
1304
+ * 从历史管理器中恢复上一个匿名快照。
1305
+ * 恢复时保留当前的日志,并添加系统提示。
1306
+ *
1307
+ * @returns 是否成功撤销
1308
+ */
1309
+ undo: () => {
1310
+ const prev = history.pop();
1311
+ if (prev) {
1312
+ set({
1313
+ stats: prev.stats,
1314
+ inventory: prev.inventory,
1315
+ flags: prev.flags,
1316
+ world: prev.world,
1317
+ extra: prev.extra
1318
+ // logs 保留当前的,不回退日志
1319
+ });
1320
+ get2().addLog(SystemMessages.UNDO_SUCCESS, "info");
1321
+ return true;
1322
+ }
1323
+ return false;
1324
+ },
1325
+ /**
1326
+ * 实现:恢复到命名快照
1327
+ *
1328
+ * 通过名称恢复到指定的快照。
1329
+ * 快照不会被删除,可以多次恢复。
1330
+ *
1331
+ * @returns 是否成功恢复
1332
+ */
1333
+ restoreSnapshot: (name) => {
1334
+ const saved = history.restoreNamed(name);
1335
+ if (saved) {
1336
+ set({
1337
+ stats: saved.stats,
1338
+ inventory: saved.inventory,
1339
+ flags: saved.flags,
1340
+ world: saved.world,
1341
+ extra: saved.extra
1342
+ // logs 保留当前的,不回退日志
1343
+ });
1344
+ get2().addLog(`\u5DF2\u6062\u590D\u5230\u5FEB\u7167\uFF1A${name}`, "info");
1345
+ return true;
1346
+ }
1347
+ return false;
1348
+ },
1349
+ /**
1350
+ * 实现:删除命名快照
1351
+ *
1352
+ * 从历史记录中删除指定名称的快照。
1353
+ *
1354
+ * @returns 是否成功删除
1355
+ */
1356
+ deleteSnapshot: (name) => {
1357
+ return history.deleteNamed(name);
1358
+ },
1359
+ /**
1360
+ * 实现:列出所有快照
1361
+ *
1362
+ * 返回所有快照的元数据列表。
1363
+ */
1364
+ listSnapshots: () => {
1365
+ return history.listSnapshots();
1366
+ },
1367
+ /**
1368
+ * 实现:检查命名快照是否存在
1369
+ *
1370
+ * @returns 是否存在指定名称的快照
1371
+ */
1372
+ hasSnapshot: (name) => {
1373
+ return history.hasNamed(name);
1374
+ }
1375
+ }),
1376
+ {
1377
+ // 持久化配置
1378
+ name: persistName,
1379
+ // localStorage中的键名
1380
+ storage: middleware.createJSONStorage(() => localStorage),
1381
+ // 使用localStorage存储
1382
+ /**
1383
+ * 部分持久化 - 只保存数据字段,排除方法
1384
+ *
1385
+ * 这很重要,因为方法不能被序列化到JSON。
1386
+ * 我们只持久化游戏状态数据,方法会在store重新创建时自动添加。
1387
+ */
1388
+ partialize: (state) => ({
1389
+ stats: state.stats,
1390
+ inventory: state.inventory,
1391
+ flags: state.flags,
1392
+ world: state.world,
1393
+ logs: state.logs,
1394
+ extra: state.extra
1395
+ })
1396
+ }
1397
+ )
1398
+ );
1399
+ };
1400
1400
  var Layout = ({
1401
1401
  sidebar,
1402
1402
  main,