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