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.
- package/README.md +427 -962
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +562 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +562 -68
- package/dist/index.mjs.map +1 -1
- package/dist/state/index.d.mts +204 -21
- package/dist/state/index.d.ts +204 -21
- package/dist/state/index.js +290 -23
- package/dist/state/index.js.map +1 -1
- package/dist/state/index.mjs +290 -23
- package/dist/state/index.mjs.map +1 -1
- package/dist/{store-xBiJ2MvB.d.mts → store-5-3GQpi9.d.mts} +100 -5
- package/dist/{store-D0SE7zJK.d.ts → store-PPh__zkF.d.ts} +100 -5
- package/dist/systems/index.d.mts +34 -3
- package/dist/systems/index.d.ts +34 -3
- package/dist/systems/index.js +272 -45
- package/dist/systems/index.js.map +1 -1
- package/dist/systems/index.mjs +272 -45
- package/dist/systems/index.mjs.map +1 -1
- package/dist/types-D-nDlnv3.d.mts +1650 -0
- package/dist/types-D-nDlnv3.d.ts +1650 -0
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/types-BLjkeE3R.d.mts +0 -536
- package/dist/types-BLjkeE3R.d.ts +0 -536
package/dist/index.mjs
CHANGED
|
@@ -323,37 +323,85 @@ var HistoryManager = class {
|
|
|
323
323
|
* @param maxSnapshots - 最大保存的快照数量,默认使用配置中的值
|
|
324
324
|
*/
|
|
325
325
|
constructor(maxSnapshots = DEFAULT_CONFIG.MAX_HISTORY_SNAPSHOTS) {
|
|
326
|
-
/**
|
|
326
|
+
/** 存储的状态快照数组(按时间顺序) */
|
|
327
327
|
__publicField(this, "snapshots", []);
|
|
328
|
-
/**
|
|
328
|
+
/** 命名快照映射表(name -> snapshot) */
|
|
329
|
+
__publicField(this, "namedSnapshots", /* @__PURE__ */ new Map());
|
|
330
|
+
/** 最大快照数量限制(不包括命名快照) */
|
|
329
331
|
__publicField(this, "maxSnapshots");
|
|
330
332
|
this.maxSnapshots = maxSnapshots;
|
|
331
333
|
}
|
|
332
334
|
/**
|
|
333
|
-
*
|
|
335
|
+
* 保存当前状态快照(匿名快照)
|
|
334
336
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
+
* 通过深拷贝保存状态,防止后续修改影响历史记录。
|
|
338
|
+
* 当快照数量超过最大限制时,自动移除最旧的快照。
|
|
339
|
+
*
|
|
340
|
+
* 匿名快照用于常规的撤销操作,会受到数量限制。
|
|
337
341
|
*
|
|
338
342
|
* @param state - 要保存的游戏状态
|
|
343
|
+
* @param description - 快照描述(可选)
|
|
339
344
|
*
|
|
340
345
|
* @example
|
|
341
346
|
* ```typescript
|
|
342
347
|
* history.push(gameState);
|
|
348
|
+
* history.push(gameState, '攻击哥布林前');
|
|
343
349
|
* ```
|
|
344
350
|
*/
|
|
345
|
-
push(state) {
|
|
346
|
-
const
|
|
351
|
+
push(state, description) {
|
|
352
|
+
const stateCopy = JSON.parse(JSON.stringify(state));
|
|
353
|
+
const snapshot = {
|
|
354
|
+
id: `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
355
|
+
timestamp: Date.now(),
|
|
356
|
+
description,
|
|
357
|
+
state: stateCopy
|
|
358
|
+
};
|
|
347
359
|
this.snapshots.push(snapshot);
|
|
348
360
|
if (this.snapshots.length > this.maxSnapshots) {
|
|
349
361
|
this.snapshots.shift();
|
|
350
362
|
}
|
|
351
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* 保存命名快照
|
|
366
|
+
*
|
|
367
|
+
* ⚠️ 新功能:保存可以通过名称访问的快照
|
|
368
|
+
*
|
|
369
|
+
* 命名快照不受数量限制,不会被自动清理。
|
|
370
|
+
* 适用于重要的游戏节点,如Boss战前、章节开始等。
|
|
371
|
+
*
|
|
372
|
+
* 如果已存在同名快照,会被覆盖。
|
|
373
|
+
*
|
|
374
|
+
* @param state - 要保存的游戏状态
|
|
375
|
+
* @param name - 快照名称(唯一标识符)
|
|
376
|
+
* @param description - 快照描述(可选)
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* // 保存Boss战前的状态
|
|
381
|
+
* history.pushNamed(gameState, 'before_boss_fight', '挑战最终Boss前');
|
|
382
|
+
*
|
|
383
|
+
* // 保存章节开始的状态
|
|
384
|
+
* history.pushNamed(gameState, 'chapter_2_start', '第二章开始');
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
pushNamed(state, name, description) {
|
|
388
|
+
const stateCopy = JSON.parse(JSON.stringify(state));
|
|
389
|
+
const snapshot = {
|
|
390
|
+
id: `named_${name}_${Date.now()}`,
|
|
391
|
+
name,
|
|
392
|
+
timestamp: Date.now(),
|
|
393
|
+
description,
|
|
394
|
+
state: stateCopy
|
|
395
|
+
};
|
|
396
|
+
this.namedSnapshots.set(name, snapshot);
|
|
397
|
+
}
|
|
352
398
|
/**
|
|
353
399
|
* 回退到上一个状态(撤销操作)
|
|
354
400
|
*
|
|
355
|
-
*
|
|
356
|
-
* 如果没有可用的快照,返回 undefined
|
|
401
|
+
* 移除并返回最近的一个匿名快照。
|
|
402
|
+
* 如果没有可用的快照,返回 undefined。
|
|
403
|
+
*
|
|
404
|
+
* 注意:此操作不会影响命名快照。
|
|
357
405
|
*
|
|
358
406
|
* @returns 上一个状态快照,如果历史为空则返回 undefined
|
|
359
407
|
*
|
|
@@ -367,13 +415,136 @@ var HistoryManager = class {
|
|
|
367
415
|
* ```
|
|
368
416
|
*/
|
|
369
417
|
pop() {
|
|
370
|
-
|
|
418
|
+
const snapshot = this.snapshots.pop();
|
|
419
|
+
return snapshot?.state;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 恢复到命名快照
|
|
423
|
+
*
|
|
424
|
+
* ⚠️ 新功能:通过名称恢复到指定的快照
|
|
425
|
+
*
|
|
426
|
+
* 返回指定名称的快照状态,但不从历史记录中删除它。
|
|
427
|
+
* 这允许多次恢复到同一个命名快照。
|
|
428
|
+
*
|
|
429
|
+
* @param name - 快照名称
|
|
430
|
+
* @returns 指定名称的状态快照,如果不存在则返回 undefined
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* ```typescript
|
|
434
|
+
* // 恢复到Boss战前
|
|
435
|
+
* const savedState = history.restoreNamed('before_boss_fight');
|
|
436
|
+
* if (savedState) {
|
|
437
|
+
* restoreState(savedState);
|
|
438
|
+
* } else {
|
|
439
|
+
* console.log('快照不存在');
|
|
440
|
+
* }
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
restoreNamed(name) {
|
|
444
|
+
const snapshot = this.namedSnapshots.get(name);
|
|
445
|
+
return snapshot?.state;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* 删除命名快照
|
|
449
|
+
*
|
|
450
|
+
* 从历史记录中删除指定名称的快照。
|
|
451
|
+
*
|
|
452
|
+
* @param name - 要删除的快照名称
|
|
453
|
+
* @returns 是否成功删除
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```typescript
|
|
457
|
+
* history.deleteNamed('before_boss_fight');
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
deleteNamed(name) {
|
|
461
|
+
return this.namedSnapshots.delete(name);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* 检查命名快照是否存在
|
|
465
|
+
*
|
|
466
|
+
* @param name - 快照名称
|
|
467
|
+
* @returns 是否存在指定名称的快照
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* if (history.hasNamed('before_boss_fight')) {
|
|
472
|
+
* console.log('可以恢复到Boss战前');
|
|
473
|
+
* }
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
hasNamed(name) {
|
|
477
|
+
return this.namedSnapshots.has(name);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 列出所有快照
|
|
481
|
+
*
|
|
482
|
+
* ⚠️ 新功能:获取所有快照的信息列表
|
|
483
|
+
*
|
|
484
|
+
* 返回所有快照(包括匿名和命名快照)的元数据。
|
|
485
|
+
* 不包含实际的状态数据,只包含快照信息。
|
|
486
|
+
*
|
|
487
|
+
* @returns 快照信息数组,按时间倒序排列(最新的在前)
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* const snapshots = history.listSnapshots();
|
|
492
|
+
* snapshots.forEach(snap => {
|
|
493
|
+
* console.log(`${snap.name || '匿名'}: ${snap.description || '无描述'}`);
|
|
494
|
+
* console.log(` 时间: ${new Date(snap.timestamp).toLocaleString()}`);
|
|
495
|
+
* });
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
listSnapshots() {
|
|
499
|
+
const allSnapshots = [];
|
|
500
|
+
this.snapshots.forEach((snap) => {
|
|
501
|
+
allSnapshots.push({
|
|
502
|
+
id: snap.id,
|
|
503
|
+
name: snap.name,
|
|
504
|
+
timestamp: snap.timestamp,
|
|
505
|
+
description: snap.description
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
this.namedSnapshots.forEach((snap) => {
|
|
509
|
+
allSnapshots.push({
|
|
510
|
+
id: snap.id,
|
|
511
|
+
name: snap.name,
|
|
512
|
+
timestamp: snap.timestamp,
|
|
513
|
+
description: snap.description
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
return allSnapshots.sort((a, b) => b.timestamp - a.timestamp);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* 列出所有命名快照
|
|
520
|
+
*
|
|
521
|
+
* 只返回命名快照的信息列表。
|
|
522
|
+
*
|
|
523
|
+
* @returns 命名快照信息数组
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```typescript
|
|
527
|
+
* const namedSnaps = history.listNamedSnapshots();
|
|
528
|
+
* console.log(`共有 ${namedSnaps.length} 个命名快照`);
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
listNamedSnapshots() {
|
|
532
|
+
const result = [];
|
|
533
|
+
this.namedSnapshots.forEach((snap) => {
|
|
534
|
+
result.push({
|
|
535
|
+
id: snap.id,
|
|
536
|
+
name: snap.name,
|
|
537
|
+
timestamp: snap.timestamp,
|
|
538
|
+
description: snap.description
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
return result.sort((a, b) => b.timestamp - a.timestamp);
|
|
371
542
|
}
|
|
372
543
|
/**
|
|
373
544
|
* 查看最近的快照但不移除
|
|
374
545
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
546
|
+
* 返回最新的匿名快照但不从历史记录中删除它。
|
|
547
|
+
* 用于预览上一个状态而不实际执行撤销操作。
|
|
377
548
|
*
|
|
378
549
|
* @returns 最近的状态快照,如果历史为空则返回 undefined
|
|
379
550
|
*
|
|
@@ -386,26 +557,36 @@ var HistoryManager = class {
|
|
|
386
557
|
* ```
|
|
387
558
|
*/
|
|
388
559
|
peek() {
|
|
389
|
-
|
|
560
|
+
const snapshot = this.snapshots[this.snapshots.length - 1];
|
|
561
|
+
return snapshot?.state;
|
|
390
562
|
}
|
|
391
563
|
/**
|
|
392
564
|
* 清空所有历史记录
|
|
393
565
|
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
566
|
+
* 移除所有保存的快照(包括匿名和命名快照),释放内存。
|
|
567
|
+
* 通常在开始新游戏或重置时使用。
|
|
568
|
+
*
|
|
569
|
+
* @param includeNamed - 是否同时清空命名快照(默认为 false)
|
|
396
570
|
*
|
|
397
571
|
* @example
|
|
398
572
|
* ```typescript
|
|
399
|
-
*
|
|
573
|
+
* // 只清空匿名快照
|
|
574
|
+
* history.clear();
|
|
575
|
+
*
|
|
576
|
+
* // 清空所有快照(包括命名快照)
|
|
577
|
+
* history.clear(true);
|
|
400
578
|
* ```
|
|
401
579
|
*/
|
|
402
|
-
clear() {
|
|
580
|
+
clear(includeNamed = false) {
|
|
403
581
|
this.snapshots = [];
|
|
582
|
+
if (includeNamed) {
|
|
583
|
+
this.namedSnapshots.clear();
|
|
584
|
+
}
|
|
404
585
|
}
|
|
405
586
|
/**
|
|
406
|
-
*
|
|
587
|
+
* 获取当前匿名快照数量
|
|
407
588
|
*
|
|
408
|
-
* @returns
|
|
589
|
+
* @returns 当前保存的匿名快照数量
|
|
409
590
|
*
|
|
410
591
|
* @example
|
|
411
592
|
* ```typescript
|
|
@@ -418,6 +599,32 @@ var HistoryManager = class {
|
|
|
418
599
|
get size() {
|
|
419
600
|
return this.snapshots.length;
|
|
420
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* 获取命名快照数量
|
|
604
|
+
*
|
|
605
|
+
* @returns 当前保存的命名快照数量
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```typescript
|
|
609
|
+
* console.log(`命名快照数量: ${history.namedSize}`);
|
|
610
|
+
* ```
|
|
611
|
+
*/
|
|
612
|
+
get namedSize() {
|
|
613
|
+
return this.namedSnapshots.size;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* 获取总快照数量
|
|
617
|
+
*
|
|
618
|
+
* @returns 所有快照的总数量(匿名 + 命名)
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```typescript
|
|
622
|
+
* console.log(`总快照数量: ${history.totalSize}`);
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
get totalSize() {
|
|
626
|
+
return this.snapshots.length + this.namedSnapshots.size;
|
|
627
|
+
}
|
|
421
628
|
};
|
|
422
629
|
|
|
423
630
|
// state/store.ts
|
|
@@ -595,19 +802,29 @@ var createGameEngineStore = (initialState, persistName = "generic-rpg-save") =>
|
|
|
595
802
|
reset: () => set({ ...initialState, logs: [] }),
|
|
596
803
|
// ========== 历史记录操作实现 ==========
|
|
597
804
|
/**
|
|
598
|
-
*
|
|
805
|
+
* 实现:保存当前状态快照(匿名快照)
|
|
599
806
|
*
|
|
600
807
|
* 保存除日志外的所有状态数据到历史管理器。
|
|
601
808
|
* 日志通常不需要回退,因此被排除以节省内存。
|
|
602
809
|
*/
|
|
603
|
-
saveSnapshot: () => {
|
|
810
|
+
saveSnapshot: (description) => {
|
|
811
|
+
const currentState = get2();
|
|
812
|
+
history.push(currentState, description);
|
|
813
|
+
},
|
|
814
|
+
/**
|
|
815
|
+
* 实现:保存命名快照
|
|
816
|
+
*
|
|
817
|
+
* 保存可以通过名称访问的快照。
|
|
818
|
+
* 命名快照不受数量限制,适用于重要的游戏节点。
|
|
819
|
+
*/
|
|
820
|
+
saveNamedSnapshot: (name, description) => {
|
|
604
821
|
const currentState = get2();
|
|
605
|
-
history.
|
|
822
|
+
history.pushNamed(currentState, name, description);
|
|
606
823
|
},
|
|
607
824
|
/**
|
|
608
825
|
* 实现:撤销到上一个状态
|
|
609
826
|
*
|
|
610
|
-
*
|
|
827
|
+
* 从历史管理器中恢复上一个匿名快照。
|
|
611
828
|
* 恢复时保留当前的日志,并添加系统提示。
|
|
612
829
|
*
|
|
613
830
|
* @returns 是否成功撤销
|
|
@@ -627,6 +844,56 @@ var createGameEngineStore = (initialState, persistName = "generic-rpg-save") =>
|
|
|
627
844
|
return true;
|
|
628
845
|
}
|
|
629
846
|
return false;
|
|
847
|
+
},
|
|
848
|
+
/**
|
|
849
|
+
* 实现:恢复到命名快照
|
|
850
|
+
*
|
|
851
|
+
* 通过名称恢复到指定的快照。
|
|
852
|
+
* 快照不会被删除,可以多次恢复。
|
|
853
|
+
*
|
|
854
|
+
* @returns 是否成功恢复
|
|
855
|
+
*/
|
|
856
|
+
restoreSnapshot: (name) => {
|
|
857
|
+
const saved = history.restoreNamed(name);
|
|
858
|
+
if (saved) {
|
|
859
|
+
set({
|
|
860
|
+
stats: saved.stats,
|
|
861
|
+
inventory: saved.inventory,
|
|
862
|
+
flags: saved.flags,
|
|
863
|
+
world: saved.world,
|
|
864
|
+
extra: saved.extra
|
|
865
|
+
// logs 保留当前的,不回退日志
|
|
866
|
+
});
|
|
867
|
+
get2().addLog(`\u5DF2\u6062\u590D\u5230\u5FEB\u7167\uFF1A${name}`, "info");
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
return false;
|
|
871
|
+
},
|
|
872
|
+
/**
|
|
873
|
+
* 实现:删除命名快照
|
|
874
|
+
*
|
|
875
|
+
* 从历史记录中删除指定名称的快照。
|
|
876
|
+
*
|
|
877
|
+
* @returns 是否成功删除
|
|
878
|
+
*/
|
|
879
|
+
deleteSnapshot: (name) => {
|
|
880
|
+
return history.deleteNamed(name);
|
|
881
|
+
},
|
|
882
|
+
/**
|
|
883
|
+
* 实现:列出所有快照
|
|
884
|
+
*
|
|
885
|
+
* 返回所有快照的元数据列表。
|
|
886
|
+
*/
|
|
887
|
+
listSnapshots: () => {
|
|
888
|
+
return history.listSnapshots();
|
|
889
|
+
},
|
|
890
|
+
/**
|
|
891
|
+
* 实现:检查命名快照是否存在
|
|
892
|
+
*
|
|
893
|
+
* @returns 是否存在指定名称的快照
|
|
894
|
+
*/
|
|
895
|
+
hasSnapshot: (name) => {
|
|
896
|
+
return history.hasNamed(name);
|
|
630
897
|
}
|
|
631
898
|
}),
|
|
632
899
|
{
|
|
@@ -657,7 +924,9 @@ var createGameEngineStore = (initialState, persistName = "generic-rpg-save") =>
|
|
|
657
924
|
// systems/query.ts
|
|
658
925
|
var QuerySystem = class {
|
|
659
926
|
/**
|
|
660
|
-
*
|
|
927
|
+
* 检查是否满足所有需求条件(带失败原因)
|
|
928
|
+
*
|
|
929
|
+
* ⚠️ 增强版本:返回详细的检查结果,包括失败原因
|
|
661
930
|
*
|
|
662
931
|
* 这是核心的条件检查方法,用于验证玩家是否满足执行某个动作的所有前置条件。
|
|
663
932
|
* 内置字段使用 AND 逻辑组合,复杂逻辑使用 custom 函数实现。
|
|
@@ -667,59 +936,140 @@ var QuerySystem = class {
|
|
|
667
936
|
* @template F - 标记键的联合类型
|
|
668
937
|
*
|
|
669
938
|
* @param state - 当前游戏状态
|
|
670
|
-
* @param reqs -
|
|
939
|
+
* @param reqs - 需求定义(可选,未定义时返回通过)
|
|
671
940
|
*
|
|
672
|
-
* @returns
|
|
941
|
+
* @returns RequirementCheckResult 包含是否通过和失败原因
|
|
673
942
|
*
|
|
674
943
|
* @example
|
|
675
944
|
* ```typescript
|
|
676
|
-
* //
|
|
677
|
-
* const
|
|
945
|
+
* // 检查需求并获取失败原因
|
|
946
|
+
* const result = QuerySystem.checkRequirementsWithReason(state, {
|
|
678
947
|
* hasItems: ['key'],
|
|
679
948
|
* stats: { strength: { min: 5 } }
|
|
680
949
|
* });
|
|
681
950
|
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
* // (有钥匙 AND 力量>=5) OR 有万能钥匙
|
|
686
|
-
* const hasKeyAndStrength =
|
|
687
|
-
* state.inventory.includes('key') && state.stats.strength >= 5;
|
|
688
|
-
* const hasMasterKey = state.inventory.includes('master_key');
|
|
689
|
-
* return hasKeyAndStrength || hasMasterKey;
|
|
690
|
-
* }
|
|
691
|
-
* });
|
|
951
|
+
* if (!result.passed) {
|
|
952
|
+
* console.log(result.reason); // 显示失败原因
|
|
953
|
+
* }
|
|
692
954
|
* ```
|
|
693
955
|
*/
|
|
694
|
-
static
|
|
695
|
-
if (!reqs) return true;
|
|
956
|
+
static checkRequirementsWithReason(state, reqs) {
|
|
957
|
+
if (!reqs) return { passed: true };
|
|
696
958
|
if (reqs.hasFlags && reqs.hasFlags.length > 0) {
|
|
697
|
-
|
|
959
|
+
const missingFlags = reqs.hasFlags.filter((f) => !state.flags[f]);
|
|
960
|
+
if (missingFlags.length > 0) {
|
|
961
|
+
return {
|
|
962
|
+
passed: false,
|
|
963
|
+
reason: `\u9700\u8981\u6EE1\u8DB3\u6761\u4EF6\uFF1A${missingFlags.join("\u3001")}`
|
|
964
|
+
};
|
|
965
|
+
}
|
|
698
966
|
}
|
|
699
967
|
if (reqs.noFlags && reqs.noFlags.length > 0) {
|
|
700
|
-
|
|
968
|
+
const conflictFlags = reqs.noFlags.filter((f) => state.flags[f]);
|
|
969
|
+
if (conflictFlags.length > 0) {
|
|
970
|
+
return {
|
|
971
|
+
passed: false,
|
|
972
|
+
reason: `\u4E0D\u80FD\u6EE1\u8DB3\u6761\u4EF6\uFF1A${conflictFlags.join("\u3001")}`
|
|
973
|
+
};
|
|
974
|
+
}
|
|
701
975
|
}
|
|
702
976
|
if (reqs.hasItems && reqs.hasItems.length > 0) {
|
|
703
|
-
|
|
704
|
-
|
|
977
|
+
const missingItems = reqs.hasItems.filter((i) => !state.inventory.includes(i));
|
|
978
|
+
if (missingItems.length > 0) {
|
|
979
|
+
return {
|
|
980
|
+
passed: false,
|
|
981
|
+
reason: `\u7F3A\u5C11\u5FC5\u9700\u7269\u54C1\uFF1A${missingItems.join("\u3001")}`
|
|
982
|
+
};
|
|
983
|
+
}
|
|
705
984
|
}
|
|
706
985
|
if (reqs.noItems && reqs.noItems.length > 0) {
|
|
707
|
-
|
|
986
|
+
const conflictItems = reqs.noItems.filter((i) => state.inventory.includes(i));
|
|
987
|
+
if (conflictItems.length > 0) {
|
|
988
|
+
return {
|
|
989
|
+
passed: false,
|
|
990
|
+
reason: `\u4E0D\u80FD\u643A\u5E26\u7269\u54C1\uFF1A${conflictItems.join("\u3001")}`
|
|
991
|
+
};
|
|
992
|
+
}
|
|
708
993
|
}
|
|
709
994
|
if (reqs.stats) {
|
|
710
995
|
for (const [key, condition] of Object.entries(reqs.stats)) {
|
|
711
996
|
const currentVal = state.stats[key] || 0;
|
|
712
997
|
const cond = condition;
|
|
713
998
|
if (typeof cond === "number") {
|
|
714
|
-
if (currentVal < cond)
|
|
999
|
+
if (currentVal < cond) {
|
|
1000
|
+
return {
|
|
1001
|
+
passed: false,
|
|
1002
|
+
reason: `${key} \u4E0D\u8DB3\uFF08\u9700\u8981 ${cond}\uFF0C\u5F53\u524D ${currentVal}\uFF09`
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
715
1005
|
} else {
|
|
716
|
-
if (cond.min !== void 0 && currentVal < cond.min)
|
|
717
|
-
|
|
1006
|
+
if (cond.min !== void 0 && currentVal < cond.min) {
|
|
1007
|
+
return {
|
|
1008
|
+
passed: false,
|
|
1009
|
+
reason: `${key} \u8FC7\u4F4E\uFF08\u9700\u8981\u81F3\u5C11 ${cond.min}\uFF0C\u5F53\u524D ${currentVal}\uFF09`
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
if (cond.max !== void 0 && currentVal > cond.max) {
|
|
1013
|
+
return {
|
|
1014
|
+
passed: false,
|
|
1015
|
+
reason: `${key} \u8FC7\u9AD8\uFF08\u9700\u8981\u6700\u591A ${cond.max}\uFF0C\u5F53\u524D ${currentVal}\uFF09`
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
718
1018
|
}
|
|
719
1019
|
}
|
|
720
1020
|
}
|
|
721
|
-
if (reqs.custom
|
|
722
|
-
|
|
1021
|
+
if (reqs.custom) {
|
|
1022
|
+
const customResult = reqs.custom(state);
|
|
1023
|
+
if (typeof customResult === "object") {
|
|
1024
|
+
if (!customResult.passed) {
|
|
1025
|
+
return customResult;
|
|
1026
|
+
}
|
|
1027
|
+
} else if (!customResult) {
|
|
1028
|
+
return {
|
|
1029
|
+
passed: false,
|
|
1030
|
+
reason: "\u4E0D\u6EE1\u8DB3\u81EA\u5B9A\u4E49\u6761\u4EF6"
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return { passed: true };
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* 检查是否满足所有需求条件(简化版本)
|
|
1038
|
+
*
|
|
1039
|
+
* 这是向后兼容的简化版本,只返回 boolean。
|
|
1040
|
+
* 如果需要失败原因,请使用 checkRequirementsWithReason。
|
|
1041
|
+
*
|
|
1042
|
+
* @template S - 数值属性键的联合类型
|
|
1043
|
+
* @template I - 物品ID的联合类型
|
|
1044
|
+
* @template F - 标记键的联合类型
|
|
1045
|
+
*
|
|
1046
|
+
* @param state - 当前游戏状态
|
|
1047
|
+
* @param reqs - 需求定义(可选,未定义时返回true)
|
|
1048
|
+
*
|
|
1049
|
+
* @returns true表示满足所有条件,false表示至少有一个条件不满足
|
|
1050
|
+
*
|
|
1051
|
+
* @example
|
|
1052
|
+
* ```typescript
|
|
1053
|
+
* // 简单需求
|
|
1054
|
+
* const simple = QuerySystem.checkRequirements(state, {
|
|
1055
|
+
* hasItems: ['key'],
|
|
1056
|
+
* stats: { strength: { min: 5 } }
|
|
1057
|
+
* });
|
|
1058
|
+
*
|
|
1059
|
+
* // 复杂逻辑使用 custom
|
|
1060
|
+
* const complex = QuerySystem.checkRequirements(state, {
|
|
1061
|
+
* custom: (state) => {
|
|
1062
|
+
* // (有钥匙 AND 力量>=5) OR 有万能钥匙
|
|
1063
|
+
* const hasKeyAndStrength =
|
|
1064
|
+
* state.inventory.includes('key') && state.stats.strength >= 5;
|
|
1065
|
+
* const hasMasterKey = state.inventory.includes('master_key');
|
|
1066
|
+
* return hasKeyAndStrength || hasMasterKey;
|
|
1067
|
+
* }
|
|
1068
|
+
* });
|
|
1069
|
+
* ```
|
|
1070
|
+
*/
|
|
1071
|
+
static checkRequirements(state, reqs) {
|
|
1072
|
+
return this.checkRequirementsWithReason(state, reqs).passed;
|
|
723
1073
|
}
|
|
724
1074
|
/**
|
|
725
1075
|
* 检查是否有足够资源支付成本
|
|
@@ -816,8 +1166,12 @@ var FlowSystem = class {
|
|
|
816
1166
|
*/
|
|
817
1167
|
static executeAction(store, action) {
|
|
818
1168
|
const state = store;
|
|
819
|
-
|
|
820
|
-
|
|
1169
|
+
const reqCheck = QuerySystem.checkRequirementsWithReason(state, action.requirements);
|
|
1170
|
+
if (!reqCheck.passed) {
|
|
1171
|
+
console.warn(`\u6761\u4EF6\u4E0D\u6EE1\u8DB3: ${action.id}`, reqCheck.reason);
|
|
1172
|
+
if (reqCheck.reason) {
|
|
1173
|
+
store.addLog(reqCheck.reason, "warn");
|
|
1174
|
+
}
|
|
821
1175
|
return false;
|
|
822
1176
|
}
|
|
823
1177
|
if (!QuerySystem.canAfford(state, action.costs)) {
|
|
@@ -833,21 +1187,88 @@ var FlowSystem = class {
|
|
|
833
1187
|
}
|
|
834
1188
|
const { effects } = action;
|
|
835
1189
|
if (effects) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1190
|
+
const applyEffectDef = (effectDef) => {
|
|
1191
|
+
if (effectDef.statsChange) {
|
|
1192
|
+
const statsToChange = typeof effectDef.statsChange === "function" ? effectDef.statsChange(store) : effectDef.statsChange;
|
|
1193
|
+
store.setStats(statsToChange);
|
|
1194
|
+
}
|
|
1195
|
+
effectDef.itemsAdd?.forEach((i) => store.addItem(i));
|
|
1196
|
+
effectDef.itemsRemove?.forEach((i) => store.removeItem(i));
|
|
1197
|
+
if (effectDef.flagsSet) {
|
|
1198
|
+
const flagsToSet = typeof effectDef.flagsSet === "function" ? effectDef.flagsSet(store) : effectDef.flagsSet;
|
|
1199
|
+
Object.entries(flagsToSet).forEach(([f, v]) => {
|
|
1200
|
+
store.setFlag(f, v);
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
if (effectDef.flagsBatch) {
|
|
1204
|
+
const batchOp = typeof effectDef.flagsBatch === "function" ? effectDef.flagsBatch(store) : effectDef.flagsBatch;
|
|
1205
|
+
if (batchOp) {
|
|
1206
|
+
if (batchOp.clear) {
|
|
1207
|
+
const allFlags = Object.keys(store.flags);
|
|
1208
|
+
let flagsToClear = [];
|
|
1209
|
+
if (batchOp.clear instanceof RegExp) {
|
|
1210
|
+
flagsToClear = allFlags.filter((f) => batchOp.clear instanceof RegExp && batchOp.clear.test(f));
|
|
1211
|
+
} else if (typeof batchOp.clear === "string") {
|
|
1212
|
+
flagsToClear = allFlags.filter((f) => f.startsWith(batchOp.clear));
|
|
1213
|
+
} else if (Array.isArray(batchOp.clear)) {
|
|
1214
|
+
flagsToClear = batchOp.clear;
|
|
1215
|
+
}
|
|
1216
|
+
flagsToClear.forEach((f) => {
|
|
1217
|
+
store.setFlag(f, false);
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
if (batchOp.set) {
|
|
1221
|
+
Object.entries(batchOp.set).forEach(([f, v]) => {
|
|
1222
|
+
store.setFlag(f, v);
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (effectDef.teleport) {
|
|
1228
|
+
store.teleport(effectDef.teleport);
|
|
1229
|
+
}
|
|
1230
|
+
if (effectDef.timeAdvance !== void 0) {
|
|
1231
|
+
const daysToAdvance = typeof effectDef.timeAdvance === "function" ? effectDef.timeAdvance(store) : effectDef.timeAdvance;
|
|
1232
|
+
if (daysToAdvance > 0) {
|
|
1233
|
+
const previousDay = store.world.day;
|
|
1234
|
+
store.advanceTime(daysToAdvance);
|
|
1235
|
+
if (effectDef.onTimeAdvance) {
|
|
1236
|
+
const actions = {
|
|
1237
|
+
updateStat: store.updateStat,
|
|
1238
|
+
setStats: store.setStats,
|
|
1239
|
+
setExtra: store.setExtra,
|
|
1240
|
+
addItem: store.addItem,
|
|
1241
|
+
removeItem: store.removeItem,
|
|
1242
|
+
setFlag: store.setFlag,
|
|
1243
|
+
addLog: store.addLog,
|
|
1244
|
+
showToast: store.showToast,
|
|
1245
|
+
showModal: store.showModal,
|
|
1246
|
+
advanceTime: store.advanceTime,
|
|
1247
|
+
teleport: store.teleport,
|
|
1248
|
+
reset: store.reset,
|
|
1249
|
+
saveSnapshot: store.saveSnapshot,
|
|
1250
|
+
undo: store.undo
|
|
1251
|
+
};
|
|
1252
|
+
for (let i = 1; i <= daysToAdvance; i++) {
|
|
1253
|
+
const currentDay = previousDay + i;
|
|
1254
|
+
const dayBefore = currentDay - 1;
|
|
1255
|
+
const currentState = store;
|
|
1256
|
+
effectDef.onTimeAdvance(currentDay, dayBefore, currentState, actions);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (effectDef.triggerEvent) {
|
|
1262
|
+
gameEvents.emit(EngineEvents.CUSTOM, { id: effectDef.triggerEvent });
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
applyEffectDef(effects);
|
|
1266
|
+
if (effects.conditionalEffects) {
|
|
1267
|
+
const currentState = store;
|
|
1268
|
+
const conditionalEffect = effects.conditionalEffects(currentState);
|
|
1269
|
+
if (conditionalEffect) {
|
|
1270
|
+
applyEffectDef(conditionalEffect);
|
|
1271
|
+
}
|
|
851
1272
|
}
|
|
852
1273
|
if (effects.custom) {
|
|
853
1274
|
const draft = { ...state.extra };
|
|
@@ -858,8 +1279,81 @@ var FlowSystem = class {
|
|
|
858
1279
|
store.setExtra(draft);
|
|
859
1280
|
}
|
|
860
1281
|
}
|
|
1282
|
+
if (effects.customFull) {
|
|
1283
|
+
const originalState = { ...state };
|
|
1284
|
+
const currentState = store;
|
|
1285
|
+
const draft = {
|
|
1286
|
+
stats: { ...currentState.stats },
|
|
1287
|
+
inventory: [...currentState.inventory],
|
|
1288
|
+
flags: { ...currentState.flags },
|
|
1289
|
+
world: { ...currentState.world },
|
|
1290
|
+
extra: { ...currentState.extra }
|
|
1291
|
+
};
|
|
1292
|
+
effects.customFull(draft, originalState);
|
|
1293
|
+
const statsChanges = {};
|
|
1294
|
+
for (const key in draft.stats) {
|
|
1295
|
+
const oldVal = currentState.stats[key] || 0;
|
|
1296
|
+
const newVal = draft.stats[key];
|
|
1297
|
+
const delta = newVal - oldVal;
|
|
1298
|
+
if (delta !== 0) {
|
|
1299
|
+
statsChanges[key] = delta;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (Object.keys(statsChanges).length > 0) {
|
|
1303
|
+
store.setStats(statsChanges);
|
|
1304
|
+
}
|
|
1305
|
+
const currentItems = new Set(currentState.inventory);
|
|
1306
|
+
const newItems = new Set(draft.inventory);
|
|
1307
|
+
currentState.inventory.forEach((item) => {
|
|
1308
|
+
if (!newItems.has(item)) {
|
|
1309
|
+
store.removeItem(item);
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
draft.inventory.forEach((item) => {
|
|
1313
|
+
if (!currentItems.has(item)) {
|
|
1314
|
+
store.addItem(item);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
for (const key in draft.flags) {
|
|
1318
|
+
const oldVal = currentState.flags[key];
|
|
1319
|
+
const newVal = draft.flags[key];
|
|
1320
|
+
if (oldVal !== newVal) {
|
|
1321
|
+
store.setFlag(key, newVal);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (draft.world.currentLocationId !== currentState.world.currentLocationId) {
|
|
1325
|
+
store.teleport(draft.world.currentLocationId);
|
|
1326
|
+
}
|
|
1327
|
+
if (draft.world.day !== currentState.world.day) {
|
|
1328
|
+
const dayDelta = draft.world.day - currentState.world.day;
|
|
1329
|
+
store.advanceTime(dayDelta);
|
|
1330
|
+
}
|
|
1331
|
+
store.setExtra(draft.extra);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (action.afterEffects) {
|
|
1335
|
+
const originalState = { ...state };
|
|
1336
|
+
const currentState = store;
|
|
1337
|
+
const actions = {
|
|
1338
|
+
updateStat: store.updateStat,
|
|
1339
|
+
setStats: store.setStats,
|
|
1340
|
+
setExtra: store.setExtra,
|
|
1341
|
+
addItem: store.addItem,
|
|
1342
|
+
removeItem: store.removeItem,
|
|
1343
|
+
setFlag: store.setFlag,
|
|
1344
|
+
addLog: store.addLog,
|
|
1345
|
+
showToast: store.showToast,
|
|
1346
|
+
showModal: store.showModal,
|
|
1347
|
+
advanceTime: store.advanceTime,
|
|
1348
|
+
teleport: store.teleport,
|
|
1349
|
+
reset: store.reset,
|
|
1350
|
+
saveSnapshot: store.saveSnapshot,
|
|
1351
|
+
undo: store.undo
|
|
1352
|
+
};
|
|
1353
|
+
action.afterEffects(currentState, originalState, actions);
|
|
861
1354
|
}
|
|
862
|
-
const
|
|
1355
|
+
const finalState = store;
|
|
1356
|
+
const resultText = typeof action.resultText === "function" ? action.resultText(finalState, state) : action.resultText;
|
|
863
1357
|
store.addLog(resultText, "result");
|
|
864
1358
|
gameEvents.emit(EngineEvents.ACTION_EXECUTED, { actionId: action.id });
|
|
865
1359
|
return true;
|