@pilaf/backends 1.2.0 → 1.2.2
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 +217 -0
- package/package.json +6 -2
- package/scripts/fix-prismarine-physics.cjs +103 -0
package/README.md
CHANGED
|
@@ -937,6 +937,208 @@ describe('Elemental Dragon Tests', () => {
|
|
|
937
937
|
|
|
938
938
|
---
|
|
939
939
|
|
|
940
|
+
## 🎯 Using QueryHelper with createTestContext
|
|
941
|
+
|
|
942
|
+
When using `@pilaf/framework`'s `createTestContext()` helper, you can access QueryHelper through either the separate RCON backend or via the MineflayerBackend:
|
|
943
|
+
|
|
944
|
+
```javascript
|
|
945
|
+
const { createTestContext, cleanupTestContext } = require('@pilaf/framework');
|
|
946
|
+
|
|
947
|
+
describe('Plugin Tests with QueryHelper', () => {
|
|
948
|
+
let context;
|
|
949
|
+
|
|
950
|
+
beforeAll(async () => {
|
|
951
|
+
context = await createTestContext({
|
|
952
|
+
username: 'TestPlayer',
|
|
953
|
+
rconPassword: 'dragon123'
|
|
954
|
+
});
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
afterAll(async () => {
|
|
958
|
+
await cleanupTestContext(context);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('should verify player position using QueryHelper', async () => {
|
|
962
|
+
// Get player info BEFORE using RCON backend directly
|
|
963
|
+
const beforeInfo = await context.rcon.send('data get entity TestPlayer Pos');
|
|
964
|
+
|
|
965
|
+
// Execute ability
|
|
966
|
+
context.bot.chat('/myplugin teleport 100 64 100');
|
|
967
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
968
|
+
|
|
969
|
+
// Get player info AFTER
|
|
970
|
+
const afterInfo = await context.rcon.send('data get entity TestPlayer Pos');
|
|
971
|
+
|
|
972
|
+
// Or use MineflayerBackend's QueryHelper (if available)
|
|
973
|
+
// const info = await context.backend.getPlayerInfo('TestPlayer');
|
|
974
|
+
// expect(info.position.x).toBeCloseTo(100);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
it('should verify server TPS', async () => {
|
|
978
|
+
// Use QueryHelper methods via backend
|
|
979
|
+
const { tps } = await context.backend.getTPS();
|
|
980
|
+
expect(tps.tps).toBeGreaterThan(15);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
it('should list online players', async () => {
|
|
984
|
+
const players = await context.backend.listPlayers();
|
|
985
|
+
expect(players.players).toContain('TestPlayer');
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
### Why Both Backends?
|
|
991
|
+
|
|
992
|
+
- **context.rcon** - For raw RCON commands that return responses (e.g., `/data get`)
|
|
993
|
+
- **context.backend** - For QueryHelper methods and bot control
|
|
994
|
+
- **context.bot** - For bot player actions (chat, movement)
|
|
995
|
+
|
|
996
|
+
This dual-backend approach is necessary because `MineflayerBackend.sendCommand()` sends via bot chat and returns empty responses.
|
|
997
|
+
|
|
998
|
+
---
|
|
999
|
+
|
|
1000
|
+
## ⚙️ Jest Configuration Recommendations
|
|
1001
|
+
|
|
1002
|
+
### maxWorkers: 1 for Bot Player Tests
|
|
1003
|
+
|
|
1004
|
+
When testing with multiple bot players, set `maxWorkers: 1` to avoid connection throttling:
|
|
1005
|
+
|
|
1006
|
+
```javascript
|
|
1007
|
+
// jest.config.js
|
|
1008
|
+
module.exports = {
|
|
1009
|
+
testMatch: ['**/*.pilaf.test.js'],
|
|
1010
|
+
testTimeout: 300000,
|
|
1011
|
+
maxWorkers: 1, // ← Important: Prevents connection throttling
|
|
1012
|
+
reporters: ['default']
|
|
1013
|
+
};
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
**Why?** Each Jest worker creates separate bot connections. Multiple workers connecting simultaneously can trigger server connection throttling or race conditions.
|
|
1017
|
+
|
|
1018
|
+
### testTimeout: 300000
|
|
1019
|
+
|
|
1020
|
+
Server operations (bot spawning, command execution) are slower than unit tests. Set a higher timeout:
|
|
1021
|
+
|
|
1022
|
+
```javascript
|
|
1023
|
+
testTimeout: 300000, // 5 minutes
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
### Module Resolution for ES Modules
|
|
1027
|
+
|
|
1028
|
+
If using ES modules (`"type": "module"`), configure Jest accordingly:
|
|
1029
|
+
|
|
1030
|
+
```javascript
|
|
1031
|
+
module.exports = {
|
|
1032
|
+
extension: ['.js', '.cjs'],
|
|
1033
|
+
transform: {},
|
|
1034
|
+
testTimeout: 300000,
|
|
1035
|
+
maxWorkers: 1
|
|
1036
|
+
};
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
---
|
|
1040
|
+
|
|
1041
|
+
## 📊 QueryHelper vs Raw RCON Comparison
|
|
1042
|
+
|
|
1043
|
+
### Before: Raw RCON with Manual Parsing
|
|
1044
|
+
|
|
1045
|
+
```javascript
|
|
1046
|
+
// ❌ Verbose and error-prone
|
|
1047
|
+
const result = await context.rcon.send('data get entity TestPlayer Pos');
|
|
1048
|
+
|
|
1049
|
+
// Manual regex parsing
|
|
1050
|
+
const match = result.raw.match(/Pos.*?\[.*?d/);
|
|
1051
|
+
if (!match) {
|
|
1052
|
+
throw new Error('Could not parse position');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const coords = match[0].split(', ').map(s => {
|
|
1056
|
+
const num = s.match(/-?\d+\.\d+/);
|
|
1057
|
+
return num ? parseFloat(num[0]) : 0;
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
const x = coords[0];
|
|
1061
|
+
const y = coords[1];
|
|
1062
|
+
const z = coords[2];
|
|
1063
|
+
|
|
1064
|
+
expect(x).toBeCloseTo(100);
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
### After: QueryHelper (Clean & Type-Safe)
|
|
1068
|
+
|
|
1069
|
+
```javascript
|
|
1070
|
+
// ✅ Clean and structured
|
|
1071
|
+
const info = await context.backend.getPlayerInfo('TestPlayer');
|
|
1072
|
+
|
|
1073
|
+
expect(info.position.x).toBeCloseTo(100);
|
|
1074
|
+
expect(info.position.y).toBeDefined();
|
|
1075
|
+
expect(info.health).toBeGreaterThan(0);
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
### Benefits Summary
|
|
1079
|
+
|
|
1080
|
+
| Feature | Raw RCON | QueryHelper |
|
|
1081
|
+
|---------|----------|-------------|
|
|
1082
|
+
| **Code length** | 20+ lines | 1-2 lines |
|
|
1083
|
+
| **Error handling** | Manual regex | Built-in parsing |
|
|
1084
|
+
| **Type safety** | Strings | Structured objects |
|
|
1085
|
+
| **Maintainability** | Brittle (breaks on format changes) | Resilient |
|
|
1086
|
+
|
|
1087
|
+
---
|
|
1088
|
+
|
|
1089
|
+
## 🤖 Bot Player Limitations
|
|
1090
|
+
|
|
1091
|
+
### Velocity-Based Abilities Don't Work on Bot Players
|
|
1092
|
+
|
|
1093
|
+
Bot players (Mineflayer) do not respond to server velocity the same way as real players:
|
|
1094
|
+
|
|
1095
|
+
```javascript
|
|
1096
|
+
// ❌ This won't work as expected with bot players
|
|
1097
|
+
context.bot.chat('/agile 1'); // Dash ability that applies velocity
|
|
1098
|
+
|
|
1099
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1100
|
+
|
|
1101
|
+
// Bot's client-side position won't reflect the server-side movement
|
|
1102
|
+
const botPos = context.bot.entity.position;
|
|
1103
|
+
console.log(botPos); // Position unchanged (bot doesn't move)
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
**Workaround**: Test ability activation instead of actual movement:
|
|
1107
|
+
|
|
1108
|
+
```javascript
|
|
1109
|
+
// ✅ Test that ability activates (not actual movement)
|
|
1110
|
+
context.bot.chat('/agile 1');
|
|
1111
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1112
|
+
|
|
1113
|
+
// Verify cooldown was applied (ability was used)
|
|
1114
|
+
const quickResult = await context.rcon.send('execute as TestPlayer run agile 1');
|
|
1115
|
+
expect(quickResult.raw).toContain('cooldown'); // Ability is on cooldown
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
### Entity Position Tracking
|
|
1119
|
+
|
|
1120
|
+
Use server-side queries instead of bot position:
|
|
1121
|
+
|
|
1122
|
+
```javascript
|
|
1123
|
+
// ❌ Don't use bot position for velocity-based abilities
|
|
1124
|
+
const botPos = context.bot.entity.position; // Not updated by server velocity
|
|
1125
|
+
|
|
1126
|
+
// ✅ Use RCON to get server-side entity position
|
|
1127
|
+
const result = await context.rcon.send('data get entity Pig Pos');
|
|
1128
|
+
// Parse the result to verify entity moved
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
### Chat and Commands Work Fine
|
|
1132
|
+
|
|
1133
|
+
Bot players correctly handle:
|
|
1134
|
+
- ✅ Chat messages
|
|
1135
|
+
- ✅ Command execution (`/command`)
|
|
1136
|
+
- ✅ Inventory manipulation
|
|
1137
|
+
- ✅ Block interaction
|
|
1138
|
+
- ✅ Entity detection (`bot.entities`)
|
|
1139
|
+
|
|
1140
|
+
---
|
|
1141
|
+
|
|
940
1142
|
## 🔧 Core Components
|
|
941
1143
|
|
|
942
1144
|
### ConnectionState
|
|
@@ -1136,6 +1338,21 @@ const {
|
|
|
1136
1338
|
|
|
1137
1339
|
## 🐛 Troubleshooting
|
|
1138
1340
|
|
|
1341
|
+
### Issue: Cannot find module './lib/features'
|
|
1342
|
+
|
|
1343
|
+
**Cause**: prismarine-physics package has a bug where it imports `./lib/features` but the file is actually `./lib/features.json`.
|
|
1344
|
+
|
|
1345
|
+
**Solution**: This is automatically fixed by the postinstall script in `@pilaf/backends`. If you're not using the postinstall script, you can create a symlink manually:
|
|
1346
|
+
|
|
1347
|
+
```bash
|
|
1348
|
+
# Navigate to node_modules/prismarine-physics/lib
|
|
1349
|
+
cd node_modules/prismarine-physics/lib
|
|
1350
|
+
# Create symlink
|
|
1351
|
+
ln -s features.json features
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
**Note**: This fix runs automatically when `@pilaf/backends` is installed via npm/pnpm.
|
|
1355
|
+
|
|
1139
1356
|
### Issue: Events not being captured
|
|
1140
1357
|
|
|
1141
1358
|
**Cause**: Not observing before triggering events
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilaf/backends",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"lib/*.js",
|
|
11
11
|
"lib/**/*.js",
|
|
12
|
+
"scripts/**/*.cjs",
|
|
12
13
|
"!lib/**/*.spec.js",
|
|
13
14
|
"!lib/**/*.test.js",
|
|
14
15
|
"README.md",
|
|
@@ -21,5 +22,8 @@
|
|
|
21
22
|
"dockerode": "^4.0.0",
|
|
22
23
|
"mineflayer": "^4.20.1",
|
|
23
24
|
"rcon-client": "^4.2.5"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"postinstall": "node scripts/fix-prismarine-physics.cjs"
|
|
24
28
|
}
|
|
25
|
-
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fix prismarine-physics dependency issue
|
|
5
|
+
*
|
|
6
|
+
* The prismarine-physics package imports './lib/features' but the file
|
|
7
|
+
* is actually './lib/features.json'. This script creates a symlink to fix the issue.
|
|
8
|
+
*
|
|
9
|
+
* This script supports both npm and pnpm package managers.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
function fixPrismarinePhysics() {
|
|
16
|
+
let fixedCount = 0;
|
|
17
|
+
|
|
18
|
+
// Handle pnpm-style node_modules (.pnpm directory)
|
|
19
|
+
const pnpmPath = path.join(__dirname, '..', '..', '.pnpm');
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(pnpmPath)) {
|
|
22
|
+
const findAndFix = (dir) => {
|
|
23
|
+
try {
|
|
24
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
25
|
+
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (!entry.isDirectory()) continue;
|
|
28
|
+
|
|
29
|
+
const fullPath = path.join(dir, entry.name);
|
|
30
|
+
|
|
31
|
+
if (entry.name.startsWith('prismarine-physics@')) {
|
|
32
|
+
const libPath = path.join(fullPath, 'node_modules', 'prismarine-physics', 'lib');
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(libPath)) {
|
|
35
|
+
const featuresJson = path.join(libPath, 'features.json');
|
|
36
|
+
const featuresLink = path.join(libPath, 'features');
|
|
37
|
+
|
|
38
|
+
if (fs.existsSync(featuresJson) && !fs.existsSync(featuresLink)) {
|
|
39
|
+
try {
|
|
40
|
+
fs.symlinkSync('features.json', featuresLink);
|
|
41
|
+
console.log(`✓ Fixed prismarine-physics: ${featuresLink}`);
|
|
42
|
+
fixedCount++;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
// On Windows, try junction instead of symlink
|
|
45
|
+
if (process.platform === 'win32') {
|
|
46
|
+
try {
|
|
47
|
+
fs.symlinkSync(path.join(libPath, 'features.json'), featuresLink, 'junction');
|
|
48
|
+
console.log(`✓ Fixed prismarine-physics (junction): ${featuresLink}`);
|
|
49
|
+
fixedCount++;
|
|
50
|
+
} catch (err2) {
|
|
51
|
+
console.log(`✗ Failed to fix prismarine-physics: ${featuresLink}`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
console.log(`✗ Failed to fix prismarine-physics: ${featuresLink}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// Ignore errors from readdirSync
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
findAndFix(pnpmPath);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle npm-style node_modules (direct path)
|
|
70
|
+
const npmLibPath = path.join(__dirname, '..', '..', 'node_modules', 'prismarine-physics', 'lib');
|
|
71
|
+
if (fs.existsSync(npmLibPath)) {
|
|
72
|
+
const featuresJson = path.join(npmLibPath, 'features.json');
|
|
73
|
+
const featuresLink = path.join(npmLibPath, 'features');
|
|
74
|
+
|
|
75
|
+
if (fs.existsSync(featuresJson) && !fs.existsSync(featuresLink)) {
|
|
76
|
+
try {
|
|
77
|
+
fs.symlinkSync('features.json', featuresLink);
|
|
78
|
+
console.log(`✓ Fixed prismarine-physics: ${featuresLink}`);
|
|
79
|
+
fixedCount++;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (process.platform === 'win32') {
|
|
82
|
+
try {
|
|
83
|
+
fs.symlinkSync(path.join(npmLibPath, 'features.json'), featuresLink, 'junction');
|
|
84
|
+
console.log(`✓ Fixed prismarine-physics (junction): ${featuresLink}`);
|
|
85
|
+
fixedCount++;
|
|
86
|
+
} catch (err2) {
|
|
87
|
+
console.log(`✗ Failed to fix prismarine-physics: ${featuresLink}`);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
console.log(`✗ Failed to fix prismarine-physics: ${featuresLink}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (fixedCount > 0) {
|
|
97
|
+
console.log(`\nFixed ${fixedCount} prismarine-physics package(s)`);
|
|
98
|
+
} else {
|
|
99
|
+
// Silent if already fixed or not found (common during development)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fixPrismarinePhysics();
|