/
InstanceContainer.java
669 lines (604 loc) · 28.9 KB
/
InstanceContainer.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
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
package net.minestom.server.instance;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.instance.InstanceChunkLoadEvent;
import net.minestom.server.event.instance.InstanceChunkUnloadEvent;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
import net.minestom.server.instance.generator.Generator;
import net.minestom.server.instance.palette.Palette;
import net.minestom.server.network.packet.server.play.BlockChangePacket;
import net.minestom.server.network.packet.server.play.BlockEntityDataPacket;
import net.minestom.server.network.packet.server.play.EffectPacket;
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockUtils;
import net.minestom.server.utils.chunk.ChunkCache;
import net.minestom.server.utils.chunk.ChunkSupplier;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import space.vectrix.flare.fastutil.Long2ObjectSyncMap;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import static net.minestom.server.utils.chunk.ChunkUtils.*;
/**
* InstanceContainer is an instance that contains chunks in contrary to SharedInstance.
*/
public class InstanceContainer extends Instance {
private static final Logger LOGGER = LoggerFactory.getLogger(InstanceContainer.class);
private static final AnvilLoader DEFAULT_LOADER = new AnvilLoader("world");
private static final BlockFace[] BLOCK_UPDATE_FACES = new BlockFace[]{
BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.BOTTOM, BlockFace.TOP
};
// the shared instances assigned to this instance
private final List<SharedInstance> sharedInstances = new CopyOnWriteArrayList<>();
// the chunk generator used, can be null
private volatile Generator generator;
// (chunk index -> chunk) map, contains all the chunks in the instance
// used as a monitor when access is required
private final Long2ObjectSyncMap<Chunk> chunks = Long2ObjectSyncMap.hashmap();
private final Map<Long, CompletableFuture<Chunk>> loadingChunks = new ConcurrentHashMap<>();
private final Lock changingBlockLock = new ReentrantLock();
private final Map<Point, Block> currentlyChangingBlocks = new HashMap<>();
// the chunk loader, used when trying to load/save a chunk from another source
private IChunkLoader chunkLoader;
// used to automatically enable the chunk loading or not
private boolean autoChunkLoad = true;
// used to supply a new chunk object at a position when requested
private ChunkSupplier chunkSupplier;
// Fields for instance copy
protected InstanceContainer srcInstance; // only present if this instance has been created using a copy
private long lastBlockChangeTime; // Time at which the last block change happened (#setBlock)
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType) {
this(uniqueId, dimensionType, null, dimensionType.getName());
}
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType, @NotNull NamespaceID dimensionName) {
this(uniqueId, dimensionType, null, dimensionName);
}
@ApiStatus.Experimental
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType, @Nullable IChunkLoader loader) {
this(uniqueId, dimensionType, loader, dimensionType.getName());
}
@ApiStatus.Experimental
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType, @Nullable IChunkLoader loader, @NotNull NamespaceID dimensionName) {
super(uniqueId, dimensionType, dimensionName);
setChunkSupplier(DynamicChunk::new);
setChunkLoader(Objects.requireNonNullElse(loader, DEFAULT_LOADER));
this.chunkLoader.loadInstance(this);
}
@Override
public void setBlock(int x, int y, int z, @NotNull Block block, boolean doBlockUpdates) {
Chunk chunk = getChunkAt(x, z);
if (chunk == null) {
Check.stateCondition(!hasEnabledAutoChunkLoad(),
"Tried to set a block to an unloaded chunk with auto chunk load disabled");
chunk = loadChunk(getChunkCoordinate(x), getChunkCoordinate(z)).join();
}
if (isLoaded(chunk)) UNSAFE_setBlock(chunk, x, y, z, block, null, null, doBlockUpdates, 0);
}
/**
* Sets a block at the specified position.
* <p>
* Unsafe because the method is not synchronized and it does not verify if the chunk is loaded or not.
*
* @param chunk the {@link Chunk} which should be loaded
* @param x the block X
* @param y the block Y
* @param z the block Z
* @param block the block to place
*/
private synchronized void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block,
@Nullable BlockHandler.Placement placement, @Nullable BlockHandler.Destroy destroy,
boolean doBlockUpdates, int updateDistance) {
if (chunk.isReadOnly()) return;
if(y >= getDimensionType().getMaxY() || y < getDimensionType().getMinY()) {
LOGGER.warn("tried to set a block outside the world bounds, should be within [{}, {}): {}", getDimensionType().getMinY(), getDimensionType().getMaxY(), y);
return;
}
synchronized (chunk) {
// Refresh the last block change time
this.lastBlockChangeTime = System.currentTimeMillis();
final Vec blockPosition = new Vec(x, y, z);
if (isAlreadyChanged(blockPosition, block)) { // do NOT change the block again.
// Avoids StackOverflowExceptions when onDestroy tries to destroy the block itself
// This can happen with nether portals which break the entire frame when a portal block is broken
return;
}
this.currentlyChangingBlocks.put(blockPosition, block);
// Change id based on neighbors
final BlockPlacementRule blockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(block);
if (placement != null && blockPlacementRule != null && doBlockUpdates) {
BlockPlacementRule.PlacementState rulePlacement;
if (placement instanceof BlockHandler.PlayerPlacement pp) {
rulePlacement = new BlockPlacementRule.PlacementState(
this, block, pp.getBlockFace(), blockPosition,
new Vec(pp.getCursorX(), pp.getCursorY(), pp.getCursorZ()),
pp.getPlayer().getPosition(),
pp.getPlayer().getItemInHand(pp.getHand()).meta(),
pp.getPlayer().isSneaking()
);
} else {
rulePlacement = new BlockPlacementRule.PlacementState(
this, block, null, blockPosition,
null, null, null,
false
);
}
block = blockPlacementRule.blockPlace(rulePlacement);
if (block == null) block = Block.AIR;
}
// Set the block
chunk.setBlock(x, y, z, block, placement, destroy);
// Refresh neighbors since a new block has been placed
if (doBlockUpdates) {
executeNeighboursBlockPlacementRule(blockPosition, updateDistance);
}
// Refresh player chunk block
{
chunk.sendPacketToViewers(new BlockChangePacket(blockPosition, block.stateId()));
var registry = block.registry();
if (registry.isBlockEntity()) {
final NBTCompound data = BlockUtils.extractClientNbt(block);
chunk.sendPacketToViewers(new BlockEntityDataPacket(blockPosition, registry.blockEntityId(), data));
}
}
}
}
@Override
public boolean placeBlock(@NotNull BlockHandler.Placement placement, boolean doBlockUpdates) {
final Point blockPosition = placement.getBlockPosition();
final Chunk chunk = getChunkAt(blockPosition);
if (!isLoaded(chunk)) return false;
UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(),
placement.getBlock(), placement, null, doBlockUpdates, 0);
return true;
}
@Override
public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace, boolean doBlockUpdates) {
final Chunk chunk = getChunkAt(blockPosition);
Check.notNull(chunk, "You cannot break blocks in a null chunk!");
if (chunk.isReadOnly()) return false;
if (!isLoaded(chunk)) return false;
final Block block = getBlock(blockPosition);
final int x = blockPosition.blockX();
final int y = blockPosition.blockY();
final int z = blockPosition.blockZ();
if (block.isAir()) {
// The player probably have a wrong version of this chunk section, send it
chunk.sendChunk(player);
return false;
}
PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(player, block, Block.AIR, blockPosition, blockFace);
EventDispatcher.call(blockBreakEvent);
final boolean allowed = !blockBreakEvent.isCancelled();
if (allowed) {
// Break or change the broken block based on event result
final Block resultBlock = blockBreakEvent.getResultBlock();
UNSAFE_setBlock(chunk, x, y, z, resultBlock, null,
new BlockHandler.PlayerDestroy(block, this, blockPosition, player), doBlockUpdates, 0);
// Send the block break effect packet
PacketUtils.sendGroupedPacket(chunk.getViewers(),
new EffectPacket(2001 /*Block break + block break sound*/, blockPosition, block.stateId(), false),
// Prevent the block breaker to play the particles and sound two times
(viewer) -> !viewer.equals(player));
}
return allowed;
}
@Override
public @NotNull CompletableFuture<Chunk> loadChunk(int chunkX, int chunkZ) {
return loadOrRetrieve(chunkX, chunkZ, () -> retrieveChunk(chunkX, chunkZ));
}
@Override
public @NotNull CompletableFuture<Chunk> loadOptionalChunk(int chunkX, int chunkZ) {
return loadOrRetrieve(chunkX, chunkZ, () -> hasEnabledAutoChunkLoad() ? retrieveChunk(chunkX, chunkZ) : AsyncUtils.empty());
}
@Override
public synchronized void unloadChunk(@NotNull Chunk chunk) {
if (!isLoaded(chunk)) return;
final int chunkX = chunk.getChunkX();
final int chunkZ = chunk.getChunkZ();
chunk.sendPacketToViewers(new UnloadChunkPacket(chunkX, chunkZ));
EventDispatcher.call(new InstanceChunkUnloadEvent(this, chunk));
// Remove all entities in chunk
getEntityTracker().chunkEntities(chunkX, chunkZ, EntityTracker.Target.ENTITIES).forEach(Entity::remove);
// Clear cache
this.chunks.remove(getChunkIndex(chunkX, chunkZ));
chunk.unload();
if (chunkLoader != null) {
chunkLoader.unloadChunk(chunk);
}
var dispatcher = MinecraftServer.process().dispatcher();
dispatcher.deletePartition(chunk);
}
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
return chunks.get(getChunkIndex(chunkX, chunkZ));
}
@Override
public @NotNull CompletableFuture<Void> saveInstance() {
return chunkLoader.saveInstance(this);
}
@Override
public @NotNull CompletableFuture<Void> saveChunkToStorage(@NotNull Chunk chunk) {
return chunkLoader.saveChunk(chunk);
}
@Override
public @NotNull CompletableFuture<Void> saveChunksToStorage() {
return chunkLoader.saveChunks(getChunks());
}
protected @NotNull CompletableFuture<@NotNull Chunk> retrieveChunk(int chunkX, int chunkZ) {
CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
final long index = getChunkIndex(chunkX, chunkZ);
final CompletableFuture<Chunk> prev = loadingChunks.putIfAbsent(index, completableFuture);
if (prev != null) return prev;
final IChunkLoader loader = chunkLoader;
final Runnable retriever = () -> loader.loadChunk(this, chunkX, chunkZ)
.thenCompose(chunk -> {
if (chunk != null) {
// Chunk has been loaded from storage
return CompletableFuture.completedFuture(chunk);
} else {
// Loader couldn't load the chunk, generate it
return createChunk(chunkX, chunkZ).whenComplete((c, a) -> c.onGenerate());
}
})
// cache the retrieved chunk
.thenAccept(chunk -> {
// TODO run in the instance thread?
cacheChunk(chunk);
chunk.onLoad();
EventDispatcher.call(new InstanceChunkLoadEvent(this, chunk));
final CompletableFuture<Chunk> future = this.loadingChunks.remove(index);
assert future == completableFuture : "Invalid future: " + future;
completableFuture.complete(chunk);
})
.exceptionally(throwable -> {
MinecraftServer.getExceptionManager().handleException(throwable);
return null;
});
if (loader.supportsParallelLoading()) {
CompletableFuture.runAsync(retriever);
} else {
retriever.run();
}
return completableFuture;
}
Map<Long, List<GeneratorImpl.SectionModifierImpl>> generationForks = new ConcurrentHashMap<>();
protected @NotNull CompletableFuture<@NotNull Chunk> createChunk(int chunkX, int chunkZ) {
final Chunk chunk = chunkSupplier.createChunk(this, chunkX, chunkZ);
Check.notNull(chunk, "Chunks supplied by a ChunkSupplier cannot be null.");
Generator generator = generator();
if (generator != null && chunk.shouldGenerate()) {
CompletableFuture<Chunk> resultFuture = new CompletableFuture<>();
// TODO: virtual thread once Loom is available
ForkJoinPool.commonPool().submit(() -> {
var chunkUnit = GeneratorImpl.chunk(chunk);
try {
// Generate block/biome palette
generator.generate(chunkUnit);
// Apply nbt/handler
if (chunkUnit.modifier() instanceof GeneratorImpl.AreaModifierImpl chunkModifier) {
for (var section : chunkModifier.sections()) {
if (section.modifier() instanceof GeneratorImpl.SectionModifierImpl sectionModifier) {
applyGenerationData(chunk, sectionModifier);
}
}
}
// Register forks or apply locally
for (var fork : chunkUnit.forks()) {
var sections = ((GeneratorImpl.AreaModifierImpl) fork.modifier()).sections();
for (var section : sections) {
if (section.modifier() instanceof GeneratorImpl.SectionModifierImpl sectionModifier) {
if (sectionModifier.blockPalette().count() == 0)
continue;
final Point start = section.absoluteStart();
final Chunk forkChunk = start.chunkX() == chunkX && start.chunkZ() == chunkZ ? chunk : getChunkAt(start);
if (forkChunk != null) {
applyFork(forkChunk, sectionModifier);
// Update players
forkChunk.invalidate();
forkChunk.sendChunk();
} else {
final long index = ChunkUtils.getChunkIndex(start);
this.generationForks.compute(index, (i, sectionModifiers) -> {
if (sectionModifiers == null) sectionModifiers = new ArrayList<>();
sectionModifiers.add(sectionModifier);
return sectionModifiers;
});
}
}
}
}
// Apply awaiting forks
processFork(chunk);
} catch (Throwable e) {
MinecraftServer.getExceptionManager().handleException(e);
} finally {
// End generation
refreshLastBlockChangeTime();
resultFuture.complete(chunk);
}
});
return resultFuture;
} else {
// No chunk generator, execute the callback with the empty chunk
processFork(chunk);
return CompletableFuture.completedFuture(chunk);
}
}
private void processFork(Chunk chunk) {
this.generationForks.compute(ChunkUtils.getChunkIndex(chunk), (aLong, sectionModifiers) -> {
if (sectionModifiers != null) {
for (var sectionModifier : sectionModifiers) {
applyFork(chunk, sectionModifier);
}
}
return null;
});
}
private void applyFork(Chunk chunk, GeneratorImpl.SectionModifierImpl sectionModifier) {
synchronized (chunk) {
Section section = chunk.getSectionAt(sectionModifier.start().blockY());
Palette currentBlocks = section.blockPalette();
// -1 is necessary because forked units handle explicit changes by changing AIR 0 to 1
sectionModifier.blockPalette().getAllPresent((x, y, z, value) -> currentBlocks.set(x, y, z, value - 1));
applyGenerationData(chunk, sectionModifier);
}
}
private void applyGenerationData(Chunk chunk, GeneratorImpl.SectionModifierImpl section) {
var cache = section.cache();
if (cache.isEmpty()) return;
final int height = section.start().blockY();
synchronized (chunk) {
Int2ObjectMaps.fastForEach(cache, blockEntry -> {
final int index = blockEntry.getIntKey();
final Block block = blockEntry.getValue();
final int x = ChunkUtils.blockIndexToChunkPositionX(index);
final int y = ChunkUtils.blockIndexToChunkPositionY(index) + height;
final int z = ChunkUtils.blockIndexToChunkPositionZ(index);
chunk.setBlock(x, y, z, block);
});
}
}
@Override
public void enableAutoChunkLoad(boolean enable) {
this.autoChunkLoad = enable;
}
@Override
public boolean hasEnabledAutoChunkLoad() {
return autoChunkLoad;
}
@Override
public boolean isInVoid(@NotNull Point point) {
// TODO: more customizable
return point.y() < getDimensionType().getMinY() - 64;
}
/**
* Changes which type of {@link Chunk} implementation to use once one needs to be loaded.
* <p>
* Uses {@link DynamicChunk} by default.
* <p>
* WARNING: if you need to save this instance's chunks later,
* the code needs to be predictable for {@link IChunkLoader#loadChunk(Instance, int, int)}
* to create the correct type of {@link Chunk}. tl;dr: Need chunk save = no random type.
*
* @param chunkSupplier the new {@link ChunkSupplier} of this instance, chunks need to be non-null
* @throws NullPointerException if {@code chunkSupplier} is null
*/
@Override
public void setChunkSupplier(@NotNull ChunkSupplier chunkSupplier) {
this.chunkSupplier = chunkSupplier;
}
/**
* Gets the current {@link ChunkSupplier}.
* <p>
* You shouldn't use it to generate a new chunk, but as a way to view which one is currently in use.
*
* @return the current {@link ChunkSupplier}
*/
public ChunkSupplier getChunkSupplier() {
return chunkSupplier;
}
/**
* Gets all the {@link SharedInstance} linked to this container.
*
* @return an unmodifiable {@link List} containing all the {@link SharedInstance} linked to this container
*/
public List<SharedInstance> getSharedInstances() {
return Collections.unmodifiableList(sharedInstances);
}
/**
* Gets if this instance has {@link SharedInstance} linked to it.
*
* @return true if {@link #getSharedInstances()} is not empty
*/
public boolean hasSharedInstances() {
return !sharedInstances.isEmpty();
}
/**
* Assigns a {@link SharedInstance} to this container.
* <p>
* Only used by {@link InstanceManager}, mostly unsafe.
*
* @param sharedInstance the shared instance to assign to this container
*/
protected void addSharedInstance(SharedInstance sharedInstance) {
this.sharedInstances.add(sharedInstance);
}
/**
* Copies all the chunks of this instance and create a new instance container with all of them.
* <p>
* Chunks are copied with {@link Chunk#copy(Instance, int, int)},
* {@link UUID} is randomized and {@link DimensionType} is passed over.
*
* @return an {@link InstanceContainer} with the exact same chunks as 'this'
* @see #getSrcInstance() to retrieve the "creation source" of the copied instance
*/
public synchronized InstanceContainer copy() {
InstanceContainer copiedInstance = new InstanceContainer(UUID.randomUUID(), getDimensionType());
copiedInstance.srcInstance = this;
copiedInstance.tagHandler = this.tagHandler.copy();
copiedInstance.lastBlockChangeTime = this.lastBlockChangeTime;
for (Chunk chunk : chunks.values()) {
final int chunkX = chunk.getChunkX();
final int chunkZ = chunk.getChunkZ();
final Chunk copiedChunk = chunk.copy(copiedInstance, chunkX, chunkZ);
copiedInstance.cacheChunk(copiedChunk);
}
return copiedInstance;
}
/**
* Gets the instance from which this one has been copied.
* <p>
* Only present if this instance has been created with {@link InstanceContainer#copy()}.
*
* @return the instance source, null if not created by a copy
* @see #copy() to create a copy of this instance with 'this' as the source
*/
public @Nullable InstanceContainer getSrcInstance() {
return srcInstance;
}
/**
* Gets the last time at which a block changed.
*
* @return the time at which the last block changed in milliseconds, 0 if never
*/
public long getLastBlockChangeTime() {
return lastBlockChangeTime;
}
/**
* Signals the instance that a block changed.
* <p>
* Useful if you change blocks values directly using a {@link Chunk} object.
*/
public void refreshLastBlockChangeTime() {
this.lastBlockChangeTime = System.currentTimeMillis();
}
@Override
public @Nullable Generator generator() {
return generator;
}
@Override
public void setGenerator(@Nullable Generator generator) {
this.generator = generator;
}
/**
* Gets all the instance chunks.
*
* @return the chunks of this instance
*/
@Override
public @NotNull Collection<@NotNull Chunk> getChunks() {
return chunks.values();
}
/**
* Gets the {@link IChunkLoader} of this instance.
*
* @return the {@link IChunkLoader} of this instance
*/
public IChunkLoader getChunkLoader() {
return chunkLoader;
}
/**
* Changes the {@link IChunkLoader} of this instance (to change how chunks are retrieved when not already loaded).
*
* @param chunkLoader the new {@link IChunkLoader}
*/
public void setChunkLoader(IChunkLoader chunkLoader) {
this.chunkLoader = chunkLoader;
}
@Override
public void tick(long time) {
// Time/world border
super.tick(time);
// Clear block change map
Lock wrlock = this.changingBlockLock;
wrlock.lock();
this.currentlyChangingBlocks.clear();
wrlock.unlock();
}
/**
* Has this block already changed since last update?
* Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace.
*
* @param blockPosition the block position
* @param block the block
* @return true if the block changed since the last update
*/
private boolean isAlreadyChanged(@NotNull Point blockPosition, @NotNull Block block) {
final Block changedBlock = currentlyChangingBlocks.get(blockPosition);
return Objects.equals(changedBlock, block);
}
/**
* Executed when a block is modified, this is used to modify the states of neighbours blocks.
* <p>
* For example, this can be used for redstone wires which need an understanding of its neighborhoods to take the right shape.
*
* @param blockPosition the position of the modified block
*/
private void executeNeighboursBlockPlacementRule(@NotNull Point blockPosition, int updateDistance) {
ChunkCache cache = new ChunkCache(this, null, null);
for (var updateFace : BLOCK_UPDATE_FACES) {
var direction = updateFace.toDirection();
final int neighborX = blockPosition.blockX() + direction.normalX();
final int neighborY = blockPosition.blockY() + direction.normalY();
final int neighborZ = blockPosition.blockZ() + direction.normalZ();
if (neighborY < getDimensionType().getMinY() || neighborY > getDimensionType().getTotalHeight())
continue;
final Block neighborBlock = cache.getBlock(neighborX, neighborY, neighborZ, Condition.TYPE);
if (neighborBlock == null)
continue;
final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(neighborBlock);
if (neighborBlockPlacementRule == null || updateDistance >= neighborBlockPlacementRule.maxUpdateDistance()) continue;
final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ);
final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(
this,
neighborPosition,
neighborBlock,
updateFace.getOppositeFace()
));
if (neighborBlock != newNeighborBlock) {
final Chunk chunk = getChunkAt(neighborPosition);
if (!isLoaded(chunk)) continue;
UNSAFE_setBlock(chunk, neighborPosition.blockX(), neighborPosition.blockY(), neighborPosition.blockZ(), newNeighborBlock,
null, null, true, updateDistance + 1);
}
}
}
private CompletableFuture<Chunk> loadOrRetrieve(int chunkX, int chunkZ, Supplier<CompletableFuture<Chunk>> supplier) {
final Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk != null) {
// Chunk already loaded
return CompletableFuture.completedFuture(chunk);
}
return supplier.get();
}
private void cacheChunk(@NotNull Chunk chunk) {
this.chunks.put(getChunkIndex(chunk), chunk);
var dispatcher = MinecraftServer.process().dispatcher();
dispatcher.createPartition(chunk);
}
}