/
Chunk.java
334 lines (289 loc) · 10.4 KB
/
Chunk.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
package net.minestom.server.instance;
import net.minestom.server.Tickable;
import net.minestom.server.Viewable;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.heightmap.Heightmap;
import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.snapshot.Snapshotable;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.Taggable;
import net.minestom.server.utils.chunk.ChunkSupplier;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.List;
import java.util.Set;
import java.util.UUID;
// TODO light data & API
/**
* A chunk is a part of an {@link Instance}, limited by a size of 16x256x16 blocks and subdivided in 16 sections of 16 blocks height.
* Should contain all the blocks located at those positions and manage their tick updates.
* Be aware that implementations do not need to be thread-safe, all chunks are guarded by their own instance ('this').
* <p>
* You can create your own implementation of this class by extending it
* and create the objects in {@link InstanceContainer#setChunkSupplier(ChunkSupplier)}.
* <p>
* You generally want to avoid storing references of this object as this could lead to a huge memory leak,
* you should store the chunk coordinates instead.
*/
public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, Biome.Setter, Viewable, Tickable, Taggable, Snapshotable {
public static final int CHUNK_SIZE_X = 16;
public static final int CHUNK_SIZE_Z = 16;
public static final int CHUNK_SECTION_SIZE = 16;
private final UUID identifier;
protected Instance instance;
protected final int chunkX, chunkZ;
protected final int minSection, maxSection;
// Options
private final boolean shouldGenerate;
private boolean readOnly;
protected volatile boolean loaded = true;
private final Viewable viewable;
// Path finding
protected PFColumnarSpace columnarSpace;
// Data
private final TagHandler tagHandler = TagHandler.newHandler();
public Chunk(@NotNull Instance instance, int chunkX, int chunkZ, boolean shouldGenerate) {
this.identifier = UUID.randomUUID();
this.instance = instance;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
this.shouldGenerate = shouldGenerate;
this.minSection = instance.getDimensionType().getMinY() / CHUNK_SECTION_SIZE;
this.maxSection = (instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight()) / CHUNK_SECTION_SIZE;
final List<SharedInstance> shared = instance instanceof InstanceContainer instanceContainer ?
instanceContainer.getSharedInstances() : List.of();
this.viewable = instance.getEntityTracker().viewable(shared, chunkX, chunkZ);
}
/**
* Sets a block at a position.
* <p>
* This is used when the previous block has to be destroyed/replaced, meaning that it clears the previous data and update method.
* <p>
* WARNING: this method is not thread-safe (in order to bring performance improvement with {@link net.minestom.server.instance.batch.Batch batches})
* The thread-safe version is {@link Instance#setBlock(int, int, int, Block)} (or any similar instance methods)
* Otherwise, you can simply do not forget to have this chunk synchronized when this is called.
*
* @param x the block X
* @param y the block Y
* @param z the block Z
* @param block the block to place
*/
@Override
public void setBlock(int x, int y, int z, @NotNull Block block) {
setBlock(x, y, z, block, null, null);
}
protected abstract void setBlock(int x, int y, int z, @NotNull Block block,
@Nullable BlockHandler.Placement placement,
@Nullable BlockHandler.Destroy destroy);
public abstract @NotNull List<Section> getSections();
public abstract @NotNull Section getSection(int section);
public abstract @NotNull Heightmap motionBlockingHeightmap();
public abstract @NotNull Heightmap worldSurfaceHeightmap();
public abstract void loadHeightmapsFromNBT(NBTCompound heightmaps);
public @NotNull Section getSectionAt(int blockY) {
return getSection(ChunkUtils.getChunkCoordinate(blockY));
}
/**
* Executes a chunk tick.
* <p>
* Should be used to update all the blocks in the chunk.
* <p>
* WARNING: this method doesn't necessary have to be thread-safe, proceed with caution.
*
* @param time the time of the update in milliseconds
*/
@Override
public abstract void tick(long time);
/**
* Gets the last time that this chunk changed.
* <p>
* "Change" means here data used in {@link ChunkDataPacket}.
* It is necessary to see if the cached version of this chunk can be used
* instead of re-writing and compressing everything.
*
* @return the last change time in milliseconds
*/
public abstract long getLastChangeTime();
/**
* Sends the chunk data to {@code player}.
*
* @param player the player
*/
public void sendChunk(@NotNull Player player) {
player.sendChunk(this);
}
public void sendChunk() {
getViewers().forEach(this::sendChunk);
}
@ApiStatus.Internal
public abstract @NotNull SendablePacket getFullDataPacket();
/**
* Creates a copy of this chunk, including blocks state id, custom block id, biomes, update data.
* <p>
* The chunk position (X/Z) can be modified using the given arguments.
*
* @param instance the chunk owner
* @param chunkX the chunk X of the copy
* @param chunkZ the chunk Z of the copy
* @return a copy of this chunk with a potentially new instance and position
*/
public abstract @NotNull Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ);
/**
* Resets the chunk, this means clearing all the data making it empty.
*/
public abstract void reset();
/**
* Gets the unique identifier of this chunk.
* <p>
* WARNING: this UUID is not persistent but randomized once the object is instantiated.
*
* @return the chunk identifier
*/
public @NotNull UUID getIdentifier() {
return identifier;
}
/**
* Gets the instance where this chunk is stored
*
* @return the linked instance
*/
public @NotNull Instance getInstance() {
return instance;
}
/**
* Gets the chunk X.
*
* @return the chunk X
*/
public int getChunkX() {
return chunkX;
}
/**
* Gets the chunk Z.
*
* @return the chunk Z
*/
public int getChunkZ() {
return chunkZ;
}
/**
* Gets the lowest (inclusive) section Y available in this chunk
*
* @return the lowest (inclusive) section Y available in this chunk
*/
public int getMinSection() {
return minSection;
}
/**
* Gets the highest (exclusive) section Y available in this chunk
*
* @return the highest (exclusive) section Y available in this chunk
*/
public int getMaxSection() {
return maxSection;
}
/**
* Gets the world position of this chunk.
*
* @return the position of this chunk
*/
public @NotNull Point toPosition() {
return new Vec(CHUNK_SIZE_X * getChunkX(), 0, CHUNK_SIZE_Z * getChunkZ());
}
/**
* Gets if this chunk will or had been loaded with a {@link ChunkGenerator}.
* <p>
* If false, the chunk will be entirely empty when loaded.
*
* @return true if this chunk is affected by a {@link ChunkGenerator}
*/
public boolean shouldGenerate() {
return shouldGenerate;
}
/**
* Gets if this chunk is read-only.
* <p>
* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}.
* It does not affect {@link IChunkLoader} and {@link ChunkGenerator}.
*
* @return true if the chunk is read-only
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Changes the read state of the chunk.
* <p>
* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}.
* It does not affect {@link IChunkLoader} and {@link ChunkGenerator}.
*
* @param readOnly true to make the chunk read-only, false otherwise
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Changes this chunk columnar space.
*
* @param columnarSpace the new columnar space
*/
public void setColumnarSpace(PFColumnarSpace columnarSpace) {
this.columnarSpace = columnarSpace;
}
/**
* Used to verify if the chunk should still be kept in memory.
*
* @return true if the chunk is loaded
*/
public boolean isLoaded() {
return loaded;
}
/**
* Called when the chunk has been successfully loaded.
*/
protected void onLoad() {}
/**
* Called when the chunk generator has finished generating the chunk.
*/
public void onGenerate() {}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + chunkX + ":" + chunkZ + "]";
}
@Override
public boolean addViewer(@NotNull Player player) {
return viewable.addViewer(player);
}
@Override
public boolean removeViewer(@NotNull Player player) {
return viewable.removeViewer(player);
}
@Override
public @NotNull Set<Player> getViewers() {
return viewable.getViewers();
}
@Override
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
/**
* Sets the chunk as "unloaded".
*/
protected void unload() {
this.loaded = false;
}
/**
* Invalidate the chunk caches
*/
public abstract void invalidate();
}