@pilaf/framework 1.2.2 → 1.3.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.
@@ -0,0 +1,325 @@
1
+ /**
2
+ * CorrelationUtils - Server-side correlation for player actions
3
+ *
4
+ * Provides utilities for correlating player actions with server-side
5
+ * confirmation events. Works with or without EventObserver:
6
+ *
7
+ * - WITH EventObserver: Event-driven correlation (faster, more reliable)
8
+ * - WITHOUT EventObserver: Timeout-based fallback (simple, compatible)
9
+ *
10
+ * Responsibilities (MECE):
11
+ * - ONLY: Wait for server confirmation of player actions
12
+ * - NOT: Execute player actions (use bot methods directly)
13
+ * - NOT: Parse server logs (EventObserver/LogParser's responsibility)
14
+ * - NOT: Collect logs (LogCollector's responsibility)
15
+ */
16
+
17
+ /**
18
+ * Simple glob pattern matcher
19
+ *
20
+ * Supports:
21
+ * - * : matches any sequence of characters
22
+ * - ? : matches any single character
23
+ *
24
+ * @private
25
+ * @param {string} text - Text to match against
26
+ * @param {string} pattern - Glob pattern (e.g., "*placed*", "entity.*")
27
+ * @returns {boolean} True if pattern matches
28
+ */
29
+ function _matchPattern(text, pattern) {
30
+ if (!text || !pattern) return false;
31
+
32
+ // Convert glob pattern to regex
33
+ // First escape all special regex characters except * and ?
34
+ let regexPattern = pattern
35
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&'); // Escape special regex chars
36
+
37
+ // Then convert * and ? to their regex equivalents
38
+ regexPattern = '^' + regexPattern
39
+ .replace(/\*/g, '.*') // * becomes .*
40
+ .replace(/\?/g, '.') + '$'; // ? becomes .
41
+
42
+ const regex = new RegExp(regexPattern, 'i'); // Case-insensitive
43
+ return regex.test(text);
44
+ }
45
+
46
+ /**
47
+ * Wait for server confirmation of a player action
48
+ *
49
+ * This is the main correlation utility. It waits for a server log event
50
+ * that matches the expected pattern, confirming that the player's action
51
+ * was processed by the server.
52
+ *
53
+ * Supports two modes:
54
+ * 1. Event-driven mode (with EventObserver): Listens for matching events
55
+ * 2. Timeout mode (without EventObserver): Waits for specified timeout
56
+ *
57
+ * @param {Object} storyRunner - StoryRunner instance (provides access to backends)
58
+ * @param {Object} options - Options
59
+ * @param {string} options.pattern - Glob pattern to match in server logs
60
+ * @param {number} [options.timeout=5000] - Timeout in milliseconds
61
+ * @param {boolean} [options.invert=false] - If true, wait for ABSENCE of pattern
62
+ * @param {string} [options.player] - Player name to filter events (optional)
63
+ * @returns {Promise<Object|null>} Matching event object, or null if timeout/inverted
64
+ *
65
+ * @example
66
+ * // Wait for block placement confirmation
67
+ * await CorrelationUtils.waitForServerConfirmation(storyRunner, {
68
+ * pattern: '*Cannot place block*',
69
+ * invert: true,
70
+ * timeout: 3000
71
+ * });
72
+ *
73
+ * @example
74
+ * // Wait for command execution
75
+ * const event = await CorrelationUtils.waitForServerConfirmation(storyRunner, {
76
+ * pattern: '*issued command* /gamemode*',
77
+ * timeout: 5000
78
+ * });
79
+ */
80
+ async function waitForServerConfirmation(storyRunner, options) {
81
+ const {
82
+ pattern = '*',
83
+ timeout = 5000,
84
+ invert = false,
85
+ player = null
86
+ } = options || {};
87
+
88
+ // Try EventObserver mode first (event-driven, faster)
89
+ const eventObserver = _getEventObserver(storyRunner);
90
+
91
+ if (eventObserver && eventObserver.isObserving) {
92
+ return await _waitForEventObserver(eventObserver, {
93
+ pattern,
94
+ timeout,
95
+ invert,
96
+ player
97
+ });
98
+ }
99
+
100
+ // Fall back to timeout mode (simple, works without EventObserver)
101
+ return await _waitForTimeout({ timeout, invert });
102
+ }
103
+
104
+ /**
105
+ * Get EventObserver from StoryRunner's backends
106
+ *
107
+ * Tries multiple strategies to find an available EventObserver:
108
+ * 1. Any player backend's EventObserver (MineflayerBackend)
109
+ * 2. RCON backend's EventObserver (if available)
110
+ *
111
+ * @private
112
+ * @param {Object} storyRunner - StoryRunner instance
113
+ * @returns {EventObserver|null} EventObserver instance or null
114
+ */
115
+ function _getEventObserver(storyRunner) {
116
+ if (!storyRunner || !storyRunner.backends) {
117
+ return null;
118
+ }
119
+
120
+ // Strategy 1: Check player backends (MineflayerBackend may have EventObserver)
121
+ if (storyRunner.backends.players) {
122
+ for (const [username, backend] of storyRunner.backends.players) {
123
+ if (backend && typeof backend.getEventObserver === 'function') {
124
+ const observer = backend.getEventObserver();
125
+ if (observer && observer.isObserving) {
126
+ return observer;
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ // Strategy 2: Check RCON backend (unlikely to have EventObserver, but future-proof)
133
+ if (storyRunner.backends.rcon && typeof storyRunner.backends.rcon.getEventObserver === 'function') {
134
+ const observer = storyRunner.backends.rcon.getEventObserver();
135
+ if (observer && observer.isObserving) {
136
+ return observer;
137
+ }
138
+ }
139
+
140
+ return null;
141
+ }
142
+
143
+ /**
144
+ * Wait for event using EventObserver (event-driven mode)
145
+ *
146
+ * @private
147
+ * @param {EventObserver} eventObserver - EventObserver instance
148
+ * @param {Object} options - Options
149
+ * @returns {Promise<Object|null>} Matching event or null
150
+ */
151
+ async function _waitForEventObserver(eventObserver, options) {
152
+ const { pattern, timeout, invert, player } = options;
153
+
154
+ return new Promise((resolve) => {
155
+ let timer = null;
156
+ let unsubscribe = null;
157
+
158
+ const cleanup = (result) => {
159
+ if (timer) {
160
+ clearTimeout(timer);
161
+ timer = null;
162
+ }
163
+ if (unsubscribe) {
164
+ unsubscribe();
165
+ unsubscribe = null;
166
+ }
167
+ resolve(result);
168
+ };
169
+
170
+ // Set timeout
171
+ timer = setTimeout(() => {
172
+ cleanup(null); // Timeout - return null
173
+ }, timeout);
174
+
175
+ // Subscribe to all events
176
+ unsubscribe = eventObserver.onEvent('*', (event) => {
177
+ // Filter by player if specified
178
+ if (player && event.data?.player !== player) {
179
+ return;
180
+ }
181
+
182
+ // Check if pattern matches
183
+ const matches = _checkEventMatch(event, pattern);
184
+
185
+ if (invert) {
186
+ // Inverted mode: wait for event that DOESN'T match pattern
187
+ // For inverted mode, we can't easily detect "absence" of events
188
+ // So we just wait for timeout and verify no matching events occurred
189
+ // This is a simplified approach - could be enhanced with event tracking
190
+ if (!matches) {
191
+ // Got a non-matching event, but we can't conclude yet
192
+ // Continue waiting for timeout or matching event
193
+ } else {
194
+ // Got a matching event in inverted mode - this is "bad"
195
+ // We could reject here, but for now we just continue
196
+ // The caller will interpret null as "no matching events during timeout"
197
+ }
198
+ } else {
199
+ // Normal mode: wait for event that MATCHES pattern
200
+ if (matches) {
201
+ cleanup(event); // Found matching event - return it
202
+ }
203
+ }
204
+ });
205
+
206
+ // For inverted mode, we need to track if we saw any matching events
207
+ // This is a simple implementation - could be enhanced
208
+ if (invert) {
209
+ // In inverted mode, we just wait for timeout
210
+ // The assumption is: no matching events = success
211
+ // This is not perfect but works for simple cases
212
+ }
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Wait using simple timeout (fallback mode)
218
+ *
219
+ * @private
220
+ * @param {Object} options - Options
221
+ * @returns {Promise<null>} Always returns null after timeout
222
+ */
223
+ async function _waitForTimeout(options) {
224
+ const { timeout } = options;
225
+ await new Promise(resolve => setTimeout(resolve, timeout));
226
+ return null;
227
+ }
228
+
229
+ /**
230
+ * Check if event matches the pattern
231
+ *
232
+ * Checks multiple fields in the event for pattern matching:
233
+ * - event.type (e.g., "command.issued", "block.broken")
234
+ * - event.message (raw log message)
235
+ * - event.data (additional event data)
236
+ *
237
+ * @private
238
+ * @param {Object} event - Event object from EventObserver
239
+ * @param {string} pattern - Glob pattern to match
240
+ * @returns {boolean} True if pattern matches event
241
+ */
242
+ function _checkEventMatch(event, pattern) {
243
+ if (!event || !pattern) {
244
+ return false;
245
+ }
246
+
247
+ // Check event type
248
+ if (event.type && _matchPattern(event.type, pattern)) {
249
+ return true;
250
+ }
251
+
252
+ // Check raw message (if available)
253
+ if (event.message && _matchPattern(event.message, pattern)) {
254
+ return true;
255
+ }
256
+
257
+ // Check data fields
258
+ if (event.data) {
259
+ // Convert data to string for matching
260
+ const dataStr = JSON.stringify(event.data);
261
+ if (_matchPattern(dataStr, pattern)) {
262
+ return true;
263
+ }
264
+ }
265
+
266
+ return false;
267
+ }
268
+
269
+ /**
270
+ * Get default timeout for an action type
271
+ *
272
+ * Returns reasonable defaults for different action types:
273
+ * - Block actions: 3-5 seconds
274
+ * - Movement: 1-2 seconds
275
+ * - Entity interaction: 3-5 seconds
276
+ * - Commands: 2-3 seconds
277
+ *
278
+ * @param {string} actionType - Action type (e.g., 'break_block', 'place_block')
279
+ * @returns {number} Timeout in milliseconds
280
+ */
281
+ function getDefaultTimeout(actionType) {
282
+ const timeouts = {
283
+ // Block interaction (slowest)
284
+ 'break_block': 5000,
285
+ 'place_block': 5000,
286
+ 'interact_with_block': 3000,
287
+
288
+ // Movement (fast)
289
+ 'move_forward': 2000,
290
+ 'move_backward': 2000,
291
+ 'move_left': 2000,
292
+ 'move_right': 2000,
293
+ 'jump': 1000,
294
+ 'look_at': 1000,
295
+ 'navigate_to': 15000, // Pathfinding can be slow
296
+
297
+ // Entity interaction (medium)
298
+ 'attack_entity': 3000,
299
+ 'interact_with_entity': 5000,
300
+ 'mount_entity': 3000,
301
+ 'dismount': 1000,
302
+
303
+ // Inventory (medium)
304
+ 'drop_item': 2000,
305
+ 'consume_item': 2000,
306
+ 'equip_item': 2000,
307
+ 'swap_inventory_slots': 1000,
308
+
309
+ // Commands (fast)
310
+ 'execute_command': 3000,
311
+ 'execute_player_command': 3000,
312
+ 'chat': 2000,
313
+
314
+ // Default
315
+ 'default': 5000
316
+ };
317
+
318
+ return timeouts[actionType] || timeouts['default'];
319
+ }
320
+
321
+ module.exports = {
322
+ waitForServerConfirmation,
323
+ _matchPattern, // Exported for testing
324
+ getDefaultTimeout
325
+ };
@@ -0,0 +1,344 @@
1
+ /**
2
+ * EntityUtils - Utility functions for working with Mineflayer entities
3
+ *
4
+ * Provides helper methods for finding and querying entities in a
5
+ * Mineflayer bot's entity list.
6
+ *
7
+ * Responsibilities (MECE):
8
+ * - ONLY: Entity lookup and query utilities
9
+ * - NOT: Entity manipulation (use bot methods directly)
10
+ * - NOT: Entity spawning/despawning (server-side actions)
11
+ */
12
+
13
+ /**
14
+ * Entity alias mappings
15
+ * Maps generic entity names to their specific Minecraft entity type names
16
+ * Used when the summon command accepts a generic name but spawns a specific variant
17
+ *
18
+ * @private
19
+ * @constant {Object<string, string[]>}
20
+ */
21
+ const ENTITY_ALIASES = {
22
+ // Generic boat → oak_boat (default boat type in Minecraft)
23
+ 'boat': ['oak_boat', 'boat'],
24
+ // Generic horse → horse (already correct)
25
+ // Generic minecart → minecart (already correct)
26
+ };
27
+
28
+ /**
29
+ * Get all possible entity names to search for an entity
30
+ * Includes the original name plus any known aliases/variants
31
+ *
32
+ * @private
33
+ * @param {string} identifier - Entity identifier to search for
34
+ * @returns {Array<string>} Array of possible entity names to search
35
+ */
36
+ function _getSearchNames(identifier) {
37
+ // Always include the original identifier
38
+ const names = [identifier];
39
+
40
+ // Check if this is an alias for a specific entity type
41
+ if (ENTITY_ALIASES[identifier]) {
42
+ names.push(...ENTITY_ALIASES[identifier]);
43
+ }
44
+
45
+ // For boats, also try all wood variants if searching for generic "boat"
46
+ if (identifier === 'boat') {
47
+ const woodTypes = ['oak', 'birch', 'spruce', 'jungle', 'acacia', 'dark_oak', 'mangrove', 'cherry', 'pale_oak'];
48
+ woodTypes.forEach(wood => {
49
+ names.push(`${wood}_boat`);
50
+ });
51
+ }
52
+
53
+ return [...new Set(names)]; // Remove duplicates
54
+ }
55
+
56
+ /**
57
+ * Find an entity by identifier
58
+ *
59
+ * Searches for an entity using multiple strategies:
60
+ * 1. Direct entity ID (number)
61
+ * 2. Custom name (named entities, mobs with nametags)
62
+ * 3. Display name (player names, entity display names)
63
+ * 4. Entity type name (zombie, skeleton, etc.) with alias support
64
+ *
65
+ * @param {Object} bot - Mineflayer bot instance
66
+ * @param {number|string} identifier - Entity ID, name, or custom name
67
+ * @returns {Object|null} Entity object or null if not found
68
+ *
69
+ * @example
70
+ * // Find by entity ID
71
+ * const entity = EntityUtils.findEntity(bot, 123);
72
+ *
73
+ * @example
74
+ * // Find by custom name
75
+ * const entity = EntityUtils.findEntity(bot, 'MyPet');
76
+ *
77
+ * @example
78
+ * // Find by entity type
79
+ * const entity = EntityUtils.findEntity(bot, 'zombie');
80
+ *
81
+ * @example
82
+ * // Find by alias (boat searches for oak_boat, birch_boat, etc.)
83
+ * const entity = EntityUtils.findEntity(bot, 'boat');
84
+ */
85
+ function findEntity(bot, identifier) {
86
+ if (!bot || !bot.entities) {
87
+ return null;
88
+ }
89
+
90
+ // Strategy 1: Direct entity ID (number)
91
+ if (typeof identifier === 'number') {
92
+ return bot.entities[identifier] || null;
93
+ }
94
+
95
+ const allEntities = Object.values(bot.entities).filter(e => e);
96
+
97
+ // Strategy 2: Try custom name (named mobs, nametags)
98
+ let entity = Object.values(bot.entities).find(e => {
99
+ if (!e) return false;
100
+ const customName = e.customName;
101
+ // Check both string and text component formats
102
+ if (typeof customName === 'string') {
103
+ return customName === identifier;
104
+ }
105
+ if (customName && typeof customName === 'object' && customName.text) {
106
+ return customName.text === identifier;
107
+ }
108
+ return false;
109
+ });
110
+
111
+ if (entity) {
112
+ return entity;
113
+ }
114
+
115
+ // Strategy 3: Try display name (players, some entities)
116
+ entity = Object.values(bot.entities).find(e => {
117
+ if (!e) return false;
118
+ return e.displayName === identifier ||
119
+ (e.username && e.username === identifier);
120
+ });
121
+
122
+ if (entity) {
123
+ return entity;
124
+ }
125
+
126
+ // Strategy 4: Try entity type name with alias support
127
+ const searchNames = _getSearchNames(identifier);
128
+
129
+ entity = Object.values(bot.entities).find(e => {
130
+ if (!e) return false;
131
+ // Check if entity name matches any of the search names
132
+ return searchNames.includes(e.name) ||
133
+ (e.type && searchNames.includes(e.type));
134
+ });
135
+
136
+ if (entity) {
137
+ return entity;
138
+ }
139
+
140
+ return null;
141
+ }
142
+
143
+ /**
144
+ * Calculate distance between two positions
145
+ *
146
+ * @private
147
+ * @param {Object} pos1 - First position {x, y, z}
148
+ * @param {Object} pos2 - Second position {x, y, z}
149
+ * @returns {number} Distance in blocks
150
+ */
151
+ function _calculateDistance(pos1, pos2) {
152
+ if (!pos1 || !pos2) return Infinity;
153
+ const dx = pos2.x - pos1.x;
154
+ const dy = pos2.y - pos1.y;
155
+ const dz = pos2.z - pos1.z;
156
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
157
+ }
158
+
159
+ /**
160
+ * Get the nearest entity of a specific type
161
+ *
162
+ * @param {Object} bot - Mineflayer bot instance
163
+ * @param {string} typeName - Entity type name (e.g., 'zombie', 'player')
164
+ * @param {number} [maxDistance=32] - Maximum search distance in blocks
165
+ * @returns {Object|null} Nearest entity or null if not found
166
+ *
167
+ * @example
168
+ * // Find nearest zombie within 32 blocks
169
+ * const zombie = EntityUtils.getNearestEntity(bot, 'zombie', 32);
170
+ *
171
+ * @example
172
+ * // Find nearest player within 16 blocks
173
+ * const player = EntityUtils.getNearestEntity(bot, 'player', 16);
174
+ */
175
+ function getNearestEntity(bot, typeName, maxDistance = 32) {
176
+ if (!bot || !bot.entities || !bot.entity) {
177
+ return null;
178
+ }
179
+
180
+ const playerPos = bot.entity.position;
181
+
182
+ // Find all entities of the specified type within max distance
183
+ const nearbyEntities = Object.values(bot.entities)
184
+ .filter(e => {
185
+ if (!e || !e.position || !e.name) return false;
186
+ // Match entity type
187
+ return e.name === typeName;
188
+ })
189
+ .map(e => ({
190
+ entity: e,
191
+ distance: _calculateDistance(playerPos, e.position)
192
+ }))
193
+ .filter(e => e.distance <= maxDistance);
194
+
195
+ // Sort by distance (nearest first)
196
+ nearbyEntities.sort((a, b) => a.distance - b.distance);
197
+
198
+ // Return nearest entity or null
199
+ return nearbyEntities.length > 0 ? nearbyEntities[0].entity : null;
200
+ }
201
+
202
+ /**
203
+ * Get all entities of a specific type within distance
204
+ *
205
+ * @param {Object} bot - Mineflayer bot instance
206
+ * @param {string} typeName - Entity type name
207
+ * @param {number} [maxDistance=32] - Maximum search distance in blocks
208
+ * @returns {Array<Object>} Array of matching entities (sorted by distance)
209
+ *
210
+ * @example
211
+ * // Get all zombies within 32 blocks
212
+ * const zombies = EntityUtils.getEntitiesOfType(bot, 'zombie', 32);
213
+ */
214
+ function getEntitiesOfType(bot, typeName, maxDistance = 32) {
215
+ if (!bot || !bot.entities || !bot.entity) {
216
+ return [];
217
+ }
218
+
219
+ const playerPos = bot.entity.position;
220
+
221
+ const nearbyEntities = Object.values(bot.entities)
222
+ .filter(e => {
223
+ if (!e || !e.position || !e.name) return false;
224
+ return e.name === typeName && _calculateDistance(playerPos, e.position) <= maxDistance;
225
+ })
226
+ .map(e => ({
227
+ entity: e,
228
+ distance: _calculateDistance(playerPos, e.position)
229
+ }))
230
+ .sort((a, b) => a.distance - b.distance);
231
+
232
+ return nearbyEntities.map(e => e.entity);
233
+ }
234
+
235
+ /**
236
+ * Find multiple entities by matching a predicate
237
+ *
238
+ * @param {Object} bot - Mineflayer bot instance
239
+ * @param {Function} predicate - Function that returns true for matching entities
240
+ * @param {number} [maxDistance=32] - Maximum search distance in blocks
241
+ * @returns {Array<Object>} Array of matching entities (sorted by distance)
242
+ *
243
+ * @example
244
+ * // Find all hostile mobs with low health
245
+ * const weakHostiles = EntityUtils.findEntities(bot, (e) => {
246
+ * return e.health < 10 && isHostile(e.name);
247
+ * }, 32);
248
+ */
249
+ function findEntities(bot, predicate, maxDistance = 32) {
250
+ if (!bot || !bot.entities || !bot.entity || typeof predicate !== 'function') {
251
+ return [];
252
+ }
253
+
254
+ const playerPos = bot.entity.position;
255
+
256
+ const matchingEntities = Object.values(bot.entities)
257
+ .filter(e => {
258
+ if (!e || !e.position) return false;
259
+ return _calculateDistance(playerPos, e.position) <= maxDistance && predicate(e);
260
+ })
261
+ .map(e => ({
262
+ entity: e,
263
+ distance: _calculateDistance(playerPos, e.position)
264
+ }))
265
+ .sort((a, b) => a.distance - b.distance);
266
+
267
+ return matchingEntities.map(e => e.entity);
268
+ }
269
+
270
+ /**
271
+ * Get distance from player to an entity
272
+ *
273
+ * @param {Object} bot - Mineflayer bot instance
274
+ * @param {Object} entity - Entity object
275
+ * @returns {number|null} Distance in blocks, or null if invalid
276
+ */
277
+ function getDistanceToEntity(bot, entity) {
278
+ if (!bot || !bot.entity || !entity || !entity.position) {
279
+ return null;
280
+ }
281
+
282
+ return _calculateDistance(bot.entity.position, entity.position);
283
+ }
284
+
285
+ /**
286
+ * Check if an entity is alive (has health > 0)
287
+ *
288
+ * @param {Object} entity - Entity object
289
+ * @returns {boolean} True if entity is alive
290
+ */
291
+ function isEntityAlive(entity) {
292
+ if (!entity) return false;
293
+ // Some entities may not have health property
294
+ return entity.health === undefined || entity.health > 0;
295
+ }
296
+
297
+ /**
298
+ * Get entity display name (with fallbacks)
299
+ *
300
+ * Returns the most appropriate name for display:
301
+ * 1. Custom name (nametag)
302
+ * 2. Username (for players)
303
+ * 3. Display name
304
+ * 4. Entity type name
305
+ *
306
+ * @param {Object} entity - Entity object
307
+ * @returns {string} Display name
308
+ */
309
+ function getEntityDisplayName(entity) {
310
+ if (!entity) return 'Unknown';
311
+
312
+ // Try custom name first
313
+ if (entity.customName) {
314
+ if (typeof entity.customName === 'string') {
315
+ return entity.customName;
316
+ }
317
+ if (typeof entity.customName === 'object' && entity.customName.text) {
318
+ return entity.customName.text;
319
+ }
320
+ }
321
+
322
+ // Try username (for players)
323
+ if (entity.username) {
324
+ return entity.username;
325
+ }
326
+
327
+ // Try display name
328
+ if (entity.displayName) {
329
+ return entity.displayName;
330
+ }
331
+
332
+ // Fall back to entity type name
333
+ return entity.name || 'Unknown';
334
+ }
335
+
336
+ module.exports = {
337
+ findEntity,
338
+ getNearestEntity,
339
+ getEntitiesOfType,
340
+ findEntities,
341
+ getDistanceToEntity,
342
+ isEntityAlive,
343
+ getEntityDisplayName
344
+ };
package/lib/index.js CHANGED
@@ -3,6 +3,8 @@ const { PilafReporter } = require('./reporters/pilaf-reporter');
3
3
  const { StoryRunner } = require('./StoryRunner');
4
4
  const { waitForEvents, captureEvents } = require('./helpers/events');
5
5
  const { captureState, compareStates } = require('./helpers/state');
6
+ const { CorrelationUtils } = require('./helpers/correlation');
7
+ const { EntityUtils } = require('./helpers/entities');
6
8
  const { toHaveReceivedLightningStrikes } = require('./matchers/game-matchers');
7
9
  const { createTestContext, cleanupTestContext } = require('./test-context');
8
10
 
@@ -49,6 +51,8 @@ module.exports = {
49
51
  captureEvents,
50
52
  captureState,
51
53
  compareStates,
54
+ CorrelationUtils,
55
+ EntityUtils,
52
56
 
53
57
  // Matchers
54
58
  toHaveReceivedLightningStrikes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pilaf/framework",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "main": "lib/index.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,8 +20,9 @@
20
20
  "dependencies": {
21
21
  "jest": "^29.7.0",
22
22
  "js-yaml": "^4.1.0",
23
- "@pilaf/backends": "1.2.2",
24
- "@pilaf/reporting": "1.2.2"
23
+ "mineflayer-pathfinder": "^2.4.5",
24
+ "@pilaf/backends": "1.3.0",
25
+ "@pilaf/reporting": "1.3.0"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@jest/globals": "^29.7.0"