-
Notifications
You must be signed in to change notification settings - Fork 43
/
ChunkUtil.java
438 lines (406 loc) · 16.5 KB
/
ChunkUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
package com.bergerkiller.bukkit.common.utils;
import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.chunk.ForcedChunk;
import com.bergerkiller.bukkit.common.collections.FilteredCollection;
import com.bergerkiller.bukkit.common.conversion.type.HandleConversion;
import com.bergerkiller.bukkit.common.internal.CommonNMS;
import com.bergerkiller.bukkit.common.internal.CommonPlugin;
import com.bergerkiller.bukkit.common.internal.logic.RegionHandler;
import com.bergerkiller.bukkit.common.wrappers.BlockData;
import com.bergerkiller.bukkit.common.wrappers.ChunkSection;
import com.bergerkiller.bukkit.common.wrappers.HeightMap;
import com.bergerkiller.generated.net.minecraft.server.level.PlayerChunkMapHandle;
import com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle;
import com.bergerkiller.generated.net.minecraft.world.level.EnumSkyBlockHandle;
import com.bergerkiller.generated.net.minecraft.world.level.WorldHandle;
import com.bergerkiller.generated.net.minecraft.world.level.chunk.ChunkHandle;
import com.bergerkiller.generated.net.minecraft.world.level.chunk.ChunkSectionHandle;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Player;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* Contains utilities to get and set chunks of a world
*/
public class ChunkUtil {
/**
* Forces a chunk to stay loaded. Call {@link ForcedChunk#close()} to release
* the chunk again to allow it to unload. The chunk is loaded asynchronously
* if it is not already loaded.
* Loads with a radius of 2, so that entities inside are ticked.
*
* @param world
* @param chunkX
* @param chunkZ
* @return forced chunk
*/
public static ForcedChunk forceChunkLoaded(World world, int chunkX, int chunkZ) {
return CommonPlugin.getInstance().getForcedChunkManager().newForcedChunk(world, chunkX, chunkZ);
}
/**
* Forces a chunk to stay loaded. Call {@link ForcedChunk#close()} to release
* the chunk again to allow it to unload. If the provided chunk is currently not
* actually loaded, it is loaded asynchronously.
* Loads with a radius of 2, so that entities inside are ticked.
*
* @param chunk
* @return forced chunk
*/
public static ForcedChunk forceChunkLoaded(org.bukkit.Chunk chunk) {
return forceChunkLoaded(chunk.getWorld(), chunk.getX(), chunk.getZ());
}
/**
* Forces a chunk to stay loaded. Call {@link ForcedChunk#close()} to release
* the chunk again to allow it to unload. The chunk is loaded asynchronously
* if it is not already loaded.
*
* @param world
* @param chunkX
* @param chunkZ
* @param radius Number of chunks around the chunk to keep loaded.
* Radius 2 or higher will make entities inside the chunk get ticked.
* @return forced chunk
*/
public static ForcedChunk forceChunkLoaded(World world, int chunkX, int chunkZ, int radius) {
return CommonPlugin.getInstance().getForcedChunkManager().newForcedChunk(world, chunkX, chunkZ, radius);
}
/**
* Forces a chunk to stay loaded. Call {@link ForcedChunk#close()} to release
* the chunk again to allow it to unload. If the provided chunk is currently not
* actually loaded, it is loaded asynchronously.
*
* @param chunk
* @param radius Number of chunks around the chunk to keep loaded.
* Radius 2 or higher will make entities inside the chunk get ticked.
* @return forced chunk
*/
public static ForcedChunk forceChunkLoaded(org.bukkit.Chunk chunk, int radius) {
return forceChunkLoaded(chunk.getWorld(), chunk.getX(), chunk.getZ(), radius);
}
/**
* Gets an array of vertical Chunk Sections that make up the data of a chunk.
* Some array elements may be null, if that slice stores all-air.<br>
* <br>
* <b>Deprecated: Does not take into account infinite-y size worlds when a modded
* server is used that adds that functionality. Only returns the first 16 slices.</b>
*
* @param chunk to get the sections of
* @return the first 16 chunk sections of the chunk
*/
@Deprecated
public static ChunkSection[] getSections(org.bukkit.Chunk chunk) {
return ChunkHandle.T.getSections.invoke(HandleConversion.toChunkHandle(chunk));
}
/**
* Gets the Y-coordinates of the chunk sections (slices of height 16) that are loaded
* of a chunk.
*
* @param chunk
* @return loaded chunk section coordinates
*/
public static List<Integer> getLoadedSectionCoordinates(org.bukkit.Chunk chunk) {
return ChunkHandle.T.getLoadedSectionCoordinates.invoke(HandleConversion.toChunkHandle(chunk));
}
/**
* Gets a vertical 16-high cube slice of the chunk
*
* @param chunk The chunk to get the section of
* @param cy The 16x16x16 block section coordinate (same coordinate space as chunk x/z)
* @return chunk section at this coordinate, or null if at this coordinate all blocks are air
* and no data is stored here.
*/
public static ChunkSection getSection(org.bukkit.Chunk chunk, int cy) {
return ChunkHandle.T.getSection.invoke(HandleConversion.toChunkHandle(chunk), cy);
}
/**
* Gets the light-level height map of a chunk.
* This stores the height above which all (sky) light levels are 15.
* The heightmap is not (re-) initialized, instead storing the values as
* reported by the server.
*
* @param chunk to get the light heightmap for
* @return light heightmap
*/
public static HeightMap getLightHeightMap(org.bukkit.Chunk chunk) {
return getLightHeightMap(chunk, false);
}
/**
* Gets the light-level height map of a chunk.
* This stores the height above which all (sky) light levels are 15.
*
* @param chunk to get the light heightmap for
* @param initialize whether to force a complete recalculation of the light heightmap
* @return light heightmap
*/
public static HeightMap getLightHeightMap(org.bukkit.Chunk chunk, boolean initialize) {
return ChunkHandle.fromBukkit(chunk).getLightHeightMap(initialize);
}
/**
* Gets the block light level
*
* @param chunk the block is in
* @param x - coordinate of the block
* @param y - coordinate of the block
* @param z - coordinate of the block
* @return Block light level
*/
public static int getBlockLight(org.bukkit.Chunk chunk, int x, int y, int z) {
if (y < 0) {
return 0;
} else if (y >= chunk.getWorld().getMaxHeight()) {
return 0;
} else {
return ChunkHandle.fromBukkit(chunk).getBrightness(EnumSkyBlockHandle.BLOCK,
new IntVector3(x & 0xf, y, z & 0xf));
}
}
/**
* Gets the sky light level
*
* @param chunk the block is in
* @param x - coordinate of the block
* @param y - coordinate of the block
* @param z - coordinate of the block
* @return Sky light level
*/
public static int getSkyLight(org.bukkit.Chunk chunk, int x, int y, int z) {
if (y < 0) {
return 0;
} else if (y >= chunk.getWorld().getMaxHeight()) {
return 15;
} else {
return ChunkHandle.fromBukkit(chunk).getBrightness(EnumSkyBlockHandle.SKY,
new IntVector3(x & 0xf, y, z & 0xf));
}
}
/**
* Gets the block type Id
*
* @param chunk the block is in
* @param x - coordinate of the block
* @param y - coordinate of the block
* @param z - coordinate of the block
* @return block type
*/
public static Material getBlockType(org.bukkit.Chunk chunk, int x, int y, int z) {
return getBlockData(chunk, x, y, z).getType();
}
/**
* Gets the block type and data from a chunk
*
* @param chunk the block is in
* @param x - coordinate of the block
* @param y - coordinate of the block
* @param z - coordinate of the block
* @return block data information
*/
public static BlockData getBlockData(org.bukkit.Chunk chunk, int x, int y, int z) {
return ChunkHandle.T.getBlockDataAtCoord.invoke(HandleConversion.toChunkHandle(chunk), x, y, z);
/*
Object chunkHandleRaw = HandleConversion.toChunkHandle(chunk);
Object blockPos = BlockPositionHandle.T.constr_x_y_z.raw.newInstance(x, y, z);
Object iBlockData = ChunkHandle.T.getBlockData.raw.invoke(chunkHandleRaw, blockPos);
return BlockData.fromBlockData(iBlockData);
*/
}
/**
* Sets a block type id and data without causing physics or lighting updates
*
* @param chunk the block is in
* @param block - the block coordinates within the chunk to set
* @param data to set to
*/
public static void setBlockFast(org.bukkit.Chunk chunk, Block block, BlockData data) {
final int secIndex = block.getY() >> 4;
Object section = ChunkHandle.T.getSectionRaw.invoke(HandleConversion.toChunkHandle(chunk), secIndex);
if (section != null) {
ChunkSectionHandle.T.setBlockDataAtBlock.invoke(section, block, data);
} else {
// Slow method, to initialize the empty chunk
WorldUtil.setBlockData(block, data);
}
}
/**
* Sets a block type id and data without causing physics or lighting updates
*
* @param chunk the block is in
* @param x - coordinate of the block
* @param y - coordinate of the block
* @param z - coordinate of the block
* @param data to set to
*/
public static void setBlockFast(org.bukkit.Chunk chunk, int x, int y, int z, BlockData data) {
final int secIndex = y >> 4;
Object chunkHandle = HandleConversion.toChunkHandle(chunk);
Object section = ChunkHandle.T.getSectionRaw.invoke(chunkHandle, secIndex);
if (section != null) {
ChunkSectionHandle.T.setBlockData.invoke(section, x & 0xf, y & 0xf, z & 0xf, data);
ChunkHandle.T.markDirty.invoker.invoke(chunkHandle);
} else {
// Slow method, to initialize the empty chunk
WorldUtil.setBlockData(chunk.getWorld(),
(chunk.getX() << 4) | (x & 0xf),
y,
(chunk.getZ() << 4) | (z & 0xf),
data);
}
}
/**
* Gets a live collection of all the entities in a chunk<br>
* Changes to this collection are reflected back in the chunk
*
* @param chunk for which to get the entities
* @return Live collection of entities in the chunk
*/
public static List<org.bukkit.entity.Entity> getEntities(org.bukkit.Chunk chunk) {
return ChunkHandle.fromBukkit(chunk).getEntities();
}
/**
* Gets a collection of players that are presently viewing / in range of a chunk.
* Block changes are notified to these players.
*
* @param chunk Chunk
* @return Unmodifiable collection of players that view this chunk
*/
public static Collection<Player> getChunkViewers(org.bukkit.Chunk chunk) {
return getChunkViewers(chunk.getWorld(), chunk.getX(), chunk.getZ());
}
/**
* Gets a collection of players that are presently viewing / in range of a chunk.
* Block changes are notified to these players.
*
* @param world World the chunk is in
* @param chunkX The chunk X-coordinate
* @param chunkZ The chunk Z-coordinate
* @return Unmodifiable collection of players that view this chunk
*/
public static Collection<Player> getChunkViewers(org.bukkit.World world, int chunkX, int chunkZ) {
return WorldServerHandle.fromBukkit(world).getPlayerChunkMap().getChunkEnteredPlayers(chunkX, chunkZ);
}
/**
* Checks if a chunk is about be loaded
*
* @param player who will receive the chunk
* @param cx location for the chunk X
* @param cz location for the chunk Z
* @return chunk being loaded soon?
*/
@Deprecated
public static boolean isLoadRequested(Player player, int cx, int cz) {
throw new RuntimeException("BROKEN");
}
/**
* Gets whether a given chunk is readily available. If this method returns
* False, the chunk is not yet generated.
*
* @param world the chunk is in
* @param x - coordinate of the chunk
* @param z - coordinate of the chunk
* @return True if the chunk can be obtained without generating it, False if
* not
*/
public static boolean isChunkAvailable(World world, int x, int z) {
if (WorldUtil.isLoaded(world, x, z)) {
// Chunk is loaded into memory, True
return true;
} else {
return RegionHandler.INSTANCE.isChunkSaved(world, x, z);
}
}
/**
* Gets all the chunks loaded on a given world
*
* @param world to get the loaded chunks from
* @return Loaded chunks
*/
public static Collection<org.bukkit.Chunk> getChunks(World world) {
// Bukkit alternative
return FilteredCollection.createNullFilter(Arrays.asList(world.getLoadedChunks()));
}
/**
* Gets a chunk from a world without loading or generating it.
* This method guarantees no event will be generated at all trying to get this chunk.
* If the chunk is not loaded, null is returned.
*
* @param world to obtain the chunk from
* @param x - coordinate of the chunk
* @param z - coordinate of the chunk
* @return The chunk, or null if it is not loaded
*/
public static org.bukkit.Chunk getChunk(World world, final int x, final int z) {
return WorldServerHandle.T.getChunkIfLoaded.invoke(HandleConversion.toWorldHandle(world), x, z);
}
/**
* Gets, loads or generates a chunk without loading or generating it on the
* main thread. Allows the lazy-loading of chunks without locking the
* server.
*
* @param world to obtain the chunk from
* @param x - coordinate of the chunk
* @param z - coordinate of the chunk
* @return chunk future that is completed when the chunk is ready
*/
public static CompletableFuture<org.bukkit.Chunk> getChunkAsync(World world, final int x, final int z) {
final CompletableFuture<org.bukkit.Chunk> result = new CompletableFuture<org.bukkit.Chunk>();
final ForcedChunk forced = WorldUtil.forceChunkLoaded(world, x, z);
forced.getChunkAsync().whenComplete((chunk, exception) -> {
try {
if (exception != null) {
result.completeExceptionally(exception);
} else {
result.complete(chunk);
}
} finally {
forced.close();
}
});
return result;
}
/**
* Gets, loads or generates a chunk without loading or generating it on the
* main thread. Allows the lazy-loading of chunks without locking the
* server.
*
* @param world to obtain the chunk from
* @param x - coordinate of the chunk
* @param z - coordinate of the chunk
* @param runnable to execute once the chunk is loaded or obtained
*/
@Deprecated
public static void getChunkAsync(World world, final int x, final int z, Runnable runnable) {
getChunkAsync(world, x, z).thenRun(runnable);
}
/**
* Saves a single chunk to disk.
* This method is only valid for chunks that are loaded.
* Unloaded chunks should not be saved (again).
*
* @param chunk to save
*/
public static void saveChunk(org.bukkit.Chunk chunk) {
CommonNMS.getHandle(chunk.getWorld()).getChunkProviderServer().saveLoadedChunk(CommonNMS.getHandle(chunk));
}
/**
* Gets whether a chunk needs to be saved
*
* @param chunk to check
* @return True if it needs to be saved, False if not
*/
public static boolean needsSaving(org.bukkit.Chunk chunk) {
return ChunkHandle.T.checkCanSave.invoke(HandleConversion.toChunkHandle(chunk));
}
/**
* Obtains all the Block State tile entities available in a Chunk
*
* @param chunk to get the Block States for
* @return collection of Block States (mutable)
*/
public static Collection<BlockState> getBlockStates(org.bukkit.Chunk chunk) {
return ChunkHandle.fromBukkit(chunk).getTileEntities();
}
}