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.CachedPacket;
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<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.<ChunkStore>and(ChunkSection.getComponentType(), Query.not(FluidSection.getComponentType()));
public EnsureFluidSection() {
}
public void onEntityAdd(@Nonnull Holder<ChunkStore> holder, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store) {
holder.addComponent(FluidSection.getComponentType(), new FluidSection());
}
public void onEntityRemoved(@Nonnull Holder<ChunkStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store) {
}
@Nonnull
public Query<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
public Set<Dependency<ChunkStore>> getDependencies() {
return RootDependency.firstSet();
}
}
public static class MigrateFromColumn extends ChunkColumnMigrationSystem {
@Nonnull
private final Query<ChunkStore> QUERY = Query.<ChunkStore>and(ChunkColumn.getComponentType(), BlockChunk.getComponentType());
@Nonnull
private final Set<Dependency<ChunkStore>> DEPENDENCIES;
public MigrateFromColumn() {
this.DEPENDENCIES = Set.of(new SystemDependency(Order.BEFORE, LegacyModule.MigrateLegacySections.class));
}
public void onEntityAdd(@Nonnull Holder<ChunkStore> holder, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store) {
ChunkColumn chunkColumnComponent = (ChunkColumn)holder.getComponent(ChunkColumn.getComponentType());
assert chunkColumnComponent != null;
BlockChunk blockChunkComponent = (BlockChunk)holder.getComponent(BlockChunk.getComponentType());
assert blockChunkComponent != null;
Holder<ChunkStore>[] sections = chunkColumnComponent.getSectionHolders();
BlockSection[] legacySections = blockChunkComponent.getMigratedSections();
if (legacySections != null) {
for(int i = 0; i < sections.length; ++i) {
Holder<ChunkStore> 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();
}
}
}
}
}
public void onEntityRemoved(@Nonnull Holder<ChunkStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store) {
}
@Nonnull
public Query<ChunkStore> getQuery() {
return this.QUERY;
}
@Nonnull
public Set<Dependency<ChunkStore>> getDependencies() {
return this.DEPENDENCIES;
}
}
public static class SetupSection extends HolderSystem<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.<ChunkStore>and(ChunkSection.getComponentType(), FluidSection.getComponentType());
@Nonnull
private static final Set<Dependency<ChunkStore>> DEPENDENCIES;
public SetupSection() {
}
public void onEntityAdd(@Nonnull Holder<ChunkStore> holder, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store) {
ChunkSection chunkSectionComponent = (ChunkSection)holder.getComponent(ChunkSection.getComponentType());
assert chunkSectionComponent != null;
FluidSection fluidSectionComponent = (FluidSection)holder.getComponent(FluidSection.getComponentType());
assert fluidSectionComponent != null;
fluidSectionComponent.load(chunkSectionComponent.getX(), chunkSectionComponent.getY(), chunkSectionComponent.getZ());
}
public void onEntityRemoved(@Nonnull Holder<ChunkStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store) {
}
@Nonnull
public Query<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
public Set<Dependency<ChunkStore>> getDependencies() {
return DEPENDENCIES;
}
static {
DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, MigrateFromColumn.class));
}
}
public static class LoadPacketGenerator extends ChunkStore.LoadFuturePacketDataQuerySystem {
public LoadPacketGenerator() {
}
public void fetch(int index, @Nonnull ArchetypeChunk<ChunkStore> archetypeChunk, Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> commandBuffer, PlayerRef query, @Nonnull List<CompletableFuture<Packet>> results) {
ChunkColumn chunkColumnComponent = (ChunkColumn)archetypeChunk.getComponent(index, ChunkColumn.getComponentType());
assert chunkColumnComponent != null;
for(Ref<ChunkStore> sectionRef : chunkColumnComponent.getSections()) {
FluidSection fluidSectionComponent = (FluidSection)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()));
}
}
}
public Query<ChunkStore> getQuery() {
return ChunkColumn.getComponentType();
}
}
public static class ReplicateChanges extends EntityTickingSystem<ChunkStore> implements RunWhenPausedSystem<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.<ChunkStore>and(ChunkSection.getComponentType(), FluidSection.getComponentType());
public ReplicateChanges() {
}
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
}
public void tick(float dt, int index, @Nonnull ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> commandBuffer) {
FluidSection fluidSectionComponent = (FluidSection)archetypeChunk.getComponent(index, FluidSection.getComponentType());
assert fluidSectionComponent != null;
IntOpenHashSet changes = fluidSectionComponent.getAndClearChangedPositions();
if (!changes.isEmpty()) {
ChunkSection chunkSectionComponent = (ChunkSection)archetypeChunk.getComponent(index, ChunkSection.getComponentType());
assert chunkSectionComponent != null;
World world = ((ChunkStore)commandBuffer.getExternalData()).getWorld();
WorldChunk worldChunkComponent = (WorldChunk)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<PlayerRef> playerRefs = ((ChunkStore)store.getExternalData()).getWorld().getPlayerRefs();
if (playerRefs.isEmpty()) {
changes.clear();
} else {
long chunkIndex = ChunkUtil.indexChunk(fluidSectionComponent.getX(), fluidSectionComponent.getZ());
if (changes.size() >= 1024) {
ObjectArrayList<PlayerRef> playersCopy = new ObjectArrayList<PlayerRef>(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 {
for(PlayerRef playerRef : playersCopy) {
Ref<EntityStore> ref = playerRef.getReference();
if (ref != null && ref.isValid()) {
ChunkTracker tracker = playerRef.getChunkTracker();
if (tracker.isLoaded(chunkIndex)) {
playerRef.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<EntityStore> 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 change;
int fluid;
byte level;
for(int i = 0; iter.hasNext(); cmds[i++] = new SetFluidCmd((short)change, fluid, level)) {
change = iter.nextInt();
fluid = fluidSectionComponent.getFluidId(change);
level = fluidSectionComponent.getFluidLevel(change);
}
ServerSetFluids packet = new ServerSetFluids(fluidSectionComponent.getX(), fluidSectionComponent.getY(), fluidSectionComponent.getZ(), cmds);
for(PlayerRef playerRef : playerRefs) {
Ref<EntityStore> ref = playerRef.getReference();
if (ref != null && ref.isValid()) {
ChunkTracker tracker = playerRef.getChunkTracker();
if (tracker.isLoaded(chunkIndex)) {
playerRef.getPacketHandler().writeNoCache(packet);
}
}
}
}
changes.clear();
}
}
}
}
@Nonnull
public Query<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
public Set<Dependency<ChunkStore>> getDependencies() {
return RootDependency.lastSet();
}
}
public static class Ticking extends EntityTickingSystem<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.<ChunkStore>and(FluidSection.getComponentType(), ChunkSection.getComponentType());
@Nonnull
private static final Set<Dependency<ChunkStore>> DEPENDENCIES;
public Ticking() {
}
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return EntityTickingSystem.useParallel(archetypeChunkSize, taskCount);
}
public void tick(float dt, int index, @Nonnull ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> commandBuffer) {
ChunkSection chunkSectionComponent = (ChunkSection)archetypeChunk.getComponent(index, ChunkSection.getComponentType());
assert chunkSectionComponent != null;
FluidSection fluidSectionComponent = (FluidSection)archetypeChunk.getComponent(index, FluidSection.getComponentType());
assert fluidSectionComponent != null;
Ref<ChunkStore> chunkRef = chunkSectionComponent.getChunkColumnReference();
BlockChunk blockChunkComponent = (BlockChunk)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)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
public Query<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
public Set<Dependency<ChunkStore>> getDependencies() {
return DEPENDENCIES;
}
static {
DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, ChunkBlockTickSystem.PreTick.class), new SystemDependency(Order.BEFORE, ChunkBlockTickSystem.Ticking.class));
}
}
}