prismarine-world 3.6.0 → 3.6.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/.github/dependabot.yml +14 -0
- package/.github/workflows/ci.yml +1 -4
- package/.github/workflows/publish.yml +17 -10
- package/README.md +1 -1
- package/docs/API.md +17 -0
- package/docs/HISTORY.md +11 -0
- package/examples/iterator.js +65 -7
- package/package.json +11 -8
- package/src/iterators.js +81 -12
- package/src/world.js +24 -2
- package/test/iterator.test.js +39 -0
- package/test/raycast.test.js +1 -1
- package/test/test.js +5 -5
- package/types/index.d.ts +10 -0
- package/types/iterators.d.ts +145 -0
- package/types/world.d.ts +215 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: npm
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: daily
|
|
7
|
+
open-pull-requests-limit: 10
|
|
8
|
+
ignore:
|
|
9
|
+
- dependency-name: event-promise
|
|
10
|
+
versions:
|
|
11
|
+
- ">= 1.a, < 2"
|
|
12
|
+
- dependency-name: mkdirp
|
|
13
|
+
versions:
|
|
14
|
+
- ">= 1.a, < 2"
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
-
|
|
4
1
|
name: CI
|
|
5
2
|
|
|
6
3
|
on:
|
|
@@ -25,4 +22,4 @@ jobs:
|
|
|
25
22
|
with:
|
|
26
23
|
node-version: ${{ matrix.node-version }}
|
|
27
24
|
- run: npm install
|
|
28
|
-
- run: npm test
|
|
25
|
+
- run: npm test
|
|
@@ -13,13 +13,20 @@ jobs:
|
|
|
13
13
|
- name: Set up Node.js
|
|
14
14
|
uses: actions/setup-node@master
|
|
15
15
|
with:
|
|
16
|
-
node-version:
|
|
17
|
-
-
|
|
18
|
-
uses:
|
|
19
|
-
with:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
node-version: 14.0.0
|
|
17
|
+
- id: publish
|
|
18
|
+
uses: JS-DevTools/npm-publish@v1
|
|
19
|
+
with:
|
|
20
|
+
token: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
21
|
+
- name: Create Release
|
|
22
|
+
if: steps.publish.outputs.type != 'none'
|
|
23
|
+
id: create_release
|
|
24
|
+
uses: actions/create-release@v1
|
|
25
|
+
env:
|
|
26
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
with:
|
|
28
|
+
tag_name: ${{ steps.publish.outputs.version }}
|
|
29
|
+
release_name: Release ${{ steps.publish.outputs.version }}
|
|
30
|
+
body: ${{ steps.publish.outputs.version }}
|
|
31
|
+
draft: false
|
|
32
|
+
prerelease: false
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-world)
|
|
10
10
|
|
|
11
|
-
Provides a minecraft world: an infinite collection of
|
|
11
|
+
Provides a minecraft world: an infinite collection of 16x256x16 chunks.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
package/docs/API.md
CHANGED
|
@@ -179,3 +179,20 @@ It steps exactly 1 block at a time, returning the block coordinates and the face
|
|
|
179
179
|
#### next()
|
|
180
180
|
|
|
181
181
|
return null or the next position (Vec3)
|
|
182
|
+
|
|
183
|
+
### SpiralIterator2d (pos, maxDistance)
|
|
184
|
+
|
|
185
|
+
`pos` is Vec3
|
|
186
|
+
|
|
187
|
+
`maxDistance` is number
|
|
188
|
+
|
|
189
|
+
Iterates outwards along x and z axis in a cubic spiral. First position returned is the starting position. Every step is 1 step away form the previous and next point.
|
|
190
|
+
|
|
191
|
+
#### next()
|
|
192
|
+
|
|
193
|
+
return null or the next position (Vec3)
|
|
194
|
+
|
|
195
|
+
#### NUMBER_OF_POINTS
|
|
196
|
+
|
|
197
|
+
Number of points the iterator will return.
|
|
198
|
+
|
package/docs/HISTORY.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## History
|
|
2
2
|
|
|
3
|
+
### 3.6.2
|
|
4
|
+
|
|
5
|
+
* Fix chunk saving reference errors (@kf106 & @moonborrow) [#100](https://github.com/PrismarineJS/prismarine-world/pull/100)
|
|
6
|
+
* Fix typo in README.md (@takeru) [#108](https://github.com/PrismarineJS/prismarine-world/pull/108)
|
|
7
|
+
* Improved types (@TRCYX) [#99](https://github.com/PrismarineJS/prismarine-world/pull/99)
|
|
8
|
+
* Fix ManhattanIterator [#101](https://github.com/PrismarineJS/prismarine-world/pull/101) (@IceTank)
|
|
9
|
+
|
|
10
|
+
### 3.6.1
|
|
11
|
+
|
|
12
|
+
* Update mcdata
|
|
13
|
+
|
|
3
14
|
### 3.6.0
|
|
4
15
|
|
|
5
16
|
* Added match while check intersect (@sefirosweb)
|
package/examples/iterator.js
CHANGED
|
@@ -1,12 +1,70 @@
|
|
|
1
1
|
const Vec3 = require('vec3').Vec3
|
|
2
|
-
const { OctahedronIterator } = require('../index').iterators
|
|
2
|
+
const { ManhattanIterator, SpiralIterator2d, OctahedronIterator } = require('../index').iterators
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
console.info('Octahedron Iterator 3D:\n' + iterate3d(new OctahedronIterator(new Vec3(0, 0, 0), 2)))
|
|
5
|
+
console.info('ManhattanIterator Iterator:\n' + iterate2d(new ManhattanIterator(0, 0, 5)))
|
|
6
|
+
console.info('SpiralIterator2d Iterator:\n' + iterate2d(new SpiralIterator2d(new Vec3(0, 0, 0), 4)))
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
function iterate3d (iter) {
|
|
9
|
+
let n = iter.next()
|
|
10
|
+
const result = []
|
|
11
|
+
const size = 3
|
|
12
|
+
let counter = 0
|
|
13
|
+
for (let x = 0; x < size * 2; x++) {
|
|
14
|
+
for (let y = 0; y < size * 2; y++) {
|
|
15
|
+
if (!result[x]) result[x] = []
|
|
16
|
+
for (let z = 0; z < size * 2; z++) {
|
|
17
|
+
if (!result[x][y]) result[x][y] = []
|
|
18
|
+
result[x][y][z] = ' '
|
|
19
|
+
}
|
|
20
|
+
}
|
|
10
21
|
}
|
|
11
|
-
console.
|
|
22
|
+
console.info(result)
|
|
23
|
+
while (n) {
|
|
24
|
+
result[n.x + size][n.y + size][n.z + size] = pad(counter)
|
|
25
|
+
counter = counter + 1
|
|
26
|
+
n = iter.next()
|
|
27
|
+
}
|
|
28
|
+
let str = 'Layer 0: ...\n'
|
|
29
|
+
for (let x = 0; x < size * 2; x++) {
|
|
30
|
+
let line = ''
|
|
31
|
+
for (let y = 0; y < size * 2; y++) {
|
|
32
|
+
for (let z = 0; z < size * 2; z++) {
|
|
33
|
+
line += '|' + pad(result[x][y][z])
|
|
34
|
+
}
|
|
35
|
+
line += ' '
|
|
36
|
+
}
|
|
37
|
+
str += line + '\n'
|
|
38
|
+
}
|
|
39
|
+
return str
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function iterate2d (iter) {
|
|
43
|
+
let n = iter.next()
|
|
44
|
+
const result = []
|
|
45
|
+
const size = 5
|
|
46
|
+
let counter = 0
|
|
47
|
+
for (let x = 0; x < size * 2; x++) {
|
|
48
|
+
for (let z = 0; z < size * 2; z++) {
|
|
49
|
+
if (!result[x]) result[x] = []
|
|
50
|
+
result[x][z] = ' '
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
while (n) {
|
|
54
|
+
result[n.x + size][n.z + size] = pad(counter)
|
|
55
|
+
counter = counter + 1
|
|
56
|
+
n = iter.next()
|
|
57
|
+
}
|
|
58
|
+
let str = ''
|
|
59
|
+
for (let x = 0; x < size * 2; x++) {
|
|
60
|
+
for (let z = 0; z < size * 2; z++) {
|
|
61
|
+
str += '|' + result[x][z]
|
|
62
|
+
}
|
|
63
|
+
str += '|\n'
|
|
64
|
+
}
|
|
65
|
+
return str
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pad (num) {
|
|
69
|
+
return num.toString().padStart(3, ' ')
|
|
12
70
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prismarine-world",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.2",
|
|
4
4
|
"description": "The core implementation of the world for prismarine",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "./types/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"pretest": "npm run lint",
|
|
8
9
|
"lint": "standard",
|
|
9
10
|
"fix": "standard --fix",
|
|
10
|
-
"test": "
|
|
11
|
+
"test": "mocha --reporter spec --exit"
|
|
11
12
|
},
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"game"
|
|
22
23
|
],
|
|
23
24
|
"engines": {
|
|
24
|
-
"
|
|
25
|
+
"node": ">=8.0.0"
|
|
25
26
|
},
|
|
26
27
|
"enginesStrict": true,
|
|
27
28
|
"author": "Will Franzen <wtfranzen@gmail.com> (http://will.xyz/)",
|
|
@@ -32,17 +33,19 @@
|
|
|
32
33
|
"homepage": "https://github.com/PrismarineJS/prismarine-world",
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"buffer-equal": "^1.0.0",
|
|
36
|
+
"expect": "^29.1.0",
|
|
35
37
|
"flatmap": "0.0.3",
|
|
36
|
-
"
|
|
37
|
-
"minecraft-data": "^2.47.0",
|
|
38
|
+
"minecraft-data": "^3.0.0",
|
|
38
39
|
"mkdirp": "^0.5.1",
|
|
39
|
-
"
|
|
40
|
+
"mocha": "^10.0.0",
|
|
41
|
+
"prismarine-chunk": "^1.31.0",
|
|
40
42
|
"prismarine-provider-anvil": "^2.0.0",
|
|
41
43
|
"prismarine-provider-raw": "^1.0.1",
|
|
44
|
+
"prismarine-world": "file:.",
|
|
45
|
+
"process": "^0.11.0",
|
|
42
46
|
"range": "0.0.3",
|
|
43
47
|
"rimraf": "^3.0.2",
|
|
44
|
-
"standard": "^
|
|
45
|
-
"process": "^0.11.0"
|
|
48
|
+
"standard": "^17.0.0"
|
|
46
49
|
},
|
|
47
50
|
"dependencies": {
|
|
48
51
|
"vec3": "^0.1.7"
|
package/src/iterators.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
const { Vec3 } = require('vec3')
|
|
2
2
|
|
|
3
|
+
const BlockFace = {
|
|
4
|
+
UNKNOWN: -999,
|
|
5
|
+
BOTTOM: 0,
|
|
6
|
+
TOP: 1,
|
|
7
|
+
NORTH: 2,
|
|
8
|
+
SOUTH: 3,
|
|
9
|
+
WEST: 4,
|
|
10
|
+
EAST: 5
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
// 2D spiral iterator, useful to iterate on
|
|
4
14
|
// columns that are centered on bot position
|
|
5
15
|
// https://en.wikipedia.org/wiki/Taxicab_geometry
|
|
6
16
|
class ManhattanIterator {
|
|
7
17
|
constructor (x, y, maxDistance) {
|
|
8
|
-
this.maxDistance = maxDistance
|
|
18
|
+
this.maxDistance = Math.floor(maxDistance)
|
|
9
19
|
this.startx = x
|
|
10
20
|
this.starty = y
|
|
11
21
|
this.x = 2
|
|
@@ -14,11 +24,15 @@ class ManhattanIterator {
|
|
|
14
24
|
this.leg = -1
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Returns the next position. If the iterator is at the end, returns null. Position will be 2d along the x z plane and y always being 0.
|
|
29
|
+
* @returns {Vec3 | null}
|
|
30
|
+
*/
|
|
17
31
|
next () {
|
|
18
32
|
if (this.leg === -1) {
|
|
19
33
|
// use -1 as the center
|
|
20
34
|
this.leg = 0
|
|
21
|
-
return
|
|
35
|
+
return new Vec3(this.startx, 0, this.starty)
|
|
22
36
|
} else if (this.leg === 0) {
|
|
23
37
|
if (this.maxDistance === 1) return null
|
|
24
38
|
this.x--
|
|
@@ -90,16 +104,6 @@ class OctahedronIterator {
|
|
|
90
104
|
}
|
|
91
105
|
}
|
|
92
106
|
|
|
93
|
-
const BlockFace = {
|
|
94
|
-
UNKNOWN: -999,
|
|
95
|
-
BOTTOM: 0,
|
|
96
|
-
TOP: 1,
|
|
97
|
-
NORTH: 2,
|
|
98
|
-
SOUTH: 3,
|
|
99
|
-
WEST: 4,
|
|
100
|
-
EAST: 5
|
|
101
|
-
}
|
|
102
|
-
|
|
103
107
|
// This iterate along a ray starting at `pos` in `dir` direction
|
|
104
108
|
// It steps exactly 1 block at a time, returning the block coordinates
|
|
105
109
|
// and the face by which the ray entered the block.
|
|
@@ -204,10 +208,75 @@ class RaycastIterator {
|
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
|
|
211
|
+
class SpiralIterator2d {
|
|
212
|
+
/**
|
|
213
|
+
* Spiral outwards from a central position in growing squares.
|
|
214
|
+
* Every point has a constant distance to its previous and following position of 1. First point returned is the starting position.
|
|
215
|
+
* Generates positions like this:
|
|
216
|
+
* ```text
|
|
217
|
+
* 16 15 14 13 12
|
|
218
|
+
* 17 4 3 2 11
|
|
219
|
+
* 18 5 0 1 10
|
|
220
|
+
* 19 6 7 8 9
|
|
221
|
+
* 20 21 22 23 24
|
|
222
|
+
* (maxDistance = 2; points returned = 25)
|
|
223
|
+
* ```
|
|
224
|
+
* Copy and paste warrior source: https://stackoverflow.com/questions/3706219/algorithm-for-iterating-over-an-outward-spiral-on-a-discrete-2d-grid-from-the-or
|
|
225
|
+
* @param {Vec3} pos Starting position
|
|
226
|
+
* @param {number} maxDistance Max distance from starting position
|
|
227
|
+
*/
|
|
228
|
+
constructor (pos, maxDistance) {
|
|
229
|
+
this.start = pos
|
|
230
|
+
this.maxDistance = maxDistance
|
|
231
|
+
|
|
232
|
+
this.NUMBER_OF_POINTS = Math.floor(Math.pow((Math.floor(maxDistance) - 0.5) * 2, 2))
|
|
233
|
+
|
|
234
|
+
// (di, dj) is a vector - direction in which we move right now
|
|
235
|
+
this.di = 1
|
|
236
|
+
this.dj = 0
|
|
237
|
+
// length of current segment
|
|
238
|
+
this.segment_length = 1
|
|
239
|
+
// current position (i, j) and how much of current segment we passed
|
|
240
|
+
this.i = 0
|
|
241
|
+
this.j = 0
|
|
242
|
+
this.segment_passed = 0
|
|
243
|
+
// current iteration
|
|
244
|
+
this.k = 0
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
next () {
|
|
248
|
+
if (this.k >= this.NUMBER_OF_POINTS) return null
|
|
249
|
+
const output = this.start.offset(this.i, 0, this.j)
|
|
250
|
+
|
|
251
|
+
// make a step, add 'direction' vector (di, dj) to current position (i, j)
|
|
252
|
+
this.i += this.di
|
|
253
|
+
this.j += this.dj
|
|
254
|
+
this.segment_passed += 1
|
|
255
|
+
|
|
256
|
+
if (this.segment_passed === this.segment_length) {
|
|
257
|
+
// done with current segment
|
|
258
|
+
this.segment_passed = 0
|
|
259
|
+
|
|
260
|
+
// 'rotate' directions
|
|
261
|
+
const buffer = this.di
|
|
262
|
+
this.di = -this.dj
|
|
263
|
+
this.dj = buffer
|
|
264
|
+
|
|
265
|
+
// increase segment length if necessary
|
|
266
|
+
if (this.dj === 0) {
|
|
267
|
+
this.segment_length += 1
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
this.k += 1
|
|
271
|
+
return output
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
207
275
|
module.exports = {
|
|
208
276
|
ManhattanIterator,
|
|
209
277
|
ManathanIterator: ManhattanIterator, // backward compatibility
|
|
210
278
|
OctahedronIterator,
|
|
211
279
|
RaycastIterator,
|
|
280
|
+
SpiralIterator2d,
|
|
212
281
|
BlockFace
|
|
213
282
|
}
|
package/src/world.js
CHANGED
|
@@ -16,7 +16,9 @@ class World extends EventEmitter {
|
|
|
16
16
|
constructor (chunkGenerator, storageProvider = null, savingInterval = 1000) {
|
|
17
17
|
super()
|
|
18
18
|
this.savingQueue = new Map()
|
|
19
|
+
this.unloadQueue = new Map()
|
|
19
20
|
this.finishedSaving = Promise.resolve()
|
|
21
|
+
this.currentlySaving = false // semaphore for saving
|
|
20
22
|
this.columns = {}
|
|
21
23
|
this.chunkGenerator = chunkGenerator
|
|
22
24
|
this.storageProvider = storageProvider
|
|
@@ -123,6 +125,17 @@ class World extends EventEmitter {
|
|
|
123
125
|
|
|
124
126
|
unloadColumn (chunkX, chunkZ) {
|
|
125
127
|
const key = columnKeyXZ(chunkX, chunkZ)
|
|
128
|
+
if (this.storageProvider && this.savingQueue.has(key)) {
|
|
129
|
+
this.unloadQueue.set(key, { chunkX, chunkZ })
|
|
130
|
+
} else {
|
|
131
|
+
this.forceUnloadColumn(key, chunkX, chunkZ)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
forceUnloadColumn (key, chunkX, chunkZ) {
|
|
136
|
+
if (this.unloadQueue.has(key)) {
|
|
137
|
+
this.unloadQueue.delete(key)
|
|
138
|
+
}
|
|
126
139
|
delete this.columns[key]
|
|
127
140
|
const columnCorner = new Vec3(chunkX * 16, 0, chunkZ * 16)
|
|
128
141
|
this.emit('chunkColumnUnload', columnCorner)
|
|
@@ -136,15 +149,24 @@ class World extends EventEmitter {
|
|
|
136
149
|
// interval. The set structure is maintaining the order of insertion
|
|
137
150
|
for (const [key, { chunkX, chunkZ }] of this.savingQueue.entries()) {
|
|
138
151
|
this.finishedSaving = Promise.all([this.finishedSaving,
|
|
139
|
-
this.storageProvider.save(chunkX, chunkZ, this.columns[key])
|
|
152
|
+
this.storageProvider.save(chunkX, chunkZ, this.columns[key])
|
|
153
|
+
])
|
|
140
154
|
}
|
|
155
|
+
await this.finishedSaving
|
|
141
156
|
this.savingQueue.clear()
|
|
157
|
+
for (const [key, { chunkX, chunkZ }] of this.unloadQueue.entries()) {
|
|
158
|
+
this.forceUnloadColumn(key, chunkX, chunkZ)
|
|
159
|
+
}
|
|
142
160
|
this.emit('doneSaving')
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
startSaving () {
|
|
146
164
|
this.savingInt = setInterval(async () => {
|
|
147
|
-
|
|
165
|
+
if (this.currentlySaving === false) {
|
|
166
|
+
this.currentlySaving = true
|
|
167
|
+
await this.saveNow()
|
|
168
|
+
this.currentlySaving = false
|
|
169
|
+
}
|
|
148
170
|
}, this.savingInterval)
|
|
149
171
|
}
|
|
150
172
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* eslint-env mocha */
|
|
2
|
+
|
|
3
|
+
const { SpiralIterator2d, ManhattanIterator } = require('../src/iterators')
|
|
4
|
+
const { Vec3 } = require('vec3')
|
|
5
|
+
const expect = require('expect').default
|
|
6
|
+
|
|
7
|
+
describe('Spiral iterator', () => {
|
|
8
|
+
it('simple function test', async () => {
|
|
9
|
+
const startPos = new Vec3(0, 0, 0)
|
|
10
|
+
const iter = new SpiralIterator2d(startPos, 2)
|
|
11
|
+
const first = iter.next()
|
|
12
|
+
const second = iter.next()
|
|
13
|
+
|
|
14
|
+
expect(first.x === startPos.x && first.y === startPos.y && first.z === startPos.z).toBeTruthy()
|
|
15
|
+
expect(second.x === startPos.x && second.y === startPos.y && second.z === startPos.z).toBeFalsy()
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('ManhattanIterator iterator', () => {
|
|
20
|
+
it('First position is same as start', () => {
|
|
21
|
+
const start = new Vec3(1, 2, 3)
|
|
22
|
+
const iter = new ManhattanIterator(start.x, start.z, 5)
|
|
23
|
+
const first = iter.next()
|
|
24
|
+
expect(first.x === start.x && first.z === start.z).toBeTruthy()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('Sample positions match', () => {
|
|
28
|
+
const start = new Vec3(1, 2, 3)
|
|
29
|
+
const iter = new ManhattanIterator(start.x, start.z, 5)
|
|
30
|
+
const sample = [new Vec3(1, 0, 3), new Vec3(2, 0, 3), new Vec3(1, 0, 4), new Vec3(0, 0, 3)]
|
|
31
|
+
let counter = 0
|
|
32
|
+
let next = iter.next()
|
|
33
|
+
while (next && counter < sample.length) {
|
|
34
|
+
expect(next.x === sample[counter].x && next.z === sample[counter].z).toBeTruthy()
|
|
35
|
+
next = iter.next()
|
|
36
|
+
counter++
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
})
|
package/test/raycast.test.js
CHANGED
package/test/test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-env
|
|
1
|
+
/* eslint-env mocha */
|
|
2
2
|
|
|
3
3
|
const flatMap = require('flatmap')
|
|
4
4
|
const range = require('range').range
|
|
@@ -28,11 +28,11 @@ describe('saving and loading works', function () {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const regionPath = 'world/testRegion'
|
|
31
|
-
|
|
31
|
+
before((cb) => {
|
|
32
32
|
mkdirp(regionPath, cb)
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
after(cb => {
|
|
36
36
|
rimraf(regionPath, cb)
|
|
37
37
|
})
|
|
38
38
|
|
|
@@ -87,11 +87,11 @@ describe('Synchronous saving and loading works', function () {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const regionPath = 'world/testRegionSync'
|
|
90
|
-
|
|
90
|
+
before((cb) => {
|
|
91
91
|
mkdirp(regionPath, cb)
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
after(cb => {
|
|
95
95
|
rimraf(regionPath, cb)
|
|
96
96
|
})
|
|
97
97
|
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { World } from "./world";
|
|
2
|
+
|
|
3
|
+
export * as iterators from "./iterators";
|
|
4
|
+
|
|
5
|
+
// Make public the types related to World, but not the classes
|
|
6
|
+
// (since they aren't exported in js)
|
|
7
|
+
import type * as world from "./world";
|
|
8
|
+
export { world };
|
|
9
|
+
|
|
10
|
+
export default function loader(mcVersion: string): typeof World;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Vec3 } from "vec3";
|
|
2
|
+
|
|
3
|
+
export declare enum BlockFace {
|
|
4
|
+
UNKNOWN = -999,
|
|
5
|
+
BOTTOM = 0,
|
|
6
|
+
TOP = 1,
|
|
7
|
+
NORTH = 2,
|
|
8
|
+
SOUTH = 3,
|
|
9
|
+
WEST = 4,
|
|
10
|
+
EAST = 5,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type RaycastBlock = {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
z: number;
|
|
17
|
+
face: BlockFace;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type RaycastIntersection = {
|
|
21
|
+
pos: Vec3;
|
|
22
|
+
face: BlockFace;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** [x0, y0, z0, x1, y1, z1] */
|
|
26
|
+
export type Shape = [number, number, number, number, number, number];
|
|
27
|
+
|
|
28
|
+
export interface Iterator<T> {
|
|
29
|
+
next(): T | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2D spiral iterator, useful to iterate on
|
|
33
|
+
// columns that are centered on bot position
|
|
34
|
+
// https://en.wikipedia.org/wiki/Taxicab_geometry
|
|
35
|
+
export declare class ManhattanIterator implements Iterator<Vec3> {
|
|
36
|
+
public readonly maxDistance: number;
|
|
37
|
+
public readonly startx: number;
|
|
38
|
+
public readonly starty: number;
|
|
39
|
+
private x: number;
|
|
40
|
+
private y: number;
|
|
41
|
+
private layer: number;
|
|
42
|
+
private leg: number;
|
|
43
|
+
|
|
44
|
+
constructor(x: number, y: number, maxDistance: number);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @returns {Vec3 | null} The current position of the iterator OR null if exceeded maxDistance.
|
|
48
|
+
*/
|
|
49
|
+
next(): Vec3 | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export declare class OctahedronIterator implements Iterator<Vec3> {
|
|
53
|
+
public readonly start: Vec3;
|
|
54
|
+
public readonly maxDistance: number;
|
|
55
|
+
private apothem: number;
|
|
56
|
+
private x: number;
|
|
57
|
+
private y: number;
|
|
58
|
+
private z: number;
|
|
59
|
+
private L: number;
|
|
60
|
+
private R: number;
|
|
61
|
+
|
|
62
|
+
constructor(start: Vec3, maxDistance: number);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @returns {Vec3 | null} The current position of the iterator OR null if exceeded maxDistance.
|
|
66
|
+
*/
|
|
67
|
+
next(): Vec3 | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// This iterate along a ray starting at `pos` in `dir` direction
|
|
71
|
+
// It steps exactly 1 block at a time, returning the block coordinates
|
|
72
|
+
// and the face by which the ray entered the block.
|
|
73
|
+
export declare class RaycastIterator implements Iterator<RaycastBlock> {
|
|
74
|
+
private block: RaycastBlock;
|
|
75
|
+
public readonly pos: Vec3;
|
|
76
|
+
public readonly dir: Vec3;
|
|
77
|
+
private invDirX: number;
|
|
78
|
+
private invDirY: number;
|
|
79
|
+
private invDirZ: number;
|
|
80
|
+
private stepX: number;
|
|
81
|
+
private stepY: number;
|
|
82
|
+
private stepZ: number;
|
|
83
|
+
private tDeltaX: number;
|
|
84
|
+
private tDeltaY: number;
|
|
85
|
+
private tDeltaZ: number;
|
|
86
|
+
private tMaxX: number;
|
|
87
|
+
private tMaxY: number;
|
|
88
|
+
private tMaxZ: number;
|
|
89
|
+
public readonly maxDistance: number;
|
|
90
|
+
|
|
91
|
+
constructor(pos: Vec3, dir: Vec3, distance: number);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Shape[]} shapes Takes shapes from block.shapes.
|
|
95
|
+
* @param {Vec3} offset Offset from original position to start the intersect calculation from.
|
|
96
|
+
* @returns {RaycastIntersection | null} A possible intersection.
|
|
97
|
+
*/
|
|
98
|
+
intersect(shapes: Shape[], offset: Vec3): RaycastIntersection | null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @returns {RaycastBlock | null} The block the raycast is currently passing through (not hit detection, just the general AABB of a block if it were there).
|
|
102
|
+
*/
|
|
103
|
+
next(): RaycastBlock | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Spiral outwards from a central position in growing squares.
|
|
108
|
+
* Every point has a constant distance to its previous and following position of 1. First point returned is the starting position.
|
|
109
|
+
* Generates positions like this:
|
|
110
|
+
* ```text
|
|
111
|
+
* 16 15 14 13 12
|
|
112
|
+
* 17 4 3 2 11
|
|
113
|
+
* 18 5 0 1 10
|
|
114
|
+
* 19 6 7 8 9
|
|
115
|
+
* 20 21 22 23 24
|
|
116
|
+
* (maxDistance = 2; points returned = 25)
|
|
117
|
+
* ```
|
|
118
|
+
* Copy and paste warrior source: https://stackoverflow.com/questions/3706219/algorithm-for-iterating-over-an-outward-spiral-on-a-discrete-2d-grid-from-the-or
|
|
119
|
+
*/
|
|
120
|
+
export declare class SpiralIterator2d implements Iterator<Vec3> {
|
|
121
|
+
public readonly start: Vec3;
|
|
122
|
+
public readonly maxDistance: number;
|
|
123
|
+
private di: number;
|
|
124
|
+
private dj: number;
|
|
125
|
+
private i: number;
|
|
126
|
+
private j: number;
|
|
127
|
+
private k: number;
|
|
128
|
+
private segment_passed: number;
|
|
129
|
+
private segment_length: number;
|
|
130
|
+
private readonly NUMBER_OF_POINTS: number;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {Vec3} pos Starting position
|
|
134
|
+
* @param {number} maxDistance Max distance from starting position
|
|
135
|
+
*/
|
|
136
|
+
constructor(pos: Vec3, maxDistance: number);
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @returns {Vec3 | null} The current position of the iterator OR null if exceeded maxDistance.
|
|
141
|
+
*/
|
|
142
|
+
next(): Vec3 | null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export declare const ManathanIterator: ManhattanIterator;
|
package/types/world.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { EventEmitter } from "events";
|
|
2
|
+
import type { Vec3 } from "vec3";
|
|
3
|
+
import type { Block } from "prismarine-block";
|
|
4
|
+
import loaderOfChunk from "prismarine-chunk";
|
|
5
|
+
import type { RaycastBlock } from "./iterators";
|
|
6
|
+
|
|
7
|
+
export type Chunk = InstanceType<ReturnType<typeof loaderOfChunk>>;
|
|
8
|
+
|
|
9
|
+
export type ChunkGenerator = (chunkX: number, chunkZ: number) => Chunk;
|
|
10
|
+
export type ChunkCoordinates = { chunkX: number, chunkZ: number };
|
|
11
|
+
export type ChunkCoordsAndColumn = { chunkX: number, chunkZ: number, column: Chunk };
|
|
12
|
+
|
|
13
|
+
export interface StorageProvider {
|
|
14
|
+
load(chunkX: number, chunkZ: number): Promise<Chunk>;
|
|
15
|
+
save(chunkX: number, chunkZ: number, chunk: Chunk): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare class World extends EventEmitter {
|
|
19
|
+
|
|
20
|
+
private readonly savingQueue: Map<String, ChunkCoordinates>;
|
|
21
|
+
private finishedSaving: Promise<unknown>;
|
|
22
|
+
private readonly columns: { [key: string]: Chunk };
|
|
23
|
+
public readonly chunkGenerator: ChunkGenerator | null | undefined;
|
|
24
|
+
public readonly storageProvider: StorageProvider | null | undefined;
|
|
25
|
+
public readonly savingInterval: number;
|
|
26
|
+
public readonly sync: WorldSync;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {ChunkGenerator} chunkGenerator A generator that requires X and Z coordinates of lower left of chunk. Returns a {Chunk} chunk object.
|
|
30
|
+
* @param {StorageProvider} storageProvider The provider the world uses to save chunks. See "prismarine-provider-anvil" or "prismarine-provider-raw".
|
|
31
|
+
* @param {number} savingInterval The duration in milliseconds between saves.
|
|
32
|
+
*/
|
|
33
|
+
constructor(
|
|
34
|
+
chunkGenerator?: ChunkGenerator | null | undefined,
|
|
35
|
+
storageProvider?: StorageProvider | null | undefined,
|
|
36
|
+
savingInterval?: number,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
public initialize(
|
|
40
|
+
iniFunc: (x: number, y: number, z: number) => Block,
|
|
41
|
+
length: number,
|
|
42
|
+
width: number,
|
|
43
|
+
height?: number,
|
|
44
|
+
iniPos?: Vec3,
|
|
45
|
+
): Promise<ChunkCoordinates[]>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param {Vec3} from Position to start raycasting from.
|
|
50
|
+
* @param {Vec3} direction Normalized vector, direction of raycast.
|
|
51
|
+
* @param {number} range Maximum range to raycast.
|
|
52
|
+
* @param {(block: Block) => boolean} matcher Optional function with a block parameter, return true if the raycast should stop at this block, false otherwise.
|
|
53
|
+
*/
|
|
54
|
+
public raycast(
|
|
55
|
+
from: Vec3,
|
|
56
|
+
direction: Vec3,
|
|
57
|
+
range: number,
|
|
58
|
+
matcher?: (block: Block) => boolean,
|
|
59
|
+
): Promise<RaycastBlock | null>;
|
|
60
|
+
|
|
61
|
+
public getLoadedColumn(chunkX: number, chunkZ: number): Chunk;
|
|
62
|
+
|
|
63
|
+
public getColumn(chunkX: number, chunkZ: number): Promise<Chunk>;
|
|
64
|
+
|
|
65
|
+
private _emitBlockUpdate(
|
|
66
|
+
oldBlock: Block,
|
|
67
|
+
newBlock: Block,
|
|
68
|
+
position: Vec3,
|
|
69
|
+
): void;
|
|
70
|
+
|
|
71
|
+
public setLoadedColumn(
|
|
72
|
+
chunkX: number,
|
|
73
|
+
chunkZ: number,
|
|
74
|
+
chunk: Chunk,
|
|
75
|
+
save?: boolean,
|
|
76
|
+
): void;
|
|
77
|
+
|
|
78
|
+
public setColumn(
|
|
79
|
+
chunkX: number,
|
|
80
|
+
chunkZ: number,
|
|
81
|
+
chunk: Chunk,
|
|
82
|
+
save?: boolean,
|
|
83
|
+
): Promise<void>;
|
|
84
|
+
|
|
85
|
+
public unloadColumn(chunkX: number, chunkZ: number): void;
|
|
86
|
+
|
|
87
|
+
public saveNow(): Promise<void>;
|
|
88
|
+
|
|
89
|
+
public startSaving(): void;
|
|
90
|
+
|
|
91
|
+
public waitSaving(): Promise<void>;
|
|
92
|
+
|
|
93
|
+
public stopSaving(): void;
|
|
94
|
+
|
|
95
|
+
public queueSaving(chunkX: number, chunkZ: number): void;
|
|
96
|
+
|
|
97
|
+
public saveAt(pos: Vec3): void;
|
|
98
|
+
|
|
99
|
+
public getColumns(): ChunkCoordsAndColumn[];
|
|
100
|
+
|
|
101
|
+
public getLoadedColumnAt(pos: Vec3): Chunk;
|
|
102
|
+
|
|
103
|
+
public getColumnAt(pos: Vec3): Promise<Chunk>;
|
|
104
|
+
|
|
105
|
+
public setBlock(pos: Vec3, block: Block): Promise<void>;
|
|
106
|
+
|
|
107
|
+
public getBlock(pos: Vec3): Promise<Block>;
|
|
108
|
+
|
|
109
|
+
public getBlockStateId(pos: Vec3): Promise<number>;
|
|
110
|
+
|
|
111
|
+
public getBlockType(pos: Vec3): Promise<number>;
|
|
112
|
+
|
|
113
|
+
public getBlockData(pos: Vec3): Promise<number>;
|
|
114
|
+
|
|
115
|
+
public getBlockLight(pos: Vec3): Promise<number>;
|
|
116
|
+
|
|
117
|
+
public getSkyLight(pos: Vec3): Promise<number>;
|
|
118
|
+
|
|
119
|
+
public getBiome(pos: Vec3): Promise<number>;
|
|
120
|
+
|
|
121
|
+
public setBlockStateId(pos: Vec3, stateId: number): Promise<void>;
|
|
122
|
+
|
|
123
|
+
public setBlockType(pos: Vec3, blockType: number): Promise<void>;
|
|
124
|
+
|
|
125
|
+
public setBlockData(pos: Vec3, data: number): Promise<void>;
|
|
126
|
+
|
|
127
|
+
public setBlockLight(pos: Vec3, light: number): Promise<void>;
|
|
128
|
+
|
|
129
|
+
public setSkyLight(pos: Vec3, light: number): Promise<void>;
|
|
130
|
+
|
|
131
|
+
public setBiome(pos: Vec3, biome: number): Promise<void>;
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export declare class WorldSync extends EventEmitter {
|
|
136
|
+
|
|
137
|
+
public readonly async: World;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {World} world Async representation of the World object.
|
|
141
|
+
*/
|
|
142
|
+
constructor(world: World);
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
public initialize(
|
|
146
|
+
iniFunc: (x: number, y: number, z: number) => Block,
|
|
147
|
+
length: number,
|
|
148
|
+
width: number,
|
|
149
|
+
height?: number,
|
|
150
|
+
iniPos?: Vec3,
|
|
151
|
+
): void;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
*
|
|
155
|
+
* @param {Vec3} from Position to start raycasting from.
|
|
156
|
+
* @param {Vec3} direction Normalized vector, direction of raycast.
|
|
157
|
+
* @param {number} range Maximum range to raycast.
|
|
158
|
+
* @param {(block: Block) => boolean} matcher Optional function with a block parameter, return true if the raycast should stop at this block, false otherwise.
|
|
159
|
+
*/
|
|
160
|
+
public raycast(
|
|
161
|
+
from: Vec3,
|
|
162
|
+
direction: Vec3,
|
|
163
|
+
range: number,
|
|
164
|
+
matcher?: (block: Block) => boolean,
|
|
165
|
+
): RaycastBlock | null;
|
|
166
|
+
|
|
167
|
+
private _emitBlockUpdate(
|
|
168
|
+
oldBlock: Block,
|
|
169
|
+
newBlock: Block,
|
|
170
|
+
position: Vec3
|
|
171
|
+
): void;
|
|
172
|
+
|
|
173
|
+
public getColumns(): ChunkCoordsAndColumn[];
|
|
174
|
+
|
|
175
|
+
public getColumn(chunkX: number, chunkZ: number): Chunk;
|
|
176
|
+
|
|
177
|
+
public getColumnAt(pos: Vec3): Chunk;
|
|
178
|
+
|
|
179
|
+
public setColumn(
|
|
180
|
+
chunkX: number,
|
|
181
|
+
chunkZ: number,
|
|
182
|
+
chunk: Chunk,
|
|
183
|
+
save?: boolean,
|
|
184
|
+
): void;
|
|
185
|
+
|
|
186
|
+
public unloadColumn(chunkX: number, chunkZ: number): void;
|
|
187
|
+
|
|
188
|
+
public getBlock(pos: Vec3): Block;
|
|
189
|
+
|
|
190
|
+
public getBlockStateId(pos: Vec3): number;
|
|
191
|
+
|
|
192
|
+
public getBlockType(pos: Vec3): number;
|
|
193
|
+
|
|
194
|
+
public getBlockData(pos: Vec3): number;
|
|
195
|
+
|
|
196
|
+
public getBlockLight(pos: Vec3): number;
|
|
197
|
+
|
|
198
|
+
public getSkyLight(pos: Vec3): number;
|
|
199
|
+
|
|
200
|
+
public getBiome(pos: Vec3): number;
|
|
201
|
+
|
|
202
|
+
public setBlock(pos: Vec3, block: Block): void;
|
|
203
|
+
|
|
204
|
+
public setBlockStateId(pos: Vec3, stateId: number): void;
|
|
205
|
+
|
|
206
|
+
public setBlockType(pos: Vec3, blockType: number): void;
|
|
207
|
+
|
|
208
|
+
public setBlockData(pos: Vec3, data: number): void;
|
|
209
|
+
|
|
210
|
+
public setBlockLight(pos: Vec3, light: number): void;
|
|
211
|
+
|
|
212
|
+
public setSkyLight(pos: Vec3, light: number): void;
|
|
213
|
+
|
|
214
|
+
public setBiome(pos: Vec3, biome: number): void;
|
|
215
|
+
}
|