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 +487 -487
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +487 -487
- package/dist/index.mjs.map +1 -1
- package/dist/state/index.js +118 -118
- package/dist/state/index.js.map +1 -1
- package/dist/state/index.mjs +118 -118
- package/dist/state/index.mjs.map +1 -1
- package/dist/systems/index.js.map +1 -1
- package/dist/systems/index.mjs.map +1 -1
- package/dist/ui/index.js +6 -6
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +6 -6
- package/dist/ui/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
//
|
|
669
|
-
var
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
*
|
|
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
|
|
985
|
-
*
|
|
986
|
-
*
|
|
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
|
-
|
|
995
|
-
if (!
|
|
996
|
-
|
|
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
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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,
|