package com.hypixel.hytale.builtin.fluid; import com.hypixel.hytale.builtin.blocktick.system.ChunkBlockTickSystem; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.dependency.Dependency; import com.hypixel.hytale.component.dependency.Order; import com.hypixel.hytale.component.dependency.RootDependency; import com.hypixel.hytale.component.dependency.SystemDependency; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.system.HolderSystem; import com.hypixel.hytale.component.system.tick.EntityTickingSystem; import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.protocol.Packet; import com.hypixel.hytale.protocol.packets.world.ServerSetFluid; import com.hypixel.hytale.protocol.packets.world.ServerSetFluids; import com.hypixel.hytale.protocol.packets.world.SetFluidCmd; import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker; import com.hypixel.hytale.server.core.modules.LegacyModule; import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.logging.Level; import javax.annotation.Nonnull; public class FluidSystems { @Nonnull private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); private static final int MAX_CHANGES_PER_PACKET = 1024; public static class EnsureFluidSection extends HolderSystem { @Nonnull private static final Query QUERY = Query.and(ChunkSection.getComponentType(), Query.not(FluidSection.getComponentType())); @Override public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { holder.addComponent(FluidSection.getComponentType(), new FluidSection()); } @Override public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { } @Nonnull @Override public Query getQuery() { return QUERY; } @Nonnull @Override public Set> getDependencies() { return RootDependency.firstSet(); } } public static class LoadPacketGenerator extends ChunkStore.LoadFuturePacketDataQuerySystem { public void fetch( int index, @Nonnull ArchetypeChunk archetypeChunk, Store store, @Nonnull CommandBuffer commandBuffer, PlayerRef query, @Nonnull List> results ) { ChunkColumn chunkColumnComponent = archetypeChunk.getComponent(index, ChunkColumn.getComponentType()); assert chunkColumnComponent != null; for (Ref sectionRef : chunkColumnComponent.getSections()) { FluidSection fluidSectionComponent = commandBuffer.getComponent(sectionRef, FluidSection.getComponentType()); if (fluidSectionComponent != null) { results.add(fluidSectionComponent.getCachedPacket().exceptionally(throwable -> { if (throwable != null) { ((HytaleLogger.Api)FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable)).log("Exception when compressing chunk fluids:"); } return null; }).thenApply(Function.identity())); } } } @Override public Query getQuery() { return ChunkColumn.getComponentType(); } } public static class MigrateFromColumn extends ChunkColumnMigrationSystem { @Nonnull private final Query QUERY = Query.and(ChunkColumn.getComponentType(), BlockChunk.getComponentType()); @Nonnull private final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, LegacyModule.MigrateLegacySections.class)); @Override public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { ChunkColumn chunkColumnComponent = holder.getComponent(ChunkColumn.getComponentType()); assert chunkColumnComponent != null; BlockChunk blockChunkComponent = holder.getComponent(BlockChunk.getComponentType()); assert blockChunkComponent != null; Holder[] sections = chunkColumnComponent.getSectionHolders(); BlockSection[] legacySections = blockChunkComponent.getMigratedSections(); if (legacySections != null) { for (int i = 0; i < sections.length; i++) { Holder section = sections[i]; BlockSection paletteSection = legacySections[i]; if (section != null && paletteSection != null) { FluidSection fluid = paletteSection.takeMigratedFluid(); if (fluid != null) { section.putComponent(FluidSection.getComponentType(), fluid); blockChunkComponent.markNeedsSaving(); } } } } } @Override public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { } @Nonnull @Override public Query getQuery() { return this.QUERY; } @Nonnull @Override public Set> getDependencies() { return this.DEPENDENCIES; } } public static class ReplicateChanges extends EntityTickingSystem implements RunWhenPausedSystem { @Nonnull private static final Query QUERY = Query.and(ChunkSection.getComponentType(), FluidSection.getComponentType()); @Override public boolean isParallel(int archetypeChunkSize, int taskCount) { return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); } @Override public void tick( float dt, int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType()); assert fluidSectionComponent != null; IntOpenHashSet changes = fluidSectionComponent.getAndClearChangedPositions(); if (!changes.isEmpty()) { ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); assert chunkSectionComponent != null; World world = commandBuffer.getExternalData().getWorld(); WorldChunk worldChunkComponent = commandBuffer.getComponent(chunkSectionComponent.getChunkColumnReference(), WorldChunk.getComponentType()); int sectionY = chunkSectionComponent.getY(); world.execute(() -> { if (worldChunkComponent != null && worldChunkComponent.getWorld() != null) { worldChunkComponent.getWorld().getChunkLighting().invalidateLightInChunkSection(worldChunkComponent, sectionY); } }); Collection playerRefs = store.getExternalData().getWorld().getPlayerRefs(); if (playerRefs.isEmpty()) { changes.clear(); } else { long chunkIndex = ChunkUtil.indexChunk(fluidSectionComponent.getX(), fluidSectionComponent.getZ()); if (changes.size() >= 1024) { ObjectArrayList playersCopy = new ObjectArrayList(playerRefs); fluidSectionComponent.getCachedPacket().whenComplete((packetx, throwable) -> { if (throwable != null) { ((HytaleLogger.Api)FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable)).log("Exception when compressing chunk fluids:"); } else { Iterator i$ = playersCopy.iterator(); while (i$.hasNext()) { PlayerRef playerRefx = (PlayerRef)i$.next(); Ref refx = playerRefx.getReference(); if (refx != null && refx.isValid()) { ChunkTracker trackerx = playerRefx.getChunkTracker(); if (trackerx.isLoaded(chunkIndex)) { playerRefx.getPacketHandler().writeNoCache(packetx); } } } } }); changes.clear(); } else { if (changes.size() == 1) { int change = changes.iterator().nextInt(); int x = ChunkUtil.minBlock(fluidSectionComponent.getX()) + ChunkUtil.xFromIndex(change); int y = ChunkUtil.minBlock(fluidSectionComponent.getY()) + ChunkUtil.yFromIndex(change); int z = ChunkUtil.minBlock(fluidSectionComponent.getZ()) + ChunkUtil.zFromIndex(change); int fluid = fluidSectionComponent.getFluidId(change); byte level = fluidSectionComponent.getFluidLevel(change); ServerSetFluid packet = new ServerSetFluid(x, y, z, fluid, level); for (PlayerRef playerRef : playerRefs) { Ref ref = playerRef.getReference(); if (ref != null && ref.isValid()) { ChunkTracker tracker = playerRef.getChunkTracker(); if (tracker.isLoaded(chunkIndex)) { playerRef.getPacketHandler().writeNoCache(packet); } } } } else { SetFluidCmd[] cmds = new SetFluidCmd[changes.size()]; IntIterator iter = changes.intIterator(); int i = 0; while (iter.hasNext()) { int change = iter.nextInt(); int fluid = fluidSectionComponent.getFluidId(change); byte level = fluidSectionComponent.getFluidLevel(change); cmds[i++] = new SetFluidCmd((short)change, fluid, level); } ServerSetFluids packet = new ServerSetFluids( fluidSectionComponent.getX(), fluidSectionComponent.getY(), fluidSectionComponent.getZ(), cmds ); for (PlayerRef playerRefx : playerRefs) { Ref ref = playerRefx.getReference(); if (ref != null && ref.isValid()) { ChunkTracker tracker = playerRefx.getChunkTracker(); if (tracker.isLoaded(chunkIndex)) { playerRefx.getPacketHandler().writeNoCache(packet); } } } } changes.clear(); } } } } @Nonnull @Override public Query getQuery() { return QUERY; } @Nonnull @Override public Set> getDependencies() { return RootDependency.lastSet(); } } public static class SetupSection extends HolderSystem { @Nonnull private static final Query QUERY = Query.and(ChunkSection.getComponentType(), FluidSection.getComponentType()); @Nonnull private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, FluidSystems.MigrateFromColumn.class)); @Override public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { ChunkSection chunkSectionComponent = holder.getComponent(ChunkSection.getComponentType()); assert chunkSectionComponent != null; FluidSection fluidSectionComponent = holder.getComponent(FluidSection.getComponentType()); assert fluidSectionComponent != null; fluidSectionComponent.load(chunkSectionComponent.getX(), chunkSectionComponent.getY(), chunkSectionComponent.getZ()); } @Override public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { } @Nonnull @Override public Query getQuery() { return QUERY; } @Nonnull @Override public Set> getDependencies() { return DEPENDENCIES; } } public static class Ticking extends EntityTickingSystem { @Nonnull private static final Query QUERY = Query.and(FluidSection.getComponentType(), ChunkSection.getComponentType()); @Nonnull private static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, ChunkBlockTickSystem.PreTick.class), new SystemDependency<>(Order.BEFORE, ChunkBlockTickSystem.Ticking.class) ); @Override public boolean isParallel(int archetypeChunkSize, int taskCount) { return EntityTickingSystem.useParallel(archetypeChunkSize, taskCount); } @Override public void tick( float dt, int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); assert chunkSectionComponent != null; FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType()); assert fluidSectionComponent != null; Ref chunkRef = chunkSectionComponent.getChunkColumnReference(); BlockChunk blockChunkComponent = commandBuffer.getComponent(chunkRef, BlockChunk.getComponentType()); assert blockChunkComponent != null; BlockSection blockSection = blockChunkComponent.getSectionAtIndex(fluidSectionComponent.getY()); if (blockSection != null) { if (blockSection.getTickingBlocksCountCopy() != 0) { FluidTicker.CachedAccessor accessor = FluidTicker.CachedAccessor.of(commandBuffer, fluidSectionComponent, blockSection, 5); blockSection.forEachTicking(accessor, commandBuffer, fluidSectionComponent.getY(), (accessor1, commandBuffer1, x, y, z, block) -> { FluidSection fluidSection1 = accessor1.selfFluidSection; BlockSection blockSection1 = accessor1.selfBlockSection; int fluidId = fluidSection1.getFluidId(x, y, z); if (fluidId == 0) { return BlockTickStrategy.IGNORED; } else { Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); int blockX = fluidSection1.getX() << 5 | x; int blockZ = fluidSection1.getZ() << 5 | z; return fluid.getTicker().tick(commandBuffer1, accessor1, fluidSection1, blockSection1, fluid, fluidId, blockX, y, blockZ); } }); } } } @Nonnull @Override public Query getQuery() { return QUERY; } @Nonnull @Override public Set> getDependencies() { return DEPENDENCIES; } } }