@hotbunny/hackhub-content-sdk 0.9.4 → 0.9.6

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.
Files changed (3) hide show
  1. package/README.md +24 -9
  2. package/index.d.ts +45 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -39,13 +39,18 @@ export default class MyMod extends Bootstrap {
39
39
 
40
40
  ## Quests
41
41
 
42
- Quests use `CreateData()` to initialize data once when the quest is first claimed. After that, access the data via `this.Data`:
42
+ Quests use a generic type parameter for type-safe data. `CreateData()` initializes the data once when the quest is first claimed. After that, access via `this.Data` and update via `this.SetData()`:
43
43
 
44
44
  ```typescript
45
- import { Quest, Events, Network, RegisterQuest } from "@hotbunny/hackhub-content-sdk";
45
+ import { Quest, Network, RegisterQuest } from "@hotbunny/hackhub-content-sdk";
46
+
47
+ interface InfiltrationData {
48
+ targetIp: string;
49
+ attempts: number;
50
+ }
46
51
 
47
52
  @RegisterQuest
48
- class InfiltrationQuest extends Quest {
53
+ class InfiltrationQuest extends Quest<InfiltrationData> {
49
54
  Name = "Infiltration";
50
55
  Title = "Server Infiltration";
51
56
  Description = "Hack into the target server and download the data.";
@@ -60,26 +65,32 @@ class InfiltrationQuest extends Quest {
60
65
  },
61
66
  ];
62
67
 
63
- CreateData() {
68
+ CreateData(): InfiltrationData {
64
69
  return {
65
70
  targetIp: Network.randomIp(),
71
+ attempts: 0,
66
72
  };
67
73
  }
68
74
 
69
75
  OnStart() {
70
76
  Network.createSubnetNetwork({
71
- ip: this.Data.targetIp,
77
+ ip: this.Data.targetIp, // fully typed
72
78
  type: "ROUTER",
73
79
  ports: [{ external: 22, internal: 22, active: true, service: "ssh" }],
74
80
  users: [Network.createUser({ username: "admin", password: "secret123" })],
75
81
  children: [],
76
82
  });
77
83
 
78
- Events.on("Terminal.NmapScan", (data) => {
79
- if (data.ip === this.Data.targetIp) this.completeObjective("scan");
84
+ // Use this.Events.on() listeners are automatically cleaned up
85
+ // when the quest completes or is abandoned. No memory leaks!
86
+ this.Events.on("Terminal.NmapScan", (data) => {
87
+ if (data.ip === this.Data.targetIp) {
88
+ this.SetData("attempts", this.Data.attempts + 1); // type-safe key & value
89
+ this.completeObjective("scan");
90
+ }
80
91
  });
81
92
 
82
- Events.on("Terminal.SSH.Connected", (data) => {
93
+ this.Events.on("Terminal.SSH.Connected", (data) => {
83
94
  if (data.ip === this.Data.targetIp) this.completeObjective("connect");
84
95
  });
85
96
  }
@@ -96,11 +107,15 @@ Quests can send in-game emails and start phone call dialogs:
96
107
 
97
108
  ```typescript
98
109
  @RegisterQuest
99
- class StoryQuest extends Quest {
110
+ class StoryQuest extends Quest<{ contacted: boolean }> {
100
111
  Name = "StoryQuest";
101
112
  Title = "Story Quest";
102
113
  Objectives = [{ name: "start", description: "Begin the mission" }];
103
114
 
115
+ CreateData() {
116
+ return { contacted: false };
117
+ }
118
+
104
119
  Mails = [
105
120
  { title: "Mission Briefing", content: "Your target is ready. Good luck." },
106
121
  ];
package/index.d.ts CHANGED
@@ -1078,31 +1078,54 @@ export declare abstract class Bootstrap {
1078
1078
  OnModPackageUnloaded(): void;
1079
1079
  }
1080
1080
  /**
1081
- * Base class for mod quests. Mod authors extend this to create custom quests.
1081
+ * Scoped event manager for a quest. All listeners registered via
1082
+ * `this.Events.on()` are automatically removed when the quest
1083
+ * completes or is abandoned, preventing memory leaks.
1084
+ */
1085
+ export declare class QuestEvents {
1086
+ private _listeners;
1087
+ /** @internal */ _globalOn?: (event: string, callback: Function) => () => void;
1088
+ /**
1089
+ * Listen to a game event or custom event. The listener is automatically
1090
+ * removed when the quest completes or is abandoned.
1091
+ */
1092
+ on<T extends string>(event: T, callback: (data: T extends keyof ModEventMap ? ModEventMap[T] : any) => void): () => void;
1093
+ /** Remove a specific listener. */
1094
+ off(event: string, callback: Function): void;
1095
+ /** Remove all listeners registered through this quest. */
1096
+ offAll(): void;
1097
+ }
1098
+ /**
1099
+ * Base class for mod quests. Extend with a generic type for type-safe quest data.
1100
+ *
1101
+ * Use `this.Events.on()` instead of the global `Events.on()` so listeners
1102
+ * are automatically cleaned up when the quest completes or is abandoned.
1082
1103
  *
1083
1104
  * @example
1084
1105
  * ```ts
1085
- * import { Quest, RegisterQuest } from "@hotbunny/hackhub-content-sdk";
1106
+ * interface MyData { targetIp: string; attempts: number; }
1086
1107
  *
1087
1108
  * @RegisterQuest
1088
- * export class HackThePlanet extends Quest {
1109
+ * export class HackThePlanet extends Quest<MyData> {
1089
1110
  * Name = "HackThePlanet";
1090
1111
  * Title = "Hack the Planet";
1091
- * Rewards = { money: 5000 };
1092
1112
  * Objectives = [
1093
1113
  * { name: "scan", description: "Scan the target with nmap" },
1094
- * { name: "exploit", description: "Exploit the vulnerability", unlocksAfter: ["scan"] },
1095
1114
  * ];
1096
1115
  *
1116
+ * CreateData() {
1117
+ * return { targetIp: "192.168.1.50", attempts: 0 };
1118
+ * }
1119
+ *
1097
1120
  * OnStart() {
1098
- * Events.on("Terminal.NmapScan", (data) => {
1099
- * if (data.ip === "192.168.1.50") this.completeObjective("scan");
1121
+ * this.Events.on("Terminal.NmapScan", (data) => {
1122
+ * if (data.ip === this.Data.targetIp) this.completeObjective("scan");
1100
1123
  * });
1101
1124
  * }
1102
1125
  * }
1103
1126
  * ```
1104
1127
  */
1105
- export declare abstract class Quest {
1128
+ export declare abstract class Quest<T extends Record<string, any> = Record<string, any>> {
1106
1129
  abstract Name: string;
1107
1130
  abstract Title: string;
1108
1131
  abstract Objectives: QuestObjectiveDefinition[];
@@ -1130,10 +1153,15 @@ export declare abstract class Quest {
1130
1153
  /** Phone-call dialog tree for this quest. Use this.createDialog(branch) to start. */
1131
1154
  Dialog?: QuestDialogDefinition;
1132
1155
  /** Quest data created by CreateData(). Populated at runtime by the game engine. */
1133
- Data: any;
1156
+ Data: T;
1157
+ /**
1158
+ * Scoped event manager. Listeners registered here are automatically
1159
+ * cleaned up when the quest completes or is abandoned.
1160
+ */
1161
+ Events: QuestEvents;
1134
1162
  /**
1135
1163
  * Called when the quest is first claimed/started.
1136
- * Use this to set up event listeners for objective completion.
1164
+ * Use this to set up event listeners via `this.Events.on()`.
1137
1165
  */
1138
1166
  OnStart(): void | Promise<void>;
1139
1167
  /** Called when all objectives are completed and the quest finishes. */
@@ -1143,15 +1171,19 @@ export declare abstract class Quest {
1143
1171
  /** Called when the quest's objectives listener starts (e.g. after game load). */
1144
1172
  OnObjectivesStart(): void;
1145
1173
  /** Return initial quest data. Override to provide custom data storage. */
1146
- CreateData(): any;
1174
+ CreateData(): T;
1175
+ /** Update a single key in the quest data and persist it. */
1176
+ SetData<K extends keyof T>(key: K, value: T[K]): void;
1147
1177
  /** Complete an objective by its name. */
1148
1178
  completeObjective(name: string): void;
1149
1179
  /** Send a mail from the Mails array by index. */
1150
1180
  sendMail(index: number, from?: string, to?: string): void;
1151
1181
  /** Start a phone-call dialog. Defaults to "default" branch. */
1152
1182
  createDialog(branch?: string, startIndex?: number): void;
1183
+ /** @internal */ _completeObjectiveInternal?: (index: number) => void;
1153
1184
  /** @internal */ _sendMailInternal?: (index: number, from?: string, to?: string) => void;
1154
1185
  /** @internal */ _createDialogInternal?: (branch: string, startIndex: number) => void;
1186
+ /** @internal */ _saveDataInternal?: () => void;
1155
1187
  }
1156
1188
  /**
1157
1189
  * Base class for mod websites. Mod authors extend this to create
@@ -1311,6 +1343,7 @@ export declare abstract class App {
1311
1343
  */
1312
1344
  Exports?: Record<string, any>;
1313
1345
  }
1346
+ type () => void$1 = () => void;
1314
1347
  /**
1315
1348
  * Game events API. Allows mods to listen to in-game events
1316
1349
  * such as terminal commands, file operations, network events, etc.
@@ -1348,7 +1381,7 @@ export declare namespace Events {
1348
1381
  * For known `ModEventMap` keys, the callback is fully typed.
1349
1382
  * For custom string events, the callback receives `any`.
1350
1383
  */
1351
- export function on<T extends string>(event: T, callback: (data: T extends keyof ModEventMap ? ModEventMap[T] : any) => void): () => void;
1384
+ export function on<T extends string>(event: T, callback: (data: T extends keyof ModEventMap ? ModEventMap[T] : any) => void): () => void$1;
1352
1385
  /** Remove a specific event listener. */
1353
1386
  export function off(event: string, callback: Function): void;
1354
1387
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotbunny/hackhub-content-sdk",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Official modding SDK for HackHub - Ultimate Hacker Simulator on Steam. Create custom quests, websites, terminal commands, and desktop apps.",
5
5
  "types": "index.d.ts",
6
6
  "exports": {