From d579fee4e0cec393584bac3b2613cf813a2cb22b Mon Sep 17 00:00:00 2001 From: luk Date: Tue, 27 Jan 2026 20:19:44 +0000 Subject: [PATCH] src: add 3 files --- .gitignore | 4 +- .../system/ChunkBlockTickSystem.java | 142 ++++++ .../hytale/builtin/fluid/FluidSystems.java | 413 +++++++++++++++++ .../world/chunk/systems/ChunkSystems.java | 414 ++++++++++++++++++ 4 files changed, 972 insertions(+), 1 deletion(-) create mode 100644 src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java create mode 100644 src/com/hypixel/hytale/builtin/fluid/FluidSystems.java create mode 100644 src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java diff --git a/.gitignore b/.gitignore index 7b398f18..fa84e229 100755 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,6 @@ Assets.zip Assets Server -patcher \ No newline at end of file +patcher + +start.* \ No newline at end of file diff --git a/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java b/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java new file mode 100644 index 00000000..5ed3448c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.builtin.blocktick.system; + +import com.hypixel.hytale.builtin.blocktick.BlockTickPlugin; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +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.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickManager; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +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.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.time.Instant; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ChunkBlockTickSystem { + protected static final HytaleLogger LOGGER = BlockTickPlugin.get().getLogger(); + + public ChunkBlockTickSystem() { + } + + public static class PreTick extends EntityTickingSystem { + private static final ComponentType COMPONENT_TYPE = BlockChunk.getComponentType(); + + public PreTick() { + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @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 + ) { + Instant time = commandBuffer.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime(); + BlockChunk chunk = archetypeChunk.getComponent(index, COMPONENT_TYPE); + + assert chunk != null; + + try { + chunk.preTick(time); + } catch (Throwable var9) { + ChunkBlockTickSystem.LOGGER.at(Level.SEVERE).withCause(var9).log("Failed to pre-tick chunk: %s", chunk); + } + } + } + + public static class Ticking extends EntityTickingSystem { + private static final ComponentType COMPONENT_TYPE = WorldChunk.getComponentType(); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, ChunkBlockTickSystem.PreTick.class)); + + public Ticking() { + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref reference = archetypeChunk.getReferenceTo(index); + WorldChunk worldChunk = archetypeChunk.getComponent(index, COMPONENT_TYPE); + + try { + tick(reference, worldChunk); + } catch (Throwable var9) { + ChunkBlockTickSystem.LOGGER.at(Level.SEVERE).withCause(var9).log("Failed to tick chunk: %s", worldChunk); + } + } + + protected static void tick(Ref ref, @Nonnull WorldChunk worldChunk) { + int ticked = worldChunk.getBlockChunk().forEachTicking(ref, worldChunk, (r, c, localX, localY, localZ, blockId) -> { + World world = c.getWorld(); + int blockX = c.getX() << 5 | localX; + int blockZ = c.getZ() << 5 | localZ; + return tickProcedure(world, c, blockX, localY, blockZ, blockId); + }); + if (ticked > 0) { + ChunkBlockTickSystem.LOGGER.at(Level.FINER).log("Ticked %d blocks in chunk (%d, %d)", ticked, worldChunk.getX(), worldChunk.getZ()); + } + } + + protected static BlockTickStrategy tickProcedure(@Nonnull World world, @Nonnull WorldChunk chunk, int blockX, int blockY, int blockZ, int blockId) { + if (world.getWorldConfig().isBlockTicking() && BlockTickManager.hasBlockTickProvider()) { + TickProcedure procedure = BlockTickPlugin.get().getTickProcedure(blockId); + if (procedure == null) { + return BlockTickStrategy.IGNORED; + } else { + try { + return procedure.onTick(world, chunk, blockX, blockY, blockZ, blockId); + } catch (Throwable var9) { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + ChunkBlockTickSystem.LOGGER + .at(Level.WARNING) + .withCause(var9) + .log("Failed to tick block at (%d, %d, %d) ID %s in world %s:", blockX, blockY, blockZ, blockType.getId(), world.getName()); + return BlockTickStrategy.SLEEP; + } + } + } else { + return BlockTickStrategy.IGNORED; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java b/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java new file mode 100644 index 00000000..2865c7d9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java @@ -0,0 +1,413 @@ +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.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 FluidSystems() { + } + + public static class EnsureFluidSection extends HolderSystem { + @Nonnull + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), Query.not(FluidSection.getComponentType())); + + public EnsureFluidSection() { + } + + @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 LoadPacketGenerator() { + } + + 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) { + 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)); + + public MigrateFromColumn() { + } + + @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()); + + public ReplicateChanges() { + } + + @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 + ) { + 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) { + FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:"); + } else { + for (PlayerRef playerRefx : playersCopy) { + 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)); + + public SetupSection() { + } + + @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) + ); + + public Ticking() { + } + + @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; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java new file mode 100644 index 00000000..caaf8c41 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java @@ -0,0 +1,414 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.systems; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonTicking; +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.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +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.CachedPacket; +import com.hypixel.hytale.protocol.packets.world.ServerSetBlock; +import com.hypixel.hytale.protocol.packets.world.ServerSetBlocks; +import com.hypixel.hytale.protocol.packets.world.SetBlockCmd; +import com.hypixel.hytale.protocol.packets.world.SetChunk; +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.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.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.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkSystems { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final int MAX_CHANGES_PER_PACKET = 1024; + + public ChunkSystems() { + } + + public static class EnsureBlockSection extends HolderSystem { + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), Query.not(BlockSection.getComponentType())); + + public EnsureBlockSection() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(BlockSection.getComponentType()); + } + + @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 LoadBlockSection extends HolderSystem { + public LoadBlockSection() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + BlockSection section = holder.getComponent(BlockSection.getComponentType()); + + assert section != null; + + section.loaded = true; + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return BlockSection.getComponentType(); + } + } + + public static class OnChunkLoad extends RefSystem { + private static final Query QUERY = Query.and(ChunkColumn.getComponentType(), WorldChunk.getComponentType()); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, ChunkSystems.OnNewChunk.class)); + + public OnChunkLoad() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn chunk = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert chunk != null; + + WorldChunk worldChunk = commandBuffer.getComponent(ref, WorldChunk.getComponentType()); + + assert worldChunk != null; + + Ref[] sections = chunk.getSections(); + Holder[] sectionHolders = chunk.takeSectionHolders(); + boolean isNonTicking = commandBuffer.getArchetype(ref).contains(ChunkStore.REGISTRY.getNonTickingComponentType()); + if (sectionHolders != null && sectionHolders.length > 0 && sectionHolders[0] != null) { + for (int i = 0; i < sectionHolders.length; i++) { + if (isNonTicking) { + sectionHolders[i].ensureComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } else { + sectionHolders[i].tryRemoveComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } + + ChunkSection section = sectionHolders[i].getComponent(ChunkSection.getComponentType()); + if (section == null) { + sectionHolders[i].addComponent(ChunkSection.getComponentType(), new ChunkSection(ref, worldChunk.getX(), i, worldChunk.getZ())); + } else { + section.load(ref, worldChunk.getX(), i, worldChunk.getZ()); + } + } + + commandBuffer.addEntities(sectionHolders, 0, sections, 0, sections.length, AddReason.LOAD); + } + + for (int i = 0; i < sections.length; i++) { + if (sections[i] == null) { + Holder newSection = ChunkStore.REGISTRY.newHolder(); + if (isNonTicking) { + newSection.ensureComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } else { + newSection.tryRemoveComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } + + newSection.addComponent(ChunkSection.getComponentType(), new ChunkSection(ref, worldChunk.getX(), i, worldChunk.getZ())); + sections[i] = commandBuffer.addEntity(newSection, AddReason.SPAWN); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn chunk = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert chunk != null; + + Ref[] sections = chunk.getSections(); + Holder[] holders = new Holder[sections.length]; + + for (int i = 0; i < sections.length; i++) { + Ref section = sections[i]; + holders[i] = ChunkStore.REGISTRY.newHolder(); + commandBuffer.removeEntity(section, holders[i], reason); + } + + chunk.putSectionHolders(holders); + Arrays.fill(sections, null); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + } + + public static class OnNewChunk extends ChunkColumnMigrationSystem { + private static final Query QUERY = Query.and(WorldChunk.getComponentType(), Query.not(ChunkColumn.getComponentType())); + + public OnNewChunk() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Holder[] sectionHolders = new Holder[10]; + + for (int i = 0; i < sectionHolders.length; i++) { + sectionHolders[i] = ChunkStore.REGISTRY.newHolder(); + } + + holder.addComponent(ChunkColumn.getComponentType(), new ChunkColumn(sectionHolders)); + } + + @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 OnNonTicking extends RefChangeSystem> { + private final Archetype archetype = Archetype.of(WorldChunk.getComponentType(), ChunkColumn.getComponentType()); + + public OnNonTicking() { + } + + @Nonnull + @Override + public ComponentType> componentType() { + return ChunkStore.REGISTRY.getNonTickingComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn column = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert column != null; + + Ref[] sections = column.getSections(); + + for (int i = 0; i < sections.length; i++) { + Ref section = sections[i]; + commandBuffer.ensureComponent(section, ChunkStore.REGISTRY.getNonTickingComponentType()); + } + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable NonTicking oldComponent, + @Nonnull NonTicking newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn column = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert column != null; + + Ref[] sections = column.getSections(); + + for (int i = 0; i < sections.length; i++) { + Ref section = sections[i]; + commandBuffer.tryRemoveComponent(section, ChunkStore.REGISTRY.getNonTickingComponentType()); + } + } + + @Override + public Query getQuery() { + return this.archetype; + } + } + + public static class ReplicateChanges extends EntityTickingSystem implements RunWhenPausedSystem { + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), BlockSection.getComponentType()); + + public ReplicateChanges() { + } + + @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 + ) { + BlockSection blockSection = archetypeChunk.getComponent(index, BlockSection.getComponentType()); + + assert blockSection != null; + + IntOpenHashSet changes = blockSection.getAndClearChangedPositions(); + if (!changes.isEmpty()) { + ChunkSection section = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert section != null; + + Collection players = store.getExternalData().getWorld().getPlayerRefs(); + if (players.isEmpty()) { + changes.clear(); + } else { + long chunkIndex = ChunkUtil.indexChunk(section.getX(), section.getZ()); + if (changes.size() >= 1024) { + ObjectArrayList playersCopy = new ObjectArrayList<>(players); + CompletableFuture> set = blockSection.getCachedChunkPacket(section.getX(), section.getY(), section.getZ()); + set.thenAccept(s -> { + for (PlayerRef playerx : playersCopy) { + Ref refx = playerx.getReference(); + if (refx != null) { + ChunkTracker trackerx = playerx.getChunkTracker(); + if (trackerx != null && trackerx.isLoaded(chunkIndex)) { + playerx.getPacketHandler().writeNoCache(s); + } + } + } + }).exceptionally(throwable -> { + if (throwable != null) { + ChunkSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:"); + } + + return null; + }); + changes.clear(); + } else { + if (changes.size() == 1) { + int change = changes.iterator().nextInt(); + int x = ChunkUtil.minBlock(section.getX()) + ChunkUtil.xFromIndex(change); + int y = ChunkUtil.minBlock(section.getY()) + ChunkUtil.yFromIndex(change); + int z = ChunkUtil.minBlock(section.getZ()) + ChunkUtil.zFromIndex(change); + int blockId = blockSection.get(change); + int filler = blockSection.getFiller(change); + int rotation = blockSection.getRotationIndex(change); + ServerSetBlock packet = new ServerSetBlock(x, y, z, blockId, (short)filler, (byte)rotation); + + for (PlayerRef player : players) { + Ref ref = player.getReference(); + if (ref != null) { + ChunkTracker tracker = player.getChunkTracker(); + if (tracker != null && tracker.isLoaded(chunkIndex)) { + player.getPacketHandler().writeNoCache(packet); + } + } + } + } else { + SetBlockCmd[] cmds = new SetBlockCmd[changes.size()]; + IntIterator iter = changes.intIterator(); + int i = 0; + + while (iter.hasNext()) { + int change = iter.nextInt(); + int blockId = blockSection.get(change); + int filler = blockSection.getFiller(change); + int rotation = blockSection.getRotationIndex(change); + cmds[i++] = new SetBlockCmd((short)change, blockId, (short)filler, (byte)rotation); + } + + ServerSetBlocks packet = new ServerSetBlocks(section.getX(), section.getY(), section.getZ(), cmds); + + for (PlayerRef playerx : players) { + Ref ref = playerx.getReference(); + if (ref != null) { + ChunkTracker tracker = playerx.getChunkTracker(); + if (tracker != null && tracker.isLoaded(chunkIndex)) { + playerx.getPacketHandler().writeNoCache(packet); + } + } + } + } + + changes.clear(); + } + } + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.lastSet(); + } + } +}