package com.hypixel.hytale.server.core.universe.world.chunk;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.function.predicate.ObjectPositionBlockFunction;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.protocol.CachedPacket;
import com.hypixel.hytale.protocol.Opacity;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.protocol.packets.world.SetChunkEnvironments;
import com.hypixel.hytale.protocol.packets.world.SetChunkHeightmap;
import com.hypixel.hytale.protocol.packets.world.SetChunkTintmap;
import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentColumn;
import com.hypixel.hytale.server.core.universe.world.chunk.palette.IntBytePalette;
import com.hypixel.hytale.server.core.universe.world.chunk.palette.ShortBytePalette;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ShortMap;
import it.unimi.dsi.fastutil.ints.IntBinaryOperator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.lang.ref.SoftReference;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class BlockChunk implements Component<ChunkStore> {
public static final int VERSION = 3;
public static final BuilderCodec<BlockChunk> CODEC;
private static final HytaleLogger LOGGER;
public static boolean SEND_LOCAL_LIGHTING_DATA;
public static boolean SEND_GLOBAL_LIGHTING_DATA;
private long index;
private int x;
private int z;
private final ShortBytePalette height;
private final IntBytePalette tint;
@Deprecated(
forRemoval = true
)
private BlockSection[] chunkSections;
@Nullable
@Deprecated(
forRemoval = true
)
private BlockSection[] migratedChunkSections;
private EnvironmentChunk environments;
private boolean needsPhysics;
private boolean needsSaving;
@Nullable
private transient SoftReference<CompletableFuture<CachedPacket<SetChunkHeightmap>>> cachedHeightmapPacket;
@Nullable
private transient SoftReference<CompletableFuture<CachedPacket<SetChunkTintmap>>> cachedTintmapPacket;
@Nullable
private transient SoftReference<CompletableFuture<CachedPacket<SetChunkEnvironments>>> cachedEnvironmentsPacket;
public static ComponentType<ChunkStore, BlockChunk> getComponentType() {
return LegacyModule.get().getBlockChunkComponentType();
}
private BlockChunk() {
this(new ShortBytePalette(), new IntBytePalette(), new EnvironmentChunk(), new BlockSection[10]);
}
public void load(int x, int z) {
this.x = x;
this.z = z;
this.index = ChunkUtil.indexChunk(x, z);
}
public BlockChunk(int x, int z) {
this(x, z, new ShortBytePalette(), new IntBytePalette(), new EnvironmentChunk());
}
public BlockChunk(int x, int z, ShortBytePalette height, IntBytePalette tint, EnvironmentChunk environments) {
this(height, tint, environments, new BlockSection[10]);
this.x = x;
this.z = z;
this.index = ChunkUtil.indexChunk(x, z);
}
public BlockChunk(ShortBytePalette height, IntBytePalette tint, EnvironmentChunk environments, BlockSection[] chunkSections) {
this.needsPhysics = true;
this.needsSaving = false;
this.height = height;
this.tint = tint;
this.environments = environments;
this.chunkSections = chunkSections;
}
public Component<ChunkStore> clone() {
throw new UnsupportedOperationException("Not implemented!");
}
@Nonnull
public Component<ChunkStore> cloneSerializable() {
return this;
}
public long getIndex() {
return this.index;
}
public int getX() {
return this.x;
}
public int getZ() {
return this.z;
}
public EnvironmentChunk getEnvironmentChunk() {
return this.environments;
}
public void setEnvironmentChunk(EnvironmentChunk environmentChunk) {
this.environments = environmentChunk;
}
public short getHeight(int x, int z) {
return this.height.get(x, z);
}
public short getHeight(int index) {
return this.height.get(index);
}
public void setHeight(int x, int z, short height) {
this.height.set(x, z, height);
this.cachedHeightmapPacket = null;
this.markNeedsSaving();
}
public void updateHeightmap() {
for(int cx = 0; cx < 32; ++cx) {
for(int cz = 0; cz < 32; ++cz) {
this.updateHeight(cx, cz);
}
}
}
public short updateHeight(int x, int z) {
return this.updateHeight(x, z, (short)320);
}
public short updateHeight(int x, int z, short startY) {
short y = startY;
while(true) {
--y;
if (y <= 0) {
break;
}
BlockSection section = this.getSectionAtBlockY(y);
if (section.isSolidAir()) {
y = (short)(ChunkUtil.indexSection(y) * 32);
if (y == 0) {
break;
}
} else {
int blockId = section.get(x, y, z);
BlockType type = (BlockType)BlockType.getAssetMap().getAsset(blockId);
if (blockId != 0 && type != null && type.getOpacity() != Opacity.Transparent) {
break;
}
}
}
this.setHeight(x, z, y);
return y;
}
@Deprecated(
forRemoval = true
)
public void loadFromHolder(@Nonnull Holder<ChunkStore> holder) {
ChunkColumn column = (ChunkColumn)holder.getComponent(ChunkColumn.getComponentType());
if (column != null) {
Holder<ChunkStore>[] sections = column.getSectionHolders();
for(int i = 0; i < sections.length; ++i) {
Holder<ChunkStore> section = sections[i];
this.chunkSections[i] = this.migratedChunkSections != null ? this.migratedChunkSections[i] : (BlockSection)section.ensureAndGetComponent(BlockSection.getComponentType());
}
}
}
@Deprecated(
forRemoval = false
)
public BlockSection getSectionAtIndex(int index) {
if (index >= 0 && index < this.chunkSections.length) {
return this.chunkSections[index];
} else {
int var10002 = this.chunkSections.length;
throw new IllegalArgumentException("Section index must >=0 and <" + var10002 + " but was given " + index);
}
}
@Deprecated(
forRemoval = false
)
public BlockSection getSectionAtBlockY(int y) {
int index = ChunkUtil.indexSection(y);
if (index >= 0 && index < this.chunkSections.length) {
return this.chunkSections[index];
} else {
throw new IllegalArgumentException("Section y must >=0 and <320 but was given " + y);
}
}
@Deprecated(
forRemoval = false
)
public BlockSection[] getChunkSections() {
return this.chunkSections;
}
public int getSectionCount() {
return this.chunkSections.length;
}
public int getTint(int x, int z) {
return this.tint.get(x, z);
}
public void setTint(int x, int z, int tint) {
this.tint.set(x, z, tint);
this.cachedTintmapPacket = null;
this.markNeedsSaving();
}
public int getEnvironment(@Nonnull Vector3d position) {
return this.getEnvironment(MathUtil.floor(position.x), MathUtil.floor(position.y), MathUtil.floor(position.z));
}
public int getEnvironment(@Nonnull Vector3i position) {
return this.getEnvironment(position.x, position.y, position.z);
}
public int getEnvironment(int x, int y, int z) {
return y >= 0 && y < 320 ? this.environments.get(x, y, z) : 0;
}
public EnvironmentColumn getEnvironmentColumn(int x, int z) {
return this.environments.get(x, z);
}
public void setEnvironment(int x, int y, int z, int environment) {
if (y >= 0 && y < 320) {
this.environments.set(x, y, z, environment);
this.cachedEnvironmentsPacket = null;
this.markNeedsSaving();
}
}
public byte getRedBlockLight(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getRedBlockLight(x, y, z) : 0;
}
public byte getGreenBlockLight(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getGreenBlockLight(x, y, z) : 0;
}
public byte getBlueBlockLight(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getBlueBlockLight(x, y, z) : 0;
}
public short getBlockLight(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getBlockLight(x, y, z) : 0;
}
public byte getSkyLight(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getSkyLight(x, y, z) : 0;
}
public byte getBlockLightIntensity(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getBlockLightIntensity(x, y, z) : 0;
}
public int getBlock(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).get(x, y, z) : 0;
}
public boolean setBlock(int x, int y, int z, int blockId, int rotation, int filler) {
if (y >= 0 && y < 320) {
int sectionIndex = ChunkUtil.indexSection(y);
BlockSection section = this.chunkSections[sectionIndex];
boolean changed = section.set(x, y, z, blockId, rotation, filler);
if (changed) {
this.invalidateChunkSection(sectionIndex);
this.markNeedsSaving();
}
return changed;
} else {
throw new IllegalArgumentException(String.format("Failed to set block at %d, %d, %d to %d because it is outside the world bounds", x, y, z, blockId));
}
}
public boolean contains(int blockId) {
return this.count(blockId) != 0;
}
public int count(int blockId) {
int count = 0;
for(BlockSection section : this.chunkSections) {
count += section.count(blockId);
}
return count;
}
@Nonnull
public Int2IntMap blockCounts() {
Int2IntMap map = new Int2IntOpenHashMap();
for(BlockSection section : this.chunkSections) {
for(Int2ShortMap.Entry entry : section.valueCounts().int2ShortEntrySet()) {
int blockId = entry.getIntKey();
short count = entry.getShortValue();
map.mergeInt(blockId, count, (IntBinaryOperator)(Integer::sum));
}
}
return map;
}
@Nonnull
public IntSet blocks() {
IntSet set = new IntOpenHashSet();
for(BlockSection section : this.chunkSections) {
set.addAll(section.values());
}
return set;
}
public int blockCount() {
return this.blocks().size();
}
public void preTick(Instant gameTime) {
for(int sectionIndex = 0; sectionIndex < this.chunkSections.length; ++sectionIndex) {
this.chunkSections[sectionIndex].preTick(gameTime);
}
}
public <T, V> int forEachTicking(T t, V v, ObjectPositionBlockFunction<T, V, BlockTickStrategy> acceptor) {
int ticked = 0;
for(int sectionIndex = 0; sectionIndex < this.chunkSections.length; ++sectionIndex) {
BlockSection section = this.chunkSections[sectionIndex];
ticked += section.forEachTicking(t, v, sectionIndex, acceptor);
}
if (ticked > 0) {
this.markNeedsSaving();
}
return ticked;
}
public void mergeTickingBlocks() {
for(BlockSection section : this.chunkSections) {
section.mergeTickingBlocks();
}
}
public boolean setTicking(int x, int y, int z, boolean ticking) {
if (y >= 0 && y < 320) {
boolean changed = this.getSectionAtBlockY(y).setTicking(x, y, z, ticking);
if (changed) {
this.markNeedsSaving();
}
return changed;
} else {
return false;
}
}
public boolean isTicking(int x, int y, int z) {
return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).isTicking(x, y, z) : false;
}
public int getTickingBlocksCount() {
int ticking = 0;
for(BlockSection chunkSection : this.chunkSections) {
ticking += chunkSection.getTickingBlocksCount();
}
return ticking;
}
public boolean setNeighbourBlocksTicking(int x, int y, int z) {
boolean success = true;
for(int ix = -1; ix < 2; ++ix) {
int wx = x + ix;
for(int iz = -1; iz < 2; ++iz) {
int wz = z + iz;
if (!ChunkUtil.isInsideChunkRelative(wx, wz)) {
success = false;
} else {
for(int iy = -1; iy < 2; ++iy) {
int wy = y + iy;
this.setTicking(wx, wy, wz, true);
}
}
}
}
return success;
}
public void markNeedsSaving() {
this.needsSaving = true;
}
public boolean getNeedsSaving() {
return this.needsSaving;
}
public boolean consumeNeedsSaving() {
boolean out = this.needsSaving;
this.needsSaving = false;
return out;
}
public void markNeedsPhysics() {
this.needsPhysics = true;
}
public boolean consumeNeedsPhysics() {
boolean old = this.needsPhysics;
this.needsPhysics = false;
return old;
}
public void invalidateChunkSection(int sectionIndex) {
this.chunkSections[sectionIndex].invalidate();
}
@Nullable
@Deprecated(
forRemoval = true
)
public BlockSection[] takeMigratedSections() {
BlockSection[] temp = this.migratedChunkSections;
this.migratedChunkSections = null;
return temp;
}
@Nullable
@Deprecated(
forRemoval = true
)
public BlockSection[] getMigratedSections() {
return this.migratedChunkSections;
}
private byte[] serialize(ExtraInfo extraInfo) {
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
try {
buf.writeBoolean(this.needsPhysics);
this.height.serialize(buf);
this.tint.serialize(buf);
return ByteBufUtil.getBytesRelease(buf);
} catch (Throwable t) {
buf.release();
throw SneakyThrow.sneakyThrow(t);
}
}
private void deserialize(@Nonnull byte[] bytes, @Nonnull ExtraInfo extraInfo) {
ByteBuf buf = Unpooled.wrappedBuffer(bytes);
this.needsPhysics = buf.readBoolean();
this.height.deserialize(buf);
this.tint.deserialize(buf);
if (extraInfo.getVersion() <= 2) {
int sections = buf.readInt();
this.migratedChunkSections = new BlockSection[sections];
for(int y = 0; y < sections; ++y) {
BlockSection section = new BlockSection();
section.deserialize(BlockType.KEY_DESERIALIZER, buf, extraInfo.getVersion());
this.migratedChunkSections[y] = section;
}
}
}
@Nonnull
private CompletableFuture<CachedPacket<SetChunkHeightmap>> getCachedHeightmapPacket() {
SoftReference<CompletableFuture<CachedPacket<SetChunkHeightmap>>> ref = this.cachedHeightmapPacket;
CompletableFuture<CachedPacket<SetChunkHeightmap>> future = ref != null ? (CompletableFuture)ref.get() : null;
if (future != null) {
return future;
} else {
future = CompletableFuture.supplyAsync(() -> {
SetChunkHeightmap packet = new SetChunkHeightmap(this.x, this.z, this.height.serialize());
return CachedPacket.cache(packet);
});
this.cachedHeightmapPacket = new SoftReference(future);
return future;
}
}
@Nonnull
private CompletableFuture<CachedPacket<SetChunkTintmap>> getCachedTintsPacket() {
SoftReference<CompletableFuture<CachedPacket<SetChunkTintmap>>> ref = this.cachedTintmapPacket;
CompletableFuture<CachedPacket<SetChunkTintmap>> future = ref != null ? (CompletableFuture)ref.get() : null;
if (future != null) {
return future;
} else {
future = CompletableFuture.supplyAsync(() -> {
SetChunkTintmap packet = new SetChunkTintmap(this.x, this.z, this.tint.serialize());
return CachedPacket.cache(packet);
});
this.cachedTintmapPacket = new SoftReference(future);
return future;
}
}
@Nonnull
private CompletableFuture<CachedPacket<SetChunkEnvironments>> getCachedEnvironmentsPacket() {
SoftReference<CompletableFuture<CachedPacket<SetChunkEnvironments>>> ref = this.cachedEnvironmentsPacket;
CompletableFuture<CachedPacket<SetChunkEnvironments>> future = ref != null ? (CompletableFuture)ref.get() : null;
if (future != null) {
return future;
} else {
future = CompletableFuture.supplyAsync(() -> {
SetChunkEnvironments packet = new SetChunkEnvironments(this.x, this.z, this.environments.serializeProtocol());
return CachedPacket.cache(packet);
});
this.cachedEnvironmentsPacket = new SoftReference(future);
return future;
}
}
static {
CODEC = ((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)BuilderCodec.builder(BlockChunk.class, BlockChunk::new).versioned()).codecVersion(3)).append(new KeyedCodec("Data", Codec.BYTE_ARRAY), BlockChunk::deserialize, BlockChunk::serialize).add()).build();
LOGGER = HytaleLogger.forEnclosingClass();
SEND_LOCAL_LIGHTING_DATA = true;
SEND_GLOBAL_LIGHTING_DATA = false;
}
public static class LoadBlockChunkPacketSystem extends ChunkStore.LoadFuturePacketDataQuerySystem {
private final ComponentType<ChunkStore, BlockChunk> componentType;
public LoadBlockChunkPacketSystem(ComponentType<ChunkStore, BlockChunk> blockChunkComponentType) {
this.componentType = blockChunkComponentType;
}
public Query<ChunkStore> getQuery() {
return this.componentType;
}
public void fetch(int index, @Nonnull ArchetypeChunk<ChunkStore> archetypeChunk, Store<ChunkStore> store, CommandBuffer<ChunkStore> commandBuffer, PlayerRef player, @Nonnull List<CompletableFuture<Packet>> results) {
BlockChunk component = (BlockChunk)archetypeChunk.getComponent(index, this.componentType);
results.add(component.getCachedHeightmapPacket().exceptionally((throwable) -> {
if (throwable != null) {
((HytaleLogger.Api)BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable)).log("Exception when compressing chunk heightmap:");
}
return null;
}).thenApply(Function.identity()));
results.add(component.getCachedTintsPacket().exceptionally((throwable) -> {
if (throwable != null) {
((HytaleLogger.Api)BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable)).log("Exception when compressing chunk tints:");
}
return null;
}).thenApply(Function.identity()));
results.add(component.getCachedEnvironmentsPacket().exceptionally((throwable) -> {
if (throwable != null) {
((HytaleLogger.Api)BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable)).log("Exception when compressing chunk environments:");
}
return null;
}).thenApply(Function.identity()));
for(int y = 0; y < component.chunkSections.length; ++y) {
BlockSection section = component.chunkSections[y];
results.add(section.getCachedChunkPacket(component.getX(), y, component.getZ()).exceptionally((throwable) -> {
if (throwable != null) {
((HytaleLogger.Api)BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable)).log("Exception while compressing set chunk (%d, %d):", component.x, component.z);
}
return null;
}).thenApply(Function.identity()));
}
}
}
}