diff --git a/SpongeAPI b/SpongeAPI index cb4ee935103..d5a46ad7fd4 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit cb4ee935103e4cd09e9bcb7a4e8cd649e0287fc9 +Subproject commit d5a46ad7fd4e4757fd50778122c92b7fed9d0aa0 diff --git a/src/main/java/org/spongepowered/common/bridge/world/level/chunk/storage/IOWorkerBridge.java b/src/main/java/org/spongepowered/common/bridge/world/level/chunk/storage/IOWorkerBridge.java index e22178095b7..1429e65b6f1 100644 --- a/src/main/java/org/spongepowered/common/bridge/world/level/chunk/storage/IOWorkerBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/world/level/chunk/storage/IOWorkerBridge.java @@ -26,8 +26,9 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; +import org.spongepowered.common.world.level.chunk.storage.SpongeIOWorkerType; public interface IOWorkerBridge { - void bridge$setDimension(ResourceKey dimension); + void bridge$setDimension(SpongeIOWorkerType type, ResourceKey dimension); } diff --git a/src/main/java/org/spongepowered/common/event/ShouldFire.java b/src/main/java/org/spongepowered/common/event/ShouldFire.java index ca825f56e1d..494d84a79ed 100644 --- a/src/main/java/org/spongepowered/common/event/ShouldFire.java +++ b/src/main/java/org/spongepowered/common/event/ShouldFire.java @@ -116,12 +116,16 @@ public final class ShouldFire { public static boolean KICK_PLAYER_EVENT = false; - public static boolean CHUNK_EVENT_LOAD = false; - public static boolean CHUNK_EVENT_SAVE_PRE = false; - public static boolean CHUNK_EVENT_SAVE_POST = false; + public static boolean CHUNK_EVENT_BLOCKS_LOAD = false; + public static boolean CHUNK_EVENT_BLOCKS_SAVE_PRE = false; + public static boolean CHUNK_EVENT_BLOCKS_SAVE_POST = false; + public static boolean CHUNK_EVENT_ENTITIES_LOAD = false; + public static boolean CHUNK_EVENT_ENTITIES_SAVE_PRE = false; + public static boolean CHUNK_EVENT_ENTITIES_SAVE_POST = false; public static boolean CHUNK_EVENT_GENERATED = false; public static boolean CHUNK_EVENT_UNLOAD_PRE = false; public static boolean CHUNK_EVENT_UNLOAD_POST = false; + public static boolean CHUNK_EVENT_LOAD = false; public static boolean CHANGE_DATA_HOLDER_EVENT_VALUE_CHANGE = false; diff --git a/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java b/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java new file mode 100644 index 00000000000..67f5bce8b7c --- /dev/null +++ b/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java @@ -0,0 +1,233 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.world.level.chunk.storage; + +import com.google.common.collect.ImmutableList; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Tuple; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.persistence.DataContainer; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.util.AABB; +import org.spongepowered.api.world.chunk.EntityChunk; +import org.spongepowered.api.world.volume.stream.StreamOptions; +import org.spongepowered.api.world.volume.stream.VolumeStream; +import org.spongepowered.common.bridge.world.level.LevelBridge; +import org.spongepowered.common.util.VecHelper; +import org.spongepowered.common.world.storage.SpongeChunkLayout; +import org.spongepowered.common.world.volume.VolumeStreamUtils; +import org.spongepowered.common.world.volume.buffer.entity.ObjectArrayMutableEntityBuffer; +import org.spongepowered.math.vector.Vector3d; +import org.spongepowered.math.vector.Vector3i; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public final class SpongeEntityChunk implements EntityChunk { + + private final ServerLevel level; + private final Vector3i chunkPosition; + private final Stream entities; + + private @MonotonicNonNull SpongeChunkLayout chunkLayout; + private @MonotonicNonNull Vector3i blockMin; + private @MonotonicNonNull Vector3i blockMax; + private @MonotonicNonNull List newEntities; + + public SpongeEntityChunk(final ServerLevel level, final Vector3i chunkPosition, final Stream entities) { + this.level = level; + this.chunkPosition = chunkPosition; + this.entities = entities; + } + + @Override + public Vector3i min() { + if (this.blockMin == null) { + if (this.chunkLayout == null) { + this.chunkLayout = new SpongeChunkLayout(this.level.getMinBuildHeight(), this.level.getHeight()); + } + this.blockMin = this.chunkLayout.forceToWorld(this.chunkPosition); + } + return this.blockMin; + } + + @Override + public Vector3i max() { + if (this.blockMax == null) { + if (this.chunkLayout == null) { + this.chunkLayout = new SpongeChunkLayout(this.level.getMinBuildHeight(), this.level.getHeight()); + } + this.blockMax = this.min().add(this.chunkLayout.chunkSize()).sub(1, 1, 1); + } + return this.blockMax; + } + + @Override + public boolean contains(final int x, final int y, final int z) { + return VecHelper.inBounds(x, y, z, this.min(), this.max()); + } + + @Override + public boolean isAreaAvailable(final int x, final int y, final int z) { + return VecHelper.inBounds(x, y, z, this.min(), this.max()); + } + + @Override + public Collection players() { + return this.entities.filter(Player.class::isInstance).map(Player.class::cast).toList(); + } + + @Override + public Optional entity(final UUID uuid) { + return this.entities.filter(e -> e.getUUID().equals(uuid)).map(Entity.class::cast).findFirst(); + } + + @Override + public Collection entities() { + return this.entities.map(Entity.class::cast).toList(); + } + + @Override + public Collection entities(final Class entityClass, final AABB box, final @Nullable Predicate predicate) { + final net.minecraft.world.phys.AABB mcAabb = VecHelper.toMinecraftAABB(box); + return this.entities + .filter(e -> entityClass.isInstance(e) && e.getBoundingBox().intersects(mcAabb)) + .map(entityClass::cast) + .filter(e -> predicate.test(e)) + .toList(); + } + + @Override + public Collection entities(final AABB box, final Predicate filter) { + final net.minecraft.world.phys.AABB mcAabb = VecHelper.toMinecraftAABB(box); + return this.entities.map(Entity.class::cast) + .filter(e -> ((net.minecraft.world.entity.Entity) e).getBoundingBox().intersects(mcAabb) && filter.test(e)) + .toList(); + } + + @Override + public VolumeStream entityStream(final Vector3i min, final Vector3i max, final StreamOptions options) { + VolumeStreamUtils.validateStreamArgs( + Objects.requireNonNull(min, "min"), Objects.requireNonNull(max, "max"), + Objects.requireNonNull(options, "options")); + + final boolean shouldCarbonCopy = options.carbonCopy(); + final Vector3i size = max.sub(min).add(1, 1 ,1); + final @MonotonicNonNull ObjectArrayMutableEntityBuffer backingVolume; + if (shouldCarbonCopy) { + backingVolume = new ObjectArrayMutableEntityBuffer(min, size); + } else { + backingVolume = null; + } + + return VolumeStreamUtils.generateStream(options, + this, + this, + // Entity Accessor + (chunk) -> chunk.entities.filter(entity -> VecHelper.inBounds(entity.blockPosition(), min, max)) + .map(entity -> new AbstractMap.SimpleEntry<>(entity.blockPosition(), entity)), + // Entity Identity Function + VolumeStreamUtils.getOrCloneEntityWithVolume(shouldCarbonCopy, backingVolume, this.level), + (key, entity) -> entity.getUUID(), + // Filtered Position Entity Accessor + (entityUuid, chunk) -> { + final net.minecraft.world.entity.@Nullable Entity entity = shouldCarbonCopy + ? (net.minecraft.world.entity.Entity) backingVolume.entity(entityUuid).orElse(null) + : (net.minecraft.world.entity.Entity) chunk.entity(entityUuid).orElse(null); + if (entity == null) { + return null; + } + return new Tuple<>(entity.blockPosition(), entity); + } + ); + } + + @Override + public E createEntity(final EntityType type, final Vector3d position) throws IllegalArgumentException, IllegalStateException { + this.checkPositionInChunk(position); + return ((LevelBridge) this.level).bridge$createEntity(type, position, false); + } + + @Override + public E createEntityNaturally(final EntityType type, final Vector3d position) throws IllegalArgumentException, IllegalStateException { + this.checkPositionInChunk(position); + return ((LevelBridge) this.level).bridge$createEntity(type, position, true); + } + + @Override + public Optional createEntity(final DataContainer container) { + return Optional.ofNullable(((LevelBridge) this.level).bridge$createEntity(container, null, + position -> VecHelper.inBounds(position, this.min(), this.max()))); + } + + @Override + public Optional createEntity(final DataContainer container, final Vector3d position) { + this.checkPositionInChunk(position); + return Optional.ofNullable(((LevelBridge) this.level).bridge$createEntity(container, position, null)); + } + + @Override + public boolean spawnEntity(final Entity entity) { + if (this.newEntities == null) { + this.newEntities = new ArrayList<>(); + } + this.newEntities.add((net.minecraft.world.entity.Entity) entity); + return true; + } + + @Override + public Collection spawnEntities(final Iterable entities) { + final List list = new ArrayList<>(); + for (final Entity entity : entities) { + this.spawnEntity(entity); + list.add(entity); + } + return list; + } + + private void checkPositionInChunk(final Vector3d position) { + if (!VecHelper.inBounds(position, this.min(), this.max())) { + throw new IllegalArgumentException("Supplied bounds are not within this chunk."); + } + } + + public @Nullable List buildIfChanged() { + if (this.newEntities == null) { + return null; + } + + return Stream.concat(this.entities, this.newEntities.stream()).collect(ImmutableList.toImmutableList()); + } +} diff --git a/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeIOWorkerType.java b/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeIOWorkerType.java new file mode 100644 index 00000000000..66f252528cf --- /dev/null +++ b/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeIOWorkerType.java @@ -0,0 +1,30 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.world.level.chunk.storage; + +public enum SpongeIOWorkerType { + CHUNK, + ENTITY +} diff --git a/src/main/java/org/spongepowered/common/world/volume/buffer/entity/ObjectArrayMutableEntityBuffer.java b/src/main/java/org/spongepowered/common/world/volume/buffer/entity/ObjectArrayMutableEntityBuffer.java index c99032a7a87..940b5d0af3d 100644 --- a/src/main/java/org/spongepowered/common/world/volume/buffer/entity/ObjectArrayMutableEntityBuffer.java +++ b/src/main/java/org/spongepowered/common/world/volume/buffer/entity/ObjectArrayMutableEntityBuffer.java @@ -26,23 +26,18 @@ import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.api.block.BlockState; -import org.spongepowered.api.block.BlockType; import org.spongepowered.api.data.persistence.DataContainer; import org.spongepowered.api.entity.Entity; import org.spongepowered.api.entity.EntityType; import org.spongepowered.api.entity.living.player.Player; -import org.spongepowered.api.fluid.FluidState; import org.spongepowered.api.util.AABB; -import org.spongepowered.api.world.schematic.Palette; import org.spongepowered.api.world.volume.entity.EntityVolume; import org.spongepowered.api.world.volume.stream.StreamOptions; import org.spongepowered.api.world.volume.stream.VolumeElement; import org.spongepowered.api.world.volume.stream.VolumeStream; import org.spongepowered.common.world.volume.SpongeVolumeStream; import org.spongepowered.common.world.volume.VolumeStreamUtils; -import org.spongepowered.common.world.volume.buffer.block.AbstractBlockBuffer; -import org.spongepowered.common.world.volume.buffer.block.ArrayMutableBlockBuffer; +import org.spongepowered.common.world.volume.buffer.AbstractVolumeBuffer; import org.spongepowered.math.vector.Vector3d; import org.spongepowered.math.vector.Vector3i; @@ -52,81 +47,19 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; -public class ObjectArrayMutableEntityBuffer extends AbstractBlockBuffer implements EntityVolume.Mutable { - // This is our backing block buffer - private final ArrayMutableBlockBuffer blockBuffer; +public class ObjectArrayMutableEntityBuffer extends AbstractVolumeBuffer implements EntityVolume.Mutable { private final List entities; public ObjectArrayMutableEntityBuffer(final Vector3i start, final Vector3i size) { super(start, size); - this.blockBuffer = new ArrayMutableBlockBuffer(start, size); this.entities = new ArrayList<>(); } - public ObjectArrayMutableEntityBuffer(final Vector3i start, final Vector3i size, - final ArrayMutableBlockBuffer blockBuffer - ) { - super(start, size); - this.blockBuffer = blockBuffer; - this.entities = new ArrayList<>(); - } - - @Override - public Palette getPalette() { - return this.blockBuffer.getPalette(); - } - - @Override - public BlockState block(final int x, final int y, final int z) { - return this.blockBuffer.block(x, y, z); - } - - @Override - public FluidState fluid(final int x, final int y, final int z) { - return this.blockBuffer.fluid(x, y, z); - } - - @Override - public int highestYAt(final int x, final int z) { - return this.blockBuffer.highestYAt(x, z); - } - - @Override - public VolumeStream blockStateStream(final Vector3i min, final Vector3i max, - final StreamOptions options) { - VolumeStreamUtils.validateStreamArgs(min, max, this.min(), this.max(), options); - final ArrayMutableBlockBuffer buffer; - if (options.carbonCopy()) { - buffer = this.blockBuffer.copy(); - } else { - buffer = this.blockBuffer; - } - final Stream> stateStream = IntStream.rangeClosed(min.x(), max.x()) - .mapToObj(x -> IntStream.rangeClosed(min.z(), max.z()) - .mapToObj(z -> IntStream.rangeClosed(min.y(), max.y()) - .mapToObj(y -> VolumeElement.of((EntityVolume.Mutable) this, () -> buffer.block(x, y, z), new Vector3d(x, y, z))) - ).flatMap(Function.identity()) - ).flatMap(Function.identity()); - return new SpongeVolumeStream<>(stateStream, () -> this); - } - - @Override - public boolean setBlock(final int x, final int y, final int z, final BlockState block) { - return this.blockBuffer.setBlock(x, y, z, block); - } - - @Override - public boolean removeBlock(final int x, final int y, final int z) { - return this.blockBuffer.removeBlock(x, y, z); - } - @Override public E createEntity(final EntityType type, final Vector3d position) throws IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException("Cannot create entities without a world, can only add to a volume"); diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java index 52d1fb1335a..a16b1cc68ff 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java @@ -29,6 +29,7 @@ import net.minecraft.core.registries.Registries; import net.minecraft.util.Tuple; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelHeightAccessor; @@ -248,7 +249,7 @@ public VolumeStream blockStateStream( (blockPos, world) -> { final net.minecraft.world.level.block.state.BlockState tileEntity = shouldCarbonCopy ? backingVolume.getBlock(blockPos) - : ((LevelReader) world).getBlockState(blockPos); + : ((BlockGetter) world).getBlockState(blockPos); return new Tuple<>(blockPos, tileEntity); } ); diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkHolderMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkHolderMixin.java index ca887304b96..31ef0eac30e 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkHolderMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkHolderMixin.java @@ -27,6 +27,7 @@ import com.mojang.datafixers.util.Either; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.FullChunkStatus; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ImposterProtoChunk; @@ -34,7 +35,9 @@ import org.spongepowered.api.ResourceKey; import org.spongepowered.api.event.SpongeEventFactory; import org.spongepowered.api.event.world.chunk.ChunkEvent; +import org.spongepowered.api.world.chunk.WorldChunk; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -51,6 +54,11 @@ @Mixin(ChunkHolder.class) abstract class ChunkHolderMixin { + + // @formatter:off + @Shadow public abstract CompletableFuture> shadow$getEntityTickingChunkFuture(); + // @formatter:on + @Inject( method = "replaceProtoChunk(Lnet/minecraft/world/level/chunk/ImposterProtoChunk;)V", at = @At("TAIL") @@ -74,4 +82,16 @@ abstract class ChunkHolderMixin { cir.setReturnValue(ChunkHolder.UNLOADED_CHUNK_FUTURE); } } + + @Inject(method = "lambda$scheduleFullChunkPromotion$7", at = @At("TAIL")) + private void impl$onScheduleFullChunkPromotion(final ChunkMap $$0x, final FullChunkStatus $$1x, final CallbackInfo ci) { + if ($$1x == FullChunkStatus.ENTITY_TICKING && ShouldFire.CHUNK_EVENT_LOAD) { + this.shadow$getEntityTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).ifLeft(chunk -> { + final Vector3i chunkPos = VecHelper.toVector3i(chunk.getPos()); + final ChunkEvent.Load event = SpongeEventFactory.createChunkEventLoad(PhaseTracker.getInstance().currentCause(), + (WorldChunk) chunk, chunkPos, (ResourceKey) (Object) chunk.getLevel().dimension().location()); + SpongeCommon.post(event); + }); + } + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMapMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMapMixin.java index 32201337e48..5dc931f3f25 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMapMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMapMixin.java @@ -41,6 +41,7 @@ import org.spongepowered.api.event.world.chunk.ChunkEvent; import org.spongepowered.api.util.Direction; import org.spongepowered.api.world.SerializationBehavior; +import org.spongepowered.api.world.chunk.BlockChunk; import org.spongepowered.api.world.chunk.WorldChunk; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -64,6 +65,8 @@ import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.util.Constants; import org.spongepowered.common.util.DirectionUtil; +import org.spongepowered.common.util.VecHelper; +import org.spongepowered.common.world.level.chunk.storage.SpongeIOWorkerType; import org.spongepowered.math.vector.Vector3i; import java.util.concurrent.CompletableFuture; @@ -84,7 +87,7 @@ public abstract class ChunkMapMixin implements ChunkMapBridge { @Inject(method = "", at = @At("RETURN")) private void impl$setIOWorkerDimension(final CallbackInfo ci) { - ((IOWorkerBridge) ((ChunkStorageAccessor) this).accessor$worker()).bridge$setDimension(this.level.dimension()); + ((IOWorkerBridge) ((ChunkStorageAccessor) this).accessor$worker()).bridge$setDimension(SpongeIOWorkerType.CHUNK, this.level.dimension()); } @Redirect(method = "save", @@ -127,7 +130,7 @@ public abstract class ChunkMapMixin implements ChunkMapBridge { ) ) private void impl$onSetUnloaded(final ServerLevel level, final LevelChunk chunk) { - final Vector3i chunkPos = new Vector3i(chunk.getPos().x, 0, chunk.getPos().z); + final Vector3i chunkPos = VecHelper.toVector3i(chunk.getPos()); if (ShouldFire.CHUNK_EVENT_UNLOAD_PRE) { final ChunkEvent.Unload event = SpongeEventFactory.createChunkEventUnloadPre(PhaseTracker.getInstance().currentCause(), @@ -157,10 +160,10 @@ public abstract class ChunkMapMixin implements ChunkMapBridge { @Inject(method = "save", at = @At(value = "HEAD"), cancellable = true) private void impl$onSave(final ChunkAccess var1, final CallbackInfoReturnable cir) { if (var1 instanceof WorldChunk) { - if (ShouldFire.CHUNK_EVENT_SAVE_PRE) { - final Vector3i chunkPos = new Vector3i(var1.getPos().x, 0, var1.getPos().z); - final ChunkEvent.Save.Pre postSave = SpongeEventFactory.createChunkEventSavePre(PhaseTracker.getInstance().currentCause(), - ((WorldChunk) var1), chunkPos, (ResourceKey) (Object) this.level.dimension().location()); + if (ShouldFire.CHUNK_EVENT_BLOCKS_SAVE_PRE) { + final Vector3i chunkPos = VecHelper.toVector3i(var1.getPos()); + final ChunkEvent.Blocks.Save.Pre postSave = SpongeEventFactory.createChunkEventBlocksSavePre(PhaseTracker.getInstance().currentCause(), + ((BlockChunk) var1), chunkPos, (ResourceKey) (Object) this.level.dimension().location()); SpongeCommon.post(postSave); if (postSave.isCancelled()) { cir.setReturnValue(false); @@ -178,10 +181,10 @@ public abstract class ChunkMapMixin implements ChunkMapBridge { ) private void impl$onLoad(final LevelChunk levelChunk, final boolean loaded) { levelChunk.setLoaded(true); - final Vector3i chunkPos = new Vector3i(levelChunk.getPos().x, 0, levelChunk.getPos().z); - if (ShouldFire.CHUNK_EVENT_LOAD) { - final ChunkEvent.Load loadEvent = SpongeEventFactory.createChunkEventLoad(PhaseTracker.getInstance().currentCause(), - ((WorldChunk) levelChunk), chunkPos, (ResourceKey) (Object) this.level.dimension().location()); + final Vector3i chunkPos = VecHelper.toVector3i(levelChunk.getPos()); + if (ShouldFire.CHUNK_EVENT_BLOCKS_LOAD) { + final ChunkEvent.Blocks.Load loadEvent = SpongeEventFactory.createChunkEventBlocksLoad(PhaseTracker.getInstance().currentCause(), + ((BlockChunk) levelChunk), chunkPos, (ResourceKey) (Object) this.level.dimension().location()); SpongeCommon.post(loadEvent); } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/LevelChunkMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/LevelChunkMixin.java index f62daa6549a..07a4474a29c 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/LevelChunkMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/LevelChunkMixin.java @@ -47,6 +47,7 @@ import org.spongepowered.api.data.Key; import org.spongepowered.api.data.value.Value; import org.spongepowered.api.util.Direction; +import org.spongepowered.api.world.chunk.BlockChunk; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -82,7 +83,7 @@ import java.util.function.Function; @Mixin(net.minecraft.world.level.chunk.LevelChunk.class) -public abstract class LevelChunkMixin extends ChunkAccess implements LevelChunkBridge, CacheKeyBridge, SpongeMutableDataHolder, SpongeDataHolderBridge, DataCompoundHolder { +public abstract class LevelChunkMixin extends ChunkAccess implements LevelChunkBridge, CacheKeyBridge, SpongeMutableDataHolder, SpongeDataHolderBridge, DataCompoundHolder, BlockChunk { // @formatter:off @Shadow @Final private Level level; diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/EntityStorageMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/EntityStorageMixin.java new file mode 100644 index 00000000000..7e95447bd35 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/EntityStorageMixin.java @@ -0,0 +1,121 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.level.chunk.storage; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.EntityStorage; +import net.minecraft.world.level.chunk.storage.IOWorker; +import net.minecraft.world.level.entity.ChunkEntities; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.world.chunk.ChunkEvent; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.bridge.world.level.chunk.storage.IOWorkerBridge; +import org.spongepowered.common.event.ShouldFire; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.util.VecHelper; +import org.spongepowered.common.world.level.chunk.storage.SpongeEntityChunk; +import org.spongepowered.common.world.level.chunk.storage.SpongeIOWorkerType; +import org.spongepowered.math.vector.Vector3i; + +import java.util.List; +import java.util.Optional; + +@Mixin(EntityStorage.class) +public abstract class EntityStorageMixin { + + // @formatter:off + @Shadow @Final private IOWorker worker; + @Shadow @Final private ServerLevel level; + // @formatter:on + + @Inject(method = "", at = @At("RETURN")) + private void impl$setIOWorkerDimension(final CallbackInfo ci) { + ((IOWorkerBridge) this.worker).bridge$setDimension(SpongeIOWorkerType.ENTITY, this.level.dimension()); + } + + @Inject(method = "lambda$loadEntities$0", at = @At("RETURN"), cancellable = true) + private void impl$onLoadEntities(final ChunkPos $$0x, final Optional $$1, final CallbackInfoReturnable> cir) { + if (!ShouldFire.CHUNK_EVENT_ENTITIES_LOAD) { + return; + } + + final Vector3i chunkPos = VecHelper.toVector3i($$0x); + final SpongeEntityChunk entities = new SpongeEntityChunk(this.level, chunkPos, cir.getReturnValue().getEntities()); + final ChunkEvent.Entities.Load loadEvent = SpongeEventFactory.createChunkEventEntitiesLoad(PhaseTracker.getInstance().currentCause(), + entities, chunkPos, (ResourceKey) (Object) this.level.dimension().location()); + + SpongeCommon.post(loadEvent); + + final @Nullable List newList = entities.buildIfChanged(); + if (newList != null) { + cir.setReturnValue(new ChunkEntities<>($$0x, newList)); + } + } + + @ModifyVariable(method = "storeEntities", at = @At("HEAD"), argsOnly = true) + private ChunkEntities impl$onStoreEntities(final ChunkEntities $$0) { + if (!ShouldFire.CHUNK_EVENT_ENTITIES_SAVE_PRE) { + return $$0; + } + + final Vector3i chunkPos = VecHelper.toVector3i($$0.getPos()); + + final SpongeEntityChunk entities = new SpongeEntityChunk(this.level, chunkPos, $$0.getEntities()); + final ChunkEvent.Entities.Save.Pre saveEvent = SpongeEventFactory.createChunkEventEntitiesSavePre(PhaseTracker.getInstance().currentCause(), + entities, chunkPos, (ResourceKey) (Object) this.level.dimension().location()); + + if (SpongeCommon.post(saveEvent)) { + return null; + } + + final @Nullable List newList = entities.buildIfChanged(); + if (newList != null) { + return new ChunkEntities<>($$0.getPos(), newList); + } + + return $$0; + } + + @Inject(method = "storeEntities", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/entity/ChunkEntities;getPos()Lnet/minecraft/world/level/ChunkPos;"), cancellable = true) + private void impl$onCancelledEntitySave(final ChunkEntities $$0, final CallbackInfo ci) { + if ($$0 == null) { + ci.cancel(); + } + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/IOWorkerMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/IOWorkerMixin.java index f053b41f2c8..e94a7e7fddf 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/IOWorkerMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/chunk/storage/IOWorkerMixin.java @@ -55,6 +55,7 @@ import org.spongepowered.common.event.ShouldFire; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.world.level.chunk.SpongeUnloadedChunkException; +import org.spongepowered.common.world.level.chunk.storage.SpongeIOWorkerType; import org.spongepowered.math.vector.Vector3i; import java.util.BitSet; @@ -78,24 +79,33 @@ public abstract class IOWorkerMixin implements IOWorkerBridge { @Shadow protected abstract void shadow$tellStorePending(); // @formatter:on - @MonotonicNonNull private ResourceKey impl$dimension; //We only set this for chunk related IO workers + // We only set these for chunk and entity related IO workers + @MonotonicNonNull private SpongeIOWorkerType impl$type; + @MonotonicNonNull private ResourceKey impl$dimension; @Override - public void bridge$setDimension(ResourceKey dimension) { + public void bridge$setDimension(final SpongeIOWorkerType type, final ResourceKey dimension) { + this.impl$type = type; this.impl$dimension = dimension; } @Inject(method = "runStore", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Ljava/util/concurrent/CompletableFuture;complete(Ljava/lang/Object;)Z")) private void impl$onSaved(final ChunkPos param0, final @Coerce Object param1, final CallbackInfo ci) { - if (this.impl$dimension == null) { - return; + if (this.impl$type == SpongeIOWorkerType.CHUNK) { + if (ShouldFire.CHUNK_EVENT_BLOCKS_SAVE_POST) { + final Vector3i chunkPos = new Vector3i(param0.x, 0, param0.z); + final ChunkEvent.Blocks.Save.Post postSave = SpongeEventFactory.createChunkEventBlocksSavePost(PhaseTracker.getInstance().currentCause(), chunkPos, + (org.spongepowered.api.ResourceKey) (Object) this.impl$dimension.location()); + SpongeCommon.post(postSave); + } } - - if (ShouldFire.CHUNK_EVENT_SAVE_POST) { - final Vector3i chunkPos = new Vector3i(param0.x, 0, param0.z); - final ChunkEvent.Save.Post postSave = SpongeEventFactory.createChunkEventSavePost(PhaseTracker.getInstance().currentCause(), chunkPos, - (org.spongepowered.api.ResourceKey) (Object) this.impl$dimension.location()); - SpongeCommon.post(postSave); + else if (this.impl$type == SpongeIOWorkerType.ENTITY) { + if (ShouldFire.CHUNK_EVENT_ENTITIES_SAVE_POST) { + final Vector3i chunkPos = new Vector3i(param0.x, 0, param0.z); + final ChunkEvent.Entities.Save.Post postSave = SpongeEventFactory.createChunkEventEntitiesSavePost(PhaseTracker.getInstance().currentCause(), chunkPos, + (org.spongepowered.api.ResourceKey) (Object) this.impl$dimension.location()); + SpongeCommon.post(postSave); + } } } diff --git a/src/mixins/resources/mixins.sponge.core.json b/src/mixins/resources/mixins.sponge.core.json index b0947d8b34b..8f2336ef4aa 100644 --- a/src/mixins/resources/mixins.sponge.core.json +++ b/src/mixins/resources/mixins.sponge.core.json @@ -237,6 +237,7 @@ "world.level.chunk.ChunkSerializerMixin", "world.level.chunk.ChunkStatusMixin", "world.level.chunk.LevelChunkMixin", + "world.level.chunk.storage.EntityStorageMixin", "world.level.chunk.storage.IOWorkerMixin", "world.level.dimension.DimensionTypeMixin", "world.level.dimension.LevelStemMixin", diff --git a/testplugins/src/main/java/org/spongepowered/test/chunk/ChunkEventTest.java b/testplugins/src/main/java/org/spongepowered/test/chunk/ChunkEventTest.java index 347e8a8483a..3bc485f4129 100644 --- a/testplugins/src/main/java/org/spongepowered/test/chunk/ChunkEventTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/chunk/ChunkEventTest.java @@ -25,16 +25,32 @@ package org.spongepowered.test.chunk; import com.google.inject.Inject; +import io.leangen.geantyref.TypeToken; +import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.checkerframework.checker.nullness.qual.NonNull; import org.spongepowered.api.Game; import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.command.CommandResult; import org.spongepowered.api.command.parameter.CommandContext; +import org.spongepowered.api.command.parameter.Parameter; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.EntityType; import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; import org.spongepowered.api.event.world.chunk.ChunkEvent; +import org.spongepowered.api.registry.RegistryTypes; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; import org.spongepowered.test.LoadableModule; +import java.util.HashSet; +import java.util.Set; + @Plugin("chunkeventtest") public final class ChunkEventTest implements LoadableModule { @@ -42,6 +58,14 @@ public final class ChunkEventTest implements LoadableModule { private static final boolean LOG_CHUNK_EVENTS = Boolean.getBoolean("sponge.logChunkEvents"); + private boolean logEvents = ChunkEventTest.LOG_CHUNK_EVENTS; + private boolean cancelBlockSave = false; + private boolean cancelEntitySave = false; + private final Set> filterEntitySave = new HashSet<>(); + private final Set> addEntityLoad = new HashSet<>(); + private final Set filterBlockSave = new HashSet<>(); + private final Set addBlockLoad = new HashSet<>(); + @Inject public ChunkEventTest(final Game game, final PluginContainer plugin) { this.plugin = plugin; @@ -50,38 +74,207 @@ public ChunkEventTest(final Game game, final PluginContainer plugin) { } } + @Listener + private void registerCommands(final RegisterCommandEvent event) { + final Parameter.Value> entityTypeParam = + Parameter.registryElement(new TypeToken<>() {}, RegistryTypes.ENTITY_TYPE, "minecraft").key("entityType").build(); + + final Parameter.Value blockTypeParam = + Parameter.registryElement(TypeToken.get(BlockType.class), RegistryTypes.BLOCK_TYPE, "minecraft").key("blockType").build(); + + event.register(this.plugin, Command.builder() + .executor(context -> { + this.logEvents = !this.logEvents; + final Component newState = Component.text( + this.logEvents ? "ON" : "OFF", this.logEvents ? NamedTextColor.GREEN : NamedTextColor.RED); + context.sendMessage(Identity.nil(), Component.text("Turning Chunk Log: ").append(newState)); + return CommandResult.success(); + }) + .build(), "logChunkEvents" + ); + event.register(this.plugin, Command.builder() + .executor(context -> { + this.cancelBlockSave = !this.cancelBlockSave; + final Component newState = Component.text( + this.cancelBlockSave ? "OFF" : "ON", this.cancelBlockSave ? NamedTextColor.RED : NamedTextColor.GREEN); + context.sendMessage(Identity.nil(), Component.text("Turning Block Save: ").append(newState)); + return CommandResult.success(); + }) + .build(), "toggleChunkBlockSave" + ); + event.register(this.plugin, Command.builder() + .executor(context -> { + this.cancelEntitySave = !this.cancelEntitySave; + final Component newState = Component.text( + this.cancelEntitySave ? "OFF" : "ON", this.cancelEntitySave ? NamedTextColor.RED : NamedTextColor.GREEN); + context.sendMessage(Identity.nil(), Component.text("Turning Entity Save: ").append(newState)); + return CommandResult.success(); + }) + .build(), "toggleChunkEntitySave" + ); + event.register(this.plugin, Command.builder() + .addParameter(entityTypeParam) + .executor(context -> { + final EntityType entityType = context.requireOne(entityTypeParam); + if (!this.filterEntitySave.contains(entityType)) { + this.filterEntitySave.add(entityType); + context.sendMessage(Identity.nil(), Component.text("Filtering entity: " + entityType.key(RegistryTypes.ENTITY_TYPE), NamedTextColor.GREEN)); + } else { + this.filterEntitySave.remove(entityType); + context.sendMessage(Identity.nil(), Component.text("Removed entity from filter: " + entityType.key(RegistryTypes.ENTITY_TYPE), NamedTextColor.RED)); + } + return CommandResult.success(); + }) + .build(), "chunkSaveEntityFilter" + ); + event.register(this.plugin, Command.builder() + .addParameter(entityTypeParam) + .executor(context -> { + final EntityType entityType = context.requireOne(entityTypeParam); + if (!this.addEntityLoad.contains(entityType)) { + this.addEntityLoad.add(entityType); + context.sendMessage(Identity.nil(), Component.text("Adding entity: " + entityType.key(RegistryTypes.ENTITY_TYPE), NamedTextColor.GREEN)); + } else { + this.addEntityLoad.remove(entityType); + context.sendMessage(Identity.nil(), Component.text("No longer adding entity: " + entityType.key(RegistryTypes.ENTITY_TYPE), NamedTextColor.RED)); + } + return CommandResult.success(); + }) + .build(), "chunkLoadExtraEntity" + ); + event.register(this.plugin, Command.builder() + .addParameter(blockTypeParam) + .executor(context -> { + final BlockType blockType = context.requireOne(blockTypeParam); + if (!this.filterBlockSave.contains(blockType)) { + this.filterBlockSave.add(blockType); + context.sendMessage(Identity.nil(), Component.text("Filtering block: " + blockType.key(RegistryTypes.BLOCK_TYPE), NamedTextColor.GREEN)); + } else { + this.filterBlockSave.remove(blockType); + context.sendMessage(Identity.nil(), Component.text("Removed block from filter: " + blockType.key(RegistryTypes.BLOCK_TYPE), NamedTextColor.RED)); + } + return CommandResult.success(); + }) + .build(), "chunkSaveBlockFilter" + ); + event.register(this.plugin, Command.builder() + .addParameter(blockTypeParam) + .executor(context -> { + final BlockType blockType = context.requireOne(blockTypeParam); + if (!this.addBlockLoad.contains(blockType)) { + this.addBlockLoad.add(blockType); + context.sendMessage(Identity.nil(), Component.text("Adding block: " + blockType.key(RegistryTypes.BLOCK_TYPE), NamedTextColor.GREEN)); + } else { + this.addBlockLoad.remove(blockType); + context.sendMessage(Identity.nil(), Component.text("No longer adding block: " + blockType.key(RegistryTypes.BLOCK_TYPE), NamedTextColor.RED)); + } + return CommandResult.success(); + }) + .build(), "chunkLoadExtraBlock" + ); + } + @Override public void enable(final CommandContext ctx) { Sponge.eventManager().registerListeners(this.plugin, new ChunkListener()); } - static class ChunkListener { + class ChunkListener { @Listener public void onChunkGenerated(final ChunkEvent.Generated event) { - Sponge.game().systemSubject().sendMessage(Component.text("Generated Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Generated Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + } } @Listener public void onChunkLoad(final ChunkEvent.Load event) { - Sponge.game().systemSubject().sendMessage(Component.text("Load Chunk " + event.chunk().chunkPosition() + " in " + event.worldKey().asString())); + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Load Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + } } @Listener - public void onChunkSave(final ChunkEvent.Save.Pre event) { - event.setCancelled(true); - Sponge.game().systemSubject().sendMessage(Component.text("Pre Save Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + public void onChunkUnload(final ChunkEvent.Unload event) { + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Unload Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + } } @Listener - public void onChunkSave(final ChunkEvent.Save.Post event) { - Sponge.game().systemSubject().sendMessage(Component.text("Post Save Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + public void onChunkBlocksLoad(final ChunkEvent.Blocks.Load event) { + /*ChunkEventTest.this.addBlockLoad.forEach(b -> + event.blockVolume().blockStateStream(event.blockVolume().min(), event.blockVolume().max(), StreamOptions.lazily()) + .filter(e -> ((BlockState) e.type()).type().equals(b)) + .transform(e -> VolumeElement.of(e.volume(), BlockTypes.PINK_WOOL.get().defaultState(), e.position())) + .apply(VolumeCollectors.of(event.blockVolume(), VolumePositionTranslators.identity(), VolumeApplicators.applyBlocks())));*/ + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Load Chunk Blocks " + event.chunkPosition() + " in " + event.worldKey().asString())); + } } @Listener - public void onChunkUnload(final ChunkEvent.Unload event) { - Sponge.game().systemSubject().sendMessage(Component.text("Unload Chunk " + event.chunkPosition() + " in " + event.worldKey().asString())); + public void onChunkBlocksSavePre(final ChunkEvent.Blocks.Save.Pre event) { + if (ChunkEventTest.this.cancelBlockSave) { + event.setCancelled(true); + return; + } + /*ChunkEventTest.this.filterBlockSave.forEach(b -> + event.blockVolume().blockStateStream(event.blockVolume().min(), event.blockVolume().max(), StreamOptions.lazily()) + .filter(e -> ((BlockState) e.type()).type().equals(b)) + .transform(e -> VolumeElement.of(e.volume(), BlockTypes.AIR.get().defaultState(), e.position())) + .apply(VolumeCollectors.of(event.blockVolume(), VolumePositionTranslators.identity(), VolumeApplicators.applyBlocks())));*/ + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Pre Save Chunk Blocks " + event.chunkPosition() + " in " + event.worldKey().asString())); + } + } + + @Listener + public void onChunkBlocksSavePost(final ChunkEvent.Blocks.Save.Post event) { + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Post Save Chunk Blocks " + event.chunkPosition() + " in " + event.worldKey().asString())); + } + } + + @Listener + public void onChunkEntitiesLoad(final ChunkEvent.Entities.Load event) { + ChunkEventTest.this.addEntityLoad.forEach(e -> { + final Entity entity = event.chunk().createEntity(e, event.chunk().min().add(8, event.chunk().size().y() / 2, 8)); + entity.offer(Keys.TRANSIENT, true); + entity.offer(Keys.IS_GRAVITY_AFFECTED, false); + entity.offer(Keys.IS_AI_ENABLED, false); + event.chunk().spawnEntity(entity); + }); + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Load Chunk Entities " + event.chunkPosition() + " in " + event.worldKey().asString())); + } + } + + @Listener + public void onChunkEntitiesSavePre(final ChunkEvent.Entities.Save.Pre event) { + if (ChunkEventTest.this.cancelEntitySave) { + event.setCancelled(true); + return; + } + if (!ChunkEventTest.this.filterEntitySave.isEmpty()) { + event.chunk().entities().forEach(e -> { + if (ChunkEventTest.this.filterEntitySave.contains(e.type())) { + e.remove(); + } + }); + } + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Pre Save Chunk Entities " + event.chunkPosition() + " in " + event.worldKey().asString())); + } + } + + @Listener + public void onChunkEntitiesSavePost(final ChunkEvent.Entities.Save.Post event) { + if (ChunkEventTest.this.logEvents) { + Sponge.game().systemSubject().sendMessage(Component.text("Post Save Chunk Entities " + event.chunkPosition() + " in " + event.worldKey().asString())); + } } } }