@maka/maka-cli 5.3.16 → 5.4.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/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/create.js +1 -1
- package/bundle/typescript/src/commands/create.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +131 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +79 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.d.ts +42 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +224 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +101 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +149 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +378 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.d.ts +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +8 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +60 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +31 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +40 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +11 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +115 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +176 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +64 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/kill.d.ts +10 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js +11 -33
- package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +35 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +46 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +55 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +18 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/project.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/project.js +54 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/project.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/read.js +6 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/read.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +52 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +40 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +59 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +23 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +20 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +49 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +50 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +32 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +65 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +41 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +45 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +80 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +101 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js +7 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.d.ts +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +138 -21
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +119 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +816 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +70 -9
- package/bundle/typescript/src/commands/game/sideQuest/game.js +403 -164
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js +101 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js +95 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/inventory.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js +54 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +41 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +106 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +220 -47
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +213 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +525 -9
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +75 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +174 -6
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +39 -18
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +69 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +175 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +80 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +7 -2
- package/bundle/typescript/src/commands/game/sideQuest/types/player-equipment.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/exit-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/npc-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +95 -5
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +60 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +138 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +143 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +328 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +92 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +193 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +54 -7
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/set/global.sub.cmd.js +59 -5
- package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/tools/ai/ai.class.d.ts +35 -0
- package/bundle/typescript/src/tools/ai/ai.class.js +112 -0
- package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -0
- package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
- package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.class.d.ts +2 -1
- package/bundle/typescript/src/tools/https/https.class.js +43 -0
- package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.interface.d.ts +8 -0
- package/package.json +1 -1
|
@@ -16,6 +16,11 @@ export class Item extends AbstractItem {
|
|
|
16
16
|
content;
|
|
17
17
|
details;
|
|
18
18
|
satisfiesLifestyle;
|
|
19
|
+
plane = 'meat';
|
|
20
|
+
// PAN warfare (see commands/hack.ts): a cracked smart-device locks up.
|
|
21
|
+
// A jammed firearm can't fire (fists until cleared); "reboot" clears
|
|
22
|
+
// every jammed device you carry, in the lull.
|
|
23
|
+
jammed = false;
|
|
19
24
|
_transferable;
|
|
20
25
|
// New currency-related fields
|
|
21
26
|
_currencyAmount = 0;
|
|
@@ -45,9 +50,15 @@ export class Item extends AbstractItem {
|
|
|
45
50
|
this.content = options.content;
|
|
46
51
|
this.satisfiesLifestyle = options.satisfiesLifestyle;
|
|
47
52
|
this._transferable = options.transferable !== false; // default true
|
|
53
|
+
this.plane = options.plane ?? 'meat';
|
|
48
54
|
this._id = uuidv4();
|
|
49
|
-
// Optional: you can define capacity by size
|
|
50
|
-
|
|
55
|
+
// Optional: you can define capacity by size. Deliberately scoped to
|
|
56
|
+
// CurrencyContainer only (a physical wallet/pouch) -- a Credstick is
|
|
57
|
+
// digital storage, not physically size-limited, so it's left uncapped
|
|
58
|
+
// (falls through to Infinity in addCurrency()). Capping it here would
|
|
59
|
+
// silently truncate a seed's declared "currencyAmount" down to the
|
|
60
|
+
// size-based cap the moment Credstick became a currency carrier.
|
|
61
|
+
if (this.category === Category.CurrencyContainer) {
|
|
51
62
|
switch (this.size) {
|
|
52
63
|
case Size.Small:
|
|
53
64
|
this._currencyCapacity = 100;
|
|
@@ -60,6 +71,34 @@ export class Item extends AbstractItem {
|
|
|
60
71
|
break;
|
|
61
72
|
}
|
|
62
73
|
}
|
|
74
|
+
// Firearms track ammunition: a flat magazine, starting full. Melee
|
|
75
|
+
// weapons never touch it. See fireRound()/reload() and combat-exchange.
|
|
76
|
+
if (this.isFirearm()) {
|
|
77
|
+
this.ammo = Item.MAGAZINE_CAPACITY;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// ======================== Ammunition ========================
|
|
81
|
+
static MAGAZINE_CAPACITY = 10;
|
|
82
|
+
/** Rounds left in the magazine. Only meaningful for firearms. */
|
|
83
|
+
ammo = 0;
|
|
84
|
+
/** Any weapon that shoots -- everything weapon-ish except melee. */
|
|
85
|
+
isFirearm() {
|
|
86
|
+
return this.category === Category.RangedWeapon || this.category === Category.Weapon;
|
|
87
|
+
}
|
|
88
|
+
/** Spends one round; returns false on an empty magazine. */
|
|
89
|
+
fireRound() {
|
|
90
|
+
if (!this.isFirearm())
|
|
91
|
+
return true; // knives don't run dry
|
|
92
|
+
if (this.ammo <= 0)
|
|
93
|
+
return false;
|
|
94
|
+
this.ammo -= 1;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/** Refills the magazine (the spent Ammunition item is the caller's job). */
|
|
98
|
+
reload() {
|
|
99
|
+
if (this.isFirearm()) {
|
|
100
|
+
this.ammo = Item.MAGAZINE_CAPACITY;
|
|
101
|
+
}
|
|
63
102
|
}
|
|
64
103
|
get owner() {
|
|
65
104
|
return this._owner;
|
|
@@ -76,6 +115,61 @@ export class Item extends AbstractItem {
|
|
|
76
115
|
use() {
|
|
77
116
|
return `You use the ${this.name}. Nothing special happens.`;
|
|
78
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* How much this item's quality tier is worth in game mechanics: extra
|
|
120
|
+
* weapon damage/attack dice, armor soak, medkit healing (see
|
|
121
|
+
* Player.getWeaponProfile/getArmorValue and commands/use.ts). Gear
|
|
122
|
+
* quality is the game's whole progression system, so every mechanic
|
|
123
|
+
* that gear feeds scales off this one number.
|
|
124
|
+
*/
|
|
125
|
+
get qualityBonus() {
|
|
126
|
+
switch (this.rating) {
|
|
127
|
+
case Rating.Common: return 0;
|
|
128
|
+
case Rating.Standard: return 1;
|
|
129
|
+
case Rating.Rare: return 2;
|
|
130
|
+
case Rating.Legendary: return 3;
|
|
131
|
+
case Rating.MilitaryGrade: return 4;
|
|
132
|
+
default: return 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// ================= Device condition monitor (cyberdecks) =================
|
|
136
|
+
// Matrix combat damage lands on HARDWARE: the deck you jacked in with
|
|
137
|
+
// (see combat-exchange.ts routing and Player.takeMatrixDamage). A full
|
|
138
|
+
// track BRICKS the deck -- forced dump, and no jacking in on it again
|
|
139
|
+
// until it's repaired (rest somewhere safe -- see rest.ts). Damage
|
|
140
|
+
// persists across jack-outs; rebooting doesn't un-melt circuitry.
|
|
141
|
+
_deckDamage = 0;
|
|
142
|
+
isCyberdeck() {
|
|
143
|
+
return this.category === Category.Cyberdeck;
|
|
144
|
+
}
|
|
145
|
+
/** Better decks have more board to burn through: 8 + 2 per quality step. */
|
|
146
|
+
get maxDeckBoxes() {
|
|
147
|
+
return 8 + 2 * this.qualityBonus;
|
|
148
|
+
}
|
|
149
|
+
get deckDamage() {
|
|
150
|
+
return this._deckDamage;
|
|
151
|
+
}
|
|
152
|
+
/** Returns boxes actually taken. Full track = bricked. */
|
|
153
|
+
takeDeckDamage(boxes) {
|
|
154
|
+
const taken = Math.max(0, Math.floor(boxes));
|
|
155
|
+
this._deckDamage = Math.min(this.maxDeckBoxes, this._deckDamage + taken);
|
|
156
|
+
return taken;
|
|
157
|
+
}
|
|
158
|
+
get isBricked() {
|
|
159
|
+
return this._deckDamage >= this.maxDeckBoxes;
|
|
160
|
+
}
|
|
161
|
+
/** Clears boxes; returns how many were actually repaired. */
|
|
162
|
+
repairDeck(boxes) {
|
|
163
|
+
const before = this._deckDamage;
|
|
164
|
+
this._deckDamage = Math.max(0, before - Math.max(0, Math.floor(boxes)));
|
|
165
|
+
return before - this._deckDamage;
|
|
166
|
+
}
|
|
167
|
+
/** The deck track, same rendering as the body's condition monitors. */
|
|
168
|
+
deckSummary() {
|
|
169
|
+
const max = this.maxDeckBoxes;
|
|
170
|
+
const filled = Math.min(max, this._deckDamage);
|
|
171
|
+
return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
|
|
172
|
+
}
|
|
79
173
|
supportsAddiction(addiction) {
|
|
80
174
|
return this.satisfiesLifestyle?.addictionTypes?.includes(addiction) ?? false;
|
|
81
175
|
}
|
|
@@ -83,8 +177,17 @@ export class Item extends AbstractItem {
|
|
|
83
177
|
return this.satisfiesLifestyle?.habitTags?.includes(habit) ?? false;
|
|
84
178
|
}
|
|
85
179
|
// --- Currency Support ---
|
|
180
|
+
/**
|
|
181
|
+
* The generation schema explicitly allows "currencyAmount" on either a
|
|
182
|
+
* "Credstick" or "CurrencyContainer" item (see scene-seed-generator.ts),
|
|
183
|
+
* but this only ever recognized the latter -- so a Credstick's nuyen
|
|
184
|
+
* silently never got set at all (item-factory.ts gates addCurrency() on
|
|
185
|
+
* this), and never counted toward the inventory panel's Total. Found via
|
|
186
|
+
* a real session where a 1,000-nuyen credstick added nothing to the
|
|
187
|
+
* displayed total.
|
|
188
|
+
*/
|
|
86
189
|
isCurrencyCarrier() {
|
|
87
|
-
return this.category === Category.CurrencyContainer;
|
|
190
|
+
return this.category === Category.CurrencyContainer || this.category === Category.Credstick;
|
|
88
191
|
}
|
|
89
192
|
get currencyAmount() {
|
|
90
193
|
return this._currencyAmount;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/models/item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"item.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/models/item.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,8 +10,11 @@ export interface INPCConstructorConfig {
|
|
|
10
10
|
export declare class NPC extends Player {
|
|
11
11
|
private _dialogue;
|
|
12
12
|
private _description;
|
|
13
|
+
private _appearance?;
|
|
14
|
+
private hasAIAppearance;
|
|
13
15
|
private _scene?;
|
|
14
16
|
private _commandHelp;
|
|
17
|
+
private _objective?;
|
|
15
18
|
private _history;
|
|
16
19
|
private readonly _maxHistory;
|
|
17
20
|
private commandRegistry;
|
|
@@ -25,9 +28,31 @@ export declare class NPC extends Player {
|
|
|
25
28
|
set scene(scene: Scene);
|
|
26
29
|
get description(): string;
|
|
27
30
|
set description(description: string);
|
|
31
|
+
get appearance(): string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Generates a spoiler-free, player-facing first impression once per scene
|
|
34
|
+
* load. Deliberately kept separate from `_description`, which holds
|
|
35
|
+
* GM-only behavior notes (e.g. "will kill on sight") that would ruin the
|
|
36
|
+
* game if surfaced through `look at <npc>`.
|
|
37
|
+
*/
|
|
38
|
+
ensureAIAppearance(sceneContext: string): Promise<void>;
|
|
28
39
|
act(commandString: string): Promise<void>;
|
|
29
40
|
hear(actor: Player, message: string): Promise<void>;
|
|
30
41
|
see(actor: Player, verb: string, details?: string): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* The NPC's own condition, in words its AI can act on -- without this,
|
|
44
|
+
* an NPC literally cannot perceive being hurt: found via a real session
|
|
45
|
+
* where a nearly-dead fighter battled on exactly like a fresh one,
|
|
46
|
+
* because nothing ever put its wounds in its prompt. The badly-wounded
|
|
47
|
+
* nudge is what lets flee/surrender/beg behavior emerge.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Off-plane natives get told WHAT they are, so the AI plays ice like a
|
|
51
|
+
* program and a spirit like a spirit -- and neither wanders off its
|
|
52
|
+
* post or tries to pick up physical objects it can't touch.
|
|
53
|
+
*/
|
|
54
|
+
private buildPlaneSummary;
|
|
55
|
+
private buildConditionSummary;
|
|
31
56
|
private _addToHistory;
|
|
32
57
|
private getHistorySummary;
|
|
33
58
|
speak(message?: string): string;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { capitalCase } from 'change-case';
|
|
2
2
|
import { Player } from './player.js';
|
|
3
3
|
import { Door } from './door.js';
|
|
4
|
-
import {
|
|
4
|
+
import { AI } from '../../../../tools/ai/ai.class.js';
|
|
5
|
+
import { CALL_ICON, CALL_COLOR } from '../utilities/comm-style.js';
|
|
5
6
|
export class NPC extends Player {
|
|
6
7
|
_dialogue;
|
|
7
8
|
_description;
|
|
9
|
+
_appearance;
|
|
10
|
+
hasAIAppearance = false;
|
|
8
11
|
_scene;
|
|
9
12
|
_commandHelp;
|
|
13
|
+
// An evolving one-line goal, revised by the AI as significant events happen.
|
|
14
|
+
// Purely in-memory -- gives an NPC a throughline across a session without
|
|
15
|
+
// needing any persistence.
|
|
16
|
+
_objective;
|
|
10
17
|
_history = [];
|
|
11
18
|
_maxHistory = 200;
|
|
12
19
|
commandRegistry;
|
|
@@ -26,23 +33,57 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
26
33
|
|
|
27
34
|
- go <direction> — move in a direction like north, south, east, west
|
|
28
35
|
- say <message> — speak a message
|
|
29
|
-
- take <item name> — pick up an item from the current room
|
|
36
|
+
- take <item name> — pick up an item from the current room ("take all" sweeps everything here, but only AFTER you have searched this room)
|
|
30
37
|
- drop <item name> — drop an item
|
|
31
|
-
- use <item name> — use an item
|
|
38
|
+
- use <item name> — use an item (a Medical item like a medkit heals your PHYSICAL damage;
|
|
39
|
+
a StimPatch item recovers your STUN but costs 1 physical as a crash -- both are consumed.
|
|
40
|
+
Use them if you're hurt and carrying one)
|
|
41
|
+
- rest — recover Stun by resting. Only works in a Residence room or a room with no one
|
|
42
|
+
else present; never rest mid-fight
|
|
32
43
|
- equip <item name> — equip an item (e.g., equip a weapon to use)
|
|
33
44
|
- equip all - equip all wearable items in inventory
|
|
34
45
|
- unequip <item name> — remove an equipped item
|
|
35
46
|
- unlock <door name> — unlock a door (if you have the key or solution)
|
|
36
47
|
- lock <door name> — lock a door
|
|
37
|
-
- solve <
|
|
48
|
+
- solve <answer> — attempt to solve the puzzle in this room by stating the actual answer (safe, always works if correct)
|
|
49
|
+
- pick — attempt to pick the lock on the puzzle in this room without knowing the answer (50/50, a failure sets off an alarm)
|
|
50
|
+
- hack — attempt to hack the terminal/system in this room without knowing the answer (50/50, a failure trips a countermeasure)
|
|
51
|
+
- cast <spell> [at <target>] — cast a spell you know. Requires being Awakened
|
|
52
|
+
(innate talent -- you'll be told if you are) OR carrying that spell's formula
|
|
53
|
+
item or a grimoire (a Focus item boosts your pool either way). Spells:
|
|
54
|
+
"manabolt" (combat damage resisted by body, not armor), "heal [on <ally>]" (heal
|
|
55
|
+
physical wounds -- your own by default, or an ally's in your room; healing a
|
|
56
|
+
wounded ally is a strong support move), "armor [on <ally>]" (sustained protection
|
|
57
|
+
on yourself or an ally; its bearer gets +soak at -2 dice to all else; cast at them
|
|
58
|
+
again to release). EVERY cast costs drain -- stun damage to YOU the caster; fill
|
|
59
|
+
your stun track and you're out cold. Never cast if you're mundane with no magic gear.
|
|
60
|
+
- cast — bare "cast" attempts to magically bypass the ward/barrier in this room
|
|
61
|
+
without knowing the answer (requires the same talent or gear; a failure is fatal)
|
|
38
62
|
- search — search the room for hidden things
|
|
39
63
|
- look — look around the current room
|
|
40
64
|
- look at <target> - looks at an item, player, or direction
|
|
41
65
|
- store <item name> — store an item (in bank, safe, etc.)
|
|
42
66
|
- give <item name> to <target> — give an item to someone
|
|
43
67
|
- talk-to <target> — initiate conversation with another character
|
|
68
|
+
- call <target> [message] — call someone remotely over a commlink (works even if they're not in your room; requires having a commlink equipped, and the target having one too)
|
|
69
|
+
- end-call — hang up your current commlink call
|
|
70
|
+
- silent — toggle your commlink broadcast. While running silent, you can't place
|
|
71
|
+
calls and nobody can call YOU. Toggle again to go loud.
|
|
44
72
|
- read <target> - Reads an item of Category "Book"
|
|
45
|
-
-
|
|
73
|
+
- attack <target> — attack someone in your room. Resolved as opposed dice-pool combat
|
|
74
|
+
(your weapon and armor matter); an initiative roll decides who strikes first each
|
|
75
|
+
exchange (fast fighters can even land an extra strike), the target strikes back, and
|
|
76
|
+
it usually takes several exchanges to bring someone down. Only start a fight your
|
|
77
|
+
character would actually risk dying in. If your target flees the room, a failed
|
|
78
|
+
attack tells you exactly where they went and which direction to "go" -- CHASE them
|
|
79
|
+
with that instead of attacking the empty room again.
|
|
80
|
+
- kill <target> - alias for attack: real combat, NOT an instant kill.
|
|
81
|
+
- subdue <target> — take someone down ALIVE: stun damage, bare-handed, slower than
|
|
82
|
+
shooting; they retaliate lethally. Use when you want them breathing.
|
|
83
|
+
- reload — refill your equipped firearm (consumes an Ammunition item from your pack).
|
|
84
|
+
Firearms run dry: if your attack reports "dry", reload or switch to melee.
|
|
85
|
+
Fleeing someone you just fought gives them one free parting shot at your back
|
|
86
|
+
(and fleeing enemies give YOU one).
|
|
46
87
|
`.trim();
|
|
47
88
|
}
|
|
48
89
|
set scene(scene) {
|
|
@@ -54,11 +95,46 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
54
95
|
set description(description) {
|
|
55
96
|
this._description = description;
|
|
56
97
|
}
|
|
98
|
+
get appearance() {
|
|
99
|
+
return this._appearance;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Generates a spoiler-free, player-facing first impression once per scene
|
|
103
|
+
* load. Deliberately kept separate from `_description`, which holds
|
|
104
|
+
* GM-only behavior notes (e.g. "will kill on sight") that would ruin the
|
|
105
|
+
* game if surfaced through `look at <npc>`.
|
|
106
|
+
*/
|
|
107
|
+
async ensureAIAppearance(sceneContext) {
|
|
108
|
+
if (this.hasAIAppearance)
|
|
109
|
+
return;
|
|
110
|
+
if (!AI.isConfigured())
|
|
111
|
+
return;
|
|
112
|
+
const prompt = `
|
|
113
|
+
You are a game narrator. Write a brief (2-3 sentence), visually evocative first-impression
|
|
114
|
+
description of a character's appearance and demeanor, in a gritty cyberpunk tone.
|
|
115
|
+
|
|
116
|
+
Scene context: ${sceneContext}
|
|
117
|
+
|
|
118
|
+
Character name: ${this.name}
|
|
119
|
+
|
|
120
|
+
Only describe what a stranger would immediately notice on first meeting them. Do not
|
|
121
|
+
reveal hidden motives, secret plans, backstory, or intentions. Respond with ONLY the
|
|
122
|
+
description text.`;
|
|
123
|
+
try {
|
|
124
|
+
const reply = await AI.ask(prompt);
|
|
125
|
+
this._appearance = reply;
|
|
126
|
+
this.hasAIAppearance = true;
|
|
127
|
+
this.logger.write(`Generated AI appearance for ${this.name}`);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
this.logger.write(`AI fallback for appearance (${this.name}): ${err}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
57
133
|
async act(commandString) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
134
|
+
const parsed = await this.commandRegistry.parse(commandString, this);
|
|
135
|
+
if (parsed) {
|
|
60
136
|
// Capture the result of the command
|
|
61
|
-
const result = await command.execute();
|
|
137
|
+
const result = await parsed.command.execute(parsed.args);
|
|
62
138
|
await this.recursiveReflect(commandString, result);
|
|
63
139
|
}
|
|
64
140
|
else {
|
|
@@ -68,6 +144,11 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
68
144
|
async hear(actor, message) {
|
|
69
145
|
if (actor === this || actor.name === this.name)
|
|
70
146
|
return;
|
|
147
|
+
// Cross-plane silence (see Player.canPerceive): a meat guard never
|
|
148
|
+
// hears a Matrix persona or an astral form, so its AI never reacts to
|
|
149
|
+
// things its character couldn't possibly know about.
|
|
150
|
+
if (!this.canPerceive(actor))
|
|
151
|
+
return;
|
|
71
152
|
// Player wakes us up
|
|
72
153
|
if (!(actor instanceof NPC)) {
|
|
73
154
|
if (this.waitingForPlayer)
|
|
@@ -100,6 +181,9 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
100
181
|
try {
|
|
101
182
|
if (actor === this || actor.name === this.name)
|
|
102
183
|
return;
|
|
184
|
+
// Same cross-plane blindness as hear() above.
|
|
185
|
+
if (!this.canPerceive(actor))
|
|
186
|
+
return;
|
|
103
187
|
if (!(actor instanceof NPC)) {
|
|
104
188
|
if (this.waitingForPlayer)
|
|
105
189
|
this.logger.write(`[DEBUG] ${this.name} received player action, waking up.`);
|
|
@@ -129,6 +213,42 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
129
213
|
throw new Error(error);
|
|
130
214
|
}
|
|
131
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* The NPC's own condition, in words its AI can act on -- without this,
|
|
218
|
+
* an NPC literally cannot perceive being hurt: found via a real session
|
|
219
|
+
* where a nearly-dead fighter battled on exactly like a fresh one,
|
|
220
|
+
* because nothing ever put its wounds in its prompt. The badly-wounded
|
|
221
|
+
* nudge is what lets flee/surrender/beg behavior emerge.
|
|
222
|
+
*/
|
|
223
|
+
/**
|
|
224
|
+
* Off-plane natives get told WHAT they are, so the AI plays ice like a
|
|
225
|
+
* program and a spirit like a spirit -- and neither wanders off its
|
|
226
|
+
* post or tries to pick up physical objects it can't touch.
|
|
227
|
+
*/
|
|
228
|
+
buildPlaneSummary() {
|
|
229
|
+
if (this.plane === 'matrix') {
|
|
230
|
+
return `You are ICE -- a Matrix security program. You exist ONLY on the grid: you perceive and fight jacked-in personas, never the physical world. Defend your host node; do not leave it. You speak in clipped machine terms. You cannot take physical items, and "surrender" is not in your code -- you crash or you purge the intruder.`;
|
|
231
|
+
}
|
|
232
|
+
if (this.plane === 'astral') {
|
|
233
|
+
return `You are a SPIRIT -- an astral entity. You exist ONLY on the astral plane: you perceive the meat world dimly as auras but can only interact with astral beings (like projecting mages). You speak the way something made of will and memory speaks. You cannot touch physical objects.`;
|
|
234
|
+
}
|
|
235
|
+
return '';
|
|
236
|
+
}
|
|
237
|
+
buildConditionSummary() {
|
|
238
|
+
if (this.damageTaken === 0 && this.stunTaken === 0) {
|
|
239
|
+
return `You are unhurt.`;
|
|
240
|
+
}
|
|
241
|
+
const parts = [`Your current condition: physical damage ${this.damageTaken}/${this.maxConditionBoxes}, stun ${this.stunTaken}/${this.maxStunBoxes}${this.woundModifier < 0 ? ` (wound penalty ${this.woundModifier} dice on everything you attempt)` : ''}.`];
|
|
242
|
+
const nearDeath = this.damageTaken >= this.maxConditionBoxes - 3;
|
|
243
|
+
const nearCollapse = this.stunTaken >= this.maxStunBoxes - 3;
|
|
244
|
+
if (nearDeath || nearCollapse) {
|
|
245
|
+
parts.push(`You are BADLY WOUNDED -- ${nearDeath ? 'a solid hit or two will kill you' : 'you are close to blacking out'}. Your life matters to you: seriously consider fleeing (go <direction>), surrendering (the literal command "surrender" -- it visibly ends the fight and an honorable opponent will hold fire), begging off, or de-escalating instead of fighting on. Only keep fighting if dying for this would truly be in character.`);
|
|
246
|
+
}
|
|
247
|
+
else if (this.woundModifier <= -2) {
|
|
248
|
+
parts.push(`You are hurt badly enough that it's affecting you -- weigh whether this fight is still worth it.`);
|
|
249
|
+
}
|
|
250
|
+
return parts.join(' ');
|
|
251
|
+
}
|
|
132
252
|
_addToHistory(entry) {
|
|
133
253
|
this._history.push(entry);
|
|
134
254
|
if (this._history.length > this._maxHistory) {
|
|
@@ -151,9 +271,16 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
151
271
|
return;
|
|
152
272
|
}
|
|
153
273
|
const historySummary = this.getHistorySummary();
|
|
274
|
+
const worldEventsSummary = this._scene?.getWorldEventsSummary() ?? 'Nothing notable has happened elsewhere yet.';
|
|
275
|
+
const objectiveSummary = this._objective
|
|
276
|
+
? `Your current objective: ${this._objective}`
|
|
277
|
+
: `You don't have a specific objective right now.`;
|
|
154
278
|
// Build the prompt context
|
|
155
279
|
const context = `
|
|
156
280
|
You are an NPC named ${this.name}.
|
|
281
|
+
${this.buildPlaneSummary()}
|
|
282
|
+
${objectiveSummary}
|
|
283
|
+
${this.buildConditionSummary()}
|
|
157
284
|
|
|
158
285
|
You just performed the action: "${commandString}"
|
|
159
286
|
With the result: "${result}"
|
|
@@ -167,14 +294,18 @@ Based on this, what is the most logical next in-game command (from the following
|
|
|
167
294
|
|
|
168
295
|
This is what has already happened:
|
|
169
296
|
${historySummary}
|
|
297
|
+
|
|
298
|
+
${worldEventsSummary}
|
|
170
299
|
`;
|
|
171
|
-
// Send to
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
300
|
+
// Send to the configured AI provider
|
|
301
|
+
let reply;
|
|
302
|
+
try {
|
|
303
|
+
reply = await AI.ask(context);
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
this.logger.write(`${this.name} AI reflection failed: ${err}`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
178
309
|
if (!reply || reply.toLowerCase().includes('wait')) {
|
|
179
310
|
this.logger.write(`${this.name} is finished reflecting for now.`);
|
|
180
311
|
return;
|
|
@@ -183,9 +314,9 @@ ${historySummary}
|
|
|
183
314
|
const nextCommandMatch = reply.match(/^\[COMMAND\]\s*(.*)$/i);
|
|
184
315
|
if (nextCommandMatch && nextCommandMatch[1]) {
|
|
185
316
|
const nextCommand = nextCommandMatch[1];
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
const nextResult = await command.execute();
|
|
317
|
+
const parsed = await this.commandRegistry.parse(nextCommand, this);
|
|
318
|
+
if (parsed) {
|
|
319
|
+
const nextResult = await parsed.command.execute(parsed.args);
|
|
189
320
|
if (nextResult) {
|
|
190
321
|
this._addToHistory(nextResult);
|
|
191
322
|
}
|
|
@@ -196,12 +327,12 @@ ${historySummary}
|
|
|
196
327
|
}
|
|
197
328
|
}
|
|
198
329
|
else {
|
|
199
|
-
this.logger.write('No valid command parsed from
|
|
330
|
+
this.logger.write('No valid command parsed from AI reflection.');
|
|
200
331
|
}
|
|
201
332
|
}
|
|
202
333
|
async respondTo(actor, message) {
|
|
203
334
|
this.logger.write('Attempting to respond');
|
|
204
|
-
if (!
|
|
335
|
+
if (!AI.isConfigured()) {
|
|
205
336
|
this.logger.write(`No AI available`);
|
|
206
337
|
await actor.hear(this, this.speak());
|
|
207
338
|
return;
|
|
@@ -224,10 +355,48 @@ ${historySummary}
|
|
|
224
355
|
? `The overall storyline for this scene is: ${this._scene.story}`
|
|
225
356
|
: `This scene has no overall storyline`;
|
|
226
357
|
const historySummary = this.getHistorySummary();
|
|
358
|
+
const worldEventsSummary = this._scene?.getWorldEventsSummary() ?? 'Nothing notable has happened elsewhere yet.';
|
|
359
|
+
const objectiveSummary = this._objective
|
|
360
|
+
? `Your current objective: ${this._objective}`
|
|
361
|
+
: `You don't have a specific objective yet -- if this event gives you one, set it.`;
|
|
362
|
+
// Scripted plot facts from scene generation (the job, the target, the
|
|
363
|
+
// stakes) previously only surfaced in the non-AI fallback speak() --
|
|
364
|
+
// never in this prompt, so a live AI reply had no way to know them and
|
|
365
|
+
// could never actually brief the player.
|
|
366
|
+
const dialogueSummary = safeArray(this._dialogue, 'dialogue').length > 0
|
|
367
|
+
? `Key things you know and may want to bring up naturally in conversation (do not recite these verbatim -- weave them into your own words): ${this._dialogue.join(' | ')}`
|
|
368
|
+
: `You have no specific scripted talking points.`;
|
|
369
|
+
// Money talk is mechanically real: the engine pays exactly
|
|
370
|
+
// winCondition.payout on completion, so a freely-invented figure in
|
|
371
|
+
// dialogue becomes a broken promise -- found via a real session where
|
|
372
|
+
// the fixer offered "50k" and the job actually paid 2000.
|
|
373
|
+
const payout = this._scene?.getWinCondition?.()?.payout;
|
|
374
|
+
const payoutSummary = payout
|
|
375
|
+
? `The agreed payout for the current job is EXACTLY ${payout} nuyen. Any time you mention payment, reward, or what the job is worth, use exactly this figure -- never invent a different one. Payment transfers automatically when the job completes; you never need to hand it over.`
|
|
376
|
+
: '';
|
|
377
|
+
// If nothing else has happened to/around this NPC yet this scene, this
|
|
378
|
+
// is the first time anyone's reached out -- nudge toward volunteering
|
|
379
|
+
// the job rather than waiting to be asked, which real playtesting
|
|
380
|
+
// showed otherwise doesn't happen reliably. hear() already records the
|
|
381
|
+
// current message into history before calling respondTo(), so "first
|
|
382
|
+
// contact" means history has just this one entry, not zero.
|
|
383
|
+
const firstContactNudge = this._history.length <= 1
|
|
384
|
+
? `This is the first contact anyone has made with you this scene. If you have a job, objective, or key information for them (see above), proactively bring it up now instead of waiting to be asked.`
|
|
385
|
+
: '';
|
|
227
386
|
const npcDescription = this.description
|
|
228
387
|
? `This NPC has the following characteristics: ${this._description}`
|
|
229
388
|
: `This NPC is non-descript.`;
|
|
230
389
|
this.logger.write(npcDescription);
|
|
390
|
+
// Casting is stat/gear-gated (see cast.ts), but this prompt is all the
|
|
391
|
+
// NPC "knows" about itself -- without an explicit line here, a seeded
|
|
392
|
+
// Awakened mage has no way to realize it can sling spells, and a
|
|
393
|
+
// mundane may hallucinate that it can.
|
|
394
|
+
const magicSummary = this.isAwakened()
|
|
395
|
+
? `You are AWAKENED (magic ${this.magic}): you can cast manabolt, heal, and armor innately, no gear needed.`
|
|
396
|
+
: this.isCaster()
|
|
397
|
+
? `You are mundane but carry magic gear, so you can cast the spells your formulas/grimoire teach.`
|
|
398
|
+
: `You are mundane with no magic gear: you CANNOT cast spells at all.`;
|
|
399
|
+
const conditionSummary = this.buildConditionSummary();
|
|
231
400
|
const lifestyleSummary = Array.isArray(this.lifestyleChoices) && this.lifestyleChoices.length > 0
|
|
232
401
|
? `This NPC has the following lifestyle characteristics: ${this.lifestyleChoices.join(', ')}.`
|
|
233
402
|
: `This NPC has no defined lifestyle preferences.`;
|
|
@@ -306,13 +475,22 @@ You are an NPC named ${this.name} in a gritty cyberpunk text adventure game.
|
|
|
306
475
|
- Attempt to use [COMMAND] when appropriate for interacting with the environment. (e.g., "take <item>" when picking up)
|
|
307
476
|
- You should only provide one [COMMAND] at a time.
|
|
308
477
|
- Do not respond on behalf of another player or NPC
|
|
478
|
+
- If this event genuinely changes what you're trying to accomplish, include a separate line:
|
|
479
|
+
[OBJECTIVE] <your new one-sentence objective>
|
|
480
|
+
Only include this line if your objective has actually changed. Do not include it otherwise.
|
|
309
481
|
|
|
310
482
|
Your objective is to drive the story line.
|
|
311
483
|
${story}
|
|
484
|
+
${firstContactNudge}
|
|
312
485
|
|
|
313
486
|
${this._commandHelp}
|
|
314
487
|
|
|
315
488
|
${npcDescription}
|
|
489
|
+
${conditionSummary}
|
|
490
|
+
${magicSummary}
|
|
491
|
+
${objectiveSummary}
|
|
492
|
+
${dialogueSummary}
|
|
493
|
+
${payoutSummary}
|
|
316
494
|
|
|
317
495
|
${roomDesc}
|
|
318
496
|
${formattedExitsStr}
|
|
@@ -326,31 +504,13 @@ Your objective is to drive the story line.
|
|
|
326
504
|
|
|
327
505
|
${historySummary}
|
|
328
506
|
|
|
507
|
+
${worldEventsSummary}
|
|
508
|
+
|
|
329
509
|
${actor.name}: ${message}
|
|
330
510
|
`.trim();
|
|
331
|
-
// ----------
|
|
511
|
+
// ---------- AI call + parsing (defensive) ----------
|
|
332
512
|
try {
|
|
333
|
-
const
|
|
334
|
-
prompt,
|
|
335
|
-
openApiKey: process.env.OPENAI_API_KEY,
|
|
336
|
-
model: 'gpt-4.1'
|
|
337
|
-
});
|
|
338
|
-
let parsed;
|
|
339
|
-
try {
|
|
340
|
-
parsed = JSON.parse(result?.data ?? '{}');
|
|
341
|
-
}
|
|
342
|
-
catch (e) {
|
|
343
|
-
this.logger.error(`[ERROR][${this.name}] JSON.parse failed on result.data: ${e}`);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (parsed?.error) {
|
|
347
|
-
throw new Error(parsed.error.message ?? 'Unknown GPT error');
|
|
348
|
-
}
|
|
349
|
-
const reply = parsed?.choices?.[0]?.message?.content?.trim?.();
|
|
350
|
-
if (!reply || typeof reply !== 'string') {
|
|
351
|
-
this.logger.write(`[DEBUG][${this.name}] Empty or invalid reply from GPT; no action taken.`);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
513
|
+
const reply = await AI.ask(prompt);
|
|
354
514
|
this._addToHistory(reply);
|
|
355
515
|
const lines = toLines(reply);
|
|
356
516
|
// Spoken lines
|
|
@@ -359,8 +519,17 @@ Your objective is to drive the story line.
|
|
|
359
519
|
const commandLines = lines.filter(line => line.startsWith('[COMMAND]') &&
|
|
360
520
|
!line.startsWith('[COMMAND] say') &&
|
|
361
521
|
!line.startsWith('[COMMAND] talk-to'));
|
|
362
|
-
//
|
|
363
|
-
const
|
|
522
|
+
// Objective updates (parsed out before exposition, never shown to the player)
|
|
523
|
+
const objectiveLines = lines.filter(line => line.startsWith('[OBJECTIVE]'));
|
|
524
|
+
// Exposition (non-command, non-objective lines)
|
|
525
|
+
const expositionLines = lines.filter(line => !line.startsWith('[COMMAND]') && !line.startsWith('[OBJECTIVE]'));
|
|
526
|
+
if (objectiveLines.length > 0) {
|
|
527
|
+
const newObjective = objectiveLines[objectiveLines.length - 1].replace('[OBJECTIVE]', '').trim();
|
|
528
|
+
if (newObjective.length > 0) {
|
|
529
|
+
this._objective = newObjective;
|
|
530
|
+
this.logger.write(`${this.name}'s objective is now: ${newObjective}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
364
533
|
// Speak each say command
|
|
365
534
|
for (const spokenText of spokenLines) {
|
|
366
535
|
const commandText = (spokenText ?? '').replace('[COMMAND]', '').trim();
|
|
@@ -375,10 +544,14 @@ Your objective is to drive the story line.
|
|
|
375
544
|
// continue; don't throw so we can process remaining lines
|
|
376
545
|
}
|
|
377
546
|
}
|
|
378
|
-
// Log exposition
|
|
547
|
+
// Log exposition. Tagged distinctly while this NPC is mid-call (see
|
|
548
|
+
// call.ts/say.ts) so a comm exchange reads as a different "space"
|
|
549
|
+
// than in-person dialogue, even though it renders in the same log.
|
|
550
|
+
const inCall = !!this.activeCallPartner;
|
|
551
|
+
const prefix = inCall ? `${CALL_ICON} ` : '';
|
|
379
552
|
for (const exposition of expositionLines) {
|
|
380
553
|
if (exposition?.length) {
|
|
381
|
-
this.logger.logWithColor(`${exposition}`, 'blue');
|
|
554
|
+
this.logger.logWithColor(`${prefix}${exposition}`, inCall ? CALL_COLOR : 'blue');
|
|
382
555
|
}
|
|
383
556
|
}
|
|
384
557
|
// Execute other commands
|
|
@@ -397,7 +570,7 @@ Your objective is to drive the story line.
|
|
|
397
570
|
}
|
|
398
571
|
}
|
|
399
572
|
catch (err) {
|
|
400
|
-
this.logger.write(`
|
|
573
|
+
this.logger.write(`AI fallback due to error: ${err}`);
|
|
401
574
|
// surface a consistent Error instance, but don't double-wrap Errors
|
|
402
575
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
403
576
|
throw e;
|