From 4e0b86e08537ac7fcd67560a2b6630c49b762099 Mon Sep 17 00:00:00 2001 From: luk Date: Tue, 17 Feb 2026 17:37:51 +0000 Subject: [PATCH] dev --- build.gradle.kts | 2 +- .../SetMemoriesCapacityInteraction.java | 86 - .../objectives/task/GatherObjectiveTask.java | 114 -- .../system/ChunkBlockTickSystem.java | 143 -- .../crafting/component/CraftingManager.java | 982 ----------- .../crafting/state/ProcessingBenchState.java | 840 --------- .../hytale/builtin/fluid/FluidSystems.java | 414 ----- .../builtin/instances/InstancesPlugin.java | 644 ------- .../hytale/component/ArchetypeChunk.java | 52 +- src/com/hypixel/hytale/math/shape/Box.java | 1 + .../hytale/server/core/HytaleServer.java | 544 ------ .../server/core/entity/InteractionChain.java | 851 ---------- .../core/entity/InteractionManager.java | 1502 ----------------- .../hytale/server/core/io/PacketHandler.java | 570 ------- .../core/io/transport/QUICTransport.java | 52 +- .../entity/tracker/EntityTrackerSystems.java | 276 +-- .../tracker/LegacyEntityTrackerSystems.java | 58 +- .../blocktrack/TrackedPlacement.java | 124 -- .../config/client/PlaceFluidInteraction.java | 3 +- .../hytale/server/core/universe/Universe.java | 980 ----------- .../server/core/universe/world/World.java | 1247 -------------- .../core/universe/world/WorldMapTracker.java | 660 -------- .../world/chunk/BlockComponentChunk.java | 429 ----- .../world/lighting/ChunkLightingManager.java | 241 --- .../universe/world/storage/ChunkStore.java | 923 ---------- .../universe/world/storage/EntityStore.java | 183 -- .../universe/world/storage/IChunkSaver.java | 31 - .../IndexedStorageChunkStorageProvider.java | 682 -------- .../core/util/thread/TickingThread.java | 40 +- .../controllers/MotionControllerBase.java | 26 +- .../hypixel/hytale/server/npc/role/Role.java | 20 +- .../server/npc/systems/RoleSystems.java | 263 ++- .../npc/systems/SpawnReferenceSystems.java | 430 ----- .../controllers/BeaconSpawnController.java | 262 --- .../spawnmarkers/SpawnMarkerEntity.java | 547 ------ .../hytale/storage/IndexedStorageFile.java | 912 ---------- 36 files changed, 585 insertions(+), 14549 deletions(-) delete mode 100644 src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java delete mode 100644 src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java delete mode 100644 src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java delete mode 100644 src/com/hypixel/hytale/builtin/crafting/component/CraftingManager.java delete mode 100644 src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java delete mode 100644 src/com/hypixel/hytale/builtin/fluid/FluidSystems.java delete mode 100644 src/com/hypixel/hytale/builtin/instances/InstancesPlugin.java delete mode 100644 src/com/hypixel/hytale/server/core/HytaleServer.java delete mode 100644 src/com/hypixel/hytale/server/core/entity/InteractionChain.java delete mode 100644 src/com/hypixel/hytale/server/core/entity/InteractionManager.java delete mode 100644 src/com/hypixel/hytale/server/core/io/PacketHandler.java delete mode 100644 src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/Universe.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/World.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java delete mode 100644 src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java delete mode 100644 src/com/hypixel/hytale/server/npc/systems/SpawnReferenceSystems.java delete mode 100644 src/com/hypixel/hytale/server/spawning/controllers/BeaconSpawnController.java delete mode 100644 src/com/hypixel/hytale/server/spawning/spawnmarkers/SpawnMarkerEntity.java delete mode 100644 src/com/hypixel/hytale/storage/IndexedStorageFile.java diff --git a/build.gradle.kts b/build.gradle.kts index f2389b6f..da21daf2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,7 +55,7 @@ val hytaleServer: Configuration by configurations.creating val patchedJar = rootDir.resolve("repo/applications/HytaleServerPatched.jar") dependencies { - hytaleServer("com.hypixel.hytale:Server:2026.01.28-87d03be09") + hytaleServer("com.hypixel.hytale:Server:2026.02.17-255364b8e") } configurations { diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java b/src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java deleted file mode 100644 index 7e6b3c80..00000000 --- a/src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.hypixel.hytale.builtin.adventure.memories.interactions; - -import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.protocol.InteractionState; -import com.hypixel.hytale.protocol.InteractionType; -import com.hypixel.hytale.protocol.WaitForDataFrom; -import com.hypixel.hytale.protocol.packets.player.UpdateMemoriesFeatureStatus; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.entity.InteractionContext; -import com.hypixel.hytale.server.core.io.PacketHandler; -import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.util.NotificationUtil; - -import javax.annotation.Nonnull; - -public class SetMemoriesCapacityInteraction extends SimpleInstantInteraction { - public static final BuilderCodec CODEC = BuilderCodec.builder( - SetMemoriesCapacityInteraction.class, SetMemoriesCapacityInteraction::new, SimpleInstantInteraction.CODEC - ) - .documentation("Sets how many memories a player can store.") - .appendInherited( - new KeyedCodec<>("Capacity", Codec.INTEGER), (i, s) -> i.capacity = s, i -> i.capacity, (i, parent) -> i.capacity = parent.capacity - ) - .documentation("Defines the amount of memories that a player can store.") - .add() - .build(); - private int capacity; - - public SetMemoriesCapacityInteraction() { - } - - @Override - protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { - // HyFix #52: Validate ComponentType before use to prevent crash when memories module isn't loaded - if (!PlayerMemories.getComponentType().isValid()) { - context.getState().state = InteractionState.Failed; - return; - } - - Ref ref = context.getEntity(); - CommandBuffer commandBuffer = context.getCommandBuffer(); - - assert commandBuffer != null; - - PlayerMemories memoriesComponent = commandBuffer.ensureAndGetComponent(ref, PlayerMemories.getComponentType()); - if (this.capacity <= memoriesComponent.getMemoriesCapacity()) { - context.getState().state = InteractionState.Failed; - } else { - int previousCapacity = memoriesComponent.getMemoriesCapacity(); - memoriesComponent.setMemoriesCapacity(this.capacity); - if (previousCapacity <= 0) { - PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - PacketHandler playerConnection = playerRefComponent.getPacketHandler(); - playerConnection.writeNoCache(new UpdateMemoriesFeatureStatus(true)); - NotificationUtil.sendNotification( - playerConnection, Message.translation("server.memories.general.featureUnlockedNotification"), null, "NotificationIcons/MemoriesIcon.png" - ); - playerRefComponent.sendMessage(Message.translation("server.memories.general.featureUnlockedMessage")); - } - - context.getState().state = InteractionState.Finished; - } - } - - @Nonnull - @Override - public WaitForDataFrom getWaitForDataFrom() { - return WaitForDataFrom.Server; - } - - @Override - public String toString() { - return super.toString(); - } -} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java deleted file mode 100644 index 6921bc6f..00000000 --- a/src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.hypixel.hytale.builtin.adventure.objectives.task; - -import com.hypixel.hytale.builtin.adventure.objectives.Objective; -import com.hypixel.hytale.builtin.adventure.objectives.config.task.BlockTagOrItemIdField; -import com.hypixel.hytale.builtin.adventure.objectives.config.task.GatherObjectiveTaskAsset; -import com.hypixel.hytale.builtin.adventure.objectives.transaction.RegistrationTransactionRecord; -import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.math.util.MathUtil; -import com.hypixel.hytale.server.core.entity.LivingEntity; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.event.events.entity.LivingEntityInventoryChangeEvent; -import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.Universe; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Set; -import java.util.UUID; - -public class GatherObjectiveTask extends CountObjectiveTask { - public static final BuilderCodec CODEC = BuilderCodec.builder( - GatherObjectiveTask.class, GatherObjectiveTask::new, CountObjectiveTask.CODEC - ) - .build(); - - public GatherObjectiveTask(@Nonnull GatherObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { - super(asset, taskSetIndex, taskIndex); - } - - protected GatherObjectiveTask() { - } - - @Nonnull - public GatherObjectiveTaskAsset getAsset() { - return (GatherObjectiveTaskAsset) super.getAsset(); - } - - @Nullable - @Override - protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { - Set participatingPlayers = objective.getPlayerUUIDs(); - int countItem = this.countObjectiveItemInInventories(participatingPlayers, store); - if (this.areTaskConditionsFulfilled(null, null, participatingPlayers)) { - this.count = MathUtil.clamp(countItem, 0, this.getAsset().getCount()); - if (this.checkCompletion()) { - this.consumeTaskConditions(null, null, participatingPlayers); - this.complete = true; - return null; - } - } - - this.eventRegistry.register(LivingEntityInventoryChangeEvent.class, world.getName(), event -> { - LivingEntity livingEntity = event.getEntity(); - if (livingEntity instanceof Player) { - Ref ref = livingEntity.getReference(); - World refWorld = store.getExternalData().getWorld(); - refWorld.execute(() -> { - // HyFix: Validate ref before use - prevents NPE crash when player disconnects - // between event dispatch and lambda execution - if (ref == null || !ref.isValid()) { - return; - } - UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); - if (uuidComponent == null) { - return; - } - - Set activePlayerUUIDs = objective.getActivePlayerUUIDs(); - if (activePlayerUUIDs.contains(uuidComponent.getUuid())) { - int count = this.countObjectiveItemInInventories(activePlayerUUIDs, store); - this.setTaskCompletion(store, ref, count, objective); - } - }); - } - }); - return RegistrationTransactionRecord.wrap(this.eventRegistry); - } - - private int countObjectiveItemInInventories(@Nonnull Set participatingPlayers, @Nonnull ComponentAccessor componentAccessor) { - int count = 0; - BlockTagOrItemIdField blockTypeOrSet = this.getAsset().getBlockTagOrItemIdField(); - - for (UUID playerUUID : participatingPlayers) { - PlayerRef playerRefComponent = Universe.get().getPlayer(playerUUID); - if (playerRefComponent != null) { - Ref playerRef = playerRefComponent.getReference(); - if (playerRef != null && playerRef.isValid()) { - Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); - - assert playerComponent != null; - - CombinedItemContainer inventory = playerComponent.getInventory().getCombinedHotbarFirst(); - count += inventory.countItemStacks(itemStack -> blockTypeOrSet.isBlockTypeIncluded(itemStack.getItemId())); - } - } - } - - return count; - } - - @Nonnull - @Override - public String toString() { - return "GatherObjectiveTask{} " + super.toString(); - } -} diff --git a/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java b/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java deleted file mode 100644 index 06d0f41e..00000000 --- a/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java +++ /dev/null @@ -1,143 +0,0 @@ -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 javax.annotation.Nonnull; -import java.time.Instant; -import java.util.Set; -import java.util.logging.Level; - -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/crafting/component/CraftingManager.java b/src/com/hypixel/hytale/builtin/crafting/component/CraftingManager.java deleted file mode 100644 index ab9a04a6..00000000 --- a/src/com/hypixel/hytale/builtin/crafting/component/CraftingManager.java +++ /dev/null @@ -1,982 +0,0 @@ -package com.hypixel.hytale.builtin.crafting.component; - -import com.google.gson.JsonArray; -import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; -import com.hypixel.hytale.builtin.crafting.CraftingPlugin; -import com.hypixel.hytale.builtin.crafting.state.BenchState; -import com.hypixel.hytale.builtin.crafting.window.BenchWindow; -import com.hypixel.hytale.builtin.crafting.window.CraftingWindow; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.spatial.SpatialResource; -import com.hypixel.hytale.event.IEventDispatcher; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.shape.Box; -import com.hypixel.hytale.math.vector.Vector3d; -import com.hypixel.hytale.protocol.BenchRequirement; -import com.hypixel.hytale.protocol.BenchType; -import com.hypixel.hytale.protocol.GameMode; -import com.hypixel.hytale.protocol.ItemQuantity; -import com.hypixel.hytale.protocol.ItemResourceType; -import com.hypixel.hytale.protocol.SoundCategory; -import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; -import com.hypixel.hytale.server.core.HytaleServer; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchTierLevel; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchUpgradeRequirement; -import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; -import com.hypixel.hytale.server.core.asset.type.item.config.Item; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; -import com.hypixel.hytale.server.core.entity.entities.player.windows.MaterialExtraResourcesSection; -import com.hypixel.hytale.server.core.event.events.ecs.CraftRecipeEvent; -import com.hypixel.hytale.server.core.event.events.player.PlayerCraftEvent; -import com.hypixel.hytale.server.core.inventory.Inventory; -import com.hypixel.hytale.server.core.inventory.ItemStack; -import com.hypixel.hytale.server.core.inventory.MaterialQuantity; -import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; -import com.hypixel.hytale.server.core.inventory.container.DelegateItemContainer; -import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer; -import com.hypixel.hytale.server.core.inventory.container.ItemContainer; -import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; -import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; -import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; -import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction; -import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.world.SoundUtil; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.meta.BlockState; -import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; -import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.util.NotificationUtil; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; -import org.bson.BsonDocument; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.logging.Level; - -public class CraftingManager implements Component { - @Nonnull - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - @Nonnull - private final BlockingQueue queuedCraftingJobs = new LinkedBlockingQueue<>(); - @Nullable - private CraftingManager.BenchUpgradingJob upgradingJob; - private int x; - private int y; - private int z; - @Nullable - private BlockType blockType; - - @Nonnull - public static ComponentType getComponentType() { - return CraftingPlugin.get().getCraftingManagerComponentType(); - } - - public CraftingManager() { - } - - private CraftingManager(@Nonnull CraftingManager other) { - this.x = other.x; - this.y = other.y; - this.z = other.z; - this.blockType = other.blockType; - this.queuedCraftingJobs.addAll(other.queuedCraftingJobs); - this.upgradingJob = other.upgradingJob; - } - - public boolean hasBenchSet() { - return this.blockType != null; - } - - public void setBench(int x, int y, int z, @Nonnull BlockType blockType) { - Bench bench = blockType.getBench(); - Objects.requireNonNull(bench, "blockType isn't a bench!"); - if (bench.getType() != BenchType.Crafting - && bench.getType() != BenchType.DiagramCrafting - && bench.getType() != BenchType.StructuralCrafting - && bench.getType() != BenchType.Processing) { - throw new IllegalArgumentException("blockType isn't a crafting bench!"); - } - // HyFix: Auto-clear stale bench reference instead of throwing exception - // This prevents "Bench blockType is already set!" crashes when player rapidly - // opens multiple benches or previous interaction didn't properly clean up - else if (this.blockType != null) { - this.x = 0; - this.y = 0; - this.z = 0; - this.blockType = null; - this.queuedCraftingJobs.clear(); - this.upgradingJob = null; - } - if (!this.queuedCraftingJobs.isEmpty()) { - throw new IllegalArgumentException("Queue already has jobs!"); - } else if (this.upgradingJob != null) { - throw new IllegalArgumentException("Upgrading job is already set!"); - } else { - this.x = x; - this.y = y; - this.z = z; - this.blockType = blockType; - } - } - - public boolean clearBench(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { - boolean result = this.cancelAllCrafting(ref, componentAccessor); - this.x = 0; - this.y = 0; - this.z = 0; - this.blockType = null; - this.upgradingJob = null; - return result; - } - - public boolean craftItem( - @Nonnull Ref ref, - @Nonnull ComponentAccessor componentAccessor, - @Nonnull CraftingRecipe recipe, - int quantity, - @Nonnull ItemContainer itemContainer - ) { - if (this.upgradingJob != null) { - return false; - } else { - Objects.requireNonNull(recipe, "Recipe can't be null"); - CraftRecipeEvent.Pre preEvent = new CraftRecipeEvent.Pre(recipe, quantity); - componentAccessor.invoke(ref, preEvent); - if (preEvent.isCancelled()) { - return false; - } else if (!this.isValidBenchForRecipe(ref, componentAccessor, recipe)) { - return false; - } else { - World world = componentAccessor.getExternalData().getWorld(); - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - if (playerComponent.getGameMode() != GameMode.Creative && !removeInputFromInventory(itemContainer, recipe, quantity)) { - PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - String translationKey = getRecipeOutputTranslationKey(recipe); - if (translationKey != null) { - NotificationUtil.sendNotification( - playerRefComponent.getPacketHandler(), - Message.translation("server.general.crafting.missingIngredient").param("item", Message.translation(translationKey)), - NotificationStyle.Danger - ); - } - - LOGGER.at(Level.FINE).log("Missing items required to craft the item: %s", recipe); - return false; - } else { - CraftRecipeEvent.Post postEvent = new CraftRecipeEvent.Post(recipe, quantity); - componentAccessor.invoke(ref, postEvent); - if (postEvent.isCancelled()) { - return true; - } else { - giveOutput(ref, componentAccessor, recipe, quantity); - IEventDispatcher dispatcher = HytaleServer.get() - .getEventBus() - .dispatchFor(PlayerCraftEvent.class, world.getName()); - if (dispatcher.hasListener()) { - dispatcher.dispatch(new PlayerCraftEvent(ref, playerComponent, recipe, quantity)); - } - - return true; - } - } - } - } - } - - @Nullable - private static String getRecipeOutputTranslationKey(@Nonnull CraftingRecipe recipe) { - String itemId = recipe.getPrimaryOutput().getItemId(); - if (itemId == null) { - return null; - } else { - Item itemAsset = Item.getAssetMap().getAsset(itemId); - return itemAsset != null ? itemAsset.getTranslationKey() : null; - } - } - - public boolean queueCraft( - @Nonnull Ref ref, - @Nonnull ComponentAccessor componentAccessor, - @Nonnull CraftingWindow window, - int transactionId, - @Nonnull CraftingRecipe recipe, - int quantity, - @Nonnull ItemContainer inputItemContainer, - @Nonnull CraftingManager.InputRemovalType inputRemovalType - ) { - if (this.upgradingJob != null) { - return false; - } else { - Objects.requireNonNull(recipe, "Recipe can't be null"); - if (!this.isValidBenchForRecipe(ref, componentAccessor, recipe)) { - return false; - } else { - float recipeTime = recipe.getTimeSeconds(); - if (recipeTime > 0.0F) { - int level = this.getBenchTierLevel(componentAccessor); - if (level > 1) { - BenchTierLevel tierLevelData = this.getBenchTierLevelData(level); - if (tierLevelData != null) { - recipeTime -= recipeTime * tierLevelData.getCraftingTimeReductionModifier(); - } - } - } - - this.queuedCraftingJobs - .offer(new CraftingManager.CraftingJob(window, transactionId, recipe, quantity, recipeTime, inputItemContainer, inputRemovalType)); - return true; - } - } - } - - public void tick(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, float dt) { - if (this.upgradingJob != null) { - if (dt > 0.0F) { - this.upgradingJob.timeSecondsCompleted += dt; - } - - this.upgradingJob.window.updateBenchUpgradeJob(this.upgradingJob.computeLoadingPercent()); - if (this.upgradingJob.timeSecondsCompleted >= this.upgradingJob.timeSeconds) { - this.upgradingJob.window.updateBenchTierLevel(this.finishTierUpgrade(ref, componentAccessor)); - this.upgradingJob = null; - } - } else { - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - while (dt > 0.0F && !this.queuedCraftingJobs.isEmpty()) { - CraftingManager.CraftingJob currentJob = this.queuedCraftingJobs.peek(); - boolean isCreativeMode = playerComponent.getGameMode() == GameMode.Creative; - if (currentJob != null && currentJob.quantityStarted < currentJob.quantity && currentJob.quantityStarted <= currentJob.quantityCompleted) { - LOGGER.at(Level.FINE).log("Removing Items for next quantity: %s", currentJob); - int currentItemId = currentJob.quantityStarted++; - if (!isCreativeMode && !removeInputFromInventory(currentJob, currentItemId)) { - String translationKey = getRecipeOutputTranslationKey(currentJob.recipe); - if (translationKey != null) { - NotificationUtil.sendNotification( - playerRefComponent.getPacketHandler(), - Message.translation("server.general.crafting.missingIngredient").param("item", Message.translation(translationKey)), - NotificationStyle.Danger - ); - } - - LOGGER.at(Level.FINE).log("Missing items required to craft the item: %s", currentJob); - currentJob = null; - this.queuedCraftingJobs.poll(); - } - - if (!isCreativeMode - && currentJob != null - && currentJob.quantityStarted < currentJob.quantity - && currentJob.quantityStarted <= currentJob.quantityCompleted) { - NotificationUtil.sendNotification( - playerRefComponent.getPacketHandler(), - Message.translation("server.general.crafting.failedTakingCorrectQuantity"), - NotificationStyle.Danger - ); - LOGGER.at(Level.SEVERE).log("Failed to remove the correct quantity of input, removing crafting job %s", currentJob); - currentJob = null; - this.queuedCraftingJobs.poll(); - } - } - - if (currentJob != null) { - currentJob.timeSecondsCompleted += dt; - float percent = currentJob.timeSeconds <= 0.0F ? 1.0F : currentJob.timeSecondsCompleted / currentJob.timeSeconds; - if (percent > 1.0F) { - percent = 1.0F; - } - - currentJob.window.updateCraftingJob(percent); - LOGGER.at(Level.FINEST).log("Update time: %s", currentJob); - dt = 0.0F; - if (currentJob.timeSecondsCompleted >= currentJob.timeSeconds) { - dt = currentJob.timeSecondsCompleted - currentJob.timeSeconds; - int currentCompletedItemId = currentJob.quantityCompleted++; - currentJob.timeSecondsCompleted = 0.0F; - LOGGER.at(Level.FINE).log("Crafted 1 Quantity: %s", currentJob); - if (currentJob.quantityCompleted == currentJob.quantity) { - giveOutput(ref, componentAccessor, currentJob, currentCompletedItemId); - LOGGER.at(Level.FINE).log("Crafting Finished: %s", currentJob); - this.queuedCraftingJobs.poll(); - } else { - if (currentJob.quantityCompleted > currentJob.quantity) { - this.queuedCraftingJobs.poll(); - throw new RuntimeException("QuantityCompleted is greater than the Quality! " + currentJob); - } - - giveOutput(ref, componentAccessor, currentJob, currentCompletedItemId); - } - - if (this.queuedCraftingJobs.isEmpty()) { - currentJob.window.setBlockInteractionState("default", componentAccessor.getExternalData().getWorld()); - } - } - } - } - } - } - - public boolean cancelAllCrafting(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { - LOGGER.at(Level.FINE).log("Cancel Crafting!"); - ObjectList oldJobs = new ObjectArrayList<>(this.queuedCraftingJobs.size()); - this.queuedCraftingJobs.drainTo(oldJobs); - if (!oldJobs.isEmpty()) { - CraftingManager.CraftingJob currentJob = oldJobs.getFirst(); - LOGGER.at(Level.FINE).log("Refunding Items for: %s", currentJob); - refundInputToInventory(ref, componentAccessor, currentJob, currentJob.quantityStarted - 1); - return true; - } else { - return false; - } - } - - private boolean isValidBenchForRecipe( - @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, @Nonnull CraftingRecipe recipe - ) { - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); - String primaryOutputItemId = recipe.getPrimaryOutput() != null ? recipe.getPrimaryOutput().getItemId() : null; - if (!recipe.isKnowledgeRequired() || primaryOutputItemId != null && playerConfigData.getKnownRecipes().contains(primaryOutputItemId)) { - World world = componentAccessor.getExternalData().getWorld(); - if (recipe.getRequiredMemoriesLevel() > 1 && MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig()) < recipe.getRequiredMemoriesLevel()) { - LOGGER.at(Level.WARNING).log("Attempted to craft %s but doesn't have the required world memories level!", recipe.getId()); - return false; - } else { - BenchType benchType = this.blockType != null ? this.blockType.getBench().getType() : BenchType.Crafting; - String benchName = this.blockType != null ? this.blockType.getBench().getId() : "Fieldcraft"; - boolean meetsRequirements = false; - BlockState state = world.getState(this.x, this.y, this.z, true); - int benchTierLevel = state instanceof BenchState ? ((BenchState) state).getTierLevel() : 0; - BenchRequirement[] requirements = recipe.getBenchRequirement(); - if (requirements != null) { - for (BenchRequirement benchRequirement : requirements) { - if (benchRequirement.type == benchType && benchName.equals(benchRequirement.id) && benchRequirement.requiredTierLevel <= benchTierLevel) { - meetsRequirements = true; - break; - } - } - } - - if (!meetsRequirements) { - LOGGER.at(Level.WARNING) - .log("Attempted to craft %s using %s, %s but requires bench %s but a bench is NOT set!", recipe.getId(), benchType, benchName, requirements); - return false; - } else if (benchType == BenchType.Crafting && !"Fieldcraft".equals(benchName)) { - CraftingManager.CraftingJob craftingJob = this.queuedCraftingJobs.peek(); - return craftingJob == null || craftingJob.recipe.getId().equals(recipe.getId()); - } else { - return true; - } - } - } else { - LOGGER.at(Level.WARNING).log("%s - Attempted to craft %s but doesn't know the recipe!", recipe.getId()); - return false; - } - } - - private static void giveOutput( - @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, @Nonnull CraftingManager.CraftingJob job, int currentItemId - ) { - job.removedItems.remove(currentItemId); - String recipeId = job.recipe.getId(); - CraftingRecipe recipeAsset = CraftingRecipe.getAssetMap().getAsset(recipeId); - if (recipeAsset == null) { - throw new RuntimeException("A non-existent item ID was provided! " + recipeId); - } else { - giveOutput(ref, componentAccessor, recipeAsset, 1); - } - } - - private static void giveOutput( - @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, @Nonnull CraftingRecipe craftingRecipe, int quantity - ) { - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - if (playerComponent == null) { - LOGGER.at(Level.WARNING).log("Attempted to give output to a non-player entity: %s", ref); - } else { - List itemStacks = getOutputItemStacks(craftingRecipe, quantity); - Inventory inventory = playerComponent.getInventory(); - SimpleItemContainer.addOrDropItemStacks(componentAccessor, ref, inventory.getCombinedArmorHotbarStorage(), itemStacks); - } - } - - private static boolean removeInputFromInventory(@Nonnull CraftingManager.CraftingJob job, int currentItemId) { - Objects.requireNonNull(job, "Job can't be null!"); - CraftingRecipe craftingRecipe = job.recipe; - Objects.requireNonNull(craftingRecipe, "CraftingRecipe can't be null!"); - List materialsToRemove = getInputMaterials(craftingRecipe); - if (materialsToRemove.isEmpty()) { - return true; - } else { - LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", job, materialsToRemove); - ObjectList itemStackList = new ObjectArrayList<>(); - - boolean succeeded = switch (job.inputRemovalType) { - case NORMAL -> { - ListTransaction materialTransactions = job.inputItemContainer.removeMaterials(materialsToRemove, true, true, true); - - for (MaterialTransaction transaction : materialTransactions.getList()) { - for (MaterialSlotTransaction slotTransaction : transaction.getList()) { - if (!ItemStack.isEmpty(slotTransaction.getOutput())) { - itemStackList.add(slotTransaction.getOutput()); - } - } - } - - yield materialTransactions.succeeded(); - } - case ORDERED -> { - ListTransaction materialTransactions = job.inputItemContainer - .removeMaterialsOrdered(materialsToRemove, true, true, true); - - for (MaterialSlotTransaction transaction : materialTransactions.getList()) { - if (!ItemStack.isEmpty(transaction.getOutput())) { - itemStackList.add(transaction.getOutput()); - } - } - - yield materialTransactions.succeeded(); - } - default -> throw new IllegalArgumentException("Unknown enum: " + job.inputRemovalType); - }; - job.removedItems.put(currentItemId, itemStackList); - job.window.invalidateExtraResources(); - return succeeded; - } - } - - private static boolean removeInputFromInventory(@Nonnull ItemContainer itemContainer, @Nonnull CraftingRecipe craftingRecipe, int quantity) { - List materialsToRemove = getInputMaterials(craftingRecipe, quantity); - if (materialsToRemove.isEmpty()) { - return true; - } else { - LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", craftingRecipe, materialsToRemove); - ListTransaction materialTransactions = itemContainer.removeMaterials(materialsToRemove, true, true, true); - return materialTransactions.succeeded(); - } - } - - private static void refundInputToInventory( - @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, @Nonnull CraftingManager.CraftingJob job, int currentItemId - ) { - Objects.requireNonNull(job, "Job can't be null!"); - List itemStacks = job.removedItems.get(currentItemId); - if (itemStacks != null) { - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - SimpleItemContainer.addOrDropItemStacks(componentAccessor, ref, playerComponent.getInventory().getCombinedHotbarFirst(), itemStacks); - } - } - - @Nonnull - public static List getOutputItemStacks(@Nonnull CraftingRecipe recipe) { - return getOutputItemStacks(recipe, 1); - } - - @Nonnull - public static List getOutputItemStacks(@Nonnull CraftingRecipe recipe, int quantity) { - Objects.requireNonNull(recipe); - MaterialQuantity[] output = recipe.getOutputs(); - if (output == null) { - return List.of(); - } else { - ObjectList outputItemStacks = new ObjectArrayList<>(); - - for (MaterialQuantity outputMaterial : output) { - ItemStack outputItemStack = getOutputItemStack(outputMaterial, quantity); - if (outputItemStack != null) { - outputItemStacks.add(outputItemStack); - } - } - - return outputItemStacks; - } - } - - @Nullable - public static ItemStack getOutputItemStack(@Nonnull MaterialQuantity outputMaterial, @Nonnull String id) { - return getOutputItemStack(outputMaterial, 1); - } - - @Nullable - public static ItemStack getOutputItemStack(@Nonnull MaterialQuantity outputMaterial, int quantity) { - String itemId = outputMaterial.getItemId(); - if (itemId == null) { - return null; - } else { - int materialQuantity = outputMaterial.getQuantity() <= 0 ? 1 : outputMaterial.getQuantity(); - return new ItemStack(itemId, materialQuantity * quantity, outputMaterial.getMetadata()); - } - } - - @Nonnull - public static List getInputMaterials(@Nonnull CraftingRecipe recipe) { - return getInputMaterials(recipe, 1); - } - - @Nonnull - private static List getInputMaterials(@Nonnull MaterialQuantity[] input) { - return getInputMaterials(input, 1); - } - - @Nonnull - public static List getInputMaterials(@Nonnull CraftingRecipe recipe, int quantity) { - Objects.requireNonNull(recipe); - return recipe.getInput() == null ? Collections.emptyList() : getInputMaterials(recipe.getInput(), quantity); - } - - @Nonnull - private static List getInputMaterials(@Nonnull MaterialQuantity[] input, int quantity) { - ObjectList materials = new ObjectArrayList<>(); - - for (MaterialQuantity craftingMaterial : input) { - String itemId = craftingMaterial.getItemId(); - String resourceTypeId = craftingMaterial.getResourceTypeId(); - int materialQuantity = craftingMaterial.getQuantity(); - BsonDocument metadata = craftingMaterial.getMetadata(); - materials.add(new MaterialQuantity(itemId, resourceTypeId, null, materialQuantity * quantity, metadata)); - } - - return materials; - } - - public static boolean matches(@Nonnull MaterialQuantity craftingMaterial, @Nonnull ItemStack itemStack) { - String itemId = craftingMaterial.getItemId(); - if (itemId != null) { - return itemId.equals(itemStack.getItemId()); - } else { - String resourceTypeId = craftingMaterial.getResourceTypeId(); - if (resourceTypeId != null && itemStack.getItem().getResourceTypes() != null) { - for (ItemResourceType itemResourceType : itemStack.getItem().getResourceTypes()) { - if (resourceTypeId.equals(itemResourceType.id)) { - return true; - } - } - } - - return false; - } - } - - @Nonnull - public static JsonArray generateInventoryHints(@Nonnull List recipes, int inputSlotIndex, @Nonnull ItemContainer container) { - JsonArray inventoryHints = new JsonArray(); - short storageSlotIndex = 0; - - for (short bound = container.getCapacity(); storageSlotIndex < bound; storageSlotIndex++) { - ItemStack itemStack = container.getItemStack(storageSlotIndex); - if (itemStack != null && !itemStack.isEmpty() && matchesAnyRecipe(recipes, inputSlotIndex, itemStack)) { - inventoryHints.add(storageSlotIndex); - } - } - - return inventoryHints; - } - - public static boolean matchesAnyRecipe(@Nonnull List recipes, int inputSlotIndex, @Nonnull ItemStack slotItemStack) { - for (CraftingRecipe recipe : recipes) { - MaterialQuantity[] input = recipe.getInput(); - if (inputSlotIndex < input.length) { - MaterialQuantity slotCraftingMaterial = input[inputSlotIndex]; - if (slotCraftingMaterial.getItemId() != null && slotCraftingMaterial.getItemId().equals(slotItemStack.getItemId())) { - return true; - } - - if (slotCraftingMaterial.getResourceTypeId() != null && slotItemStack.getItem().getResourceTypes() != null) { - for (ItemResourceType itemResourceType : slotItemStack.getItem().getResourceTypes()) { - if (slotCraftingMaterial.getResourceTypeId().equals(itemResourceType.id)) { - return true; - } - } - } - } - } - - return false; - } - - public boolean startTierUpgrade(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, @Nonnull BenchWindow window) { - if (this.upgradingJob != null) { - return false; - } else { - BenchUpgradeRequirement requirements = this.getBenchUpgradeRequirement(this.getBenchTierLevel(componentAccessor)); - if (requirements == null) { - return false; - } else { - List input = getInputMaterials(requirements.getInput()); - if (input.isEmpty()) { - return false; - } else { - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - if (playerComponent.getGameMode() != GameMode.Creative) { - CombinedItemContainer combined = new CombinedItemContainer( - playerComponent.getInventory().getCombinedBackpackStorageHotbar(), window.getExtraResourcesSection().getItemContainer() - ); - if (!combined.canRemoveMaterials(input)) { - return false; - } - } - - this.upgradingJob = new CraftingManager.BenchUpgradingJob(window, requirements.getTimeSeconds()); - this.cancelAllCrafting(ref, componentAccessor); - return true; - } - } - } - } - - private int finishTierUpgrade(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { - if (this.upgradingJob == null) { - return 0; - } else { - World world = componentAccessor.getExternalData().getWorld(); - BlockState state = world.getState(this.x, this.y, this.z, true); - BenchState benchState = state instanceof BenchState ? (BenchState) state : null; - if (benchState != null && benchState.getTierLevel() != 0) { - BenchUpgradeRequirement requirements = this.getBenchUpgradeRequirement(benchState.getTierLevel()); - if (requirements == null) { - return benchState.getTierLevel(); - } else { - List input = getInputMaterials(requirements.getInput()); - if (input.isEmpty()) { - return benchState.getTierLevel(); - } else { - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - boolean canUpgrade = playerComponent.getGameMode() == GameMode.Creative; - if (!canUpgrade) { - CombinedItemContainer combined = new CombinedItemContainer( - playerComponent.getInventory().getCombinedBackpackStorageHotbar(), - this.upgradingJob.window.getExtraResourcesSection().getItemContainer() - ); - combined = new CombinedItemContainer(combined, this.upgradingJob.window.getExtraResourcesSection().getItemContainer()); - ListTransaction materialTransactions = combined.removeMaterials(input); - if (materialTransactions.succeeded()) { - List consumed = new ObjectArrayList<>(); - - for (MaterialTransaction transaction : materialTransactions.getList()) { - for (MaterialSlotTransaction matSlot : transaction.getList()) { - consumed.add(matSlot.getOutput()); - } - } - - benchState.addUpgradeItems(consumed); - canUpgrade = true; - } - } - - if (canUpgrade) { - benchState.setTierLevel(benchState.getTierLevel() + 1); - if (benchState.getBench().getBenchUpgradeCompletedSoundEventIndex() != 0) { - SoundUtil.playSoundEvent3d( - benchState.getBench().getBenchUpgradeCompletedSoundEventIndex(), - SoundCategory.SFX, - this.x + 0.5, - this.y + 0.5, - this.z + 0.5, - componentAccessor - ); - } - } - - return benchState.getTierLevel(); - } - } - } else { - return 0; - } - } - } - - @Nullable - private BenchTierLevel getBenchTierLevelData(int level) { - if (this.blockType == null) { - return null; - } else { - Bench bench = this.blockType.getBench(); - return bench == null ? null : bench.getTierLevel(level); - } - } - - @Nullable - private BenchUpgradeRequirement getBenchUpgradeRequirement(int tierLevel) { - BenchTierLevel tierData = this.getBenchTierLevelData(tierLevel); - return tierData == null ? null : tierData.getUpgradeRequirement(); - } - - private int getBenchTierLevel(@Nonnull ComponentAccessor componentAccessor) { - World world = componentAccessor.getExternalData().getWorld(); - BlockState state = world.getState(this.x, this.y, this.z, true); - return state instanceof BenchState ? ((BenchState) state).getTierLevel() : 0; - } - - public static int feedExtraResourcesSection(@Nonnull BenchState benchState, @Nonnull MaterialExtraResourcesSection extraResourcesSection) { - try { - CraftingManager.ChestLookupResult result = getContainersAroundBench(benchState); - List chests = result.containers; - List chestStates = result.states; - ItemContainer itemContainer = EmptyItemContainer.INSTANCE; - if (!chests.isEmpty()) { - itemContainer = new CombinedItemContainer(chests.stream().map(container -> { - DelegateItemContainer delegate = new DelegateItemContainer<>(container); - delegate.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); - return delegate; - }).toArray(ItemContainer[]::new)); - } - - Map materials = new Object2ObjectOpenHashMap<>(); - - for (ItemContainer chest : chests) { - chest.forEach((i, itemStack) -> { - if (CraftingPlugin.isValidUpgradeMaterialForBench(benchState, itemStack) || CraftingPlugin.isValidCraftingMaterialForBench(benchState, itemStack)) { - ItemQuantity var10000 = materials.computeIfAbsent(itemStack.getItemId(), k -> new ItemQuantity(itemStack.getItemId(), 0)); - var10000.quantity = var10000.quantity + itemStack.getQuantity(); - } - }); - } - - extraResourcesSection.setItemContainer(itemContainer); - extraResourcesSection.setExtraMaterials(materials.values().toArray(new ItemQuantity[0])); - extraResourcesSection.setValid(true); - return chestStates.size(); - } catch (Exception e) { - e.printStackTrace(); - return 0; - } - } - - @Nonnull - protected static CraftingManager.ChestLookupResult getContainersAroundBench(@Nonnull BenchState benchState) { - List containers = new ObjectArrayList<>(); - List states = new ObjectArrayList<>(); - List spatialResults = new ObjectArrayList<>(); - List filteredOut = new ObjectArrayList<>(); - World world = benchState.getChunk().getWorld(); - Store store = world.getChunkStore().getStore(); - int limit = world.getGameplayConfig().getCraftingConfig().getBenchMaterialChestLimit(); - double horizontalRadius = world.getGameplayConfig().getCraftingConfig().getBenchMaterialHorizontalChestSearchRadius(); - double verticalRadius = world.getGameplayConfig().getCraftingConfig().getBenchMaterialVerticalChestSearchRadius(); - Vector3d blockPos = benchState.getBlockPosition().toVector3d(); - BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(benchState.getBlockType().getHitboxTypeIndex()); - BlockBoundingBoxes.RotatedVariantBoxes rotatedHitbox = hitboxAsset.get(benchState.getRotationIndex()); - Box boundingBox = rotatedHitbox.getBoundingBox(); - double benchWidth = boundingBox.width(); - double benchHeight = boundingBox.height(); - double benchDepth = boundingBox.depth(); - double extraSearchRadius = Math.max(benchWidth, Math.max(benchDepth, benchHeight)) - 1.0; - SpatialResource, ChunkStore> blockStateSpatialStructure = store.getResource(BlockStateModule.get().getItemContainerSpatialResourceType()); - ObjectList> results = SpatialResource.getThreadLocalReferenceList(); - blockStateSpatialStructure.getSpatialStructure() - .ordered3DAxis(blockPos, horizontalRadius + extraSearchRadius, verticalRadius + extraSearchRadius, horizontalRadius + extraSearchRadius, results); - if (!results.isEmpty()) { - int benchMinBlockX = (int) Math.floor(boundingBox.min.x); - int benchMinBlockY = (int) Math.floor(boundingBox.min.y); - int benchMinBlockZ = (int) Math.floor(boundingBox.min.z); - int benchMaxBlockX = (int) Math.ceil(boundingBox.max.x) - 1; - int benchMaxBlockY = (int) Math.ceil(boundingBox.max.y) - 1; - int benchMaxBlockZ = (int) Math.ceil(boundingBox.max.z) - 1; - double minX = blockPos.x + benchMinBlockX - horizontalRadius; - double minY = blockPos.y + benchMinBlockY - verticalRadius; - double minZ = blockPos.z + benchMinBlockZ - horizontalRadius; - double maxX = blockPos.x + benchMaxBlockX + horizontalRadius; - double maxY = blockPos.y + benchMaxBlockY + verticalRadius; - double maxZ = blockPos.z + benchMaxBlockZ + horizontalRadius; - - for (Ref ref : results) { - if (BlockState.getBlockState(ref, ref.getStore()) instanceof ItemContainerState chest) { - spatialResults.add(chest); - } - } - - for (ItemContainerState chest : spatialResults) { - Vector3d chestBlockPos = chest.getBlockPosition().toVector3d(); - if (chestBlockPos.x >= minX - && chestBlockPos.x <= maxX - && chestBlockPos.y >= minY - && chestBlockPos.y <= maxY - && chestBlockPos.z >= minZ - && chestBlockPos.z <= maxZ) { - containers.add(chest.getItemContainer()); - states.add(chest); - if (containers.size() >= limit) { - break; - } - } else { - filteredOut.add(chest); - } - } - } - - return new CraftingManager.ChestLookupResult(containers, states, spatialResults, filteredOut, blockPos); - } - - @Nonnull - @Override - public String toString() { - return "CraftingManager{queuedCraftingJobs=" - + this.queuedCraftingJobs - + ", x=" - + this.x - + ", y=" - + this.y - + ", z=" - + this.z - + ", blockType=" - + this.blockType - + "}"; - } - - @Nonnull - @Override - public Component clone() { - return new CraftingManager(this); - } - - private static class BenchUpgradingJob { - @Nonnull - private final BenchWindow window; - private final float timeSeconds; - private float timeSecondsCompleted; - private float lastSentPercent; - - private BenchUpgradingJob(@Nonnull BenchWindow window, float timeSeconds) { - this.window = window; - this.timeSeconds = timeSeconds; - } - - @Override - public String toString() { - return "BenchUpgradingJob{window=" + this.window + ", timeSeconds=" + this.timeSeconds + "}"; - } - - public float computeLoadingPercent() { - return this.timeSeconds <= 0.0F ? 1.0F : Math.min(this.timeSecondsCompleted / this.timeSeconds, 1.0F); - } - } - - protected record ChestLookupResult( - List containers, - List states, - List spatialResults, - List filteredOut, - Vector3d benchCenteredPos - ) { - } - - private static class CraftingJob { - @Nonnull - private final CraftingWindow window; - private final int transactionId; - @Nonnull - private final CraftingRecipe recipe; - private final int quantity; - private final float timeSeconds; - @Nonnull - private final ItemContainer inputItemContainer; - @Nonnull - private final CraftingManager.InputRemovalType inputRemovalType; - @Nonnull - private final Int2ObjectMap> removedItems = new Int2ObjectOpenHashMap<>(); - private int quantityStarted; - private int quantityCompleted; - private float timeSecondsCompleted; - - public CraftingJob( - @Nonnull CraftingWindow window, - int transactionId, - @Nonnull CraftingRecipe recipe, - int quantity, - float timeSeconds, - @Nonnull ItemContainer inputItemContainer, - @Nonnull CraftingManager.InputRemovalType inputRemovalType - ) { - this.window = window; - this.transactionId = transactionId; - this.recipe = recipe; - this.quantity = quantity; - this.timeSeconds = timeSeconds; - this.inputItemContainer = inputItemContainer; - this.inputRemovalType = inputRemovalType; - } - - @Nonnull - @Override - public String toString() { - return "CraftingJob{window=" - + this.window - + ", transactionId=" - + this.transactionId - + ", recipe=" - + this.recipe - + ", quantity=" - + this.quantity - + ", timeSeconds=" - + this.timeSeconds - + ", inputItemContainer=" - + this.inputItemContainer - + ", inputRemovalType=" - + this.inputRemovalType - + ", removedItems=" - + this.removedItems - + ", quantityStarted=" - + this.quantityStarted - + ", quantityCompleted=" - + this.quantityCompleted - + ", timeSecondsCompleted=" - + this.timeSecondsCompleted - + "}"; - } - } - - public static enum InputRemovalType { - NORMAL, - ORDERED; - - private InputRemovalType() { - } - } -} diff --git a/src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java b/src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java deleted file mode 100644 index d68c61e6..00000000 --- a/src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java +++ /dev/null @@ -1,840 +0,0 @@ -package com.hypixel.hytale.builtin.crafting.state; - -import com.google.common.flogger.LazyArgs; -import com.hypixel.hytale.builtin.crafting.CraftingPlugin; -import com.hypixel.hytale.builtin.crafting.component.CraftingManager; -import com.hypixel.hytale.builtin.crafting.window.ProcessingBenchWindow; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ArchetypeChunk; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.event.EventPriority; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.MathUtil; -import com.hypixel.hytale.math.vector.Vector3d; -import com.hypixel.hytale.math.vector.Vector3f; -import com.hypixel.hytale.math.vector.Vector3i; -import com.hypixel.hytale.protocol.SoundCategory; -import com.hypixel.hytale.protocol.Transform; -import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; -import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchTierLevel; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.ProcessingBench; -import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; -import com.hypixel.hytale.server.core.asset.type.item.config.Item; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager; -import com.hypixel.hytale.server.core.inventory.ItemStack; -import com.hypixel.hytale.server.core.inventory.MaterialQuantity; -import com.hypixel.hytale.server.core.inventory.ResourceQuantity; -import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; -import com.hypixel.hytale.server.core.inventory.container.InternalContainerUtilMaterial; -import com.hypixel.hytale.server.core.inventory.container.ItemContainer; -import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; -import com.hypixel.hytale.server.core.inventory.container.TestRemoveItemSlotResult; -import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; -import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; -import com.hypixel.hytale.server.core.inventory.container.filter.ResourceFilter; -import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; -import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; -import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction; -import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction; -import com.hypixel.hytale.server.core.inventory.transaction.ResourceTransaction; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; -import com.hypixel.hytale.server.core.universe.world.SoundUtil; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; -import com.hypixel.hytale.server.core.universe.world.chunk.state.TickableBlockState; -import com.hypixel.hytale.server.core.universe.world.meta.BlockState; -import com.hypixel.hytale.server.core.universe.world.meta.state.DestroyableBlockState; -import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerBlockState; -import com.hypixel.hytale.server.core.universe.world.meta.state.MarkerBlockState; -import com.hypixel.hytale.server.core.universe.world.meta.state.PlacedByBlockState; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; -import com.hypixel.hytale.server.core.util.PositionUtil; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.bson.BsonDocument; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.logging.Level; - -public class ProcessingBenchState - extends BenchState - implements TickableBlockState, - ItemContainerBlockState, - DestroyableBlockState, - MarkerBlockState, - PlacedByBlockState { - public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - public static final boolean EXACT_RESOURCE_AMOUNTS = true; - public static final Codec CODEC = BuilderCodec.builder(ProcessingBenchState.class, ProcessingBenchState::new, BenchState.CODEC) - .append(new KeyedCodec<>("InputContainer", ItemContainer.CODEC), (state, o) -> state.inputContainer = o, state -> state.inputContainer) - .add() - .append(new KeyedCodec<>("FuelContainer", ItemContainer.CODEC), (state, o) -> state.fuelContainer = o, state -> state.fuelContainer) - .add() - .append(new KeyedCodec<>("OutputContainer", ItemContainer.CODEC), (state, o) -> state.outputContainer = o, state -> state.outputContainer) - .add() - .append(new KeyedCodec<>("Progress", Codec.DOUBLE), (state, d) -> state.inputProgress = d.floatValue(), state -> (double) state.inputProgress) - .add() - .append(new KeyedCodec<>("FuelTime", Codec.DOUBLE), (state, d) -> state.fuelTime = d.floatValue(), state -> (double) state.fuelTime) - .add() - .append(new KeyedCodec<>("Active", Codec.BOOLEAN), (state, b) -> state.active = b, state -> state.active) - .add() - .append(new KeyedCodec<>("NextExtra", Codec.INTEGER), (state, b) -> state.nextExtra = b, state -> state.nextExtra) - .add() - .append(new KeyedCodec<>("Marker", WorldMapManager.MarkerReference.CODEC), (state, o) -> state.marker = o, state -> state.marker) - .add() - .append(new KeyedCodec<>("RecipeId", Codec.STRING), (state, o) -> state.recipeId = o, state -> state.recipeId) - .add() - .build(); - private static final float EJECT_VELOCITY = 2.0F; - private static final float EJECT_SPREAD_VELOCITY = 1.0F; - private static final float EJECT_VERTICAL_VELOCITY = 3.25F; - public static final String PROCESSING = "Processing"; - public static final String PROCESS_COMPLETED = "ProcessCompleted"; - protected WorldMapManager.MarkerReference marker; - private ProcessingBench processingBench; - private ItemContainer inputContainer; - private ItemContainer fuelContainer; - private ItemContainer outputContainer; - private CombinedItemContainer combinedItemContainer; - private float inputProgress; - private float fuelTime; - private int lastConsumedFuelTotal; - private int nextExtra = -1; - private final Set processingSlots = new HashSet<>(); - private final Set processingFuelSlots = new HashSet<>(); - @Nullable - private String recipeId; - @Nullable - private CraftingRecipe recipe; - private boolean active = false; - - public ProcessingBenchState() { - } - - @Override - public boolean initialize(@Nonnull BlockType blockType) { - if (!super.initialize(blockType)) { - if (this.bench == null) { - List itemStacks = new ObjectArrayList<>(); - if (this.inputContainer != null) { - itemStacks.addAll(this.inputContainer.dropAllItemStacks()); - } - - if (this.fuelContainer != null) { - itemStacks.addAll(this.fuelContainer.dropAllItemStacks()); - } - - if (this.outputContainer != null) { - itemStacks.addAll(this.outputContainer.dropAllItemStacks()); - } - - World world = this.getChunk().getWorld(); - Store store = world.getEntityStore().getStore(); - Holder[] itemEntityHolders = this.ejectItems(store, itemStacks); - if (itemEntityHolders.length > 0) { - world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN)); - } - } - - return false; - } else if (!(this.bench instanceof ProcessingBench)) { - LOGGER.at(Level.SEVERE).log("Wrong bench type for processing. Got %s", this.bench.getClass().getName()); - return false; - } else { - this.processingBench = (ProcessingBench) this.bench; - if (this.nextExtra == -1) { - this.nextExtra = this.processingBench.getExtraOutput() != null ? this.processingBench.getExtraOutput().getPerFuelItemsConsumed() : 0; - } - - this.setupSlots(); - return true; - } - } - - private void setupSlots() { - List remainder = new ObjectArrayList<>(); - int tierLevel = this.getTierLevel(); - ProcessingBench.ProcessingSlot[] input = this.processingBench.getInput(tierLevel); - short inputSlotsCount = (short) input.length; - this.inputContainer = ItemContainer.ensureContainerCapacity(this.inputContainer, inputSlotsCount, SimpleItemContainer::getNewContainer, remainder); - this.inputContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); - - for (short slot = 0; slot < inputSlotsCount; slot++) { - ProcessingBench.ProcessingSlot inputSlot = input[slot]; - String resourceTypeId = inputSlot.getResourceTypeId(); - boolean shouldFilterValidIngredients = inputSlot.shouldFilterValidIngredients(); - if (resourceTypeId != null) { - this.inputContainer.setSlotFilter(FilterActionType.ADD, slot, new ResourceFilter(new ResourceQuantity(resourceTypeId, 1))); - } else if (shouldFilterValidIngredients) { - ObjectArrayList validIngredients = new ObjectArrayList<>(); - - for (CraftingRecipe recipe : CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId())) { - if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), tierLevel)) { - List inputMaterials = CraftingManager.getInputMaterials(recipe); - validIngredients.addAll(inputMaterials); - } - } - - this.inputContainer.setSlotFilter(FilterActionType.ADD, slot, (actionType, container, slotIndex, itemStack) -> { - if (itemStack == null) { - return true; - } else { - for (MaterialQuantity ingredient : validIngredients) { - if (CraftingManager.matches(ingredient, itemStack)) { - return true; - } - } - - return false; - } - }); - } - } - - input = this.processingBench.getFuel(); - inputSlotsCount = (short) (input != null ? input.length : 0); - this.fuelContainer = ItemContainer.ensureContainerCapacity(this.fuelContainer, inputSlotsCount, SimpleItemContainer::getNewContainer, remainder); - this.fuelContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); - if (inputSlotsCount > 0) { - for (int i = 0; i < input.length; i++) { - ProcessingBench.ProcessingSlot fuel = input[i]; - String resourceTypeId = fuel.getResourceTypeId(); - if (resourceTypeId != null) { - this.fuelContainer.setSlotFilter(FilterActionType.ADD, (short) i, new ResourceFilter(new ResourceQuantity(resourceTypeId, 1))); - } - } - } - - short outputSlotsCount = (short) this.processingBench.getOutputSlotsCount(tierLevel); - this.outputContainer = ItemContainer.ensureContainerCapacity(this.outputContainer, outputSlotsCount, SimpleItemContainer::getNewContainer, remainder); - this.outputContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); - if (outputSlotsCount > 0) { - this.outputContainer.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); - } - - this.combinedItemContainer = new CombinedItemContainer(this.fuelContainer, this.inputContainer, this.outputContainer); - World world = this.getChunk().getWorld(); - Store store = world.getEntityStore().getStore(); - Holder[] itemEntityHolders = this.ejectItems(store, remainder); - if (itemEntityHolders.length > 0) { - world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN)); - } - - this.inputContainer.registerChangeEvent(EventPriority.LAST, event -> this.updateRecipe()); - if (this.processingBench.getFuel() == null) { - this.setActive(true); - } - } - - @Override - public void tick(float dt, int index, ArchetypeChunk archetypeChunk, @Nonnull Store store, CommandBuffer commandBuffer) { - World world = store.getExternalData().getWorld(); - Store entityStore = world.getEntityStore().getStore(); - BlockType blockType = this.getBlockType(); - String currentState = BlockAccessor.getCurrentInteractionState(blockType); - List outputItemStacks = null; - List inputMaterials = null; - this.processingSlots.clear(); - this.checkForRecipeUpdate(); - if (this.recipe != null) { - outputItemStacks = CraftingManager.getOutputItemStacks(this.recipe); - if (!this.outputContainer.canAddItemStacks(outputItemStacks, false, false)) { - if ("Processing".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - } else if ("ProcessCompleted".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); - } - - this.setActive(false); - return; - } - - inputMaterials = CraftingManager.getInputMaterials(this.recipe); - List result = this.inputContainer.getSlotMaterialsToRemove(inputMaterials, true, true); - if (result.isEmpty()) { - if ("Processing".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - } else if ("ProcessCompleted".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); - } - - this.inputProgress = 0.0F; - this.setActive(false); - this.recipeId = null; - this.recipe = null; - return; - } - - for (TestRemoveItemSlotResult item : result) { - this.processingSlots.addAll(item.getPickedSlots()); - } - - this.sendProcessingSlots(); - } else { - if (this.processingBench.getFuel() == null) { - if ("Processing".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - } else if ("ProcessCompleted".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); - } - - return; - } - - boolean allowNoInputProcessing = this.processingBench.shouldAllowNoInputProcessing(); - if (!allowNoInputProcessing && "Processing".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - } else if ("ProcessCompleted".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); - this.setActive(false); - this.sendProgress(0.0F); - return; - } - - this.sendProgress(0.0F); - if (!allowNoInputProcessing) { - this.setActive(false); - return; - } - } - - boolean needsUpdate = false; - if (this.fuelTime > 0.0F && this.active) { - this.fuelTime -= dt; - if (this.fuelTime < 0.0F) { - this.fuelTime = 0.0F; - } - - needsUpdate = true; - } - - ProcessingBench.ProcessingSlot[] fuelSlots = this.processingBench.getFuel(); - boolean hasFuelSlots = fuelSlots != null && fuelSlots.length > 0; - if ((this.processingBench.getMaxFuel() <= 0 || this.fuelTime < this.processingBench.getMaxFuel()) && !this.fuelContainer.isEmpty()) { - if (!hasFuelSlots) { - return; - } - - if (this.active) { - if (this.fuelTime > 0.0F) { - for (int i = 0; i < fuelSlots.length; i++) { - ItemStack itemInSlot = this.fuelContainer.getItemStack((short) i); - if (itemInSlot != null) { - this.processingFuelSlots.add((short) i); - break; - } - } - } else { - if (this.fuelTime < 0.0F) { - this.fuelTime = 0.0F; - } - - this.processingFuelSlots.clear(); - - for (int ix = 0; ix < fuelSlots.length; ix++) { - ProcessingBench.ProcessingSlot fuelSlot = fuelSlots[ix]; - String resourceTypeId = fuelSlot.getResourceTypeId() != null ? fuelSlot.getResourceTypeId() : "Fuel"; - ResourceQuantity resourceQuantity = new ResourceQuantity(resourceTypeId, 1); - ItemStack slot = this.fuelContainer.getItemStack((short) ix); - if (slot != null) { - double fuelQuality = slot.getItem().getFuelQuality(); - ResourceTransaction transaction = this.fuelContainer.removeResource(resourceQuantity, true, true, true); - this.processingFuelSlots.add((short) ix); - if (transaction.getRemainder() <= 0) { - ProcessingBench.ExtraOutput extra = this.processingBench.getExtraOutput(); - if (extra != null && !extra.isIgnoredFuelSource(slot.getItem())) { - this.nextExtra--; - if (this.nextExtra <= 0) { - this.nextExtra = extra.getPerFuelItemsConsumed(); - ObjectArrayList extraItemStacks = new ObjectArrayList<>(extra.getOutputs().length); - - for (MaterialQuantity e : extra.getOutputs()) { - extraItemStacks.add(e.toItemStack()); - } - - ListTransaction addTransaction = this.outputContainer.addItemStacks(extraItemStacks, false, false, false); - List remainderItems = new ObjectArrayList<>(); - - for (ItemStackTransaction itemStackTransaction : addTransaction.getList()) { - ItemStack remainder = itemStackTransaction.getRemainder(); - if (remainder != null && !remainder.isEmpty()) { - remainderItems.add(remainder); - } - } - - if (!remainderItems.isEmpty()) { - LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition()); - Holder[] itemEntityHolders = this.ejectItems(entityStore, remainderItems); - entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); - } - } - } - - this.fuelTime = (float) (this.fuelTime + transaction.getConsumed() * fuelQuality); - needsUpdate = true; - break; - } - } - } - } - } - } - - if (needsUpdate) { - this.updateFuelValues(); - } - - if (!hasFuelSlots || this.active && !(this.fuelTime <= 0.0F)) { - if (!"Processing".equals(currentState)) { - this.setBlockInteractionState("Processing", blockType); - } - - if (this.recipe != null && (this.fuelTime > 0.0F || this.processingBench.getFuel() == null)) { - this.inputProgress += dt; - } - - if (this.recipe != null) { - float recipeTime = this.recipe.getTimeSeconds(); - float craftingTimeReductionModifier = this.getCraftingTimeReductionModifier(); - if (craftingTimeReductionModifier > 0.0F) { - recipeTime -= recipeTime * craftingTimeReductionModifier; - } - - if (this.inputProgress > recipeTime) { - if (recipeTime > 0.0F) { - this.inputProgress -= recipeTime; - float progressPercent = this.inputProgress / recipeTime; - this.sendProgress(progressPercent); - } else { - this.inputProgress = 0.0F; - this.sendProgress(0.0F); - } - - LOGGER.at(Level.FINE).log("Do Process for %s %s", this.recipeId, this.recipe); - if (inputMaterials != null) { - List remainderItems = new ObjectArrayList<>(); - int success = 0; - IntArrayList slots = new IntArrayList(); - - for (int j = 0; j < this.inputContainer.getCapacity(); j++) { - slots.add(j); - } - - for (MaterialQuantity material : inputMaterials) { - for (int ixx = 0; ixx < slots.size(); ixx++) { - int slot = slots.getInt(ixx); - MaterialSlotTransaction transaction = this.inputContainer.removeMaterialFromSlot((short) slot, material, true, true, true); - if (transaction.succeeded()) { - success++; - slots.removeInt(ixx); - break; - } - } - } - - ListTransaction addTransaction = this.outputContainer.addItemStacks(outputItemStacks, false, false, false); - if (!addTransaction.succeeded()) { - return; - } - - for (ItemStackTransaction itemStackTransactionx : addTransaction.getList()) { - ItemStack remainder = itemStackTransactionx.getRemainder(); - if (remainder != null && !remainder.isEmpty()) { - remainderItems.add(remainder); - } - } - - if (success == inputMaterials.size()) { - this.setBlockInteractionState("ProcessCompleted", blockType); - this.playSound(world, this.bench.getCompletedSoundEventIndex(), entityStore); - if (!remainderItems.isEmpty()) { - LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition()); - Holder[] itemEntityHolders = this.ejectItems(entityStore, remainderItems); - entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); - } - - return; - } - } - - List remainderItems = new ObjectArrayList<>(); - ListTransaction transaction = this.inputContainer.removeMaterials(inputMaterials, true, true, true); - if (!transaction.succeeded()) { - LOGGER.at(Level.WARNING).log("Failed to remove input materials at %s", this.getBlockPosition()); - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - return; - } - - this.setBlockInteractionState("ProcessCompleted", blockType); - this.playSound(world, this.bench.getCompletedSoundEventIndex(), entityStore); - ListTransaction addTransactionx = this.outputContainer.addItemStacks(outputItemStacks, false, false, false); - if (addTransactionx.succeeded()) { - return; - } - - LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition()); - - for (ItemStackTransaction itemStackTransactionxx : addTransactionx.getList()) { - ItemStack remainder = itemStackTransactionxx.getRemainder(); - if (remainder != null && !remainder.isEmpty()) { - remainderItems.add(remainder); - } - } - - Holder[] itemEntityHolders = this.ejectItems(entityStore, remainderItems); - entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); - } else if (this.recipe != null && recipeTime > 0.0F) { - float progressPercent = this.inputProgress / recipeTime; - this.sendProgress(progressPercent); - } else { - this.sendProgress(0.0F); - } - } - } else { - this.lastConsumedFuelTotal = 0; - if ("Processing".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - if (this.processingBench.getFuel() != null) { - this.setActive(false); - } - } else if ("ProcessCompleted".equals(currentState)) { - this.setBlockInteractionState("default", blockType); - this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); - if (this.processingBench.getFuel() != null) { - this.setActive(false); - } - } - } - } - - private float getCraftingTimeReductionModifier() { - BenchTierLevel levelData = this.bench.getTierLevel(this.getTierLevel()); - return levelData != null ? levelData.getCraftingTimeReductionModifier() : 0.0F; - } - - @Nonnull - private Holder[] ejectItems(@Nonnull ComponentAccessor accessor, @Nonnull List itemStacks) { - if (itemStacks.isEmpty()) { - return Holder.emptyArray(); - } else { - RotationTuple rotation = RotationTuple.get(this.getRotationIndex()); - Vector3d frontDir = new Vector3d(0.0, 0.0, 1.0); - rotation.yaw().rotateY(frontDir, frontDir); - BlockType blockType = this.getBlockType(); - Vector3d dropPosition; - if (blockType == null) { - dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); - } else { - BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); - if (hitboxAsset == null) { - dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); - } else { - double depth = hitboxAsset.get(0).getBoundingBox().depth(); - double frontOffset = depth / 2.0 + 0.1F; - dropPosition = this.getCenteredBlockPosition(); - dropPosition.add(frontDir.x * frontOffset, 0.0, frontDir.z * frontOffset); - } - } - - ThreadLocalRandom random = ThreadLocalRandom.current(); - ObjectArrayList> result = new ObjectArrayList<>(itemStacks.size()); - - for (ItemStack item : itemStacks) { - float velocityX = (float) (frontDir.x * 2.0 + 2.0 * (random.nextDouble() - 0.5)); - float velocityZ = (float) (frontDir.z * 2.0 + 2.0 * (random.nextDouble() - 0.5)); - Holder holder = ItemComponent.generateItemDrop(accessor, item, dropPosition, Vector3f.ZERO, velocityX, 3.25F, velocityZ); - if (holder != null) { - result.add(holder); - } - } - - return result.toArray(Holder[]::new); - } - } - - private void sendProgress(float progress) { - this.windows.forEach((uuid, window) -> ((ProcessingBenchWindow) window).setProgress(progress)); - } - - private void sendProcessingSlots() { - this.windows.forEach((uuid, window) -> ((ProcessingBenchWindow) window).setProcessingSlots(this.processingSlots)); - } - - private void sendProcessingFuelSlots() { - this.windows.forEach((uuid, window) -> ((ProcessingBenchWindow) window).setProcessingFuelSlots(this.processingFuelSlots)); - } - - public boolean isActive() { - return this.active; - } - - public boolean setActive(boolean active) { - if (this.active != active) { - if (active && this.processingBench.getFuel() != null && this.fuelContainer.isEmpty()) { - return false; - } else { - this.active = active; - if (!active) { - this.processingSlots.clear(); - this.processingFuelSlots.clear(); - this.sendProcessingSlots(); - this.sendProcessingFuelSlots(); - } - - this.updateRecipe(); - this.windows.forEach((uuid, window) -> ((ProcessingBenchWindow) window).setActive(active)); - this.markNeedsSave(); - return true; - } - } else { - return false; - } - } - - public void updateFuelValues() { - if (this.fuelTime > this.lastConsumedFuelTotal) { - this.lastConsumedFuelTotal = MathUtil.ceil(this.fuelTime); - } - - float fuelPercent = this.lastConsumedFuelTotal > 0 ? this.fuelTime / this.lastConsumedFuelTotal : 0.0F; - this.windows.forEach((uuid, window) -> { - ProcessingBenchWindow processingBenchWindow = (ProcessingBenchWindow) window; - processingBenchWindow.setFuelTime(fuelPercent); - processingBenchWindow.setMaxFuel(this.lastConsumedFuelTotal); - processingBenchWindow.setProcessingFuelSlots(this.processingFuelSlots); - }); - } - - @Override - public void onDestroy() { - super.onDestroy(); - // HyFix: Clear windows map before closeAndRemoveAll to prevent NPE cascade - // When a player breaks a bench while another player has it open, the close handlers - // try to access block data that is already being destroyed, causing NPE in - // BlockType.getDefaultStateKey() and BenchWindow.onClose0() - if (this.windows != null) { - this.windows.clear(); - } - WindowManager.closeAndRemoveAll(this.windows); - if (this.combinedItemContainer != null) { - List itemStacks = this.combinedItemContainer.dropAllItemStacks(); - this.dropFuelItems(itemStacks); - World world = this.getChunk().getWorld(); - Store entityStore = world.getEntityStore().getStore(); - Vector3d dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); - Holder[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO); - if (itemEntityHolders.length > 0) { - world.execute(() -> entityStore.addEntities(itemEntityHolders, AddReason.SPAWN)); - } - } - - if (this.marker != null) { - this.marker.remove(); - } - } - - public CombinedItemContainer getItemContainer() { - return this.combinedItemContainer; - } - - private void checkForRecipeUpdate() { - if (this.recipe == null && this.recipeId != null) { - this.updateRecipe(); - } - } - - private void updateRecipe() { - List recipes = CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId()); - if (recipes.isEmpty()) { - this.clearRecipe(); - } else { - List matching = new ObjectArrayList<>(); - - for (CraftingRecipe recipe : recipes) { - if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), this.getTierLevel())) { - MaterialQuantity[] input = recipe.getInput(); - int matches = 0; - IntArrayList slots = new IntArrayList(); - - for (int j = 0; j < this.inputContainer.getCapacity(); j++) { - slots.add(j); - } - - for (MaterialQuantity craftingMaterial : input) { - String itemId = craftingMaterial.getItemId(); - String resourceTypeId = craftingMaterial.getResourceTypeId(); - int materialQuantity = craftingMaterial.getQuantity(); - BsonDocument metadata = craftingMaterial.getMetadata(); - MaterialQuantity material = new MaterialQuantity(itemId, resourceTypeId, null, materialQuantity, metadata); - - for (int k = 0; k < slots.size(); k++) { - int j = slots.getInt(k); - int out = InternalContainerUtilMaterial.testRemoveMaterialFromSlot(this.inputContainer, (short) j, material, material.getQuantity(), true); - if (out == 0) { - matches++; - slots.removeInt(k); - break; - } - } - } - - if (matches == input.length) { - matching.add(recipe); - } - } - } - - if (matching.isEmpty()) { - this.clearRecipe(); - } else { - matching.sort(Comparator.comparingInt(o -> CraftingManager.getInputMaterials(o).size())); - Collections.reverse(matching); - if (this.recipeId != null) { - for (CraftingRecipe rec : matching) { - if (Objects.equals(this.recipeId, rec.getId())) { - LOGGER.at(Level.FINE).log("%s - Keeping existing Recipe %s %s", LazyArgs.lazy(this::getBlockPosition), this.recipeId, rec); - this.recipe = rec; - return; - } - } - } - - CraftingRecipe recipex = matching.getFirst(); - if (this.recipeId == null || !Objects.equals(this.recipeId, recipex.getId())) { - this.inputProgress = 0.0F; - this.sendProgress(0.0F); - } - - this.recipeId = recipex.getId(); - this.recipe = recipex; - LOGGER.at(Level.FINE).log("%s - Found Recipe %s %s", LazyArgs.lazy(this::getBlockPosition), this.recipeId, this.recipe); - } - } - } - - private void clearRecipe() { - this.recipeId = null; - this.recipe = null; - this.lastConsumedFuelTotal = 0; - this.inputProgress = 0.0F; - this.sendProgress(0.0F); - LOGGER.at(Level.FINE).log("%s - Cleared Recipe", LazyArgs.lazy(this::getBlockPosition)); - } - - public void dropFuelItems(@Nonnull List itemStacks) { - String fuelDropItemId = this.processingBench.getFuelDropItemId(); - if (fuelDropItemId != null) { - Item item = Item.getAssetMap().getAsset(fuelDropItemId); - int dropAmount = (int) this.fuelTime; - this.fuelTime = 0.0F; - - while (dropAmount > 0) { - int quantity = Math.min(dropAmount, item.getMaxStack()); - itemStacks.add(new ItemStack(fuelDropItemId, quantity)); - dropAmount -= quantity; - } - } else { - LOGGER.at(Level.WARNING).log("No FuelDropItemId defined for %s fuel value of %s will be lost!", this.bench.getId(), this.fuelTime); - } - } - - @Nullable - public CraftingRecipe getRecipe() { - return this.recipe; - } - - public float getInputProgress() { - return this.inputProgress; - } - - public void onItemChange(ItemContainer.ItemContainerChangeEvent event) { - this.markNeedsSave(); - } - - public void setBlockInteractionState(@Nonnull String state, @Nonnull BlockType blockType) { - this.getChunk().setBlockInteractionState(this.getBlockPosition(), blockType, state); - } - - @Override - public void setMarker(WorldMapManager.MarkerReference marker) { - this.marker = marker; - this.markNeedsSave(); - } - - @Override - public void placedBy( - @Nonnull Ref playerRef, - @Nonnull String blockTypeKey, - @Nonnull BlockState blockState, - @Nonnull ComponentAccessor componentAccessor - ) { - if (blockTypeKey.equals(this.processingBench.getIconItem()) && this.processingBench.getIcon() != null) { - Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); - - assert playerComponent != null; - - TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType()); - - assert transformComponent != null; - - Transform transformPacket = PositionUtil.toTransformPacket(transformComponent.getTransform()); - transformPacket.orientation.yaw = 0.0F; - transformPacket.orientation.pitch = 0.0F; - transformPacket.orientation.roll = 0.0F; - MapMarker marker = new MapMarker( - this.processingBench.getIconId() + "-" + UUID.randomUUID(), - this.processingBench.getIconName(), - this.processingBench.getIcon(), - transformPacket, - null - ); - ((MarkerBlockState) blockState).setMarker(WorldMapManager.createPlayerMarker(playerRef, marker, componentAccessor)); - } - } - - private void playSound(@Nonnull World world, int soundEventIndex, @Nonnull ComponentAccessor componentAccessor) { - if (soundEventIndex != 0) { - Vector3i pos = this.getBlockPosition(); - SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, componentAccessor); - } - } - - @Override - protected void onTierLevelChange() { - super.onTierLevelChange(); - this.setupSlots(); - } -} diff --git a/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java b/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java deleted file mode 100644 index 9a5d16d8..00000000 --- a/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java +++ /dev/null @@ -1,414 +0,0 @@ -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 javax.annotation.Nonnull; -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; - -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/builtin/instances/InstancesPlugin.java b/src/com/hypixel/hytale/builtin/instances/InstancesPlugin.java deleted file mode 100644 index f7e3c551..00000000 --- a/src/com/hypixel/hytale/builtin/instances/InstancesPlugin.java +++ /dev/null @@ -1,644 +0,0 @@ -package com.hypixel.hytale.builtin.instances; - -import com.hypixel.hytale.assetstore.AssetPack; -import com.hypixel.hytale.builtin.blockphysics.WorldValidationUtil; -import com.hypixel.hytale.builtin.instances.blocks.ConfigurableInstanceBlock; -import com.hypixel.hytale.builtin.instances.blocks.InstanceBlock; -import com.hypixel.hytale.builtin.instances.command.InstancesCommand; -import com.hypixel.hytale.builtin.instances.config.ExitInstance; -import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig; -import com.hypixel.hytale.builtin.instances.config.InstanceEntityConfig; -import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; -import com.hypixel.hytale.builtin.instances.config.WorldReturnPoint; -import com.hypixel.hytale.builtin.instances.event.DiscoverInstanceEvent; -import com.hypixel.hytale.builtin.instances.interactions.ExitInstanceInteraction; -import com.hypixel.hytale.builtin.instances.interactions.TeleportConfigInstanceInteraction; -import com.hypixel.hytale.builtin.instances.interactions.TeleportInstanceInteraction; -import com.hypixel.hytale.builtin.instances.page.ConfigureInstanceBlockPage; -import com.hypixel.hytale.builtin.instances.removal.IdleTimeoutCondition; -import com.hypixel.hytale.builtin.instances.removal.InstanceDataResource; -import com.hypixel.hytale.builtin.instances.removal.RemovalCondition; -import com.hypixel.hytale.builtin.instances.removal.RemovalSystem; -import com.hypixel.hytale.builtin.instances.removal.TimeoutCondition; -import com.hypixel.hytale.builtin.instances.removal.WorldEmptyCondition; -import com.hypixel.hytale.codec.schema.config.ObjectSchema; -import com.hypixel.hytale.codec.schema.config.Schema; -import com.hypixel.hytale.codec.schema.config.StringSchema; -import com.hypixel.hytale.common.util.FormatUtil; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.event.EventRegistry; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.vector.Transform; -import com.hypixel.hytale.math.vector.Vector3f; -import com.hypixel.hytale.protocol.GameMode; -import com.hypixel.hytale.protocol.SoundCategory; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.Options; -import com.hypixel.hytale.server.core.asset.AssetModule; -import com.hypixel.hytale.server.core.asset.GenerateSchemaEvent; -import com.hypixel.hytale.server.core.asset.LoadAssetEvent; -import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController; -import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; -import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; -import com.hypixel.hytale.server.core.event.events.player.DrainPlayerFromWorldEvent; -import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; -import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; -import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; -import com.hypixel.hytale.server.core.plugin.JavaPlugin; -import com.hypixel.hytale.server.core.plugin.JavaPluginInit; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.Universe; -import com.hypixel.hytale.server.core.universe.world.SoundUtil; -import com.hypixel.hytale.server.core.universe.world.ValidationOption; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.WorldConfig; -import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.provider.EmptyChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.provider.MigrationChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.resources.EmptyResourceStorageProvider; -import com.hypixel.hytale.server.core.util.EventTitleUtil; -import com.hypixel.hytale.server.core.util.io.FileUtil; -import com.hypixel.hytale.sneakythrow.SneakyThrow; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.stream.Stream; - -public class InstancesPlugin extends JavaPlugin { - private static InstancesPlugin instance; - @Nonnull - public static final String INSTANCE_PREFIX = "instance-"; - @Nonnull - public static final String CONFIG_FILENAME = "instance.bson"; - private ResourceType instanceDataResourceType; - private ComponentType instanceEntityConfigComponentType; - private ComponentType instanceBlockComponentType; - private ComponentType configurableInstanceBlockComponentType; - - public static InstancesPlugin get() { - return instance; - } - - public InstancesPlugin(@Nonnull JavaPluginInit init) { - super(init); - instance = this; - } - - @Override - protected void setup() { - EventRegistry eventRegistry = this.getEventRegistry(); - ComponentRegistryProxy chunkStoreRegistry = this.getChunkStoreRegistry(); - this.getCommandRegistry().registerCommand(new InstancesCommand()); - eventRegistry.register((short) 64, LoadAssetEvent.class, this::validateInstanceAssets); - eventRegistry.register(GenerateSchemaEvent.class, InstancesPlugin::generateSchema); - eventRegistry.registerGlobal(AddPlayerToWorldEvent.class, InstancesPlugin::onPlayerAddToWorld); - eventRegistry.registerGlobal(DrainPlayerFromWorldEvent.class, InstancesPlugin::onPlayerDrainFromWorld); - eventRegistry.register(PlayerConnectEvent.class, InstancesPlugin::onPlayerConnect); - eventRegistry.registerGlobal(PlayerReadyEvent.class, InstancesPlugin::onPlayerReady); - this.instanceBlockComponentType = chunkStoreRegistry.registerComponent(InstanceBlock.class, "Instance", InstanceBlock.CODEC); - chunkStoreRegistry.registerSystem(new InstanceBlock.OnRemove()); - this.configurableInstanceBlockComponentType = chunkStoreRegistry.registerComponent( - ConfigurableInstanceBlock.class, "InstanceConfig", ConfigurableInstanceBlock.CODEC - ); - chunkStoreRegistry.registerSystem(new ConfigurableInstanceBlock.OnRemove()); - this.instanceDataResourceType = chunkStoreRegistry.registerResource(InstanceDataResource.class, "InstanceData", InstanceDataResource.CODEC); - chunkStoreRegistry.registerSystem(new RemovalSystem()); - this.instanceEntityConfigComponentType = this.getEntityStoreRegistry() - .registerComponent(InstanceEntityConfig.class, "Instance", InstanceEntityConfig.CODEC); - this.getCodecRegistry(RemovalCondition.CODEC) - .register("WorldEmpty", WorldEmptyCondition.class, WorldEmptyCondition.CODEC) - .register("IdleTimeout", IdleTimeoutCondition.class, IdleTimeoutCondition.CODEC) - .register("Timeout", TimeoutCondition.class, TimeoutCondition.CODEC); - this.getCodecRegistry(Interaction.CODEC) - .register("TeleportInstance", TeleportInstanceInteraction.class, TeleportInstanceInteraction.CODEC) - .register("TeleportConfigInstance", TeleportConfigInstanceInteraction.class, TeleportConfigInstanceInteraction.CODEC) - .register("ExitInstance", ExitInstanceInteraction.class, ExitInstanceInteraction.CODEC); - this.getCodecRegistry(RespawnController.CODEC).register("ExitInstance", ExitInstance.class, ExitInstance.CODEC); - OpenCustomUIInteraction.registerBlockEntityCustomPage( - this, ConfigureInstanceBlockPage.class, "ConfigInstanceBlock", ConfigureInstanceBlockPage::new, () -> { - Holder holder = ChunkStore.REGISTRY.newHolder(); - holder.ensureComponent(ConfigurableInstanceBlock.getComponentType()); - return holder; - } - ); - this.getCodecRegistry(WorldConfig.PLUGIN_CODEC).register(InstanceWorldConfig.class, "Instance", InstanceWorldConfig.CODEC); - } - - @Nonnull - public CompletableFuture spawnInstance(@Nonnull String name, @Nonnull World forWorld, @Nonnull Transform returnPoint) { - return this.spawnInstance(name, null, forWorld, returnPoint); - } - - @Nonnull - public CompletableFuture spawnInstance(@Nonnull String name, @Nullable String worldName, @Nonnull World forWorld, @Nonnull Transform returnPoint) { - Universe universe = Universe.get(); - Path path = universe.getPath(); - Path assetPath = getInstanceAssetPath(name); - UUID uuid = UUID.randomUUID(); - String worldKey = worldName; - if (worldName == null) { - worldKey = "instance-" + safeName(name) + "-" + uuid; - } - - Path worldPath = path.resolve("worlds").resolve(worldKey); - String finalWorldKey = worldKey; - return WorldConfig.load(assetPath.resolve("instance.bson")) - .thenApplyAsync( - SneakyThrow.sneakyFunction( - config -> { - config.setUuid(uuid); - config.setDisplayName(WorldConfig.formatDisplayName(name)); - InstanceWorldConfig instanceConfig = InstanceWorldConfig.ensureAndGet(config); - instanceConfig.setReturnPoint( - new WorldReturnPoint(forWorld.getWorldConfig().getUuid(), returnPoint, instanceConfig.shouldPreventReconnection()) - ); - config.markChanged(); - long start = System.nanoTime(); - this.getLogger().at(Level.INFO).log("Copying instance files for %s to world %s", name, finalWorldKey); - - try (Stream files = Files.walk(assetPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { - files.forEach(SneakyThrow.sneakyConsumer(filePath -> { - Path rel = assetPath.relativize(filePath); - Path toPath = worldPath.resolve(rel.toString()); - if (Files.isDirectory(filePath)) { - Files.createDirectories(toPath); - } else { - if (Files.isRegularFile(filePath)) { - Files.copy(filePath, toPath); - } - } - })); - } - - this.getLogger() - .at(Level.INFO) - .log("Completed instance files for %s to world %s in %s", name, finalWorldKey, FormatUtil.nanosToString(System.nanoTime() - start)); - return config; - } - ) - ) - .thenCompose(config -> universe.makeWorld(finalWorldKey, worldPath, config)); - } - - public static void teleportPlayerToLoadingInstance( - @Nonnull Ref entityRef, - @Nonnull ComponentAccessor componentAccessor, - @Nonnull CompletableFuture worldFuture, - @Nullable Transform overrideReturn - ) { - World originalWorld = componentAccessor.getExternalData().getWorld(); - TransformComponent transformComponent = componentAccessor.getComponent(entityRef, TransformComponent.getComponentType()); - - assert transformComponent != null; - - Transform originalPosition = transformComponent.getTransform().clone(); - InstanceEntityConfig instanceEntityConfigComponent = componentAccessor.getComponent(entityRef, InstanceEntityConfig.getComponentType()); - if (instanceEntityConfigComponent == null) { - instanceEntityConfigComponent = componentAccessor.addComponent(entityRef, InstanceEntityConfig.getComponentType()); - } - - if (overrideReturn != null) { - instanceEntityConfigComponent.setReturnPointOverride(new WorldReturnPoint(originalWorld.getWorldConfig().getUuid(), overrideReturn, false)); - } else { - instanceEntityConfigComponent.setReturnPointOverride(null); - } - - PlayerRef playerRefComponent = componentAccessor.getComponent(entityRef, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - UUIDComponent uuidComponent = componentAccessor.getComponent(entityRef, UUIDComponent.getComponentType()); - - assert uuidComponent != null; - - UUID playerUUID = uuidComponent.getUuid(); - InstanceEntityConfig finalPlayerConfig = instanceEntityConfigComponent; - CompletableFuture.runAsync(playerRefComponent::removeFromStore, originalWorld) - .thenCombine(worldFuture.orTimeout(1L, TimeUnit.MINUTES), (ignored, world) -> (World) world) - .thenCompose(world -> { - ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); - Transform spawnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(world, playerUUID) : null; - return world.addPlayer(playerRefComponent, spawnPoint, Boolean.TRUE, Boolean.FALSE); - }) - .whenComplete((ret, ex) -> { - if (ex != null) { - get().getLogger().at(Level.SEVERE).withCause(ex).log("Failed to send %s to instance world", playerRefComponent.getUsername()); - finalPlayerConfig.setReturnPointOverride(null); - } - - if (ret == null) { - if (originalWorld.isAlive()) { - originalWorld.addPlayer(playerRefComponent, originalPosition, Boolean.TRUE, Boolean.FALSE); - } else { - World defaultWorld = Universe.get().getDefaultWorld(); - if (defaultWorld != null) { - defaultWorld.addPlayer(playerRefComponent, null, Boolean.TRUE, Boolean.FALSE); - } else { - get().getLogger().at(Level.SEVERE).log("No fallback world for %s, disconnecting", playerRefComponent.getUsername()); - playerRefComponent.getPacketHandler().disconnect("Failed to teleport - no world available"); - } - } - } - }); - } - - public static void teleportPlayerToInstance( - @Nonnull Ref playerRef, - @Nonnull ComponentAccessor componentAccessor, - @Nonnull World targetWorld, - @Nullable Transform overrideReturn - ) { - World originalWorld = componentAccessor.getExternalData().getWorld(); - WorldConfig originalWorldConfig = originalWorld.getWorldConfig(); - if (overrideReturn != null) { - InstanceEntityConfig instanceConfig = componentAccessor.ensureAndGetComponent(playerRef, InstanceEntityConfig.getComponentType()); - instanceConfig.setReturnPointOverride(new WorldReturnPoint(originalWorldConfig.getUuid(), overrideReturn, false)); - } - - UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType()); - - assert uuidComponent != null; - - UUID playerUUID = uuidComponent.getUuid(); - WorldConfig targetWorldConfig = targetWorld.getWorldConfig(); - ISpawnProvider spawnProvider = targetWorldConfig.getSpawnProvider(); - if (spawnProvider == null) { - throw new IllegalStateException("Spawn provider cannot be null when teleporting player to instance!"); - } else { - Transform spawnTransform = spawnProvider.getSpawnPoint(targetWorld, playerUUID); - Teleport teleportComponent = Teleport.createForPlayer(targetWorld, spawnTransform); - componentAccessor.addComponent(playerRef, Teleport.getComponentType(), teleportComponent); - } - } - - public static void exitInstance(@Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { - World world = componentAccessor.getExternalData().getWorld(); - InstanceEntityConfig entityConfig = componentAccessor.getComponent(targetRef, InstanceEntityConfig.getComponentType()); - WorldReturnPoint returnPoint = entityConfig != null ? entityConfig.getReturnPoint() : null; - if (returnPoint == null) { - WorldConfig config = world.getWorldConfig(); - InstanceWorldConfig instanceConfig = InstanceWorldConfig.get(config); - returnPoint = instanceConfig != null ? instanceConfig.getReturnPoint() : null; - if (returnPoint == null) { - throw new IllegalArgumentException("Player is not in an instance"); - } - } - - Universe universe = Universe.get(); - World targetWorld = universe.getWorld(returnPoint.getWorld()); - if (targetWorld == null) { - throw new IllegalArgumentException("Missing return world"); - } else { - Teleport teleportComponent = Teleport.createForPlayer(targetWorld, returnPoint.getReturnPoint()); - componentAccessor.addComponent(targetRef, Teleport.getComponentType(), teleportComponent); - } - } - - public static void safeRemoveInstance(@Nonnull String worldName) { - safeRemoveInstance(Universe.get().getWorld(worldName)); - } - - public static void safeRemoveInstance(@Nonnull UUID worldUUID) { - safeRemoveInstance(Universe.get().getWorld(worldUUID)); - } - - public static void safeRemoveInstance(@Nullable World instanceWorld) { - if (instanceWorld != null) { - Store chunkStore = instanceWorld.getChunkStore().getStore(); - chunkStore.getResource(InstanceDataResource.getResourceType()).setHadPlayer(true); - WorldConfig config = instanceWorld.getWorldConfig(); - InstanceWorldConfig instanceConfig = InstanceWorldConfig.get(config); - if (instanceConfig != null) { - instanceConfig.setRemovalConditions(WorldEmptyCondition.REMOVE_WHEN_EMPTY); - } - - config.markChanged(); - } - } - - @Nonnull - public static Path getInstanceAssetPath(@Nonnull String name) { - for (AssetPack pack : AssetModule.get().getAssetPacks()) { - Path path = pack.getRoot().resolve("Server").resolve("Instances").resolve(name); - if (Files.exists(path)) { - return path; - } - } - - return AssetModule.get().getBaseAssetPack().getRoot().resolve("Server").resolve("Instances").resolve(name); - } - - public static boolean doesInstanceAssetExist(@Nonnull String name) { - return Files.exists(getInstanceAssetPath(name).resolve("instance.bson")); - } - - @Nonnull - public static CompletableFuture loadInstanceAssetForEdit(@Nonnull String name) { - Path path = getInstanceAssetPath(name); - Universe universe = Universe.get(); - return WorldConfig.load(path.resolve("instance.bson")).thenCompose(config -> { - config.setUuid(UUID.randomUUID()); - config.setSavingPlayers(false); - config.setIsAllNPCFrozen(true); - config.setTicking(false); - config.setGameMode(GameMode.Creative); - config.setDeleteOnRemove(false); - InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY); - config.markChanged(); - String worldName = "instance-edit-" + safeName(name); - return universe.makeWorld(worldName, path, config); - }); - } - - @Nonnull - public List getInstanceAssets() { - final List instances = new ObjectArrayList<>(); - - for (AssetPack pack : AssetModule.get().getAssetPacks()) { - final Path path = pack.getRoot().resolve("Server").resolve("Instances"); - if (Files.isDirectory(path)) { - try { - Files.walkFileTree(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { - @Nonnull - public FileVisitResult preVisitDirectory(@Nonnull Path dir, @Nonnull BasicFileAttributes attrs) { - if (Files.exists(dir.resolve("instance.bson"))) { - Path relative = path.relativize(dir); - String name = relative.toString(); - instances.add(name); - return FileVisitResult.SKIP_SUBTREE; - } else { - return FileVisitResult.CONTINUE; - } - } - }); - } catch (IOException var6) { - throw SneakyThrow.sneakyThrow(var6); - } - } - } - - return instances; - } - - private static void onPlayerConnect(@Nonnull PlayerConnectEvent event) { - Holder holder = event.getHolder(); - Player playerComponent = holder.getComponent(Player.getComponentType()); - - assert playerComponent != null; - - PlayerConfigData playerConfig = playerComponent.getPlayerConfigData(); - InstanceEntityConfig config = InstanceEntityConfig.ensureAndGet(holder); - String lastWorldName = playerConfig.getWorld(); - World lastWorld = Universe.get().getWorld(lastWorldName); - WorldReturnPoint fallbackWorld = config.getReturnPoint(); - if (fallbackWorld != null && (lastWorld == null || fallbackWorld.isReturnOnReconnect())) { - lastWorld = Universe.get().getWorld(fallbackWorld.getWorld()); - if (lastWorld != null) { - Transform transform = fallbackWorld.getReturnPoint(); - TransformComponent transformComponent = holder.ensureAndGetComponent(TransformComponent.getComponentType()); - transformComponent.setPosition(transform.getPosition()); - Vector3f rotationClone = transformComponent.getRotation().clone(); - rotationClone.setYaw(transform.getRotation().getYaw()); - transformComponent.setRotation(rotationClone); - HeadRotation headRotationComponent = holder.ensureAndGetComponent(HeadRotation.getComponentType()); - headRotationComponent.teleportRotation(transform.getRotation()); - } - } else if (lastWorld != null) { - config.setReturnPointOverride(config.getReturnPoint()); - } - } - - private static void onPlayerAddToWorld(@Nonnull AddPlayerToWorldEvent event) { - Holder holder = event.getHolder(); - InstanceWorldConfig worldConfig = InstanceWorldConfig.get(event.getWorld().getWorldConfig()); - if (worldConfig == null) { - InstanceEntityConfig entityConfig = holder.getComponent(InstanceEntityConfig.getComponentType()); - if (entityConfig != null && entityConfig.getReturnPoint() != null) { - entityConfig.setReturnPoint(null); - } - } else { - InstanceEntityConfig entityConfig = InstanceEntityConfig.ensureAndGet(holder); - if (entityConfig.getReturnPointOverride() == null) { - entityConfig.setReturnPoint(worldConfig.getReturnPoint()); - } else { - WorldReturnPoint override = entityConfig.getReturnPointOverride(); - override.setReturnOnReconnect(worldConfig.shouldPreventReconnection()); - entityConfig.setReturnPoint(override); - entityConfig.setReturnPointOverride(null); - } - } - } - - private static void onPlayerReady(@Nonnull PlayerReadyEvent event) { - Player player = event.getPlayer(); - World world = player.getWorld(); - if (world != null) { - WorldConfig worldConfig = world.getWorldConfig(); - InstanceWorldConfig instanceWorldConfig = InstanceWorldConfig.get(worldConfig); - if (instanceWorldConfig != null) { - InstanceDiscoveryConfig discoveryConfig = instanceWorldConfig.getDiscovery(); - if (discoveryConfig != null) { - PlayerConfigData playerConfigData = player.getPlayerConfigData(); - UUID instanceUuid = worldConfig.getUuid(); - if (discoveryConfig.alwaysDisplay() || !playerConfigData.getDiscoveredInstances().contains(instanceUuid)) { - Set discoveredInstances = new HashSet<>(playerConfigData.getDiscoveredInstances()); - discoveredInstances.add(instanceUuid); - playerConfigData.setDiscoveredInstances(discoveredInstances); - Ref playerRef = event.getPlayerRef(); - if (playerRef.isValid()) { - world.execute(() -> { - Store store = world.getEntityStore().getStore(); - showInstanceDiscovery(playerRef, store, instanceUuid, discoveryConfig); - }); - } - } - } - } - } - } - - private static void showInstanceDiscovery( - @Nonnull Ref ref, @Nonnull Store store, @Nonnull UUID instanceUuid, @Nonnull InstanceDiscoveryConfig discoveryConfig - ) { - DiscoverInstanceEvent.Display discoverInstanceEvent = new DiscoverInstanceEvent.Display(instanceUuid, discoveryConfig.clone()); - store.invoke(ref, discoverInstanceEvent); - discoveryConfig = discoverInstanceEvent.getDiscoveryConfig(); - if (!discoverInstanceEvent.isCancelled() && discoverInstanceEvent.shouldDisplay()) { - PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); - if (playerRefComponent != null) { - String subtitleKey = discoveryConfig.getSubtitleKey(); - Message subtitle = subtitleKey != null ? Message.translation(subtitleKey) : Message.empty(); - EventTitleUtil.showEventTitleToPlayer( - playerRefComponent, - Message.translation(discoveryConfig.getTitleKey()), - subtitle, - discoveryConfig.isMajor(), - discoveryConfig.getIcon(), - discoveryConfig.getDuration(), - discoveryConfig.getFadeInDuration(), - discoveryConfig.getFadeOutDuration() - ); - String discoverySoundEventId = discoveryConfig.getDiscoverySoundEventId(); - if (discoverySoundEventId != null) { - int assetIndex = SoundEvent.getAssetMap().getIndex(discoverySoundEventId); - if (assetIndex != Integer.MIN_VALUE) { - SoundUtil.playSoundEvent2d(ref, assetIndex, SoundCategory.UI, store); - } - } - } - } - } - - private static void onPlayerDrainFromWorld(@Nonnull DrainPlayerFromWorldEvent event) { - InstanceEntityConfig config = InstanceEntityConfig.removeAndGet(event.getHolder()); - if (config != null) { - WorldReturnPoint returnPoint = config.getReturnPoint(); - if (returnPoint != null) { - World returnWorld = Universe.get().getWorld(returnPoint.getWorld()); - if (returnWorld != null) { - event.setWorld(returnWorld); - event.setTransform(returnPoint.getReturnPoint()); - return; - } - } - // HyFix: Fallback to default world when return world is null or unloaded - // Prevents "Missing return world" crash that kicks players from the server - World defaultWorld = Universe.get().getDefaultWorld(); - if (defaultWorld != null) { - event.setWorld(defaultWorld); - // Use default spawn point if available - if (defaultWorld.getWorldConfig().getSpawnProvider() != null) { - event.setTransform(defaultWorld.getWorldConfig().getSpawnProvider().getSpawnPoint(defaultWorld, null)); - } - } - } - } - - private static void generateSchema(@Nonnull GenerateSchemaEvent event) { - ObjectSchema worldConfig = WorldConfig.CODEC.toSchema(event.getContext()); - Map props = worldConfig.getProperties(); - props.put("UUID", Schema.anyOf(new StringSchema(), new ObjectSchema())); - worldConfig.setTitle("Instance Configuration"); - worldConfig.setId("InstanceConfig.json"); - Schema.HytaleMetadata hytaleMetadata = worldConfig.getHytale(); - if (hytaleMetadata != null) { - hytaleMetadata.setPath("Instances"); - hytaleMetadata.setExtension("instance.bson"); - hytaleMetadata.setUiEditorIgnore(Boolean.TRUE); - } - - event.addSchema("InstanceConfig.json", worldConfig); - event.addSchemaLink("InstanceConfig", List.of("Instances/**/instance.bson"), ".bson"); - } - - private void validateInstanceAssets(@Nonnull LoadAssetEvent event) { - Path path = AssetModule.get().getBaseAssetPack().getRoot().resolve("Server").resolve("Instances"); - if (Options.getOptionSet().has(Options.VALIDATE_ASSETS) && Files.isDirectory(path) && !event.isShouldShutdown()) { - StringBuilder errors = new StringBuilder(); - - for (String name : this.getInstanceAssets()) { - StringBuilder sb = new StringBuilder(); - Path instancePath = getInstanceAssetPath(name); - Universe universe = Universe.get(); - WorldConfig config = WorldConfig.load(instancePath.resolve("instance.bson")).join(); - IChunkStorageProvider storage = config.getChunkStorageProvider(); - config.setChunkStorageProvider(new MigrationChunkStorageProvider(new IChunkStorageProvider[]{storage}, EmptyChunkStorageProvider.INSTANCE)); - config.setResourceStorageProvider(EmptyResourceStorageProvider.INSTANCE); - config.setUuid(UUID.randomUUID()); - config.setSavingPlayers(false); - config.setIsAllNPCFrozen(true); - config.setSavingConfig(false); - config.setTicking(false); - config.setGameMode(GameMode.Creative); - config.setDeleteOnRemove(false); - config.setCompassUpdating(false); - InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY); - config.markChanged(); - String worldName = "instance-validate-" + safeName(name); - - try { - World world = universe.makeWorld(worldName, instancePath, config, false).join(); - EnumSet options = EnumSet.of(ValidationOption.BLOCK_STATES, ValidationOption.BLOCKS); - world.validate(sb, WorldValidationUtil.blockValidator(errors, options), options); - } catch (Exception var18) { - sb.append("\t").append(var18.getMessage()); - this.getLogger().at(Level.SEVERE).withCause(var18).log("Failed to validate: " + name); - } finally { - if (!sb.isEmpty()) { - errors.append("Instance: ").append(name).append('\n').append((CharSequence) sb).append('\n'); - } - } - - if (universe.getWorld(worldName) != null) { - universe.removeWorld(worldName); - } - } - - if (!errors.isEmpty()) { - this.getLogger().at(Level.SEVERE).log("Failed to validate instances:\n" + errors); - event.failed(true, "failed to validate instances"); - } - - HytaleLogger.getLogger() - .at(Level.INFO) - .log("Loading Instance assets phase completed! Boot time %s", FormatUtil.nanosToString(System.nanoTime() - event.getBootStart())); - } - } - - @Nonnull - public static String safeName(@Nonnull String name) { - return name.replace('/', '-'); - } - - @Nonnull - public ResourceType getInstanceDataResourceType() { - return this.instanceDataResourceType; - } - - @Nonnull - public ComponentType getInstanceEntityConfigComponentType() { - return this.instanceEntityConfigComponentType; - } - - @Nonnull - public ComponentType getInstanceBlockComponentType() { - return this.instanceBlockComponentType; - } - - @Nonnull - public ComponentType getConfigurableInstanceBlockComponentType() { - return this.configurableInstanceBlockComponentType; - } -} diff --git a/src/com/hypixel/hytale/component/ArchetypeChunk.java b/src/com/hypixel/hytale/component/ArchetypeChunk.java index 2e5adad1..11e42475 100644 --- a/src/com/hypixel/hytale/component/ArchetypeChunk.java +++ b/src/com/hypixel/hytale/component/ArchetypeChunk.java @@ -69,17 +69,11 @@ public class ArchetypeChunk { @Nullable public > T getComponent(int index, @Nonnull ComponentType componentType) { - // HyFix #20: Handle stale entity references gracefully - try { - componentType.validateRegistry(this.store.getRegistry()); - if (index < 0 || index >= this.entitiesSize) { - throw new IndexOutOfBoundsException(index); - } else { - return (T) (!this.archetype.contains(componentType) ? null : this.components[componentType.getIndex()][index]); - } - } catch (IndexOutOfBoundsException e) { - System.out.println("[HyFix] WARNING: getComponent() IndexOutOfBounds - returning null (stale entity ref)"); - return null; + componentType.validateRegistry(this.store.getRegistry()); + if (index < 0 || index >= this.entitiesSize) { + throw new IndexOutOfBoundsException(index); + } else { + return (T) (!this.archetype.contains(componentType) ? null : this.components[componentType.getIndex()][index]); } } @@ -141,30 +135,24 @@ public class ArchetypeChunk { @Nonnull public Holder copySerializableEntity(@Nonnull ComponentRegistry.Data data, int entityIndex, @Nonnull Holder target) { - // HyFix #29: Handle IndexOutOfBoundsException during chunk saving gracefully - try { - if (entityIndex >= this.entitiesSize) { - throw new IndexOutOfBoundsException(entityIndex); - } else { - Archetype serializableArchetype = this.archetype.getSerializableArchetype(data); - Component[] entityComponents = target.ensureComponentsSize(serializableArchetype.length()); + if (entityIndex >= this.entitiesSize) { + throw new IndexOutOfBoundsException(entityIndex); + } else { + Archetype serializableArchetype = this.archetype.getSerializableArchetype(data); + Component[] entityComponents = target.ensureComponentsSize(serializableArchetype.length()); - for (int i = serializableArchetype.getMinIndex(); i < serializableArchetype.length(); i++) { - ComponentType> componentType = (ComponentType>) serializableArchetype.get( - i - ); - if (componentType != null) { - int componentTypeIndex = componentType.getIndex(); - Component component = this.components[componentTypeIndex][entityIndex]; - entityComponents[componentTypeIndex] = component.cloneSerializable(); - } + for (int i = serializableArchetype.getMinIndex(); i < serializableArchetype.length(); i++) { + ComponentType> componentType = (ComponentType>) serializableArchetype.get( + i + ); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + Component component = this.components[componentTypeIndex][entityIndex]; + entityComponents[componentTypeIndex] = component.cloneSerializable(); } - - target.init(serializableArchetype, entityComponents); - return target; } - } catch (IndexOutOfBoundsException e) { - System.out.println("[HyFix] WARNING: copySerializableEntity() IndexOutOfBounds - skipping (stale entity ref)"); + + target.init(serializableArchetype, entityComponents); return target; } } diff --git a/src/com/hypixel/hytale/math/shape/Box.java b/src/com/hypixel/hytale/math/shape/Box.java index 31791f44..6e1353e4 100644 --- a/src/com/hypixel/hytale/math/shape/Box.java +++ b/src/com/hypixel/hytale/math/shape/Box.java @@ -34,6 +34,7 @@ public class Box implements Shape { }) .build(); public static final Box UNIT = new Box(Vector3d.ZERO, Vector3d.ALL_ONES); + public static final Box ZERO = new Box(Vector3d.ZERO, Vector3d.ZERO); @Nonnull public final Vector3d min = new Vector3d(); @Nonnull diff --git a/src/com/hypixel/hytale/server/core/HytaleServer.java b/src/com/hypixel/hytale/server/core/HytaleServer.java deleted file mode 100644 index 439905e7..00000000 --- a/src/com/hypixel/hytale/server/core/HytaleServer.java +++ /dev/null @@ -1,544 +0,0 @@ -package com.hypixel.hytale.server.core; - -import com.hypixel.hytale.assetstore.AssetPack; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.common.plugin.PluginManifest; -import com.hypixel.hytale.common.thread.HytaleForkJoinThreadFactory; -import com.hypixel.hytale.common.util.FormatUtil; -import com.hypixel.hytale.common.util.GCUtil; -import com.hypixel.hytale.common.util.HardwareUtil; -import com.hypixel.hytale.common.util.NetworkUtil; -import com.hypixel.hytale.common.util.java.ManifestUtil; -import com.hypixel.hytale.event.EventBus; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.logger.backend.HytaleLogManager; -import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; -import com.hypixel.hytale.math.util.MathUtil; -import com.hypixel.hytale.math.util.TrigMathUtil; -import com.hypixel.hytale.metrics.JVMMetrics; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.plugin.early.EarlyPluginLoader; -import com.hypixel.hytale.server.core.asset.AssetModule; -import com.hypixel.hytale.server.core.asset.AssetRegistryLoader; -import com.hypixel.hytale.server.core.asset.LoadAssetEvent; -import com.hypixel.hytale.server.core.auth.ServerAuthManager; -import com.hypixel.hytale.server.core.auth.SessionServiceClient; -import com.hypixel.hytale.server.core.command.system.CommandManager; -import com.hypixel.hytale.server.core.console.ConsoleSender; -import com.hypixel.hytale.server.core.event.events.BootEvent; -import com.hypixel.hytale.server.core.event.events.ShutdownEvent; -import com.hypixel.hytale.server.core.io.ServerManager; -import com.hypixel.hytale.server.core.io.netty.NettyUtil; -import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; -import com.hypixel.hytale.server.core.plugin.PluginBase; -import com.hypixel.hytale.server.core.plugin.PluginClassLoader; -import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.plugin.PluginState; -import com.hypixel.hytale.server.core.universe.Universe; -import com.hypixel.hytale.server.core.universe.datastore.DataStoreProvider; -import com.hypixel.hytale.server.core.universe.datastore.DiskDataStoreProvider; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.update.UpdateModule; -import com.hypixel.hytale.server.core.util.concurrent.ThreadUtil; -import io.netty.handler.codec.quic.Quic; -import io.sentry.Sentry; -import io.sentry.SentryOptions; -import io.sentry.protocol.Contexts; -import io.sentry.protocol.OperatingSystem; -import io.sentry.protocol.User; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import joptsimple.OptionSet; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.time.Instant; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; - -public class HytaleServer { - public static final int DEFAULT_PORT = 5520; - public static final ScheduledExecutorService SCHEDULED_EXECUTOR = Executors.newSingleThreadScheduledExecutor(ThreadUtil.daemon("Scheduler")); - @Nonnull - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register("Time", server -> Instant.now(), Codec.INSTANT) - .register("Boot", server -> server.boot, Codec.INSTANT) - .register("BootStart", server -> server.bootStart, Codec.LONG) - .register("Booting", server -> server.booting.get(), Codec.BOOLEAN) - .register("ShutdownReason", server -> { - ShutdownReason reason = server.shutdown.get(); - return reason == null ? null : reason.toString(); - }, Codec.STRING) - .register("PluginManager", HytaleServer::getPluginManager, PluginManager.METRICS_REGISTRY) - .register("Config", HytaleServer::getConfig, HytaleServerConfig.CODEC) - .register("JVM", JVMMetrics.METRICS_REGISTRY); - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - private static HytaleServer instance; - private final Semaphore aliveLock = new Semaphore(0); - private final AtomicBoolean booting = new AtomicBoolean(false); - private final AtomicBoolean booted = new AtomicBoolean(false); - private final AtomicReference shutdown = new AtomicReference<>(); - private final EventBus eventBus = new EventBus(Options.getOptionSet().has(Options.EVENT_DEBUG)); - private final PluginManager pluginManager = new PluginManager(); - private final CommandManager commandManager = new CommandManager(); - @Nonnull - private final HytaleServerConfig hytaleServerConfig; - private final Instant boot; - private final long bootStart; - private int pluginsProgress; - - public HytaleServer() throws IOException { - instance = this; - Quic.ensureAvailability(); - HytaleLoggerBackend.setIndent(25); - ThreadUtil.forceTimeHighResolution(); - ThreadUtil.createKeepAliveThread(this.aliveLock); - this.boot = Instant.now(); - this.bootStart = System.nanoTime(); - LOGGER.at(Level.INFO).log("Starting HytaleServer"); - Constants.init(); - DataStoreProvider.CODEC.register("Disk", DiskDataStoreProvider.class, DiskDataStoreProvider.CODEC); - LOGGER.at(Level.INFO).log("Loading config..."); - this.hytaleServerConfig = HytaleServerConfig.load(); - HytaleLoggerBackend.reloadLogLevels(); - System.setProperty("java.util.concurrent.ForkJoinPool.common.threadFactory", HytaleForkJoinThreadFactory.class.getName()); - OptionSet optionSet = Options.getOptionSet(); - LOGGER.at(Level.INFO).log("Authentication mode: %s", optionSet.valueOf(Options.AUTH_MODE)); - ServerAuthManager.getInstance().initialize(); - if (EarlyPluginLoader.hasTransformers()) { - HytaleLogger.getLogger().at(Level.INFO).log("Early plugins loaded!! Disabling Sentry!!"); - } else if (!optionSet.has(Options.DISABLE_SENTRY)) { - LOGGER.at(Level.INFO).log("Enabling Sentry"); - SentryOptions options = new SentryOptions(); - options.setDsn("https://6043a13c7b5c45b5c834b6d896fb378e@sentry.hytale.com/4"); - options.setRelease(ManifestUtil.getImplementationVersion()); - options.setDist(ManifestUtil.getImplementationRevisionId()); - options.setEnvironment("release"); - options.setTag("patchline", ManifestUtil.getPatchline()); - options.setServerName(NetworkUtil.getHostName()); - options.setBeforeSend((event, hint) -> { - Throwable throwable = event.getThrowable(); - if (PluginClassLoader.isFromThirdPartyPlugin(throwable)) { - return null; - } else { - Contexts contexts = event.getContexts(); - HashMap serverContext = new HashMap<>(); - serverContext.put("name", this.getServerName()); - serverContext.put("max-players", this.getConfig().getMaxPlayers()); - ServerManager serverManager = ServerManager.get(); - if (serverManager != null) { - serverContext.put("listeners", serverManager.getListeners().stream().map(Object::toString).toList()); - } - - contexts.put("server", serverContext); - Universe universe = Universe.get(); - if (universe != null) { - HashMap universeContext = new HashMap<>(); - universeContext.put("path", universe.getPath().toString()); - universeContext.put("player-count", universe.getPlayerCount()); - universeContext.put("worlds", universe.getWorlds().keySet().stream().toList()); - contexts.put("universe", universeContext); - } - - HashMap pluginsContext = new HashMap<>(); - - for (PluginBase plugin : this.pluginManager.getPlugins()) { - PluginManifest manifestxx = plugin.getManifest(); - HashMap pluginInfo = new HashMap<>(); - pluginInfo.put("version", manifestxx.getVersion().toString()); - pluginInfo.put("state", plugin.getState().name()); - pluginsContext.put(plugin.getIdentifier().toString(), pluginInfo); - } - - contexts.put("plugins", pluginsContext); - AssetModule assetModule = AssetModule.get(); - if (assetModule != null) { - HashMap packsContext = new HashMap<>(); - - for (AssetPack pack : assetModule.getAssetPacks()) { - HashMap packInfo = new HashMap<>(); - PluginManifest manifestx = pack.getManifest(); - if (manifestx != null && manifestx.getVersion() != null) { - packInfo.put("version", manifestx.getVersion().toString()); - } - - packInfo.put("immutable", pack.isImmutable()); - packsContext.put(pack.getName(), packInfo); - } - - contexts.put("packs", packsContext); - } - - User user = new User(); - HashMap unknown = new HashMap<>(); - user.setUnknown(unknown); - UUID hardwareUUID = HardwareUtil.getUUID(); - if (hardwareUUID != null) { - unknown.put("hardware-uuid", hardwareUUID.toString()); - } - - ServerAuthManager authManager = ServerAuthManager.getInstance(); - unknown.put("auth-mode", authManager.getAuthMode().toString()); - SessionServiceClient.GameProfile profile = authManager.getSelectedProfile(); - if (profile != null) { - user.setUsername(profile.username); - user.setId(profile.uuid.toString()); - } - - user.setIpAddress("{{auto}}"); - event.setUser(user); - return event; - } - }); - Sentry.init(options); - Sentry.configureScope( - scope -> { - UUID hardwareUUID = HardwareUtil.getUUID(); - if (hardwareUUID != null) { - scope.setContexts("hardware", Map.of("uuid", hardwareUUID.toString())); - } - - OperatingSystem os = new OperatingSystem(); - os.setName(System.getProperty("os.name")); - os.setVersion(System.getProperty("os.version")); - scope.getContexts().setOperatingSystem(os); - scope.setContexts( - "build", - Map.of( - "version", - String.valueOf(ManifestUtil.getImplementationVersion()), - "revision-id", - String.valueOf(ManifestUtil.getImplementationRevisionId()), - "patchline", - String.valueOf(ManifestUtil.getPatchline()), - "environment", - "release" - ) - ); - if (Constants.SINGLEPLAYER) { - scope.setContexts( - "singleplayer", Map.of("owner-uuid", String.valueOf(SingleplayerModule.getUuid()), "owner-name", SingleplayerModule.getUsername()) - ); - } - } - ); - HytaleLogger.getLogger().setSentryClient(Sentry.getCurrentScopes()); - } - - ServerAuthManager.getInstance().checkPendingFatalError(); - NettyUtil.init(); - float sin = TrigMathUtil.sin(0.0F); - float atan2 = TrigMathUtil.atan2(0.0F, 0.0F); - Thread shutdownHook = new Thread(() -> { - if (this.shutdown.getAndSet(ShutdownReason.SIGINT) == null) { - this.shutdown0(ShutdownReason.SIGINT); - } - }, "ShutdownHook"); - shutdownHook.setDaemon(false); - Runtime.getRuntime().addShutdownHook(shutdownHook); - AssetRegistryLoader.init(); - - for (PluginManifest manifest : Constants.CORE_PLUGINS) { - this.pluginManager.registerCorePlugin(manifest); - } - - GCUtil.register(info -> { - Universe universe = Universe.get(); - if (universe != null) { - for (World world : universe.getWorlds().values()) { - world.markGCHasRun(); - } - } - }); - this.boot(); - } - - @Nonnull - public EventBus getEventBus() { - return this.eventBus; - } - - @Nonnull - public PluginManager getPluginManager() { - return this.pluginManager; - } - - @Nonnull - public CommandManager getCommandManager() { - return this.commandManager; - } - - @Nonnull - public HytaleServerConfig getConfig() { - return this.hytaleServerConfig; - } - - private void boot() { - if (!this.booting.getAndSet(true)) { - LOGGER.at(Level.INFO) - .log("Booting up HytaleServer - Version: " + ManifestUtil.getImplementationVersion() + ", Revision: " + ManifestUtil.getImplementationRevisionId()); - - try { - this.pluginsProgress = 0; - this.sendSingleplayerProgress(); - if (this.isShuttingDown()) { - return; - } - - LOGGER.at(Level.INFO).log("Setup phase..."); - this.commandManager.registerCommands(); - this.pluginManager.setup(); - ServerAuthManager.getInstance().initializeCredentialStore(); - LOGGER.at(Level.INFO).log("Setup phase completed! Boot time %s", FormatUtil.nanosToString(System.nanoTime() - this.bootStart)); - if (this.isShuttingDown()) { - return; - } - - LoadAssetEvent loadAssetEvent = get() - .getEventBus() - .dispatchFor(LoadAssetEvent.class) - .dispatch(new LoadAssetEvent(this.bootStart)); - if (this.isShuttingDown()) { - return; - } - - if (loadAssetEvent.isShouldShutdown()) { - List reasons = loadAssetEvent.getReasons(); - String join = String.join(", ", reasons); - LOGGER.at(Level.SEVERE).log("Asset validation FAILED with %d reason(s): %s", reasons.size(), join); - this.shutdownServer(ShutdownReason.VALIDATE_ERROR.withMessage(join)); - return; - } - - if (Options.getOptionSet().has(Options.SHUTDOWN_AFTER_VALIDATE)) { - LOGGER.at(Level.INFO).log("Asset validation passed"); - this.shutdownServer(ShutdownReason.SHUTDOWN); - return; - } - - this.pluginsProgress = 0; - this.sendSingleplayerProgress(); - if (this.isShuttingDown()) { - return; - } - - LOGGER.at(Level.INFO).log("Starting plugin manager..."); - this.pluginManager.start(); - LOGGER.at(Level.INFO).log("Plugin manager started! Startup time so far: %s", FormatUtil.nanosToString(System.nanoTime() - this.bootStart)); - if (this.isShuttingDown()) { - return; - } - - this.sendSingleplayerSignal("-=|Enabled|0"); - } catch (Throwable var6) { - LOGGER.at(Level.SEVERE).withCause(var6).log("Failed to boot HytaleServer!"); - Throwable t = var6; - - while (t.getCause() != null) { - t = t.getCause(); - } - - this.shutdownServer(ShutdownReason.CRASH.withMessage("Failed to start server! " + t.getMessage())); - } - - if (this.hytaleServerConfig.consumeHasChanged()) { - HytaleServerConfig.save(this.hytaleServerConfig).join(); - } - - SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { - try { - if (this.hytaleServerConfig.consumeHasChanged()) { - HytaleServerConfig.save(this.hytaleServerConfig).join(); - } - } catch (Exception var2x) { - LOGGER.at(Level.SEVERE).withCause(var2x).log("Failed to save server config!"); - } - }, 1L, 1L, TimeUnit.MINUTES); - LOGGER.at(Level.INFO).log("Getting Hytale Universe ready..."); - Universe.get().getUniverseReady().join(); - LOGGER.at(Level.INFO).log("Universe ready!"); - List tags = new ObjectArrayList<>(); - if (Constants.SINGLEPLAYER) { - tags.add("Singleplayer"); - } else { - tags.add("Multiplayer"); - } - - if (Constants.FRESH_UNIVERSE) { - tags.add("Fresh Universe"); - } - - this.booted.set(true); - ServerManager.get().waitForBindComplete(); - this.eventBus.dispatch(BootEvent.class); - List bootCommands = Options.getOptionSet().valuesOf(Options.BOOT_COMMAND); - if (!bootCommands.isEmpty()) { - CommandManager.get().handleCommands(ConsoleSender.INSTANCE, new ArrayDeque<>(bootCommands)).join(); - } - - String border = "\u001b[0;32m==============================================================================================="; - LOGGER.at(Level.INFO).log("\u001b[0;32m==============================================================================================="); - LOGGER.at(Level.INFO) - .log( - "%s Hytale Server Booted! [%s] took %s", - "\u001b[0;32m", - String.join(", ", tags), - FormatUtil.nanosToString(System.nanoTime() - this.bootStart) - ); - LOGGER.at(Level.INFO).log("\u001b[0;32m==============================================================================================="); - UpdateModule updateModule = UpdateModule.get(); - if (updateModule != null) { - updateModule.onServerReady(); - } - - ServerAuthManager authManager = ServerAuthManager.getInstance(); - if (!authManager.isSingleplayer() && authManager.getAuthMode() == ServerAuthManager.AuthMode.NONE) { - LOGGER.at(Level.WARNING).log("%sNo server tokens configured. Use /auth login to authenticate.", "\u001b[0;31m"); - } - - this.sendSingleplayerSignal(">> Singleplayer Ready <<"); - } - } - - public void shutdownServer() { - this.shutdownServer(ShutdownReason.SHUTDOWN); - } - - public void shutdownServer(@Nonnull ShutdownReason reason) { - Objects.requireNonNull(reason, "Server shutdown reason can't be null!"); - if (this.shutdown.getAndSet(reason) == null) { - if (reason.getMessage() != null) { - this.sendSingleplayerSignal("-=|Shutdown|" + reason.getMessage()); - } - - Thread shutdownThread = new Thread(() -> this.shutdown0(reason), "ShutdownThread"); - shutdownThread.setDaemon(false); - shutdownThread.start(); - } - } - - void shutdown0(@Nonnull ShutdownReason reason) { - LOGGER.at(Level.INFO).log("Shutdown triggered!!!"); - - try { - LOGGER.at(Level.INFO).log("Shutting down... %d '%s'", reason.getExitCode(), reason.getMessage()); - this.eventBus.dispatch(ShutdownEvent.class); - this.pluginManager.shutdown(); - this.commandManager.shutdown(); - this.eventBus.shutdown(); - ServerAuthManager.getInstance().shutdown(); - LOGGER.at(Level.INFO).log("Saving config..."); - if (this.hytaleServerConfig.consumeHasChanged()) { - HytaleServerConfig.save(this.hytaleServerConfig).join(); - } - - LOGGER.at(Level.INFO).log("Shutdown completed!"); - } catch (Throwable var3) { - LOGGER.at(Level.SEVERE).withCause(var3).log("Exception while shutting down:"); - } - - this.aliveLock.release(); - HytaleLogManager.resetFinally(); - SCHEDULED_EXECUTOR.schedule(() -> { - LOGGER.at(Level.SEVERE).log("Forcing shutdown!"); - Runtime.getRuntime().halt(reason.getExitCode()); - }, 3L, TimeUnit.SECONDS); - if (reason != ShutdownReason.SIGINT) { - System.exit(reason.getExitCode()); - } - } - - public void doneSetup(PluginBase plugin) { - this.pluginsProgress++; - this.sendSingleplayerProgress(); - } - - public void doneStart(PluginBase plugin) { - this.pluginsProgress++; - this.sendSingleplayerProgress(); - } - - public void doneStop(PluginBase plugin) { - this.pluginsProgress--; - this.sendSingleplayerProgress(); - } - - public void sendSingleplayerProgress() { - List plugins = this.pluginManager.getPlugins(); - if (this.shutdown.get() != null) { - this.sendSingleplayerSignal("-=|Shutdown Modules|" + MathUtil.round((double) (plugins.size() - this.pluginsProgress) / plugins.size(), 2) * 100.0); - } else if (this.pluginManager.getState() == PluginState.SETUP) { - this.sendSingleplayerSignal("-=|Setup|" + MathUtil.round((double) this.pluginsProgress / plugins.size(), 2) * 100.0); - } else if (this.pluginManager.getState() == PluginState.START) { - this.sendSingleplayerSignal("-=|Starting|" + MathUtil.round((double) this.pluginsProgress / plugins.size(), 2) * 100.0); - } - } - - public String getServerName() { - return this.getConfig().getServerName(); - } - - public boolean isBooting() { - return this.booting.get(); - } - - public boolean isBooted() { - return this.booted.get(); - } - - public boolean isShuttingDown() { - return this.shutdown.get() != null; - } - - @Nonnull - public Instant getBoot() { - return this.boot; - } - - public long getBootStart() { - return this.bootStart; - } - - @Nullable - public ShutdownReason getShutdownReason() { - return this.shutdown.get(); - } - - private void sendSingleplayerSignal(String message) { - if (Constants.SINGLEPLAYER) { - HytaleLoggerBackend.rawLog(message); - } - } - - public void reportSingleplayerStatus(String message) { - if (Constants.SINGLEPLAYER) { - HytaleLoggerBackend.rawLog("-=|" + message + "|0"); - } - } - - public void reportSaveProgress(@Nonnull World world, int saved, int total) { - if (this.isShuttingDown()) { - double progress = MathUtil.round((double) saved / total, 2) * 100.0; - if (Constants.SINGLEPLAYER) { - this.sendSingleplayerSignal("-=|Saving world " + world.getName() + " chunks|" + progress); - } else if (total < 10 || saved % (total / 10) == 0) { - world.getLogger().at(Level.INFO).log("Saving chunks: %.0f%%", progress); - } - } - } - - public static HytaleServer get() { - return instance; - } -} diff --git a/src/com/hypixel/hytale/server/core/entity/InteractionChain.java b/src/com/hypixel/hytale/server/core/entity/InteractionChain.java deleted file mode 100644 index 4314c0f3..00000000 --- a/src/com/hypixel/hytale/server/core/entity/InteractionChain.java +++ /dev/null @@ -1,851 +0,0 @@ -package com.hypixel.hytale.server.core.entity; - -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.protocol.ForkedChainId; -import com.hypixel.hytale.protocol.InteractionChainData; -import com.hypixel.hytale.protocol.InteractionCooldown; -import com.hypixel.hytale.protocol.InteractionState; -import com.hypixel.hytale.protocol.InteractionSyncData; -import com.hypixel.hytale.protocol.InteractionType; -import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; -import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import it.unimi.dsi.fastutil.longs.Long2LongMap; -import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; -import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectIterator; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.logging.Level; - -public class InteractionChain implements ChainSyncStorage { - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - private static final long NULL_FORK_ID = forkedIdToIndex(new ForkedChainId(-1, Integer.MAX_VALUE, null)); - private final InteractionType type; - private InteractionType baseType; - private final InteractionChainData chainData; - private int chainId; - private final ForkedChainId forkedChainId; - private final ForkedChainId baseForkedChainId; - private boolean predicted; - private final InteractionContext context; - @Nonnull - private final Long2ObjectMap forkedChains = new Long2ObjectOpenHashMap<>(); - @Nonnull - private final Long2ObjectMap tempForkedChainData = new Long2ObjectOpenHashMap<>(); - @Nonnull - private final Long2LongMap forkedChainsMap = new Long2LongOpenHashMap(); - @Nonnull - private final List newForks = new ObjectArrayList<>(); - @Nonnull - private final RootInteraction initialRootInteraction; - private RootInteraction rootInteraction; - private int operationCounter = 0; - @Nonnull - private final List callStack = new ObjectArrayList<>(); - private int simulatedCallStack = 0; - private final boolean requiresClient; - private int simulatedOperationCounter = 0; - private RootInteraction simulatedRootInteraction; - private int operationIndex = 0; - private int operationIndexOffset = 0; - private int clientOperationIndex = 0; - @Nonnull - private final List interactions = new ObjectArrayList<>(); - @Nonnull - private final List tempSyncData = new ObjectArrayList<>(); - private int tempSyncDataOffset = 0; - private long timestamp = System.nanoTime(); - private long waitingForServerFinished; - private long waitingForClientFinished; - private InteractionState clientState = InteractionState.NotFinished; - private InteractionState serverState = InteractionState.NotFinished; - private InteractionState finalState = InteractionState.Finished; - @Nullable - private Runnable onCompletion; - private boolean sentInitial; - private boolean desynced; - private float timeShift; - private boolean firstRun = true; - private boolean isFirstRun = true; - private boolean completed = false; - private boolean preTicked; - boolean skipChainOnClick; - - public InteractionChain( - InteractionType type, - InteractionContext context, - InteractionChainData chainData, - @Nonnull RootInteraction rootInteraction, - @Nullable Runnable onCompletion, - boolean requiresClient - ) { - this(null, null, type, context, chainData, rootInteraction, onCompletion, requiresClient); - } - - public InteractionChain( - ForkedChainId forkedChainId, - ForkedChainId baseForkedChainId, - InteractionType type, - InteractionContext context, - InteractionChainData chainData, - @Nonnull RootInteraction rootInteraction, - @Nullable Runnable onCompletion, - boolean requiresClient - ) { - this.type = this.baseType = type; - this.context = context; - this.chainData = chainData; - this.forkedChainId = forkedChainId; - this.baseForkedChainId = baseForkedChainId; - this.onCompletion = onCompletion; - this.initialRootInteraction = this.rootInteraction = this.simulatedRootInteraction = rootInteraction; - this.requiresClient = requiresClient || rootInteraction.needsRemoteSync(); - this.forkedChainsMap.defaultReturnValue(NULL_FORK_ID); - } - - public InteractionType getType() { - return this.type; - } - - public int getChainId() { - return this.chainId; - } - - public ForkedChainId getForkedChainId() { - return this.forkedChainId; - } - - public ForkedChainId getBaseForkedChainId() { - return this.baseForkedChainId; - } - - @Nonnull - public RootInteraction getInitialRootInteraction() { - return this.initialRootInteraction; - } - - public boolean isPredicted() { - return this.predicted; - } - - public InteractionContext getContext() { - return this.context; - } - - public InteractionChainData getChainData() { - return this.chainData; - } - - public InteractionState getServerState() { - return this.serverState; - } - - public boolean requiresClient() { - return this.requiresClient; - } - - public RootInteraction getRootInteraction() { - return this.rootInteraction; - } - - public RootInteraction getSimulatedRootInteraction() { - return this.simulatedRootInteraction; - } - - public int getOperationCounter() { - return this.operationCounter; - } - - public void setOperationCounter(int operationCounter) { - this.operationCounter = operationCounter; - } - - public int getSimulatedOperationCounter() { - return this.simulatedOperationCounter; - } - - public void setSimulatedOperationCounter(int simulatedOperationCounter) { - this.simulatedOperationCounter = simulatedOperationCounter; - } - - public boolean wasPreTicked() { - return this.preTicked; - } - - public void setPreTicked(boolean preTicked) { - this.preTicked = preTicked; - } - - public int getOperationIndex() { - return this.operationIndex; - } - - public void nextOperationIndex() { - this.operationIndex++; - this.clientOperationIndex++; - } - - public int getClientOperationIndex() { - return this.clientOperationIndex; - } - - @Nullable - public InteractionChain findForkedChain(@Nonnull ForkedChainId chainId, @Nullable InteractionChainData data) { - long id = forkedIdToIndex(chainId); - long altId = this.forkedChainsMap.get(id); - if (altId != NULL_FORK_ID) { - id = altId; - } - - InteractionChain chain = this.forkedChains.get(id); - if (chain == null && chainId.subIndex < 0 && data != null) { - InteractionEntry entry = this.getInteraction(chainId.entryIndex); - if (entry == null) { - return null; - } else { - int rootId = entry.getServerState().rootInteraction; - int opCounter = entry.getServerState().operationCounter; - RootInteraction root = RootInteraction.getAssetMap().getAsset(rootId); - if (root.getOperation(opCounter).getInnerOperation() instanceof Interaction interaction) { - this.context.initEntry(this, entry, null); - chain = interaction.mapForkChain(this.context, data); - this.context.deinitEntry(this, entry, null); - if (chain != null) { - this.forkedChainsMap.put(id, forkedIdToIndex(chain.getBaseForkedChainId())); - } - - return chain; - } else { - return null; - } - } - } else { - return chain; - } - } - - public InteractionChain getForkedChain(@Nonnull ForkedChainId chainId) { - long id = forkedIdToIndex(chainId); - if (chainId.subIndex < 0) { - long altId = this.forkedChainsMap.get(id); - if (altId != NULL_FORK_ID) { - id = altId; - } - } - - return this.forkedChains.get(id); - } - - public void putForkedChain(@Nonnull ForkedChainId chainId, @Nonnull InteractionChain chain) { - this.newForks.add(chain); - this.forkedChains.put(forkedIdToIndex(chainId), chain); - } - - @Nullable - public InteractionChain.TempChain getTempForkedChain(@Nonnull ForkedChainId chainId) { - InteractionEntry entry = this.getInteraction(chainId.entryIndex); - if (entry != null) { - if (chainId.subIndex < entry.getNextForkId()) { - return null; - } - } else if (chainId.entryIndex < this.operationIndexOffset) { - return null; - } - - return this.tempForkedChainData.computeIfAbsent(forkedIdToIndex(chainId), i -> new InteractionChain.TempChain()); - } - - @Nullable - InteractionChain.TempChain removeTempForkedChain(@Nonnull ForkedChainId chainId, InteractionChain forkChain) { - long id = forkedIdToIndex(chainId); - long altId = this.forkedChainsMap.get(id); - if (altId != NULL_FORK_ID) { - id = altId; - } - - InteractionChain.TempChain found = this.tempForkedChainData.remove(id); - if (found != null) { - return found; - } else { - InteractionEntry iEntry = this.context.getEntry(); - RootInteraction root = RootInteraction.getAssetMap().getAsset(iEntry.getState().rootInteraction); - if (root.getOperation(iEntry.getState().operationCounter).getInnerOperation() instanceof Interaction interaction) { - ObjectIterator> it = Long2ObjectMaps.fastIterator(this.getTempForkedChainData()); - - while (it.hasNext()) { - Entry entry = it.next(); - InteractionChain.TempChain tempChain = entry.getValue(); - if (tempChain.baseForkedChainId != null) { - int entryId = tempChain.baseForkedChainId.entryIndex; - if (entryId == iEntry.getIndex()) { - InteractionChain chain = interaction.mapForkChain(this.getContext(), tempChain.chainData); - if (chain != null) { - this.forkedChainsMap.put(forkedIdToIndex(tempChain.baseForkedChainId), forkedIdToIndex(chain.getBaseForkedChainId())); - } - - if (chain == forkChain) { - it.remove(); - return tempChain; - } - } - } - } - } - - return null; - } - } - - public boolean hasSentInitial() { - return this.sentInitial; - } - - public void setSentInitial(boolean sentInitial) { - this.sentInitial = sentInitial; - } - - public float getTimeShift() { - return this.timeShift; - } - - public void setTimeShift(float timeShift) { - this.timeShift = timeShift; - } - - public boolean consumeFirstRun() { - this.isFirstRun = this.firstRun; - this.firstRun = false; - return this.isFirstRun; - } - - public boolean isFirstRun() { - return this.isFirstRun; - } - - public void setFirstRun(boolean firstRun) { - this.isFirstRun = firstRun; - } - - public int getCallDepth() { - return this.callStack.size(); - } - - public int getSimulatedCallDepth() { - return this.simulatedCallStack; - } - - public void pushRoot(RootInteraction nextInteraction, boolean simulate) { - if (simulate) { - this.simulatedRootInteraction = nextInteraction; - this.simulatedOperationCounter = 0; - this.simulatedCallStack++; - } else { - this.callStack.add(new InteractionChain.CallState(this.rootInteraction, this.operationCounter)); - this.operationCounter = 0; - this.rootInteraction = nextInteraction; - } - } - - public void popRoot() { - InteractionChain.CallState state = this.callStack.removeLast(); - this.rootInteraction = state.rootInteraction; - this.operationCounter = state.operationCounter + 1; - this.simulatedRootInteraction = this.rootInteraction; - this.simulatedOperationCounter = this.operationCounter; - this.simulatedCallStack--; - } - - public float getTimeInSeconds() { - if (this.timestamp == 0L) { - return 0.0F; - } else { - long diff = System.nanoTime() - this.timestamp; - return (float) diff / 1.0E9F; - } - } - - public void setOnCompletion(Runnable onCompletion) { - this.onCompletion = onCompletion; - } - - void onCompletion(CooldownHandler cooldownHandler, boolean isRemote) { - if (!this.completed) { - this.completed = true; - if (this.onCompletion != null) { - this.onCompletion.run(); - this.onCompletion = null; - } - - if (isRemote) { - InteractionCooldown cooldown = this.initialRootInteraction.getCooldown(); - String cooldownId = this.initialRootInteraction.getId(); - if (cooldown != null && cooldown.cooldownId != null) { - cooldownId = cooldown.cooldownId; - } - - CooldownHandler.Cooldown cooldownTracker = cooldownHandler.getCooldown(cooldownId); - if (cooldownTracker != null) { - cooldownTracker.tick(0.016666668F); - } - } - } - } - - void updateServerState() { - if (this.serverState == InteractionState.NotFinished) { - if (this.operationCounter >= this.rootInteraction.getOperationMax()) { - this.serverState = this.finalState; - } else { - InteractionEntry entry = this.getOrCreateInteractionEntry(this.operationIndex); - - this.serverState = switch (entry.getServerState().state) { - case NotFinished, Finished -> InteractionState.NotFinished; - default -> InteractionState.Failed; - }; - } - } - } - - void updateSimulatedState() { - if (this.clientState == InteractionState.NotFinished) { - if (this.simulatedOperationCounter >= this.rootInteraction.getOperationMax()) { - this.clientState = this.finalState; - } else { - InteractionEntry entry = this.getOrCreateInteractionEntry(this.clientOperationIndex); - - this.clientState = switch (entry.getSimulationState().state) { - case NotFinished, Finished -> InteractionState.NotFinished; - default -> InteractionState.Failed; - }; - } - } - } - - @Override - public InteractionState getClientState() { - return this.clientState; - } - - @Override - public void setClientState(InteractionState state) { - this.clientState = state; - } - - @Nonnull - public InteractionEntry getOrCreateInteractionEntry(int index) { - int oIndex = index - this.operationIndexOffset; - if (oIndex < 0) { - throw new IllegalArgumentException("Trying to access removed interaction entry"); - } else { - InteractionEntry entry = oIndex < this.interactions.size() ? this.interactions.get(oIndex) : null; - if (entry == null) { - if (oIndex != this.interactions.size()) { - throw new IllegalArgumentException("Trying to add interaction entry at a weird location: " + oIndex + " " + this.interactions.size()); - } - - entry = new InteractionEntry(index, this.operationCounter, RootInteraction.getRootInteractionIdOrUnknown(this.rootInteraction.getId())); - this.interactions.add(entry); - } - - return entry; - } - } - - @Nullable - @Override - public InteractionEntry getInteraction(int index) { - index -= this.operationIndexOffset; - return index >= 0 && index < this.interactions.size() ? this.interactions.get(index) : null; - } - - // HyFix #40: Wrap in try-catch to handle out-of-order removal gracefully - public void removeInteractionEntry(@Nonnull InteractionManager interactionManager, int index) { - try { - int oIndex = index - this.operationIndexOffset; - if (oIndex != 0) { - throw new IllegalArgumentException("Trying to remove out of order"); - } else { - InteractionEntry entry = this.interactions.remove(oIndex); - this.operationIndexOffset++; - this.tempForkedChainData.values().removeIf(fork -> { - if (fork.baseForkedChainId.entryIndex != entry.getIndex()) { - return false; - } else { - interactionManager.sendCancelPacket(this.getChainId(), fork.forkedChainId); - return true; - } - }); - } - } catch (IllegalArgumentException e) { - System.out.println("[HyFix] WARNING: Suppressed out-of-order removal in InteractionChain (Issue #40)"); - } - } - - // HyFix: Expand buffer backwards instead of dropping data when index < offset - @Override - public void putInteractionSyncData(int index, InteractionSyncData data) { - int adjustedIndex = index - this.tempSyncDataOffset; - - // HyFix: Handle negative indices by expanding buffer backwards - if (adjustedIndex < 0) { - int expansion = -adjustedIndex; - for (int i = 0; i < expansion; i++) { - this.tempSyncData.add(0, null); // prepend nulls - } - this.tempSyncDataOffset = this.tempSyncDataOffset + adjustedIndex; // shift offset down - adjustedIndex = 0; - } - - if (adjustedIndex < this.tempSyncData.size()) { - this.tempSyncData.set(adjustedIndex, data); - } else if (adjustedIndex == this.tempSyncData.size()) { - this.tempSyncData.add(data); - } else { - LOGGER.at(Level.WARNING).log("Temp sync data sent out of order: " + adjustedIndex + " " + this.tempSyncData.size()); - } - } - - @Override - public void clearInteractionSyncData(int operationIndex) { - int tempIdx = operationIndex - this.tempSyncDataOffset; - if (!this.tempSyncData.isEmpty()) { - for (int end = this.tempSyncData.size() - 1; end >= tempIdx && end >= 0; end--) { - this.tempSyncData.remove(end); - } - } - - int idx = operationIndex - this.operationIndexOffset; - - for (int i = Math.max(idx, 0); i < this.interactions.size(); i++) { - this.interactions.get(i).setClientState(null); - } - } - - @Nullable - public InteractionSyncData removeInteractionSyncData(int index) { - index -= this.tempSyncDataOffset; - if (index != 0) { - return null; - } else if (this.tempSyncData.isEmpty()) { - return null; - } else if (this.tempSyncData.get(index) == null) { - return null; - } else { - this.tempSyncDataOffset++; - return this.tempSyncData.remove(index); - } - } - - // HyFix: Handle sync gaps gracefully instead of throwing - @Override - public void updateSyncPosition(int index) { - // HyFix: Accept any index >= offset, not just == offset - if (index >= this.tempSyncDataOffset) { - this.tempSyncDataOffset = index + 1; - } - // index < offset is silently ignored (already processed) - } - - @Override - public boolean isSyncDataOutOfOrder(int index) { - return index > this.tempSyncDataOffset + this.tempSyncData.size(); - } - - @Override - public void syncFork(@Nonnull Ref ref, @Nonnull InteractionManager manager, @Nonnull SyncInteractionChain packet) { - ForkedChainId baseId = packet.forkedId; - - while (baseId.forkedId != null) { - baseId = baseId.forkedId; - } - - InteractionChain fork = this.findForkedChain(baseId, packet.data); - if (fork != null) { - manager.sync(ref, fork, packet); - } else { - InteractionChain.TempChain temp = this.getTempForkedChain(baseId); - if (temp == null) { - return; - } - - temp.setForkedChainId(packet.forkedId); - temp.setBaseForkedChainId(baseId); - temp.setChainData(packet.data); - manager.sync(ref, temp, packet); - } - } - - public void copyTempFrom(@Nonnull InteractionChain.TempChain temp) { - this.setClientState(temp.clientState); - this.tempSyncData.addAll(temp.tempSyncData); - this.getTempForkedChainData().putAll(temp.tempForkedChainData); - } - - private static long forkedIdToIndex(@Nonnull ForkedChainId chainId) { - return (long) chainId.entryIndex << 32 | chainId.subIndex & 4294967295L; - } - - public void setChainId(int chainId) { - this.chainId = chainId; - } - - public InteractionType getBaseType() { - return this.baseType; - } - - public void setBaseType(InteractionType baseType) { - this.baseType = baseType; - } - - @Nonnull - public Long2ObjectMap getForkedChains() { - return this.forkedChains; - } - - @Nonnull - public Long2ObjectMap getTempForkedChainData() { - return this.tempForkedChainData; - } - - public long getTimestamp() { - return this.timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public long getWaitingForServerFinished() { - return this.waitingForServerFinished; - } - - public void setWaitingForServerFinished(long waitingForServerFinished) { - this.waitingForServerFinished = waitingForServerFinished; - } - - public long getWaitingForClientFinished() { - return this.waitingForClientFinished; - } - - public void setWaitingForClientFinished(long waitingForClientFinished) { - this.waitingForClientFinished = waitingForClientFinished; - } - - public void setServerState(InteractionState serverState) { - this.serverState = serverState; - } - - public InteractionState getFinalState() { - return this.finalState; - } - - public void setFinalState(InteractionState finalState) { - this.finalState = finalState; - } - - void setPredicted(boolean predicted) { - this.predicted = predicted; - } - - public void flagDesync() { - this.desynced = true; - this.forkedChains.forEach((k, c) -> c.flagDesync()); - } - - public boolean isDesynced() { - return this.desynced; - } - - @Nonnull - public List getNewForks() { - return this.newForks; - } - - @Nonnull - @Override - public String toString() { - return "InteractionChain{type=" - + this.type - + ", chainData=" - + this.chainData - + ", chainId=" - + this.chainId - + ", forkedChainId=" - + this.forkedChainId - + ", predicted=" - + this.predicted - + ", context=" - + this.context - + ", forkedChains=" - + this.forkedChains - + ", tempForkedChainData=" - + this.tempForkedChainData - + ", initialRootInteraction=" - + this.initialRootInteraction - + ", rootInteraction=" - + this.rootInteraction - + ", operationCounter=" - + this.operationCounter - + ", callStack=" - + this.callStack - + ", simulatedCallStack=" - + this.simulatedCallStack - + ", requiresClient=" - + this.requiresClient - + ", simulatedOperationCounter=" - + this.simulatedOperationCounter - + ", simulatedRootInteraction=" - + this.simulatedRootInteraction - + ", operationIndex=" - + this.operationIndex - + ", operationIndexOffset=" - + this.operationIndexOffset - + ", clientOperationIndex=" - + this.clientOperationIndex - + ", interactions=" - + this.interactions - + ", tempSyncData=" - + this.tempSyncData - + ", tempSyncDataOffset=" - + this.tempSyncDataOffset - + ", timestamp=" - + this.timestamp - + ", waitingForServerFinished=" - + this.waitingForServerFinished - + ", waitingForClientFinished=" - + this.waitingForClientFinished - + ", clientState=" - + this.clientState - + ", serverState=" - + this.serverState - + ", onCompletion=" - + this.onCompletion - + ", sentInitial=" - + this.sentInitial - + ", desynced=" - + this.desynced - + ", timeShift=" - + this.timeShift - + ", firstRun=" - + this.firstRun - + ", skipChainOnClick=" - + this.skipChainOnClick - + "}"; - } - - private record CallState(RootInteraction rootInteraction, int operationCounter) { - } - - static class TempChain implements ChainSyncStorage { - final Long2ObjectMap tempForkedChainData = new Long2ObjectOpenHashMap<>(); - final List tempSyncData = new ObjectArrayList<>(); - ForkedChainId forkedChainId; - InteractionState clientState = InteractionState.NotFinished; - ForkedChainId baseForkedChainId; - InteractionChainData chainData; - - TempChain() { - } - - @Nonnull - public InteractionChain.TempChain getOrCreateTempForkedChain(@Nonnull ForkedChainId chainId) { - return this.tempForkedChainData.computeIfAbsent(InteractionChain.forkedIdToIndex(chainId), i -> new InteractionChain.TempChain()); - } - - @Override - public InteractionState getClientState() { - return this.clientState; - } - - @Override - public void setClientState(InteractionState state) { - this.clientState = state; - } - - @Nullable - @Override - public InteractionEntry getInteraction(int index) { - return null; - } - - @Override - public void putInteractionSyncData(int index, InteractionSyncData data) { - if (index < this.tempSyncData.size()) { - this.tempSyncData.set(index, data); - } else { - if (index != this.tempSyncData.size()) { - throw new IllegalArgumentException("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size()); - } - - this.tempSyncData.add(data); - } - } - - @Override - public void updateSyncPosition(int index) { - } - - @Override - public boolean isSyncDataOutOfOrder(int index) { - return index > this.tempSyncData.size(); - } - - @Override - public void syncFork(@Nonnull Ref ref, @Nonnull InteractionManager manager, @Nonnull SyncInteractionChain packet) { - ForkedChainId baseId = packet.forkedId; - - while (baseId.forkedId != null) { - baseId = baseId.forkedId; - } - - InteractionChain.TempChain temp = this.getOrCreateTempForkedChain(baseId); - temp.setForkedChainId(packet.forkedId); - temp.setBaseForkedChainId(baseId); - temp.setChainData(packet.data); - manager.sync(ref, temp, packet); - } - - @Override - public void clearInteractionSyncData(int index) { - for (int end = this.tempSyncData.size() - 1; end >= index; end--) { - this.tempSyncData.remove(end); - } - } - - public InteractionChainData getChainData() { - return this.chainData; - } - - public void setChainData(InteractionChainData chainData) { - this.chainData = chainData; - } - - public ForkedChainId getBaseForkedChainId() { - return this.baseForkedChainId; - } - - public void setBaseForkedChainId(ForkedChainId baseForkedChainId) { - this.baseForkedChainId = baseForkedChainId; - } - - public void setForkedChainId(ForkedChainId forkedChainId) { - this.forkedChainId = forkedChainId; - } - - @Nonnull - @Override - public String toString() { - return "TempChain{tempForkedChainData=" + this.tempForkedChainData + ", tempSyncData=" + this.tempSyncData + ", clientState=" + this.clientState + "}"; - } - } -} diff --git a/src/com/hypixel/hytale/server/core/entity/InteractionManager.java b/src/com/hypixel/hytale/server/core/entity/InteractionManager.java deleted file mode 100644 index 6eccc449..00000000 --- a/src/com/hypixel/hytale/server/core/entity/InteractionManager.java +++ /dev/null @@ -1,1502 +0,0 @@ -package com.hypixel.hytale.server.core.entity; - -import com.hypixel.hytale.common.util.FormatUtil; -import com.hypixel.hytale.common.util.ListUtil; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.function.function.TriFunction; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.math.vector.Vector4d; -import com.hypixel.hytale.protocol.BlockPosition; -import com.hypixel.hytale.protocol.ForkedChainId; -import com.hypixel.hytale.protocol.GameMode; -import com.hypixel.hytale.protocol.InteractionChainData; -import com.hypixel.hytale.protocol.InteractionCooldown; -import com.hypixel.hytale.protocol.InteractionState; -import com.hypixel.hytale.protocol.InteractionSyncData; -import com.hypixel.hytale.protocol.InteractionType; -import com.hypixel.hytale.protocol.RootInteractionSettings; -import com.hypixel.hytale.protocol.Vector3f; -import com.hypixel.hytale.protocol.WaitForDataFrom; -import com.hypixel.hytale.protocol.packets.interaction.CancelInteractionChain; -import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; -import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.inventory.Inventory; -import com.hypixel.hytale.server.core.inventory.ItemStack; -import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler; -import com.hypixel.hytale.server.core.modules.interaction.IInteractionSimulationHandler; -import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionTypeUtils; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; -import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; -import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation; -import com.hypixel.hytale.server.core.modules.time.TimeResource; -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.WorldChunk; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.util.UUIDUtil; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.logging.Level; - -public class InteractionManager implements Component { - public static final double MAX_REACH_DISTANCE = 8.0; - public static final float[] DEFAULT_CHARGE_TIMES = new float[]{0.0F}; - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - @Nonnull - private final Int2ObjectMap chains = new Int2ObjectOpenHashMap<>(); - @Nonnull - private final Int2ObjectMap unmodifiableChains = Int2ObjectMaps.unmodifiable(this.chains); - @Nonnull - private final CooldownHandler cooldownHandler = new CooldownHandler(); - @Nonnull - private final LivingEntity entity; - @Nullable - private final PlayerRef playerRef; - private boolean hasRemoteClient; - @Nonnull - private final IInteractionSimulationHandler interactionSimulationHandler; - @Nonnull - private final ObjectList tempSyncDataList = new ObjectArrayList<>(); - private int lastServerChainId; - private int lastClientChainId; - private long packetQueueTime; - private final float[] globalTimeShift = new float[InteractionType.VALUES.length]; - private final boolean[] globalTimeShiftDirty = new boolean[InteractionType.VALUES.length]; - private boolean timeShiftsDirty; - private final ObjectList syncPackets = new ObjectArrayList<>(); - private long currentTime = 1L; - @Nonnull - private final ObjectList chainStartQueue = new ObjectArrayList<>(); - @Nonnull - private final Predicate cachedTickChain = this::tickChain; - @Nullable - protected CommandBuffer commandBuffer; - - public InteractionManager(@Nonnull LivingEntity entity, @Nullable PlayerRef playerRef, @Nonnull IInteractionSimulationHandler simulationHandler) { - this.entity = entity; - this.playerRef = playerRef; - this.hasRemoteClient = playerRef != null; - this.interactionSimulationHandler = simulationHandler; - } - - @Nonnull - public Int2ObjectMap getChains() { - return this.unmodifiableChains; - } - - @Nonnull - public IInteractionSimulationHandler getInteractionSimulationHandler() { - return this.interactionSimulationHandler; - } - - private long getOperationTimeoutThreshold() { - if (this.playerRef != null) { - return this.playerRef.getPacketHandler().getOperationTimeoutThreshold(); - } else { - assert this.commandBuffer != null; - - World world = this.commandBuffer.getExternalData().getWorld(); - return world.getTickStepNanos() / 1000000 * 10; - } - } - - private boolean waitingForClient(@Nonnull Ref ref) { - assert this.commandBuffer != null; - - Player playerComponent = this.commandBuffer.getComponent(ref, Player.getComponentType()); - return playerComponent != null && playerComponent.isWaitingForClientReady(); - } - - @Deprecated(forRemoval = true) - public void setHasRemoteClient(boolean hasRemoteClient) { - this.hasRemoteClient = hasRemoteClient; - } - - @Deprecated - public void copyFrom(@Nonnull InteractionManager interactionManager) { - this.chains.putAll(interactionManager.chains); - } - - public void tick(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, float dt) { - this.currentTime = this.currentTime + commandBuffer.getExternalData().getWorld().getTickStepNanos(); - this.commandBuffer = commandBuffer; - this.clearAllGlobalTimeShift(dt); - this.cooldownHandler.tick(dt); - - for (InteractionChain interactionChain : this.chainStartQueue) { - this.executeChain0(ref, interactionChain); - } - - this.chainStartQueue.clear(); - Deque packetQueue = null; - if (this.playerRef != null) { - packetQueue = ((GamePacketHandler) this.playerRef.getPacketHandler()).getInteractionPacketQueue(); - } - - if (packetQueue != null && !packetQueue.isEmpty()) { - for (boolean first = true; this.tryConsumePacketQueue(ref, packetQueue) || first; first = false) { - if (!this.chains.isEmpty()) { - this.chains.values().removeIf(this.cachedTickChain); - } - - float cooldownDt = 0.0F; - - for (float shift : this.globalTimeShift) { - cooldownDt = Math.max(cooldownDt, shift); - } - - if (cooldownDt > 0.0F) { - this.cooldownHandler.tick(cooldownDt); - } - } - - this.commandBuffer = null; - } else { - if (!this.chains.isEmpty()) { - this.chains.values().removeIf(this.cachedTickChain); - } - - this.commandBuffer = null; - } - } - - private boolean tryConsumePacketQueue(@Nonnull Ref ref, @Nonnull Deque packetQueue) { - Iterator it = packetQueue.iterator(); - boolean finished = false; - boolean desynced = false; - int highestChainId = -1; - boolean changed = false; - - label114: - while (it.hasNext()) { - SyncInteractionChain packet = it.next(); - if (packet.desync) { - HytaleLogger.Api context = LOGGER.at(Level.FINE); - if (context.isEnabled()) { - context.log("Client packet flagged as desync"); - } - - desynced = true; - } - - InteractionChain chain = this.chains.get(packet.chainId); - if (chain != null && packet.forkedId != null) { - for (ForkedChainId id = packet.forkedId; id != null; id = id.forkedId) { - InteractionChain subChain = chain.getForkedChain(id); - if (subChain == null) { - InteractionChain.TempChain tempChain = chain.getTempForkedChain(id); - if (tempChain != null) { - tempChain.setBaseForkedChainId(id); - ForkedChainId lastId = id; - - for (ForkedChainId var17 = id.forkedId; var17 != null; var17 = var17.forkedId) { - tempChain = tempChain.getOrCreateTempForkedChain(var17); - tempChain.setBaseForkedChainId(var17); - lastId = var17; - } - - tempChain.setForkedChainId(packet.forkedId); - tempChain.setBaseForkedChainId(lastId); - tempChain.setChainData(packet.data); - this.sync(ref, tempChain, packet); - changed = true; - it.remove(); - this.packetQueueTime = 0L; - } - continue label114; - } - - chain = subChain; - } - } - - highestChainId = Math.max(highestChainId, packet.chainId); - boolean isProxy = packet.data != null && !UUIDUtil.isEmptyOrNull(packet.data.proxyId); - if ((chain != null || finished) && !isProxy) { - if (chain != null) { - this.sync(ref, chain, packet); - changed = true; - it.remove(); - this.packetQueueTime = 0L; - } else if (desynced) { - this.sendCancelPacket(packet.chainId, packet.forkedId); - it.remove(); - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - ctx.log("Discarding packet due to desync: %s", packet); - } - } else if (this.syncStart(ref, packet)) { - changed = true; - it.remove(); - this.packetQueueTime = 0L; - } else { - if (!this.waitingForClient(ref)) { - long queuedTime; - if (this.packetQueueTime == 0L) { - this.packetQueueTime = this.currentTime; - queuedTime = 0L; - } else { - queuedTime = this.currentTime - this.packetQueueTime; - } - - HytaleLogger.Api context = LOGGER.at(Level.FINE); - if (context.isEnabled()) { - context.log("Queued chain %d for %s", packet.chainId, FormatUtil.nanosToString(queuedTime)); - } - - if (queuedTime > TimeUnit.MILLISECONDS.toNanos(this.getOperationTimeoutThreshold())) { - this.sendCancelPacket(packet.chainId, packet.forkedId); - it.remove(); - context = LOGGER.at(Level.FINE); - if (context.isEnabled()) { - context.log("Discarding packet due to queuing for too long: %s", packet); - } - } - } - - if (!desynced && !isProxy) { - finished = true; - } - } - } - - if (desynced && !packetQueue.isEmpty()) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Discarding previous packets in queue: (before) %d", packetQueue.size()); - } - - packetQueue.removeIf(v -> { - boolean shouldRemove = this.getChain(v.chainId, v.forkedId) == null && UUIDUtil.isEmptyOrNull(v.data.proxyId) && v.initial; - if (shouldRemove) { - HytaleLogger.Api ctx1 = LOGGER.at(Level.FINE); - if (ctx1.isEnabled()) { - ctx1.log("Discarding: %s", v); - } - - this.sendCancelPacket(v.chainId, v.forkedId); - } - - return shouldRemove; - }); - ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Discarded previous packets in queue: (after) %d", packetQueue.size()); - } - } - - return changed; - } - - @Nullable - private InteractionChain getChain(int chainId, @Nullable ForkedChainId forkedChainId) { - InteractionChain chain = this.chains.get(chainId); - if (chain != null && forkedChainId != null) { - for (ForkedChainId id = forkedChainId; id != null; id = id.forkedId) { - InteractionChain subChain = chain.getForkedChain(id); - if (subChain == null) { - return null; - } - - chain = subChain; - } - } - - return chain; - } - - private boolean tickChain(@Nonnull InteractionChain chain) { - // HyFix: Validate chain context and owningEntity before ticking - // Prevents NPE crash in TickInteractionManagerSystem when chains have null context - // or invalid owningEntity refs, which can kick players from the server - InteractionContext context = chain.getContext(); - if (context == null) { - LOGGER.at(Level.WARNING).log("Removing chain with null context to prevent crash"); - return true; // Remove this chain - } - Ref owningEntity = context.getOwningEntity(); - if (owningEntity == null || !owningEntity.isValid()) { - LOGGER.at(Level.WARNING).log("Removing chain with null/invalid owningEntity to prevent crash"); - return true; // Remove this chain - } - - if (chain.wasPreTicked()) { - chain.setPreTicked(false); - return false; - } else { - if (!this.hasRemoteClient) { - chain.updateSimulatedState(); - } - - chain.getForkedChains().values().removeIf(this.cachedTickChain); - Ref ref = this.entity.getReference(); - - assert ref != null; - - if (chain.getServerState() != InteractionState.NotFinished) { - if (chain.requiresClient() && chain.getClientState() == InteractionState.NotFinished) { - if (!this.waitingForClient(ref)) { - if (chain.getWaitingForClientFinished() == 0L) { - chain.setWaitingForClientFinished(this.currentTime); - } - - long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - chain.getWaitingForClientFinished()); - HytaleLogger.Api context2 = LOGGER.at(Level.FINE); - if (context2.isEnabled()) { - context2.log("Server finished chain but client hasn't! %d, %s, %s", chain.getChainId(), chain, waitMillis); - } - - long threshold = this.getOperationTimeoutThreshold(); - TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); - if (timeResource.getTimeDilationModifier() == 1.0F && waitMillis > threshold) { - this.cancelChains(chain); - return chain.getForkedChains().isEmpty(); - } - } - - return false; - } else { - LOGGER.at(Level.FINE).log("Remove Chain: %d, %s", chain.getChainId(), chain); - this.handleCancelledChain(ref, chain); - chain.onCompletion(this.cooldownHandler, this.hasRemoteClient); - return chain.getForkedChains().isEmpty(); - } - } else { - int baseOpIndex = chain.getOperationIndex(); - - try { - this.doTickChain(ref, chain); - } catch (InteractionManager.ChainCancelledException var9) { - chain.setServerState(var9.state); - chain.setClientState(var9.state); - chain.updateServerState(); - if (!this.hasRemoteClient) { - chain.updateSimulatedState(); - } - - if (chain.requiresClient()) { - this.sendSyncPacket(chain, baseOpIndex, this.tempSyncDataList); - this.sendCancelPacket(chain); - } - } - - if (chain.getServerState() != InteractionState.NotFinished) { - HytaleLogger.Api contextx = LOGGER.at(Level.FINE); - if (contextx.isEnabled()) { - contextx.log("Server finished chain: %d-%s, %s in %fs", chain.getChainId(), chain.getForkedChainId(), chain, chain.getTimeInSeconds()); - } - - if (!chain.requiresClient() || chain.getClientState() != InteractionState.NotFinished) { - contextx = LOGGER.at(Level.FINE); - if (contextx.isEnabled()) { - contextx.log("Remove Chain: %d-%s, %s", chain.getChainId(), chain.getForkedChainId(), chain); - } - - this.handleCancelledChain(ref, chain); - chain.onCompletion(this.cooldownHandler, this.hasRemoteClient); - return chain.getForkedChains().isEmpty(); - } - } else if (chain.getClientState() != InteractionState.NotFinished && !this.waitingForClient(ref)) { - if (chain.getWaitingForServerFinished() == 0L) { - chain.setWaitingForServerFinished(this.currentTime); - } - - long waitMillisx = TimeUnit.NANOSECONDS.toMillis(this.currentTime - chain.getWaitingForServerFinished()); - HytaleLogger.Api contextxx = LOGGER.at(Level.FINE); - if (contextxx.isEnabled()) { - contextxx.log("Client finished chain but server hasn't! %d, %s, %s", chain.getChainId(), chain, waitMillisx); - } - - long threshold = this.getOperationTimeoutThreshold(); - if (waitMillisx > threshold) { - LOGGER.at(Level.SEVERE).log("Client finished chain earlier than server! %d, %s", chain.getChainId(), chain); - } - } - - return false; - } - } - } - - private void handleCancelledChain(@Nonnull Ref ref, @Nonnull InteractionChain chain) { - assert this.commandBuffer != null; - - RootInteraction root = chain.getRootInteraction(); - int maxOperations = root.getOperationMax(); - if (chain.getOperationCounter() < maxOperations) { - InteractionEntry entry = chain.getInteraction(chain.getOperationIndex()); - if (entry != null) { - Operation operation = root.getOperation(chain.getOperationCounter()); - if (operation == null) { - throw new IllegalStateException("Failed to find operation during simulation tick of chain '" + root.getId() + "'"); - } else { - InteractionContext context = chain.getContext(); - entry.getServerState().state = InteractionState.Failed; - if (entry.getClientState() != null) { - entry.getClientState().state = InteractionState.Failed; - } - - try { - context.initEntry(chain, entry, this.entity); - TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); - operation.handle(ref, false, entry.getTimeInSeconds(this.currentTime) * timeResource.getTimeDilationModifier(), chain.getType(), context); - } finally { - context.deinitEntry(chain, entry, this.entity); - } - - chain.setOperationCounter(maxOperations); - } - } - } - } - - private void doTickChain(@Nonnull Ref ref, @Nonnull InteractionChain chain) { - ObjectList interactionData = this.tempSyncDataList; - interactionData.clear(); - RootInteraction root = chain.getRootInteraction(); - int maxOperations = root.getOperationMax(); - int currentOp = chain.getOperationCounter(); - int baseOpIndex = chain.getOperationIndex(); - int callDepth = chain.getCallDepth(); - if (chain.consumeFirstRun()) { - if (chain.getForkedChainId() == null) { - chain.setTimeShift(this.getGlobalTimeShift(chain.getType())); - } else { - InteractionChain parent = this.chains.get(chain.getChainId()); - chain.setFirstRun(parent != null && parent.isFirstRun()); - } - } else { - chain.setTimeShift(0.0F); - } - - if (!chain.getContext().getEntity().isValid()) { - throw new InteractionManager.ChainCancelledException(chain.getServerState()); - } else { - while (true) { - Operation simOp = !this.hasRemoteClient ? root.getOperation(chain.getSimulatedOperationCounter()) : null; - WaitForDataFrom simWaitFrom = simOp != null ? simOp.getWaitForDataFrom() : null; - long tickTime = this.currentTime; - if (!this.hasRemoteClient && simWaitFrom != WaitForDataFrom.Server) { - this.simulationTick(ref, chain, tickTime); - } - - interactionData.add(this.serverTick(ref, chain, tickTime)); - if (!chain.getContext().getEntity().isValid() - && chain.getServerState() != InteractionState.Finished - && chain.getServerState() != InteractionState.Failed) { - throw new InteractionManager.ChainCancelledException(chain.getServerState()); - } - - if (!this.hasRemoteClient && simWaitFrom == WaitForDataFrom.Server) { - this.simulationTick(ref, chain, tickTime); - } - - if (!this.hasRemoteClient) { - if (chain.getRootInteraction() != chain.getSimulatedRootInteraction()) { - throw new IllegalStateException( - "Simulation and server tick are not in sync (root interaction).\n" - + chain.getRootInteraction().getId() - + " vs " - + chain.getSimulatedRootInteraction() - ); - } - - if (chain.getOperationCounter() != chain.getSimulatedOperationCounter()) { - throw new IllegalStateException( - "Simulation and server tick are not in sync (operation position).\nRoot: " - + chain.getRootInteraction().getId() - + "\nCounter: " - + chain.getOperationCounter() - + " vs " - + chain.getSimulatedOperationCounter() - + "\nIndex: " - + chain.getOperationIndex() - ); - } - } - - if (callDepth != chain.getCallDepth()) { - callDepth = chain.getCallDepth(); - root = chain.getRootInteraction(); - maxOperations = root.getOperationMax(); - } else if (currentOp == chain.getOperationCounter()) { - break; - } - - chain.nextOperationIndex(); - currentOp = chain.getOperationCounter(); - if (currentOp >= maxOperations) { - while (callDepth > 0) { - chain.popRoot(); - callDepth = chain.getCallDepth(); - currentOp = chain.getOperationCounter(); - root = chain.getRootInteraction(); - maxOperations = root.getOperationMax(); - if (currentOp < maxOperations || callDepth == 0) { - break; - } - } - - if (callDepth == 0 && currentOp >= maxOperations) { - break; - } - } - } - - chain.updateServerState(); - if (!this.hasRemoteClient) { - chain.updateSimulatedState(); - } - - if (chain.requiresClient()) { - this.sendSyncPacket(chain, baseOpIndex, interactionData); - } - } - } - - @Nullable - private InteractionSyncData serverTick(@Nonnull Ref ref, @Nonnull InteractionChain chain, long tickTime) { - assert this.commandBuffer != null; - - RootInteraction root = chain.getRootInteraction(); - Operation operation = root.getOperation(chain.getOperationCounter()); - - assert operation != null; - - InteractionEntry entry = chain.getOrCreateInteractionEntry(chain.getOperationIndex()); - InteractionSyncData returnData = null; - boolean wasWrong = entry.consumeDesyncFlag(); - if (entry.getClientState() == null) { - wasWrong |= !entry.setClientState(chain.removeInteractionSyncData(chain.getOperationIndex())); - } - - if (wasWrong) { - returnData = entry.getServerState(); - chain.flagDesync(); - chain.clearInteractionSyncData(chain.getOperationIndex()); - } - - TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); - float tickTimeDilation = timeResource.getTimeDilationModifier(); - if (operation.getWaitForDataFrom() != WaitForDataFrom.Client || entry.getClientState() != null) { - int serverDataHashCode = entry.getServerDataHashCode(); - InteractionContext context = chain.getContext(); - float time = entry.getTimeInSeconds(tickTime); - boolean firstRun = false; - if (entry.getTimestamp() == 0L) { - time = chain.getTimeShift(); - entry.setTimestamp(tickTime, time); - firstRun = true; - } - - time *= tickTimeDilation; - - try { - context.initEntry(chain, entry, this.entity); - operation.tick(ref, this.entity, firstRun, time, chain.getType(), context, this.cooldownHandler); - } finally { - context.deinitEntry(chain, entry, this.entity); - } - - InteractionSyncData serverData = entry.getServerState(); - if (firstRun || serverDataHashCode != entry.getServerDataHashCode()) { - returnData = serverData; - } - - try { - context.initEntry(chain, entry, this.entity); - operation.handle(ref, firstRun, time, chain.getType(), context); - } finally { - context.deinitEntry(chain, entry, this.entity); - } - - this.removeInteractionIfFinished(ref, chain, entry); - return returnData; - } else if (this.waitingForClient(ref)) { - return null; - } else { - if (entry.getWaitingForSyncData() == 0L) { - entry.setWaitingForSyncData(this.currentTime); - } - - long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - entry.getWaitingForSyncData()); - HytaleLogger.Api contextx = LOGGER.at(Level.FINE); - if (contextx.isEnabled()) { - contextx.log("Wait for interaction clientData: %d, %s, %s", chain.getOperationIndex(), entry, waitMillis); - } - - long threshold = this.getOperationTimeoutThreshold(); - if (tickTimeDilation == 1.0F && waitMillis > threshold) { - LOGGER.atWarning().log("Client failed to send client data, ending early to prevent desync."); - chain.setServerState(InteractionState.Failed); - chain.setClientState(InteractionState.Failed); - this.cancelChains(chain); - return null; - } else { - if (entry.consumeSendInitial() || wasWrong) { - returnData = entry.getServerState(); - } - - return returnData; - } - } - } - - private void removeInteractionIfFinished(@Nonnull Ref ref, @Nonnull InteractionChain chain, @Nonnull InteractionEntry entry) { - if (chain.getOperationIndex() == entry.getIndex() && entry.getServerState().state != InteractionState.NotFinished) { - chain.setFinalState(entry.getServerState().state); - } - - if (entry.getServerState().state != InteractionState.NotFinished) { - LOGGER.at(Level.FINE).log("Server finished interaction: %d, %s", entry.getIndex(), entry); - if (!chain.requiresClient() || entry.getClientState() != null && entry.getClientState().state != InteractionState.NotFinished) { - LOGGER.at(Level.FINER).log("Remove Interaction: %d, %s", entry.getIndex(), entry); - chain.removeInteractionEntry(this, entry.getIndex()); - } - } else if (entry.getClientState() != null && entry.getClientState().state != InteractionState.NotFinished && !this.waitingForClient(ref)) { - if (entry.getWaitingForServerFinished() == 0L) { - entry.setWaitingForServerFinished(this.currentTime); - } - - long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - entry.getWaitingForServerFinished()); - HytaleLogger.Api context = LOGGER.at(Level.FINE); - if (context.isEnabled()) { - context.log("Client finished interaction but server hasn't! %s, %d, %s, %s", entry.getClientState().state, entry.getIndex(), entry, waitMillis); - } - - long threshold = this.getOperationTimeoutThreshold(); - if (waitMillis > threshold) { - HytaleLogger.Api ctx = LOGGER.at(Level.SEVERE); - if (ctx.isEnabled()) { - ctx.log("Client finished interaction earlier than server! %d, %s", entry.getIndex(), entry); - } - } - } - } - - private void simulationTick(@Nonnull Ref ref, @Nonnull InteractionChain chain, long tickTime) { - assert this.commandBuffer != null; - - RootInteraction rootInteraction = chain.getRootInteraction(); - Operation operation = rootInteraction.getOperation(chain.getSimulatedOperationCounter()); - if (operation == null) { - throw new IllegalStateException("Failed to find operation during simulation tick of chain '" + rootInteraction.getId() + "'"); - } else { - InteractionEntry entry = chain.getOrCreateInteractionEntry(chain.getClientOperationIndex()); - InteractionContext context = chain.getContext(); - entry.setUseSimulationState(true); - - try { - context.initEntry(chain, entry, this.entity); - float time = entry.getTimeInSeconds(tickTime); - boolean firstRun = false; - if (entry.getTimestamp() == 0L) { - time = chain.getTimeShift(); - entry.setTimestamp(tickTime, time); - firstRun = true; - } - - TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); - float tickTimeDilation = timeResource.getTimeDilationModifier(); - time *= tickTimeDilation; - operation.simulateTick(ref, this.entity, firstRun, time, chain.getType(), context, this.cooldownHandler); - } finally { - context.deinitEntry(chain, entry, this.entity); - entry.setUseSimulationState(false); - } - - if (!entry.setClientState(entry.getSimulationState())) { - throw new RuntimeException("Simulation failed"); - } else { - this.removeInteractionIfFinished(ref, chain, entry); - } - } - } - - private boolean syncStart(@Nonnull Ref ref, @Nonnull SyncInteractionChain packet) { - assert this.commandBuffer != null; - - int index = packet.chainId; - if (!packet.initial) { - if (packet.forkedId == null) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Got syncStart for %d-%s but packet wasn't the first.", index, packet.forkedId); - } - - this.sendCancelPacket(index, packet.forkedId); - } - - return true; - } else if (packet.forkedId != null) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Can't start a forked chain from the client: %d %s", index, packet.forkedId); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } else { - InteractionType type = packet.interactionType; - if (index <= 0) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Invalid client chainId! Got %d but client id's should be > 0", index); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } else if (index <= this.lastClientChainId) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Invalid client chainId! The last clientChainId was %d but just got %d", this.lastClientChainId, index); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } else { - UUID proxyId = packet.data.proxyId; - InteractionContext context; - if (!UUIDUtil.isEmptyOrNull(proxyId)) { - World world = this.commandBuffer.getExternalData().getWorld(); - Ref proxyTarget = world.getEntityStore().getRefFromUUID(proxyId); - if (proxyTarget == null) { - if (this.packetQueueTime != 0L - && this.currentTime - this.packetQueueTime > TimeUnit.MILLISECONDS.toNanos(this.getOperationTimeoutThreshold()) / 2L) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Proxy entity never spawned"); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } - - return false; - } - - context = InteractionContext.forProxyEntity(this, this.entity, proxyTarget); - } else { - context = InteractionContext.forInteraction(this, ref, type, packet.equipSlot, this.commandBuffer); - } - - String rootInteractionId = context.getRootInteractionId(type); - if (rootInteractionId == null) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log("Missing root interaction: %d, %s, %s", index, this.entity.getInventory().getItemInHand(), type); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } else { - RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId); - if (rootInteraction == null) { - return false; - } else if (!this.applyRules(context, packet.data, type, rootInteraction)) { - return false; - } else { - Inventory entityInventory = this.entity.getInventory(); - ItemStack itemInHand = entityInventory.getActiveHotbarItem(); - ItemStack utilityItem = entityInventory.getUtilityItem(); - String serverItemInHandId = itemInHand != null ? itemInHand.getItemId() : null; - String serverUtilityItemId = utilityItem != null ? utilityItem.getItemId() : null; - if (packet.activeHotbarSlot != entityInventory.getActiveHotbarSlot()) { - HytaleLogger.Api ctx = LOGGER.at(Level.FINE); - if (ctx.isEnabled()) { - ctx.log( - "Active slot miss match: %d, %d != %d, %s, %s, %s", - index, - entityInventory.getActiveHotbarSlot(), - packet.activeHotbarSlot, - serverItemInHandId, - packet.itemInHandId, - type - ); - } - - this.sendCancelPacket(index, packet.forkedId); - if (this.playerRef != null) { - this.playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, entityInventory.getActiveHotbarSlot())); - } - - return true; - } else if (packet.activeUtilitySlot != entityInventory.getActiveUtilitySlot()) { - HytaleLogger.Api ctxx = LOGGER.at(Level.FINE); - if (ctxx.isEnabled()) { - ctxx.log( - "Active slot miss match: %d, %d != %d, %s, %s, %s", - index, - entityInventory.getActiveUtilitySlot(), - packet.activeUtilitySlot, - serverItemInHandId, - packet.itemInHandId, - type - ); - } - - this.sendCancelPacket(index, packet.forkedId); - if (this.playerRef != null) { - this.playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-5, entityInventory.getActiveUtilitySlot())); - } - - return true; - } else if (!Objects.equals(serverItemInHandId, packet.itemInHandId)) { - HytaleLogger.Api ctxxx = LOGGER.at(Level.FINE); - if (ctxxx.isEnabled()) { - ctxxx.log("ItemInHand miss match: %d, %s, %s, %s", index, serverItemInHandId, packet.itemInHandId, type); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } else if (!Objects.equals(serverUtilityItemId, packet.utilityItemId)) { - HytaleLogger.Api ctxxx = LOGGER.at(Level.FINE); - if (ctxxx.isEnabled()) { - ctxxx.log("UtilityItem miss match: %d, %s, %s, %s", index, serverUtilityItemId, packet.utilityItemId, type); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } else if (this.isOnCooldown(ref, type, rootInteraction, true)) { - return false; - } else { - InteractionChain chain = this.initChain(packet.data, type, context, rootInteraction, null, true); - chain.setChainId(index); - this.sync(ref, chain, packet); - World world = this.commandBuffer.getExternalData().getWorld(); - if (packet.data.blockPosition != null) { - BlockPosition targetBlock = world.getBaseBlock(packet.data.blockPosition); - context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK, targetBlock); - context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK_RAW, packet.data.blockPosition); - if (!packet.data.blockPosition.equals(targetBlock)) { - WorldChunk otherChunk = world.getChunkIfInMemory( - ChunkUtil.indexChunkFromBlock(packet.data.blockPosition.x, packet.data.blockPosition.z) - ); - if (otherChunk == null) { - HytaleLogger.Api ctxxx = LOGGER.at(Level.FINE); - if (ctxxx.isEnabled()) { - ctxxx.log("Unloaded chunk interacted with: %d, %s", index, type); - } - - this.sendCancelPacket(index, packet.forkedId); - return true; - } - - int blockId = world.getBlock(targetBlock.x, targetBlock.y, targetBlock.z); - int otherBlockId = world.getBlock(packet.data.blockPosition.x, packet.data.blockPosition.y, packet.data.blockPosition.z); - if (blockId != otherBlockId) { - otherChunk.setBlock( - packet.data.blockPosition.x, packet.data.blockPosition.y, packet.data.blockPosition.z, 0, BlockType.EMPTY, 0, 0, 1052 - ); - } - } - } - - if (packet.data.entityId >= 0) { - EntityStore entityComponentStore = world.getEntityStore(); - Ref entityReference = entityComponentStore.getRefFromNetworkId(packet.data.entityId); - if (entityReference != null) { - context.getMetaStore().putMetaObject(Interaction.TARGET_ENTITY, entityReference); - } - } - - if (packet.data.targetSlot != Integer.MIN_VALUE) { - context.getMetaStore().putMetaObject(Interaction.TARGET_SLOT, packet.data.targetSlot); - } - - if (packet.data.hitLocation != null) { - Vector3f hit = packet.data.hitLocation; - context.getMetaStore().putMetaObject(Interaction.HIT_LOCATION, new Vector4d(hit.x, hit.y, hit.z, 1.0)); - } - - if (packet.data.hitDetail != null) { - context.getMetaStore().putMetaObject(Interaction.HIT_DETAIL, packet.data.hitDetail); - } - - this.lastClientChainId = index; - if (!this.tickChain(chain)) { - chain.setPreTicked(true); - this.chains.put(index, chain); - } - - return true; - } - } - } - } - } - } - - public void sync(@Nonnull Ref ref, @Nonnull ChainSyncStorage chainSyncStorage, @Nonnull SyncInteractionChain packet) { - assert this.commandBuffer != null; - - if (packet.newForks != null) { - for (SyncInteractionChain fork : packet.newForks) { - chainSyncStorage.syncFork(ref, this, fork); - } - } - - if (packet.interactionData == null) { - chainSyncStorage.setClientState(packet.state); - } else { - for (int i = 0; i < packet.interactionData.length; i++) { - InteractionSyncData syncData = packet.interactionData[i]; - if (syncData != null) { - int index = packet.operationBaseIndex + i; - if (!chainSyncStorage.isSyncDataOutOfOrder(index)) { - InteractionEntry interaction = chainSyncStorage.getInteraction(index); - if (interaction != null && chainSyncStorage instanceof InteractionChain interactionChain) { - if (interaction.getClientState() != null - && interaction.getClientState().state != InteractionState.NotFinished - && syncData.state == InteractionState.NotFinished - || !interaction.setClientState(syncData)) { - chainSyncStorage.clearInteractionSyncData(index); - interaction.flagDesync(); - interactionChain.flagDesync(); - return; - } - - chainSyncStorage.updateSyncPosition(index); - HytaleLogger.Api context = LOGGER.at(Level.FINEST); - if (context.isEnabled()) { - TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); - float tickTimeDilation = timeResource.getTimeDilationModifier(); - context.log( - "%d, %d: Time (Sync) - Server: %s vs Client: %s", - packet.chainId, - index, - interaction.getTimeInSeconds(this.currentTime) * tickTimeDilation, - interaction.getClientState().progress - ); - } - - this.removeInteractionIfFinished(ref, interactionChain, interaction); - } else { - chainSyncStorage.putInteractionSyncData(index, syncData); - } - } - } - } - - int last = packet.operationBaseIndex + packet.interactionData.length; - chainSyncStorage.clearInteractionSyncData(last); - chainSyncStorage.setClientState(packet.state); - } - } - - public boolean canRun(@Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction) { - return this.canRun(type, (short) -1, rootInteraction); - } - - public boolean canRun(@Nonnull InteractionType type, short equipSlot, @Nonnull RootInteraction rootInteraction) { - return applyRules(null, type, equipSlot, rootInteraction, this.chains, null); - } - - public boolean applyRules( - @Nonnull InteractionContext context, @Nonnull InteractionChainData data, @Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction - ) { - List chainsToCancel = new ObjectArrayList<>(); - if (!applyRules(data, type, context.getHeldItemSlot(), rootInteraction, this.chains, chainsToCancel)) { - return false; - } else { - for (InteractionChain interactionChain : chainsToCancel) { - this.cancelChains(interactionChain); - } - - return true; - } - } - - public void cancelChains(@Nonnull InteractionChain chain) { - chain.setServerState(InteractionState.Failed); - chain.setClientState(InteractionState.Failed); - this.sendCancelPacket(chain); - - for (InteractionChain fork : chain.getForkedChains().values()) { - this.cancelChains(fork); - } - } - - private static boolean applyRules( - @Nullable InteractionChainData data, - @Nonnull InteractionType type, - int heldItemSlot, - @Nullable RootInteraction rootInteraction, - @Nonnull Map chains, - @Nullable List chainsToCancel - ) { - if (!chains.isEmpty() && rootInteraction != null) { - for (InteractionChain chain : chains.values()) { - if ((chain.getForkedChainId() == null || chain.isPredicted()) - && (data == null || Objects.equals(chain.getChainData().proxyId, data.proxyId)) - && (type != InteractionType.Equipped || chain.getType() != InteractionType.Equipped || chain.getContext().getHeldItemSlot() == heldItemSlot)) { - if (chain.getServerState() == InteractionState.NotFinished) { - RootInteraction currentRoot = chain.getRootInteraction(); - Operation currentOp = currentRoot.getOperation(chain.getOperationCounter()); - if (rootInteraction.getRules() - .validateInterrupts(type, rootInteraction.getData().getTags(), chain.getType(), currentRoot.getData().getTags(), currentRoot.getRules())) { - if (chainsToCancel != null) { - chainsToCancel.add(chain); - } - } else if (currentOp != null - && currentOp.getRules() != null - && rootInteraction.getRules() - .validateInterrupts(type, rootInteraction.getData().getTags(), chain.getType(), currentOp.getTags(), currentOp.getRules())) { - if (chainsToCancel != null) { - chainsToCancel.add(chain); - } - } else { - if (rootInteraction.getRules() - .validateBlocked(type, rootInteraction.getData().getTags(), chain.getType(), currentRoot.getData().getTags(), currentRoot.getRules())) { - return false; - } - - if (currentOp != null - && currentOp.getRules() != null - && rootInteraction.getRules() - .validateBlocked(type, rootInteraction.getData().getTags(), chain.getType(), currentOp.getTags(), currentOp.getRules())) { - return false; - } - } - } - - if ((chainsToCancel == null || chainsToCancel.isEmpty()) - && !applyRules(data, type, heldItemSlot, rootInteraction, chain.getForkedChains(), chainsToCancel)) { - return false; - } - } - } - - return true; - } else { - return true; - } - } - - public boolean tryStartChain( - @Nonnull Ref ref, - @Nonnull CommandBuffer commandBuffer, - @Nonnull InteractionType type, - @Nonnull InteractionContext context, - @Nonnull RootInteraction rootInteraction - ) { - InteractionChain chain = this.initChain(type, context, rootInteraction, false); - if (!this.applyRules(context, chain.getChainData(), type, rootInteraction)) { - return false; - } else { - this.executeChain(ref, commandBuffer, chain); - return true; - } - } - - public void startChain( - @Nonnull Ref ref, - @Nonnull CommandBuffer commandBuffer, - @Nonnull InteractionType type, - @Nonnull InteractionContext context, - @Nonnull RootInteraction rootInteraction - ) { - InteractionChain chain = this.initChain(type, context, rootInteraction, false); - this.executeChain(ref, commandBuffer, chain); - } - - @Nonnull - public InteractionChain initChain( - @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, boolean forceRemoteSync - ) { - return this.initChain(type, context, rootInteraction, -1, null, forceRemoteSync); - } - - @Nonnull - public InteractionChain initChain( - @Nonnull InteractionType type, - @Nonnull InteractionContext context, - @Nonnull RootInteraction rootInteraction, - int entityId, - @Nullable BlockPosition blockPosition, - boolean forceRemoteSync - ) { - InteractionChainData data = new InteractionChainData(entityId, UUIDUtil.EMPTY_UUID, null, null, blockPosition, Integer.MIN_VALUE, null); - return this.initChain(data, type, context, rootInteraction, null, forceRemoteSync); - } - - @Nonnull - public InteractionChain initChain( - @Nonnull InteractionChainData data, - @Nonnull InteractionType type, - @Nonnull InteractionContext context, - @Nonnull RootInteraction rootInteraction, - @Nullable Runnable onCompletion, - boolean forceRemoteSync - ) { - return new InteractionChain(type, context, data, rootInteraction, onCompletion, forceRemoteSync || !this.hasRemoteClient); - } - - public void queueExecuteChain(@Nonnull InteractionChain chain) { - this.chainStartQueue.add(chain); - } - - public void executeChain(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull InteractionChain chain) { - this.commandBuffer = commandBuffer; - this.executeChain0(ref, chain); - this.commandBuffer = null; - } - - private void executeChain0(@Nonnull Ref ref, @Nonnull InteractionChain chain) { - if (this.isOnCooldown(ref, chain.getType(), chain.getInitialRootInteraction(), false)) { - chain.setServerState(InteractionState.Failed); - chain.setClientState(InteractionState.Failed); - } else { - int index = --this.lastServerChainId; - if (index >= 0) { - index = this.lastServerChainId = -1; - } - - chain.setChainId(index); - if (!this.tickChain(chain)) { - LOGGER.at(Level.FINE).log("Add Chain: %d, %s", index, chain); - chain.setPreTicked(true); - this.chains.put(index, chain); - } - } - } - - private boolean isOnCooldown(@Nonnull Ref ref, @Nonnull InteractionType type, @Nonnull RootInteraction root, boolean remote) { - assert this.commandBuffer != null; - - InteractionCooldown cooldown = root.getCooldown(); - String cooldownId = root.getId(); - float cooldownTime = InteractionTypeUtils.getDefaultCooldown(type); - float[] cooldownChargeTimes = DEFAULT_CHARGE_TIMES; - boolean interruptRecharge = false; - if (cooldown != null) { - cooldownTime = cooldown.cooldown; - if (cooldown.chargeTimes != null && cooldown.chargeTimes.length > 0) { - cooldownChargeTimes = cooldown.chargeTimes; - } - - if (cooldown.cooldownId != null) { - cooldownId = cooldown.cooldownId; - } - - if (cooldown.interruptRecharge) { - interruptRecharge = true; - } - - if (cooldown.clickBypass && remote) { - this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); - return false; - } - } - - Player playerComponent = this.commandBuffer.getComponent(ref, Player.getComponentType()); - GameMode gameMode = playerComponent != null ? playerComponent.getGameMode() : GameMode.Adventure; - RootInteractionSettings settings = root.getSettings().get(gameMode); - if (settings != null) { - cooldown = settings.cooldown; - if (cooldown != null) { - cooldownTime = cooldown.cooldown; - if (cooldown.chargeTimes != null && cooldown.chargeTimes.length > 0) { - cooldownChargeTimes = cooldown.chargeTimes; - } - - if (cooldown.cooldownId != null) { - cooldownId = cooldown.cooldownId; - } - - if (cooldown.interruptRecharge) { - interruptRecharge = true; - } - - if (cooldown.clickBypass && remote) { - this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); - return false; - } - } - - if (settings.allowSkipChainOnClick && remote) { - this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); - return false; - } - } - - return this.cooldownHandler.isOnCooldown(root, cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); - } - - public void tryRunHeldInteraction(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull InteractionType type) { - this.tryRunHeldInteraction(ref, commandBuffer, type, (short) -1); - } - - public void tryRunHeldInteraction(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull InteractionType type, short equipSlot) { - Inventory inventory = this.entity.getInventory(); - ItemStack itemStack; - switch (type) { - case Held: - itemStack = inventory.getItemInHand(); - break; - case HeldOffhand: - itemStack = inventory.getUtilityItem(); - break; - case Equipped: - if (equipSlot == -1) { - throw new IllegalArgumentException(); - } - - itemStack = inventory.getArmor().getItemStack(equipSlot); - break; - default: - throw new IllegalArgumentException(); - } - - if (itemStack != null && !itemStack.isEmpty()) { - String rootId = (String) itemStack.getItem().getInteractions().get(type); - if (rootId != null) { - RootInteraction root = (RootInteraction) RootInteraction.getAssetMap().getAsset(rootId); - if (root != null && this.canRun(type, equipSlot, root)) { - InteractionContext context = InteractionContext.forInteraction(this, ref, type, equipSlot, commandBuffer); - this.startChain(ref, commandBuffer, type, context, root); - } - } - } - } - - public void sendSyncPacket(@Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List interactionData) { - if (!chain.hasSentInitial() || interactionData != null && !ListUtil.emptyOrAllNull(interactionData) || !chain.getNewForks().isEmpty()) { - if (this.playerRef != null) { - SyncInteractionChain packet = makeSyncPacket(chain, operationBaseIndex, interactionData); - this.syncPackets.add(packet); - } - } - } - - @Nonnull - private static SyncInteractionChain makeSyncPacket( - @Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List interactionData - ) { - SyncInteractionChain[] forks = null; - List newForks = chain.getNewForks(); - if (!newForks.isEmpty()) { - forks = new SyncInteractionChain[newForks.size()]; - - for (int i = 0; i < newForks.size(); i++) { - InteractionChain fc = newForks.get(i); - forks[i] = makeSyncPacket(fc, 0, null); - } - - newForks.clear(); - } - - SyncInteractionChain packet = new SyncInteractionChain( - 0, - 0, - 0, - null, - null, - null, - !chain.hasSentInitial(), - false, - chain.hasSentInitial() ? Integer.MIN_VALUE : RootInteraction.getRootInteractionIdOrUnknown(chain.getInitialRootInteraction().getId()), - chain.getType(), - chain.getContext().getHeldItemSlot(), - chain.getChainId(), - chain.getForkedChainId(), - chain.getChainData(), - chain.getServerState(), - forks, - operationBaseIndex, - interactionData == null ? null : interactionData.toArray(InteractionSyncData[]::new) - ); - chain.setSentInitial(true); - return packet; - } - - private void sendCancelPacket(@Nonnull InteractionChain chain) { - this.sendCancelPacket(chain.getChainId(), chain.getForkedChainId()); - } - - public void sendCancelPacket(int chainId, ForkedChainId forkedChainId) { - if (this.playerRef != null) { - this.playerRef.getPacketHandler().writeNoCache(new CancelInteractionChain(chainId, forkedChainId)); - } - } - - public void clear() { - this.forEachInteraction((chain, _i, _a) -> { - chain.setServerState(InteractionState.Failed); - chain.setClientState(InteractionState.Failed); - this.sendCancelPacket(chain); - return null; - }, null); - this.chainStartQueue.clear(); - } - - public void clearAllGlobalTimeShift(float dt) { - if (this.timeShiftsDirty) { - boolean clearFlag = true; - - for (int i = 0; i < this.globalTimeShift.length; i++) { - if (!this.globalTimeShiftDirty[i]) { - this.globalTimeShift[i] = 0.0F; - } else { - clearFlag = false; - this.globalTimeShift[i] = this.globalTimeShift[i] + dt; - } - } - - Arrays.fill(this.globalTimeShiftDirty, false); - if (clearFlag) { - this.timeShiftsDirty = false; - } - } - } - - public void setGlobalTimeShift(@Nonnull InteractionType type, float shift) { - if (shift < 0.0F) { - throw new IllegalArgumentException("Can't shift backwards"); - } else { - this.globalTimeShift[type.ordinal()] = shift; - this.globalTimeShiftDirty[type.ordinal()] = true; - this.timeShiftsDirty = true; - } - } - - public float getGlobalTimeShift(@Nonnull InteractionType type) { - return this.globalTimeShift[type.ordinal()]; - } - - public T forEachInteraction(@Nonnull TriFunction func, @Nonnull T val) { - return forEachInteraction(this.chains, func, val); - } - - private static T forEachInteraction( - @Nonnull Map chains, @Nonnull TriFunction func, @Nonnull T val - ) { - if (chains.isEmpty()) { - return val; - } else { - for (InteractionChain chain : chains.values()) { - Operation operation = chain.getRootInteraction().getOperation(chain.getOperationCounter()); - if (operation != null && operation.getInnerOperation() instanceof Interaction interaction) { - val = func.apply(chain, interaction, val); - } - - val = forEachInteraction(chain.getForkedChains(), func, val); - } - - return val; - } - } - - public void walkChain( - @Nonnull Ref ref, @Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull ComponentAccessor componentAccessor - ) { - this.walkChain(ref, collector, type, null, componentAccessor); - } - - public void walkChain( - @Nonnull Ref ref, - @Nonnull Collector collector, - @Nonnull InteractionType type, - @Nullable RootInteraction rootInteraction, - @Nonnull ComponentAccessor componentAccessor - ) { - walkChain(collector, type, InteractionContext.forInteraction(this, ref, type, componentAccessor), rootInteraction); - } - - public static void walkChain( - @Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable RootInteraction rootInteraction - ) { - if (rootInteraction == null) { - String rootInteractionId = context.getRootInteractionId(type); - if (rootInteractionId == null) { - throw new IllegalArgumentException("No interaction ID found for " + type + ", " + context); - } - - rootInteraction = RootInteraction.getAssetMap().getAsset(rootInteractionId); - } - - if (rootInteraction == null) { - throw new IllegalArgumentException("No interactions are defined for " + type + ", " + context); - } else { - collector.start(); - collector.into(context, null); - walkInteractions(collector, context, CollectorTag.ROOT, rootInteraction.getInteractionIds()); - collector.outof(); - collector.finished(); - } - } - - public static boolean walkInteractions( - @Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nonnull String[] interactionIds - ) { - for (String id : interactionIds) { - if (walkInteraction(collector, context, tag, id)) { - return true; - } - } - - return false; - } - - public static boolean walkInteraction(@Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nullable String id) { - if (id == null) { - return false; - } else { - Interaction interaction = Interaction.getAssetMap().getAsset(id); - if (interaction == null) { - throw new IllegalArgumentException("Failed to find interaction: " + id); - } else if (collector.collect(tag, context, interaction)) { - return true; - } else { - collector.into(context, interaction); - interaction.walk(collector, context); - collector.outof(); - return false; - } - } - } - - public ObjectList getSyncPackets() { - return this.syncPackets; - } - - @Nonnull - @Override - public Component clone() { - InteractionManager manager = new InteractionManager(this.entity, this.playerRef, this.interactionSimulationHandler); - manager.copyFrom(this); - return manager; - } - - public static class ChainCancelledException extends RuntimeException { - @Nonnull - private final InteractionState state; - - public ChainCancelledException(@Nonnull InteractionState state) { - this.state = state; - } - } -} diff --git a/src/com/hypixel/hytale/server/core/io/PacketHandler.java b/src/com/hypixel/hytale/server/core/io/PacketHandler.java deleted file mode 100644 index 8eb07aca..00000000 --- a/src/com/hypixel/hytale/server/core/io/PacketHandler.java +++ /dev/null @@ -1,570 +0,0 @@ -package com.hypixel.hytale.server.core.io; - -import com.google.common.flogger.LazyArgs; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.codecs.EnumCodec; -import com.hypixel.hytale.common.util.FormatUtil; -import com.hypixel.hytale.common.util.NetworkUtil; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.metrics.metric.HistoricMetric; -import com.hypixel.hytale.metrics.metric.Metric; -import com.hypixel.hytale.protocol.CachedPacket; -import com.hypixel.hytale.protocol.Packet; -import com.hypixel.hytale.protocol.io.PacketStatsRecorder; -import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; -import com.hypixel.hytale.protocol.packets.connection.Disconnect; -import com.hypixel.hytale.protocol.packets.connection.DisconnectType; -import com.hypixel.hytale.protocol.packets.connection.Ping; -import com.hypixel.hytale.protocol.packets.connection.Pong; -import com.hypixel.hytale.protocol.packets.connection.PongType; -import com.hypixel.hytale.server.core.auth.PlayerAuthentication; -import com.hypixel.hytale.server.core.io.adapter.PacketAdapters; -import com.hypixel.hytale.server.core.io.handlers.login.AuthenticationPacketHandler; -import com.hypixel.hytale.server.core.io.handlers.login.PasswordPacketHandler; -import com.hypixel.hytale.server.core.io.netty.NettyUtil; -import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; -import com.hypixel.hytale.server.core.receiver.IPacketReceiver; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.local.LocalAddress; -import io.netty.channel.unix.DomainSocketAddress; -import io.netty.handler.codec.quic.QuicStreamChannel; -import io.netty.util.Attribute; -import io.netty.util.AttributeKey; -import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue; -import it.unimi.dsi.fastutil.ints.IntPriorityQueue; -import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -import it.unimi.dsi.fastutil.longs.LongPriorityQueue; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.security.SecureRandom; -import java.time.Duration; -import java.time.Instant; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.BooleanSupplier; -import java.util.logging.Level; - -public abstract class PacketHandler implements IPacketReceiver { - public static final int MAX_PACKET_ID = 512; - private static final HytaleLogger LOGIN_TIMING_LOGGER = HytaleLogger.get("LoginTiming"); - private static final AttributeKey LOGIN_START_ATTRIBUTE_KEY = AttributeKey.newInstance("LOGIN_START"); - @Nonnull - protected final Channel channel; - @Nonnull - protected final ProtocolVersion protocolVersion; - @Nullable - protected PlayerAuthentication auth; - protected boolean queuePackets; - protected final AtomicInteger queuedPackets = new AtomicInteger(); - protected final SecureRandom pingIdRandom = new SecureRandom(); - @Nonnull - protected final PacketHandler.PingInfo[] pingInfo; - private float pingTimer; - protected boolean registered; - private ScheduledFuture timeoutTask; - @Nullable - protected Throwable clientReadyForChunksFutureStack; - @Nullable - protected CompletableFuture clientReadyForChunksFuture; - @Nonnull - protected final PacketHandler.DisconnectReason disconnectReason = new PacketHandler.DisconnectReason(); - - public PacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion) { - this.channel = channel; - this.protocolVersion = protocolVersion; - this.pingInfo = new PacketHandler.PingInfo[PongType.VALUES.length]; - - for (PongType pingType : PongType.VALUES) { - this.pingInfo[pingType.ordinal()] = new PacketHandler.PingInfo(pingType); - } - } - - @Nonnull - public Channel getChannel() { - return this.channel; - } - - @Deprecated(forRemoval = true) - public void setCompressionEnabled(boolean compressionEnabled) { - HytaleLogger.getLogger().at(Level.INFO).log(this.getIdentifier() + " compression now handled by encoder"); - } - - @Deprecated(forRemoval = true) - public boolean isCompressionEnabled() { - return true; - } - - @Nonnull - public abstract String getIdentifier(); - - @Nonnull - public ProtocolVersion getProtocolVersion() { - return this.protocolVersion; - } - - public final void registered(@Nullable PacketHandler oldHandler) { - this.registered = true; - this.registered0(oldHandler); - } - - protected void registered0(@Nullable PacketHandler oldHandler) { - } - - public final void unregistered(@Nullable PacketHandler newHandler) { - this.registered = false; - this.clearTimeout(); - this.unregistered0(newHandler); - } - - protected void unregistered0(@Nullable PacketHandler newHandler) { - } - - public void handle(@Nonnull Packet packet) { - this.accept(packet); - } - - public abstract void accept(@Nonnull Packet var1); - - public void logCloseMessage() { - HytaleLogger.getLogger().at(Level.INFO).log("%s was closed.", this.getIdentifier()); - } - - public void closed(ChannelHandlerContext ctx) { - this.clearTimeout(); - } - - public void setQueuePackets(boolean queuePackets) { - this.queuePackets = queuePackets; - } - - public void tryFlush() { - if (this.queuedPackets.getAndSet(0) > 0) { - this.channel.flush(); - } - } - - public void write(@Nonnull Packet... packets) { - Packet[] cachedPackets = new Packet[packets.length]; - this.handleOutboundAndCachePackets(packets, cachedPackets); - if (this.queuePackets) { - this.channel.write(cachedPackets, this.channel.voidPromise()); - this.queuedPackets.getAndIncrement(); - } else { - this.channel.writeAndFlush(cachedPackets, this.channel.voidPromise()); - } - } - - public void write(@Nonnull Packet[] packets, @Nonnull Packet finalPacket) { - Packet[] cachedPackets = new Packet[packets.length + 1]; - this.handleOutboundAndCachePackets(packets, cachedPackets); - cachedPackets[cachedPackets.length - 1] = this.handleOutboundAndCachePacket(finalPacket); - if (this.queuePackets) { - this.channel.write(cachedPackets, this.channel.voidPromise()); - this.queuedPackets.getAndIncrement(); - } else { - this.channel.writeAndFlush(cachedPackets, this.channel.voidPromise()); - } - } - - @Override - public void write(@Nonnull Packet packet) { - this.writePacket(packet, true); - } - - @Override - public void writeNoCache(@Nonnull Packet packet) { - this.writePacket(packet, false); - } - - public void writePacket(@Nonnull Packet packet, boolean cache) { - if (!PacketAdapters.__handleOutbound(this, packet)) { - Packet toSend; - if (cache) { - toSend = this.handleOutboundAndCachePacket(packet); - } else { - toSend = packet; - } - - if (this.queuePackets) { - this.channel.write(toSend, this.channel.voidPromise()); - this.queuedPackets.getAndIncrement(); - } else { - this.channel.writeAndFlush(toSend, this.channel.voidPromise()); - } - } - } - - private void handleOutboundAndCachePackets(@Nonnull Packet[] packets, @Nonnull Packet[] cachedPackets) { - for (int i = 0; i < packets.length; i++) { - Packet packet = packets[i]; - if (!PacketAdapters.__handleOutbound(this, packet)) { - cachedPackets[i] = this.handleOutboundAndCachePacket(packet); - } - } - } - - @Nonnull - private Packet handleOutboundAndCachePacket(@Nonnull Packet packet) { - return (Packet) (packet instanceof CachedPacket ? packet : CachedPacket.cache(packet)); - } - - public void disconnect(@Nonnull String message) { - this.disconnectReason.setServerDisconnectReason(message); - HytaleLogger.getLogger().at(Level.INFO).log("Disconnecting %s with the message: %s", NettyUtil.formatRemoteAddress(this.channel), message); - this.disconnect0(message); - } - - protected void disconnect0(@Nonnull String message) { - this.channel.writeAndFlush(new Disconnect(message, DisconnectType.Disconnect)).addListener(ProtocolUtil.CLOSE_ON_COMPLETE); - } - - @Nullable - public PacketStatsRecorder getPacketStatsRecorder() { - return this.channel.attr(PacketStatsRecorder.CHANNEL_KEY).get(); - } - - @Nonnull - public PacketHandler.PingInfo getPingInfo(@Nonnull PongType pongType) { - return this.pingInfo[pongType.ordinal()]; - } - - public long getOperationTimeoutThreshold() { - double average = this.getPingInfo(PongType.Tick).getPingMetricSet().getAverage(0); - return PacketHandler.PingInfo.TIME_UNIT.toMillis(Math.round(average * 2.0)) + 3000L; - } - - public void tickPing(float dt) { - this.pingTimer -= dt; - if (this.pingTimer <= 0.0F) { - this.pingTimer = 1.0F; - this.sendPing(); - } - } - - public void sendPing() { - int id = this.pingIdRandom.nextInt(); - Instant nowInstant = Instant.now(); - long nowTimestamp = System.nanoTime(); - - for (PacketHandler.PingInfo info : this.pingInfo) { - info.recordSent(id, nowTimestamp); - } - - this.writeNoCache( - new Ping( - id, - WorldTimeResource.instantToInstantData(nowInstant), - (int) this.getPingInfo(PongType.Raw).getPingMetricSet().getLastValue(), - (int) this.getPingInfo(PongType.Direct).getPingMetricSet().getLastValue(), - (int) this.getPingInfo(PongType.Tick).getPingMetricSet().getLastValue() - ) - ); - } - - public void handlePong(@Nonnull Pong packet) { - this.pingInfo[packet.type.ordinal()].handlePacket(packet); - } - - protected void initStage(@Nonnull String stage, @Nonnull Duration timeout, @Nonnull BooleanSupplier condition) { - NettyUtil.TimeoutContext.init(this.channel, stage, this.getIdentifier()); - this.setStageTimeout(stage, timeout, condition); - } - - protected void enterStage(@Nonnull String stage, @Nonnull Duration timeout, @Nonnull BooleanSupplier condition) { - NettyUtil.TimeoutContext.update(this.channel, stage, this.getIdentifier()); - this.updatePacketTimeout(timeout); - this.setStageTimeout(stage, timeout, condition); - } - - protected void enterStage(@Nonnull String stage, @Nonnull Duration timeout) { - NettyUtil.TimeoutContext.update(this.channel, stage, this.getIdentifier()); - this.updatePacketTimeout(timeout); - } - - protected void continueStage(@Nonnull String stage, @Nonnull Duration timeout, @Nonnull BooleanSupplier condition) { - NettyUtil.TimeoutContext.update(this.channel, stage); - this.updatePacketTimeout(timeout); - this.setStageTimeout(stage, timeout, condition); - } - - private void setStageTimeout(@Nonnull String stageId, @Nonnull Duration timeout, @Nonnull BooleanSupplier meets) { - if (this.timeoutTask != null) { - this.timeoutTask.cancel(false); - } - - if (this instanceof AuthenticationPacketHandler || !(this instanceof PasswordPacketHandler) || this.auth != null) { - logConnectionTimings(this.channel, "Entering stage '" + stageId + "'", Level.FINEST); - long timeoutMillis = timeout.toMillis(); - this.timeoutTask = this.channel - .eventLoop() - .schedule( - () -> { - if (this.channel.isOpen()) { - if (!meets.getAsBoolean()) { - NettyUtil.TimeoutContext context = this.channel.attr(NettyUtil.TimeoutContext.KEY).get(); - String duration = context != null ? FormatUtil.nanosToString(System.nanoTime() - context.connectionStartNs()) : "unknown"; - HytaleLogger.getLogger() - .at(Level.WARNING) - .log("Stage timeout for %s at stage '%s' after %s connected", this.getIdentifier(), stageId, duration); - this.disconnect("Either you took too long to login or we took too long to process your request! Retry again in a moment."); - } - } - }, - timeoutMillis, - TimeUnit.MILLISECONDS - ); - } - } - - private void updatePacketTimeout(@Nonnull Duration timeout) { - this.channel.attr(ProtocolUtil.PACKET_TIMEOUT_KEY).set(timeout); - } - - protected void clearTimeout() { - if (this.timeoutTask != null) { - this.timeoutTask.cancel(false); - } - - if (this.clientReadyForChunksFuture != null) { - this.clientReadyForChunksFuture.cancel(true); - this.clientReadyForChunksFuture = null; - this.clientReadyForChunksFutureStack = null; - } - } - - @Nullable - public PlayerAuthentication getAuth() { - return this.auth; - } - - public boolean stillActive() { - return this.channel.isActive(); - } - - public int getQueuedPacketsCount() { - return this.queuedPackets.get(); - } - - public boolean isLocalConnection() { - SocketAddress socketAddress; - if (this.channel instanceof QuicStreamChannel quicStreamChannel) { - socketAddress = quicStreamChannel.parent().remoteSocketAddress(); - } else { - socketAddress = this.channel.remoteAddress(); - } - - if (socketAddress instanceof InetSocketAddress) { - InetAddress address = ((InetSocketAddress) socketAddress).getAddress(); - return NetworkUtil.addressMatchesAny(address, NetworkUtil.AddressType.ANY_LOCAL, NetworkUtil.AddressType.LOOPBACK); - } else { - return socketAddress instanceof DomainSocketAddress || socketAddress instanceof LocalAddress; - } - } - - public boolean isLANConnection() { - SocketAddress socketAddress; - if (this.channel instanceof QuicStreamChannel quicStreamChannel) { - socketAddress = quicStreamChannel.parent().remoteSocketAddress(); - } else { - socketAddress = this.channel.remoteAddress(); - } - - if (socketAddress instanceof InetSocketAddress) { - InetAddress address = ((InetSocketAddress) socketAddress).getAddress(); - return NetworkUtil.addressMatchesAny(address); - } else { - return socketAddress instanceof DomainSocketAddress || socketAddress instanceof LocalAddress; - } - } - - @Nonnull - public PacketHandler.DisconnectReason getDisconnectReason() { - return this.disconnectReason; - } - - public void setClientReadyForChunksFuture(@Nonnull CompletableFuture clientReadyFuture) { - if (this.clientReadyForChunksFuture != null) { - throw new IllegalStateException("Tried to hook client ready but something is already waiting for it!", this.clientReadyForChunksFutureStack); - } else { - HytaleLogger.getLogger().at(Level.WARNING).log("%s Added future for ClientReady packet?", this.getIdentifier()); - this.clientReadyForChunksFutureStack = new Throwable(); - this.clientReadyForChunksFuture = clientReadyFuture; - } - } - - @Nullable - public CompletableFuture getClientReadyForChunksFuture() { - return this.clientReadyForChunksFuture; - } - - public static void logConnectionTimings(@Nonnull Channel channel, @Nonnull String message, @Nonnull Level level) { - Attribute loginStartAttribute = channel.attr(LOGIN_START_ATTRIBUTE_KEY); - long now = System.nanoTime(); - Long before = loginStartAttribute.getAndSet(now); - NettyUtil.TimeoutContext context = channel.attr(NettyUtil.TimeoutContext.KEY).get(); - String identifier = context != null ? context.playerIdentifier() : NettyUtil.formatRemoteAddress(channel); - if (before == null) { - LOGIN_TIMING_LOGGER.at(level).log("[%s] %s", identifier, message); - } else { - LOGIN_TIMING_LOGGER.at(level).log("[%s] %s took %s", identifier, message, LazyArgs.lazy(() -> FormatUtil.nanosToString(now - before))); - } - } - - static { - LOGIN_TIMING_LOGGER.setLevel(Level.ALL); - } - - public static class DisconnectReason { - @Nullable - private String serverDisconnectReason; - @Nullable - private DisconnectType clientDisconnectType; - - protected DisconnectReason() { - } - - @Nullable - public String getServerDisconnectReason() { - return this.serverDisconnectReason; - } - - public void setServerDisconnectReason(String serverDisconnectReason) { - this.serverDisconnectReason = serverDisconnectReason; - this.clientDisconnectType = null; - } - - @Nullable - public DisconnectType getClientDisconnectType() { - return this.clientDisconnectType; - } - - public void setClientDisconnectType(DisconnectType clientDisconnectType) { - this.clientDisconnectType = clientDisconnectType; - this.serverDisconnectReason = null; - } - - @Nonnull - @Override - public String toString() { - return "DisconnectReason{serverDisconnectReason='" + this.serverDisconnectReason + "', clientDisconnectType=" + this.clientDisconnectType + "}"; - } - } - - public static class PingInfo { - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register("PingType", pingInfo -> pingInfo.pingType, new EnumCodec<>(PongType.class)) - .register("PingMetrics", PacketHandler.PingInfo::getPingMetricSet, HistoricMetric.METRICS_CODEC) - .register("PacketQueueMin", pingInfo -> pingInfo.packetQueueMetric.getMin(), Codec.LONG) - .register("PacketQueueAvg", pingInfo -> pingInfo.packetQueueMetric.getAverage(), Codec.DOUBLE) - .register("PacketQueueMax", pingInfo -> pingInfo.packetQueueMetric.getMax(), Codec.LONG); - public static final TimeUnit TIME_UNIT = TimeUnit.MICROSECONDS; - public static final int ONE_SECOND_INDEX = 0; - public static final int ONE_MINUTE_INDEX = 1; - public static final int FIVE_MINUTE_INDEX = 2; - public static final double PERCENTILE = 0.99F; - public static final int PING_FREQUENCY = 1; - public static final TimeUnit PING_FREQUENCY_UNIT = TimeUnit.SECONDS; - public static final int PING_FREQUENCY_MILLIS = 1000; - public static final int PING_HISTORY_MILLIS = 15000; - public static final int PING_HISTORY_LENGTH = 15; - protected final PongType pingType; - protected final Lock queueLock = new ReentrantLock(); - protected final IntPriorityQueue pingIdQueue = new IntArrayFIFOQueue(15); - protected final LongPriorityQueue pingTimestampQueue = new LongArrayFIFOQueue(15); - protected final Lock pingLock = new ReentrantLock(); - @Nonnull - protected final HistoricMetric pingMetricSet; - protected final Metric packetQueueMetric = new Metric(); - - public PingInfo(PongType pingType) { - this.pingType = pingType; - this.pingMetricSet = HistoricMetric.builder(1000L, TimeUnit.MILLISECONDS) - .addPeriod(1L, TimeUnit.SECONDS) - .addPeriod(1L, TimeUnit.MINUTES) - .addPeriod(5L, TimeUnit.MINUTES) - .build(); - } - - protected void recordSent(int id, long timestamp) { - this.queueLock.lock(); - - try { - this.pingIdQueue.enqueue(id); - this.pingTimestampQueue.enqueue(timestamp); - } finally { - this.queueLock.unlock(); - } - } - - protected void handlePacket(@Nonnull Pong packet) { - if (packet.type != this.pingType) { - throw new IllegalArgumentException("Got packet for " + packet.type + " but expected " + this.pingType); - } else { - this.queueLock.lock(); - - int nextIdToHandle; - long sentTimestamp; - try { - nextIdToHandle = this.pingIdQueue.dequeueInt(); - sentTimestamp = this.pingTimestampQueue.dequeueLong(); - } finally { - this.queueLock.unlock(); - } - - if (packet.id != nextIdToHandle) { - throw new IllegalArgumentException(String.valueOf(packet.id)); - } else { - long nanoTime = System.nanoTime(); - long pingValue = nanoTime - sentTimestamp; - if (pingValue <= 0L) { - throw new IllegalArgumentException(String.format("Ping must be received after its sent! %s", pingValue)); - } else { - this.pingLock.lock(); - - try { - this.pingMetricSet.add(nanoTime, TIME_UNIT.convert(pingValue, TimeUnit.NANOSECONDS)); - this.packetQueueMetric.add(packet.packetQueueSize); - } finally { - this.pingLock.unlock(); - } - } - } - } - } - - public PongType getPingType() { - return this.pingType; - } - - @Nonnull - public Metric getPacketQueueMetric() { - return this.packetQueueMetric; - } - - @Nonnull - public HistoricMetric getPingMetricSet() { - return this.pingMetricSet; - } - - public void clear() { - this.pingLock.lock(); - - try { - this.packetQueueMetric.clear(); - this.pingMetricSet.clear(); - } finally { - this.pingLock.unlock(); - } - } - } -} diff --git a/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java b/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java index 8d6b6b68..5589f03d 100644 --- a/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java +++ b/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java @@ -20,21 +20,25 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.SocketProtocolFamily; import io.netty.channel.socket.nio.NioChannelOption; -import io.netty.handler.codec.quic.InsecureQuicTokenHandler; +import io.netty.handler.codec.quic.QLogConfiguration; import io.netty.handler.codec.quic.QuicChannel; +import io.netty.handler.codec.quic.QuicChannelOption; import io.netty.handler.codec.quic.QuicCongestionControlAlgorithm; import io.netty.handler.codec.quic.QuicServerCodecBuilder; import io.netty.handler.codec.quic.QuicSslContext; import io.netty.handler.codec.quic.QuicSslContextBuilder; import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SniCompletionEvent; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import io.netty.util.AttributeKey; +import io.netty.util.Mapping; import jdk.net.ExtendedSocketOptions; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.net.ssl.SSLEngine; +import java.lang.reflect.Field; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetSocketAddress; @@ -49,6 +53,7 @@ public class QUICTransport implements Transport { private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); public static final AttributeKey CLIENT_CERTIFICATE_ATTR = AttributeKey.valueOf("CLIENT_CERTIFICATE"); public static final AttributeKey ALPN_REJECT_ERROR_CODE_ATTR = AttributeKey.valueOf("ALPN_REJECT_ERROR_CODE"); + public static final AttributeKey SNI_HOSTNAME_ATTR = AttributeKey.valueOf("SNI_HOSTNAME"); @Nonnull private final EventLoopGroup workerGroup = NettyUtil.getEventLoopGroup("ServerWorkerGroup"); private final Bootstrap bootstrapIpv4; @@ -59,18 +64,31 @@ public class QUICTransport implements Transport { try { ssc = new SelfSignedCertificate("localhost"); - } catch (CertificateException var5) { - throw new RuntimeException(var5); + } catch (CertificateException var7) { + throw new RuntimeException(var7); } ServerAuthManager.getInstance().setServerCertificate(ssc.cert()); LOGGER.at(Level.INFO).log("Server certificate registered for mutual auth, fingerprint: %s", CertificateUtil.computeCertificateFingerprint(ssc.cert())); - QuicSslContext sslContext = QuicSslContextBuilder.forServer(ssc.key(), null, ssc.cert()) + QuicSslContext baseSslContext = QuicSslContextBuilder.forServer(ssc.key(), null, ssc.cert()) .applicationProtocols("hytale/2", "hytale/1") .earlyData(false) .clientAuth(ClientAuth.REQUIRE) .trustManager(InsecureTrustManagerFactory.INSTANCE) .build(); + + QuicSslContext sslContext; + try { + QuicSslContextBuilder builder = QuicSslContextBuilder.forServer(ssc.key(), null, ssc.cert()).earlyData(false).clientAuth(ClientAuth.REQUIRE); + Field field = builder.getClass().getDeclaredField("mapping"); + field.setAccessible(true); + field.set(builder, (Mapping) sni -> baseSslContext); + sslContext = builder.build(); + } catch (IllegalAccessException | NoSuchFieldException var6) { + LOGGER.at(Level.WARNING).withCause(var6).log("Failed to set SNI mapping via reflection, SNI support disabled"); + sslContext = baseSslContext; + } + NettyUtil.ReflectiveChannelFactory channelFactoryIpv4 = NettyUtil.getDatagramChannelFactory(SocketProtocolFamily.INET); LOGGER.at(Level.INFO).log("Using IPv4 Datagram Channel: %s...", channelFactoryIpv4.getSimpleName()); this.bootstrapIpv4 = new Bootstrap() @@ -139,7 +157,8 @@ public class QUICTransport implements Transport { Duration playTimeout = HytaleServer.get().getConfig().getConnectionTimeouts().getPlay(); ChannelHandler quicHandler = new QuicServerCodecBuilder() .sslContext(this.sslContext) - .tokenHandler(InsecureQuicTokenHandler.INSTANCE) + .tokenHandler(null) + .activeMigration(false) .maxIdleTimeout(playTimeout.toMillis(), TimeUnit.MILLISECONDS) .ackDelayExponent(3L) .initialMaxData(524288L) @@ -150,6 +169,7 @@ public class QUICTransport implements Transport { .initialMaxStreamsBidirectional(1L) .discoverPmtu(true) .congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR) + .option(QuicChannelOption.QLOG, System.getProperty("hytale.qlog") != null ? new QLogConfiguration(".", "hytale-server-quic-qlogs", "") : null) .handler( new ChannelInboundHandlerAdapter() { @Override @@ -157,18 +177,34 @@ public class QUICTransport implements Transport { return true; } + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof SniCompletionEvent sniEvent) { + ctx.channel().attr(QUICTransport.SNI_HOSTNAME_ATTR).set(sniEvent.hostname()); + } + + super.userEventTriggered(ctx, evt); + } + @Override public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception { QuicChannel channel = (QuicChannel) ctx.channel(); + String sni = channel.attr(QUICTransport.SNI_HOSTNAME_ATTR).get(); QUICTransport.LOGGER .at(Level.INFO) - .log("Received connection from %s to %s", NettyUtil.formatRemoteAddress(channel), NettyUtil.formatLocalAddress(channel)); + .log("Received connection from %s to %s (SNI: %s)", NettyUtil.formatRemoteAddress(channel), NettyUtil.formatLocalAddress(channel), sni); String negotiatedAlpn = channel.sslEngine().getApplicationProtocol(); int negotiatedVersion = this.parseProtocolVersion(negotiatedAlpn); if (negotiatedVersion < 2) { QUICTransport.LOGGER .at(Level.INFO) - .log("Marking connection from %s for rejection: ALPN %s < required %d", NettyUtil.formatRemoteAddress(channel), negotiatedAlpn, 2); + .log( + "Marking connection from %s (SNI: %s) for rejection: ALPN %s < required %d", + NettyUtil.formatRemoteAddress(channel), + sni, + negotiatedAlpn, + 2 + ); channel.attr(QUICTransport.ALPN_REJECT_ERROR_CODE_ATTR).set(5); } @@ -176,7 +212,7 @@ public class QUICTransport implements Transport { if (clientCert == null) { QUICTransport.LOGGER .at(Level.WARNING) - .log("Connection rejected: no client certificate from %s", NettyUtil.formatRemoteAddress(channel)); + .log("Connection rejected: no client certificate from %s (SNI: %s)", NettyUtil.formatRemoteAddress(channel), sni); ProtocolUtil.closeConnection(channel); } else { channel.attr(QUICTransport.CLIENT_CERTIFICATE_ATTR).set(clientCert); diff --git a/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java index c42fa94a..8aa39b3f 100644 --- a/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java +++ b/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java @@ -24,6 +24,7 @@ import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.protocol.ComponentUpdate; import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.EntityEffectsUpdate; import com.hypixel.hytale.protocol.packets.entities.EntityUpdates; import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; import com.hypixel.hytale.server.core.modules.entity.EntityModule; @@ -55,54 +56,69 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.StampedLock; public class EntityTrackerSystems { + @Nonnull public static final SystemGroup FIND_VISIBLE_ENTITIES_GROUP = EntityStore.REGISTRY.registerSystemGroup(); + @Nonnull public static final SystemGroup QUEUE_UPDATE_GROUP = EntityStore.REGISTRY.registerSystemGroup(); public EntityTrackerSystems() { } public static boolean despawnAll(@Nonnull Ref viewerRef, @Nonnull Store store) { - EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); - if (viewer == null) { + if (!viewerRef.isValid()) { return false; } else { - int networkId = viewer.sent.removeInt(viewerRef); - EntityUpdates packet = new EntityUpdates(); - packet.removed = viewer.sent.values().toIntArray(); - viewer.packetReceiver.writeNoCache(packet); - clear(viewerRef, store); - viewer.sent.put(viewerRef, networkId); - return true; + EntityTrackerSystems.EntityViewer entityViewerComponent = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent == null) { + return false; + } else { + int networkId = entityViewerComponent.sent.removeInt(viewerRef); + EntityUpdates packet = new EntityUpdates(); + packet.removed = entityViewerComponent.sent.values().toIntArray(); + entityViewerComponent.packetReceiver.writeNoCache(packet); + clear(viewerRef, store); + entityViewerComponent.sent.put(viewerRef, networkId); + return true; + } } } public static boolean clear(@Nonnull Ref viewerRef, @Nonnull Store store) { - EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); - if (viewer == null) { + if (!viewerRef.isValid()) { return false; } else { - for (Ref ref : viewer.sent.keySet()) { - EntityTrackerSystems.Visible visible = store.getComponent(ref, EntityTrackerSystems.Visible.getComponentType()); - if (visible != null) { - visible.visibleTo.remove(viewerRef); + EntityTrackerSystems.EntityViewer entityViewerComponent = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent == null) { + return false; + } else { + for (Ref ref : entityViewerComponent.sent.keySet()) { + if (ref != null && ref.isValid()) { + EntityTrackerSystems.Visible visibleComponent = store.getComponent(ref, EntityTrackerSystems.Visible.getComponentType()); + if (visibleComponent != null) { + visibleComponent.visibleTo.remove(viewerRef); + } + } } - } - viewer.sent.clear(); - return true; + entityViewerComponent.sent.clear(); + return true; + } } } public static class AddToVisible extends EntityTickingSystem { + @Nonnull public static final Set> DEPENDENCIES = Collections.singleton( new SystemDependency<>(Order.AFTER, EntityTrackerSystems.EnsureVisibleComponent.class) ); + @Nonnull private final ComponentType entityViewerComponentType; + @Nonnull private final ComponentType visibleComponentType; public AddToVisible( - ComponentType entityViewerComponentType, - ComponentType visibleComponentType + @Nonnull ComponentType entityViewerComponentType, + @Nonnull ComponentType visibleComponentType ) { this.entityViewerComponentType = entityViewerComponentType; this.visibleComponentType = visibleComponentType; @@ -132,23 +148,32 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - Ref viewerRef = archetypeChunk.getReferenceTo(index); - EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.entityViewerComponentType); + Ref ref = archetypeChunk.getReferenceTo(index); + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); - for (Ref ref : viewer.visible) { - commandBuffer.getComponent(ref, this.visibleComponentType).addViewerParallel(viewerRef, viewer); + assert entityViewerComponent != null; + + for (Ref vislbleRef : entityViewerComponent.visible) { + if (vislbleRef != null && vislbleRef.isValid()) { + EntityTrackerSystems.Visible visibleComponent = commandBuffer.getComponent(vislbleRef, this.visibleComponentType); + if (visibleComponent != null) { + visibleComponent.addViewerParallel(ref, entityViewerComponent); + } + } } } } public static class ClearEntityViewers extends EntityTickingSystem { + @Nonnull public static final Set> DEPENDENCIES = Collections.singleton( new SystemGroupDependency<>(Order.BEFORE, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP) ); - private final ComponentType componentType; + @Nonnull + private final ComponentType entityViewerComponentType; - public ClearEntityViewers(ComponentType componentType) { - this.componentType = componentType; + public ClearEntityViewers(@Nonnull ComponentType entityViewerComponentType) { + this.entityViewerComponentType = entityViewerComponentType; } @Nonnull @@ -159,7 +184,7 @@ public class EntityTrackerSystems { @Override public Query getQuery() { - return this.componentType; + return this.entityViewerComponentType; } @Override @@ -175,22 +200,27 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType); - viewer.visible.clear(); - viewer.lodExcludedCount = 0; - viewer.hiddenCount = 0; + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + assert entityViewerComponent != null; + + entityViewerComponent.visible.clear(); + entityViewerComponent.lodExcludedCount = 0; + entityViewerComponent.hiddenCount = 0; } } public static class ClearPreviouslyVisible extends EntityTickingSystem { + @Nonnull public static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearEntityViewers.class), new SystemGroupDependency(Order.AFTER, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP) ); - private final ComponentType componentType; + @Nonnull + private final ComponentType visibleComponentType; - public ClearPreviouslyVisible(ComponentType componentType) { - this.componentType = componentType; + public ClearPreviouslyVisible(@Nonnull ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; } @Nonnull @@ -201,7 +231,7 @@ public class EntityTrackerSystems { @Override public Query getQuery() { - return this.componentType; + return this.visibleComponentType; } @Override @@ -217,24 +247,29 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); - Map, EntityTrackerSystems.EntityViewer> oldVisibleTo = visible.previousVisibleTo; - visible.previousVisibleTo = visible.visibleTo; - visible.visibleTo = oldVisibleTo; - visible.visibleTo.clear(); - visible.newlyVisibleTo.clear(); + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + Map, EntityTrackerSystems.EntityViewer> oldVisibleTo = visibleComponent.previousVisibleTo; + visibleComponent.previousVisibleTo = visibleComponent.visibleTo; + visibleComponent.visibleTo = oldVisibleTo; + visibleComponent.visibleTo.clear(); + visibleComponent.newlyVisibleTo.clear(); } } public static class CollectVisible extends EntityTickingSystem { - private final ComponentType componentType; + @Nonnull + private final ComponentType entityViewerComponentType; + @Nonnull private final Query query; @Nonnull private final Set> dependencies; - public CollectVisible(ComponentType componentType) { - this.componentType = componentType; - this.query = Archetype.of(componentType, TransformComponent.getComponentType()); + public CollectVisible(@Nonnull ComponentType entityViewerComponentType) { + this.entityViewerComponentType = entityViewerComponentType; + this.query = Archetype.of(entityViewerComponentType, TransformComponent.getComponentType()); this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, NetworkSendableSpatialSystem.class)); } @@ -268,14 +303,20 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); - Vector3d position = transform.getPosition(); - EntityTrackerSystems.EntityViewer entityViewer = archetypeChunk.getComponent(index, this.componentType); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + assert entityViewerComponent != null; + SpatialStructure> spatialStructure = store.getResource(EntityModule.get().getNetworkSendableSpatialResourceType()) .getSpatialStructure(); ObjectList> results = SpatialResource.getThreadLocalReferenceList(); - spatialStructure.collect(position, entityViewer.viewRadiusBlocks, results); - entityViewer.visible.addAll(results); + spatialStructure.collect(position, entityViewerComponent.viewRadiusBlocks, results); + entityViewerComponent.visible.addAll(results); } } @@ -325,17 +366,17 @@ public class EntityTrackerSystems { assert visibleComponent != null; - Ref entityRef = archetypeChunk.getReferenceTo(index); EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, this.effectControllerComponentType); assert effectControllerComponent != null; + Ref ref = archetypeChunk.getReferenceTo(index); if (!visibleComponent.newlyVisibleTo.isEmpty()) { - queueFullUpdate(entityRef, effectControllerComponent, visibleComponent.newlyVisibleTo); + queueFullUpdate(ref, effectControllerComponent, visibleComponent.newlyVisibleTo); } if (effectControllerComponent.consumeNetworkOutdated()) { - queueUpdatesFor(entityRef, effectControllerComponent, visibleComponent.visibleTo, visibleComponent.newlyVisibleTo); + queueUpdatesFor(ref, effectControllerComponent, visibleComponent.visibleTo, visibleComponent.newlyVisibleTo); } } @@ -344,8 +385,7 @@ public class EntityTrackerSystems { @Nonnull EffectControllerComponent effectControllerComponent, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo ) { - ComponentUpdate update = new ComponentUpdate(); - update.type = ComponentUpdateType.EntityEffects; + EntityEffectsUpdate update = new EntityEffectsUpdate(); update.entityEffectUpdates = effectControllerComponent.createInitUpdates(); for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { @@ -359,8 +399,7 @@ public class EntityTrackerSystems { @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, @Nonnull Map, EntityTrackerSystems.EntityViewer> exclude ) { - ComponentUpdate update = new ComponentUpdate(); - update.type = ComponentUpdateType.EntityEffects; + EntityEffectsUpdate update = new EntityEffectsUpdate(); update.entityEffectUpdates = effectControllerComponent.consumeChanges(); if (!exclude.isEmpty()) { for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { @@ -377,15 +416,18 @@ public class EntityTrackerSystems { } public static class EnsureVisibleComponent extends EntityTickingSystem { + @Nonnull public static final Set> DEPENDENCIES = Collections.singleton( new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearPreviouslyVisible.class) ); + @Nonnull private final ComponentType entityViewerComponentType; + @Nonnull private final ComponentType visibleComponentType; public EnsureVisibleComponent( - ComponentType entityViewerComponentType, - ComponentType visibleComponentType + @Nonnull ComponentType entityViewerComponentType, + @Nonnull ComponentType visibleComponentType ) { this.entityViewerComponentType = entityViewerComponentType; this.visibleComponentType = visibleComponentType; @@ -415,9 +457,13 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - for (Ref ref : archetypeChunk.getComponent(index, this.entityViewerComponentType).visible) { - if (!commandBuffer.getArchetype(ref).contains(this.visibleComponentType)) { - commandBuffer.ensureComponent(ref, this.visibleComponentType); + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + assert entityViewerComponent != null; + + for (Ref visibleRef : entityViewerComponent.visible) { + if (visibleRef != null && visibleRef.isValid() && !commandBuffer.getArchetype(visibleRef).contains(this.visibleComponentType)) { + commandBuffer.ensureComponent(visibleRef, this.visibleComponentType); } } } @@ -483,9 +529,13 @@ public class EntityTrackerSystems { private static final float VISIBILITY_UPDATE_INTERVAL = 0.2f; public int viewRadiusBlocks; + @Nonnull public IPacketReceiver packetReceiver; + @Nonnull public Set> visible; + @Nonnull public Map, EntityTrackerSystems.EntityUpdate> updates; + @Nonnull public Object2IntMap> sent; public int lodExcludedCount; public int hiddenCount; @@ -495,7 +545,7 @@ public class EntityTrackerSystems { return EntityModule.get().getEntityViewerComponentType(); } - public EntityViewer(int viewRadiusBlocks, IPacketReceiver packetReceiver) { + public EntityViewer(int viewRadiusBlocks, @Nonnull IPacketReceiver packetReceiver) { this.viewRadiusBlocks = viewRadiusBlocks; this.packetReceiver = packetReceiver; this.visible = new ObjectOpenHashSet<>(); @@ -524,7 +574,7 @@ public class EntityTrackerSystems { return new EntityTrackerSystems.EntityViewer(this); } - public void queueRemove(Ref ref, ComponentUpdateType type) { + public void queueRemove(@Nonnull Ref ref, @Nonnull ComponentUpdateType type) { if (!this.visible.contains(ref)) { throw new IllegalArgumentException("Entity is not visible!"); } else { @@ -532,7 +582,7 @@ public class EntityTrackerSystems { } } - public void queueUpdate(Ref ref, ComponentUpdate update) { + public void queueUpdate(@Nonnull Ref ref, @Nonnull ComponentUpdate update) { if (!this.visible.contains(ref)) { throw new IllegalArgumentException("Entity is not visible!"); } else { @@ -567,14 +617,16 @@ public class EntityTrackerSystems { } public static class RemoveEmptyVisibleComponent extends EntityTickingSystem { + @Nonnull public static final Set> DEPENDENCIES = Set.of( new SystemDependency<>(Order.AFTER, EntityTrackerSystems.AddToVisible.class), new SystemGroupDependency(Order.BEFORE, EntityTrackerSystems.QUEUE_UPDATE_GROUP) ); - private final ComponentType componentType; + @Nonnull + private final ComponentType visibleComponentType; - public RemoveEmptyVisibleComponent(ComponentType componentType) { - this.componentType = componentType; + public RemoveEmptyVisibleComponent(@Nonnull ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; } @Nonnull @@ -585,7 +637,7 @@ public class EntityTrackerSystems { @Override public Query getQuery() { - return this.componentType; + return this.visibleComponentType; } @Override @@ -601,22 +653,27 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - if (archetypeChunk.getComponent(index, this.componentType).visibleTo.isEmpty()) { - commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.componentType); + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + if (visibleComponent.visibleTo.isEmpty()) { + commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.visibleComponentType); } } } public static class RemoveVisibleComponent extends HolderSystem { - private final ComponentType componentType; + @Nonnull + private final ComponentType visibleComponentType; - public RemoveVisibleComponent(ComponentType componentType) { - this.componentType = componentType; + public RemoveVisibleComponent(@Nonnull ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; } @Override public Query getQuery() { - return this.componentType; + return this.visibleComponentType; } @Override @@ -625,18 +682,22 @@ public class EntityTrackerSystems { @Override public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { - holder.removeComponent(this.componentType); + holder.removeComponent(this.visibleComponentType); } } public static class SendPackets extends EntityTickingSystem { + @Nonnull public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull public static final ThreadLocal INT_LIST_THREAD_LOCAL = ThreadLocal.withInitial(IntArrayList::new); + @Nonnull public static final Set> DEPENDENCIES = Set.of(new SystemGroupDependency<>(Order.AFTER, EntityTrackerSystems.QUEUE_UPDATE_GROUP)); - private final ComponentType componentType; + @Nonnull + private final ComponentType entityViewerComponentType; - public SendPackets(ComponentType componentType) { - this.componentType = componentType; + public SendPackets(@Nonnull ComponentType entityViewerComponentType) { + this.entityViewerComponentType = entityViewerComponentType; } @Nullable @@ -653,7 +714,7 @@ public class EntityTrackerSystems { @Override public Query getQuery() { - return this.componentType; + return this.entityViewerComponentType; } @Override @@ -669,61 +730,70 @@ public class EntityTrackerSystems { @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { - EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType); + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + assert entityViewerComponent != null; + IntList removedEntities = INT_LIST_THREAD_LOCAL.get(); removedEntities.clear(); - int before = viewer.updates.size(); - viewer.updates.entrySet().removeIf(v -> !v.getKey().isValid()); - if (before != viewer.updates.size()) { - LOGGER.atWarning().log("Removed %d invalid updates for removed entities.", before - viewer.updates.size()); + int before = entityViewerComponent.updates.size(); + entityViewerComponent.updates.entrySet().removeIf(v -> !v.getKey().isValid()); + if (before != entityViewerComponent.updates.size()) { + LOGGER.atWarning().log("Removed %d invalid updates for removed entities.", before - entityViewerComponent.updates.size()); } - ObjectIterator>> iterator = viewer.sent.object2IntEntrySet().iterator(); + ObjectIterator>> iterator = entityViewerComponent.sent + .object2IntEntrySet() + .iterator(); while (iterator.hasNext()) { it.unimi.dsi.fastutil.objects.Object2IntMap.Entry> entry = iterator.next(); Ref ref = entry.getKey(); - if (!ref.isValid() || !viewer.visible.contains(ref)) { + if (ref == null || !ref.isValid() || !entityViewerComponent.visible.contains(ref)) { removedEntities.add(entry.getIntValue()); iterator.remove(); - if (viewer.updates.remove(ref) != null) { + if (entityViewerComponent.updates.remove(ref) != null) { LOGGER.atSevere().log("Entity can't be removed and also receive an update! " + ref); } } } - if (!removedEntities.isEmpty() || !viewer.updates.isEmpty()) { - Iterator> iteratorx = viewer.updates.keySet().iterator(); + if (!removedEntities.isEmpty() || !entityViewerComponent.updates.isEmpty()) { + Iterator> iteratorx = entityViewerComponent.updates.keySet().iterator(); while (iteratorx.hasNext()) { Ref ref = iteratorx.next(); - if (!ref.isValid() || ref.getStore() != store) { + if (ref == null || !ref.isValid() || ref.getStore() != store) { iteratorx.remove(); - } else if (!viewer.sent.containsKey(ref)) { - int networkId = commandBuffer.getComponent(ref, NetworkId.getComponentType()).getId(); + } else if (!entityViewerComponent.sent.containsKey(ref)) { + NetworkId networkIdComponent = commandBuffer.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int networkId = networkIdComponent.getId(); if (networkId == -1) { throw new IllegalArgumentException("Invalid entity network id: " + ref); } - viewer.sent.put(ref, networkId); + entityViewerComponent.sent.put(ref, networkId); } } EntityUpdates packet = new EntityUpdates(); packet.removed = !removedEntities.isEmpty() ? removedEntities.toIntArray() : null; - packet.updates = new com.hypixel.hytale.protocol.EntityUpdate[viewer.updates.size()]; + packet.updates = new com.hypixel.hytale.protocol.EntityUpdate[entityViewerComponent.updates.size()]; int i = 0; - for (Entry, EntityTrackerSystems.EntityUpdate> entry : viewer.updates.entrySet()) { + for (Entry, EntityTrackerSystems.EntityUpdate> entry : entityViewerComponent.updates.entrySet()) { com.hypixel.hytale.protocol.EntityUpdate entityUpdate = packet.updates[i++] = new com.hypixel.hytale.protocol.EntityUpdate(); - entityUpdate.networkId = viewer.sent.getInt(entry.getKey()); + entityUpdate.networkId = entityViewerComponent.sent.getInt(entry.getKey()); EntityTrackerSystems.EntityUpdate update = entry.getValue(); entityUpdate.removed = update.toRemovedArray(); entityUpdate.updates = update.toUpdatesArray(); } - viewer.updates.clear(); - viewer.packetReceiver.writeNoCache(packet); + entityViewerComponent.updates.clear(); + entityViewerComponent.packetReceiver.writeNoCache(packet); } } } @@ -752,13 +822,13 @@ public class EntityTrackerSystems { return new EntityTrackerSystems.Visible(); } - public void addViewerParallel(Ref ref, EntityTrackerSystems.EntityViewer entityViewer) { + public void addViewerParallel(@Nonnull Ref ref, @Nonnull EntityTrackerSystems.EntityViewer entityViewerComponent) { long stamp = this.lock.writeLock(); try { - this.visibleTo.put(ref, entityViewer); + this.visibleTo.put(ref, entityViewerComponent); if (!this.previousVisibleTo.containsKey(ref)) { - this.newlyVisibleTo.put(ref, entityViewer); + this.newlyVisibleTo.put(ref, entityViewerComponent); } } finally { this.lock.unlockWrite(stamp); diff --git a/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java index 6cdabf6f..a99d42b2 100644 --- a/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java +++ b/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java @@ -13,10 +13,11 @@ 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.math.vector.Vector3d; -import com.hypixel.hytale.protocol.ComponentUpdate; -import com.hypixel.hytale.protocol.ComponentUpdateType; -import com.hypixel.hytale.protocol.Equipment; +import com.hypixel.hytale.protocol.EquipmentUpdate; import com.hypixel.hytale.protocol.ItemArmorSlot; +import com.hypixel.hytale.protocol.ModelUpdate; +import com.hypixel.hytale.protocol.PlayerSkinUpdate; +import com.hypixel.hytale.protocol.PropUpdate; import com.hypixel.hytale.server.core.asset.type.gameplay.PlayerConfig; import com.hypixel.hytale.server.core.entity.Entity; import com.hypixel.hytale.server.core.entity.EntityUtils; @@ -30,6 +31,7 @@ import com.hypixel.hytale.server.core.modules.entity.EntityModule; import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PropComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; @@ -119,24 +121,35 @@ public class LegacyEntityTrackerSystems { } boolean modelOutdated = modelComponent.consumeNetworkOutdated(); + Ref ref = archetypeChunk.getReferenceTo(index); + boolean isProp = store.getComponent(ref, PropComponent.getComponentType()) != null; if (modelOutdated || scaleOutdated) { - queueUpdatesFor(archetypeChunk.getReferenceTo(index), modelComponent, entityScale, visibleComponent.visibleTo); + queueUpdatesFor(ref, modelComponent, entityScale, isProp, visibleComponent.visibleTo); } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { - queueUpdatesFor(archetypeChunk.getReferenceTo(index), modelComponent, entityScale, visibleComponent.newlyVisibleTo); + queueUpdatesFor(ref, modelComponent, entityScale, isProp, visibleComponent.newlyVisibleTo); } } private static void queueUpdatesFor( - Ref ref, @Nullable ModelComponent model, float entityScale, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + Ref ref, + @Nullable ModelComponent model, + float entityScale, + boolean isProp, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo ) { - ComponentUpdate update = new ComponentUpdate(); - update.type = ComponentUpdateType.Model; - update.model = model != null ? model.getModel().toPacket() : null; - update.entityScale = entityScale; + ModelUpdate update = new ModelUpdate(model != null ? model.getModel().toPacket() : null, entityScale); for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { viewer.queueUpdate(ref, update); } + + if (isProp) { + PropUpdate propUpdate = new PropUpdate(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, propUpdate); + } + } } } @@ -198,8 +211,7 @@ public class LegacyEntityTrackerSystems { private static void queueUpdatesFor( Ref ref, @Nonnull PlayerSkinComponent component, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo ) { - ComponentUpdate update = new ComponentUpdate(); - update.type = ComponentUpdateType.PlayerSkin; + PlayerSkinUpdate update = new PlayerSkinUpdate(); update.skin = component.getPlayerSkin(); for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { @@ -261,14 +273,12 @@ public class LegacyEntityTrackerSystems { private static void queueUpdatesFor( @Nonnull Ref ref, @Nonnull LivingEntity entity, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo ) { - ComponentUpdate update = new ComponentUpdate(); - update.type = ComponentUpdateType.Equipment; - update.equipment = new Equipment(); + EquipmentUpdate update = new EquipmentUpdate(); Inventory inventory = entity.getInventory(); ItemContainer armor = inventory.getArmor(); - update.equipment.armorIds = new String[armor.getCapacity()]; - Arrays.fill(update.equipment.armorIds, ""); - armor.forEachWithMeta((slot, itemStack, armorIds) -> armorIds[slot] = itemStack.getItemId(), update.equipment.armorIds); + update.armorIds = new String[armor.getCapacity()]; + Arrays.fill(update.armorIds, ""); + armor.forEachWithMeta((slot, itemStack, armorIds) -> armorIds[slot] = itemStack.getItemId(), update.armorIds); Store store = ref.getStore(); PlayerSettings playerSettings = store.getComponent(ref, PlayerSettings.getComponentType()); if (playerSettings != null) { @@ -278,26 +288,26 @@ public class LegacyEntityTrackerSystems { .getPlayerConfig() .getArmorVisibilityOption(); if (armorVisibilityOption.canHideHelmet() && playerSettings.hideHelmet()) { - update.equipment.armorIds[ItemArmorSlot.Head.ordinal()] = ""; + update.armorIds[ItemArmorSlot.Head.ordinal()] = ""; } if (armorVisibilityOption.canHideCuirass() && playerSettings.hideCuirass()) { - update.equipment.armorIds[ItemArmorSlot.Chest.ordinal()] = ""; + update.armorIds[ItemArmorSlot.Chest.ordinal()] = ""; } if (armorVisibilityOption.canHideGauntlets() && playerSettings.hideGauntlets()) { - update.equipment.armorIds[ItemArmorSlot.Hands.ordinal()] = ""; + update.armorIds[ItemArmorSlot.Hands.ordinal()] = ""; } if (armorVisibilityOption.canHidePants() && playerSettings.hidePants()) { - update.equipment.armorIds[ItemArmorSlot.Legs.ordinal()] = ""; + update.armorIds[ItemArmorSlot.Legs.ordinal()] = ""; } } ItemStack itemInHand = inventory.getItemInHand(); - update.equipment.rightHandItemId = itemInHand != null ? itemInHand.getItemId() : "Empty"; + update.rightHandItemId = itemInHand != null ? itemInHand.getItemId() : "Empty"; ItemStack utilityItem = inventory.getUtilityItem(); - update.equipment.leftHandItemId = utilityItem != null ? utilityItem.getItemId() : "Empty"; + update.leftHandItemId = utilityItem != null ? utilityItem.getItemId() : "Empty"; for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { viewer.queueUpdate(ref, update); diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java b/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java deleted file mode 100644 index 4aa11d13..00000000 --- a/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.hypixel.hytale.server.core.modules.interaction.blocktrack; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.RefSystem; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -import com.hypixel.hytale.server.core.modules.block.BlockModule; -import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; -import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class TrackedPlacement implements Component { - public static final BuilderCodec CODEC = BuilderCodec.builder(TrackedPlacement.class, TrackedPlacement::new) - .append(new KeyedCodec<>("BlockName", Codec.STRING), (o, v) -> o.blockName = v, o -> o.blockName) - .add() - .build(); - private String blockName; - - public static ComponentType getComponentType() { - return InteractionModule.get().getTrackedPlacementComponentType(); - } - - public TrackedPlacement() { - } - - public TrackedPlacement(String blockName) { - this.blockName = blockName; - } - - @Nullable - @Override - public Component clone() { - return new TrackedPlacement(this.blockName); - } - - public static class OnAddRemove extends RefSystem { - private static final ComponentType COMPONENT_TYPE = TrackedPlacement.getComponentType(); - private static final ResourceType BLOCK_COUNTER_RESOURCE_TYPE = BlockCounter.getResourceType(); - - public OnAddRemove() { - } - - @Override - public void onEntityAdded( - @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - if (reason == AddReason.SPAWN) { - BlockModule.BlockStateInfo blockInfo = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); - - assert blockInfo != null; - - Ref chunkRef = blockInfo.getChunkRef(); - if (chunkRef != null && chunkRef.isValid()) { - BlockChunk blockChunk = commandBuffer.getComponent(chunkRef, BlockChunk.getComponentType()); - - assert blockChunk != null; - - int blockId = blockChunk.getBlock( - ChunkUtil.xFromBlockInColumn(blockInfo.getIndex()), - ChunkUtil.yFromBlockInColumn(blockInfo.getIndex()), - ChunkUtil.zFromBlockInColumn(blockInfo.getIndex()) - ); - BlockType blockType = BlockType.getAssetMap().getAsset(blockId); - if (blockType != null) { - BlockCounter counter = commandBuffer.getResource(BLOCK_COUNTER_RESOURCE_TYPE); - String blockName = blockType.getId(); - counter.trackBlock(blockName); - TrackedPlacement tracked = commandBuffer.getComponent(ref, COMPONENT_TYPE); - - assert tracked != null; - - tracked.blockName = blockName; - } - } - } - } - - @Override - public void onEntityRemove( - @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - if (reason == RemoveReason.REMOVE) { - TrackedPlacement tracked = commandBuffer.getComponent(ref, COMPONENT_TYPE); - - // HyFix #11: Add null check for tracked component - if (tracked == null) { - System.out.println("[HyFix] WARNING: TrackedPlacement component was null on entity remove - BlockCounter not decremented"); - return; - } - - String blockName = tracked.blockName; - - // HyFix #11: Add null/empty check for blockName - if (blockName == null || blockName.isEmpty()) { - System.out.println("[HyFix] WARNING: TrackedPlacement.blockName was null/empty on entity remove - BlockCounter not decremented"); - return; - } - - BlockCounter counter = commandBuffer.getResource(BLOCK_COUNTER_RESOURCE_TYPE); - counter.untrackBlock(blockName); - } - } - - @Nullable - @Override - public Query getQuery() { - return COMPONENT_TYPE; - } - } -} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java index 92849582..c3db4f7f 100644 --- a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java @@ -84,7 +84,8 @@ public class PlaceFluidInteraction extends SimpleBlockInteraction { Fluid fluid = Fluid.getAssetMap().getAsset(fluidIndex); Vector3i target = targetBlock; BlockType targetBlockType = world.getBlockType(targetBlock); - if (FluidTicker.isSolid(targetBlockType)) { + FluidTicker ticker = fluid.getTicker(); + if (!ticker.canOccupySolidBlocks() && FluidTicker.isSolid(targetBlockType)) { target = targetBlock.clone(); BlockFace face = BlockFace.fromProtocolFace(context.getClientState().blockFace); target.add(face.getDirection()); diff --git a/src/com/hypixel/hytale/server/core/universe/Universe.java b/src/com/hypixel/hytale/server/core/universe/Universe.java deleted file mode 100644 index 4e74c2b6..00000000 --- a/src/com/hypixel/hytale/server/core/universe/Universe.java +++ /dev/null @@ -1,980 +0,0 @@ -package com.hypixel.hytale.server.core.universe; - -import com.hypixel.hytale.assetstore.AssetRegistry; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.codec.lookup.Priority; -import com.hypixel.hytale.common.plugin.PluginIdentifier; -import com.hypixel.hytale.common.plugin.PluginManifest; -import com.hypixel.hytale.common.semver.SemverRange; -import com.hypixel.hytale.common.util.CompletableFutureUtil; -import com.hypixel.hytale.component.ComponentRegistryProxy; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.event.EventRegistry; -import com.hypixel.hytale.event.IEventDispatcher; -import com.hypixel.hytale.math.vector.Transform; -import com.hypixel.hytale.metrics.MetricProvider; -import com.hypixel.hytale.metrics.MetricResults; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.protocol.Packet; -import com.hypixel.hytale.protocol.PlayerSkin; -import com.hypixel.hytale.protocol.packets.setup.ServerTags; -import com.hypixel.hytale.server.core.Constants; -import com.hypixel.hytale.server.core.HytaleServer; -import com.hypixel.hytale.server.core.HytaleServerConfig; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.NameMatching; -import com.hypixel.hytale.server.core.Options; -import com.hypixel.hytale.server.core.auth.PlayerAuthentication; -import com.hypixel.hytale.server.core.command.system.CommandRegistry; -import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; -import com.hypixel.hytale.server.core.event.events.PrepareUniverseEvent; -import com.hypixel.hytale.server.core.event.events.ShutdownEvent; -import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; -import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; -import com.hypixel.hytale.server.core.io.PacketHandler; -import com.hypixel.hytale.server.core.io.ProtocolVersion; -import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler; -import com.hypixel.hytale.server.core.io.netty.NettyUtil; -import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; -import com.hypixel.hytale.server.core.modules.entity.component.MovementAudioComponent; -import com.hypixel.hytale.server.core.modules.entity.component.PositionDataComponent; -import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; -import com.hypixel.hytale.server.core.modules.entity.player.PlayerConnectionFlushSystem; -import com.hypixel.hytale.server.core.modules.entity.player.PlayerPingSystem; -import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; -import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; -import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; -import com.hypixel.hytale.server.core.plugin.JavaPlugin; -import com.hypixel.hytale.server.core.plugin.JavaPluginInit; -import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.receiver.IMessageReceiver; -import com.hypixel.hytale.server.core.universe.playerdata.PlayerStorage; -import com.hypixel.hytale.server.core.universe.system.PlayerRefAddedSystem; -import com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.WorldConfig; -import com.hypixel.hytale.server.core.universe.world.WorldConfigProvider; -import com.hypixel.hytale.server.core.universe.world.commands.SetTickingCommand; -import com.hypixel.hytale.server.core.universe.world.commands.block.BlockCommand; -import com.hypixel.hytale.server.core.universe.world.commands.block.BlockSelectCommand; -import com.hypixel.hytale.server.core.universe.world.commands.world.WorldCommand; -import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; -import com.hypixel.hytale.server.core.universe.world.events.AllWorldsLoadedEvent; -import com.hypixel.hytale.server.core.universe.world.events.RemoveWorldEvent; -import com.hypixel.hytale.server.core.universe.world.spawn.FitToHeightMapSpawnProvider; -import com.hypixel.hytale.server.core.universe.world.spawn.GlobalSpawnProvider; -import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; -import com.hypixel.hytale.server.core.universe.world.spawn.IndividualSpawnProvider; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; -import com.hypixel.hytale.server.core.universe.world.storage.provider.DefaultChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.provider.EmptyChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.provider.IndexedStorageChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.provider.MigrationChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.resources.DefaultResourceStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.resources.DiskResourceStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.resources.EmptyResourceStorageProvider; -import com.hypixel.hytale.server.core.universe.world.storage.resources.IResourceStorageProvider; -import com.hypixel.hytale.server.core.universe.world.system.WorldPregenerateSystem; -import com.hypixel.hytale.server.core.universe.world.worldgen.provider.DummyWorldGenProvider; -import com.hypixel.hytale.server.core.universe.world.worldgen.provider.FlatWorldGenProvider; -import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; -import com.hypixel.hytale.server.core.universe.world.worldgen.provider.VoidWorldGenProvider; -import com.hypixel.hytale.server.core.universe.world.worldmap.provider.DisabledWorldMapProvider; -import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider; -import com.hypixel.hytale.server.core.universe.world.worldmap.provider.chunk.WorldGenWorldMapProvider; -import com.hypixel.hytale.server.core.util.AssetUtil; -import com.hypixel.hytale.server.core.util.BsonUtil; -import com.hypixel.hytale.server.core.util.backup.BackupTask; -import com.hypixel.hytale.server.core.util.io.FileUtil; -import com.hypixel.hytale.sneakythrow.SneakyThrow; -import io.netty.channel.Channel; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import joptsimple.OptionSet; -import org.bson.BsonDocument; -import org.bson.BsonValue; - -import javax.annotation.CheckReturnValue; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.Collections; -import java.util.Comparator; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.function.BiPredicate; -import java.util.logging.Level; - -public class Universe extends JavaPlugin implements IMessageReceiver, MetricProvider { - @Nonnull - public static final PluginManifest MANIFEST = PluginManifest.corePlugin(Universe.class).build(); - @Nonnull - private static Map LEGACY_BLOCK_ID_MAP = Collections.emptyMap(); - @Nonnull - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register("Worlds", universe -> universe.getWorlds().values().toArray(World[]::new), new ArrayCodec<>(World.METRICS_REGISTRY, World[]::new)) - .register("PlayerCount", Universe::getPlayerCount, Codec.INTEGER); - private static Universe instance; - private ComponentType playerRefComponentType; - @Nonnull - private final Path path = Constants.UNIVERSE_PATH; - @Nonnull - private final Map players = new ConcurrentHashMap<>(); - @Nonnull - private final Map worlds = new ConcurrentHashMap<>(); - @Nonnull - private final Map worldsByUuid = new ConcurrentHashMap<>(); - @Nonnull - private final Map unmodifiableWorlds = Collections.unmodifiableMap(this.worlds); - private PlayerStorage playerStorage; - private WorldConfigProvider worldConfigProvider; - private ResourceType indexedStorageCacheResourceType; - private CompletableFuture universeReady; - - public static Universe get() { - return instance; - } - - public Universe(@Nonnull JavaPluginInit init) { - super(init); - instance = this; - if (!Files.isDirectory(this.path) && !Options.getOptionSet().has(Options.BARE)) { - try { - Files.createDirectories(this.path); - } catch (IOException var3) { - throw new RuntimeException("Failed to create universe directory", var3); - } - } - - if (Options.getOptionSet().has(Options.BACKUP)) { - int frequencyMinutes = Math.max(Options.getOptionSet().valueOf(Options.BACKUP_FREQUENCY_MINUTES), 1); - this.getLogger().at(Level.INFO).log("Scheduled backup to run every %d minute(s)", frequencyMinutes); - HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { - try { - this.getLogger().at(Level.INFO).log("Backing up universe..."); - this.runBackup().thenAccept(aVoid -> this.getLogger().at(Level.INFO).log("Completed scheduled backup.")); - } catch (Exception var2x) { - this.getLogger().at(Level.SEVERE).withCause(var2x).log("Error backing up universe"); - } - }, frequencyMinutes, frequencyMinutes, TimeUnit.MINUTES); - } - } - - @Nonnull - public CompletableFuture runBackup() { - return CompletableFuture.allOf(this.worlds.values().stream().map(world -> CompletableFuture.supplyAsync(() -> { - Store componentStore = world.getChunkStore().getStore(); - ChunkSavingSystems.Data data = componentStore.getResource(ChunkStore.SAVE_RESOURCE); - data.isSaving = false; - return data; - }, world).thenCompose(ChunkSavingSystems.Data::waitForSavingChunks)).toArray(CompletableFuture[]::new)) - .thenCompose(aVoid -> BackupTask.start(this.path, Options.getOptionSet().valueOf(Options.BACKUP_DIRECTORY))) - .thenCompose(success -> CompletableFuture.allOf(this.worlds.values().stream().map(world -> CompletableFuture.runAsync(() -> { - Store componentStore = world.getChunkStore().getStore(); - ChunkSavingSystems.Data data = componentStore.getResource(ChunkStore.SAVE_RESOURCE); - data.isSaving = true; - }, world)).toArray(CompletableFuture[]::new)).thenApply(aVoid -> success)); - } - - @Override - protected void setup() { - EventRegistry eventRegistry = this.getEventRegistry(); - ComponentRegistryProxy chunkStoreRegistry = this.getChunkStoreRegistry(); - ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); - CommandRegistry commandRegistry = this.getCommandRegistry(); - eventRegistry.register((short) -48, ShutdownEvent.class, event -> this.disconnectAllPLayers()); - eventRegistry.register((short) -32, ShutdownEvent.class, event -> this.shutdownAllWorlds()); - ISpawnProvider.CODEC.register("Global", GlobalSpawnProvider.class, GlobalSpawnProvider.CODEC); - ISpawnProvider.CODEC.register("Individual", IndividualSpawnProvider.class, IndividualSpawnProvider.CODEC); - ISpawnProvider.CODEC.register("FitToHeightMap", FitToHeightMapSpawnProvider.class, FitToHeightMapSpawnProvider.CODEC); - IWorldGenProvider.CODEC.register("Flat", FlatWorldGenProvider.class, FlatWorldGenProvider.CODEC); - IWorldGenProvider.CODEC.register("Dummy", DummyWorldGenProvider.class, DummyWorldGenProvider.CODEC); - IWorldGenProvider.CODEC.register(Priority.DEFAULT, "Void", VoidWorldGenProvider.class, VoidWorldGenProvider.CODEC); - IWorldMapProvider.CODEC.register("Disabled", DisabledWorldMapProvider.class, DisabledWorldMapProvider.CODEC); - IWorldMapProvider.CODEC.register(Priority.DEFAULT, "WorldGen", WorldGenWorldMapProvider.class, WorldGenWorldMapProvider.CODEC); - IChunkStorageProvider.CODEC.register(Priority.DEFAULT, "Hytale", DefaultChunkStorageProvider.class, DefaultChunkStorageProvider.CODEC); - IChunkStorageProvider.CODEC.register("Migration", MigrationChunkStorageProvider.class, MigrationChunkStorageProvider.CODEC); - IChunkStorageProvider.CODEC.register("IndexedStorage", IndexedStorageChunkStorageProvider.class, IndexedStorageChunkStorageProvider.CODEC); - IChunkStorageProvider.CODEC.register("Empty", EmptyChunkStorageProvider.class, EmptyChunkStorageProvider.CODEC); - IResourceStorageProvider.CODEC.register(Priority.DEFAULT, "Hytale", DefaultResourceStorageProvider.class, DefaultResourceStorageProvider.CODEC); - IResourceStorageProvider.CODEC.register("Disk", DiskResourceStorageProvider.class, DiskResourceStorageProvider.CODEC); - IResourceStorageProvider.CODEC.register("Empty", EmptyResourceStorageProvider.class, EmptyResourceStorageProvider.CODEC); - this.indexedStorageCacheResourceType = chunkStoreRegistry.registerResource( - IndexedStorageChunkStorageProvider.IndexedStorageCache.class, IndexedStorageChunkStorageProvider.IndexedStorageCache::new - ); - chunkStoreRegistry.registerSystem(new IndexedStorageChunkStorageProvider.IndexedStorageCacheSetupSystem()); - chunkStoreRegistry.registerSystem(new WorldPregenerateSystem()); - entityStoreRegistry.registerSystem(new WorldConfigSaveSystem()); - this.playerRefComponentType = entityStoreRegistry.registerComponent(PlayerRef.class, () -> { - throw new UnsupportedOperationException(); - }); - entityStoreRegistry.registerSystem(new PlayerPingSystem()); - entityStoreRegistry.registerSystem(new PlayerConnectionFlushSystem(this.playerRefComponentType)); - entityStoreRegistry.registerSystem(new PlayerRefAddedSystem(this.playerRefComponentType)); - commandRegistry.registerCommand(new SetTickingCommand()); - commandRegistry.registerCommand(new BlockCommand()); - commandRegistry.registerCommand(new BlockSelectCommand()); - commandRegistry.registerCommand(new WorldCommand()); - } - - @Override - protected void start() { - HytaleServerConfig config = HytaleServer.get().getConfig(); - if (config == null) { - throw new IllegalStateException("Server config is not loaded!"); - } else { - this.playerStorage = config.getPlayerStorageProvider().getPlayerStorage(); - WorldConfigProvider.Default defaultConfigProvider = new WorldConfigProvider.Default(); - PrepareUniverseEvent event = HytaleServer.get() - .getEventBus() - .dispatchFor(PrepareUniverseEvent.class) - .dispatch(new PrepareUniverseEvent(defaultConfigProvider)); - WorldConfigProvider worldConfigProvider = event.getWorldConfigProvider(); - if (worldConfigProvider == null) { - worldConfigProvider = defaultConfigProvider; - } - - this.worldConfigProvider = worldConfigProvider; - - try { - Path blockIdMapPath = this.path.resolve("blockIdMap.json"); - Path path = this.path.resolve("blockIdMap.legacy.json"); - if (Files.isRegularFile(blockIdMapPath)) { - Files.move(blockIdMapPath, path, StandardCopyOption.REPLACE_EXISTING); - } - - Files.deleteIfExists(this.path.resolve("blockIdMap.json.bak")); - if (Files.isRegularFile(path)) { - Map map = new Int2ObjectOpenHashMap<>(); - - for (BsonValue bsonValue : BsonUtil.readDocument(path).thenApply(document -> document.getArray("Blocks")).join()) { - BsonDocument bsonDocument = bsonValue.asDocument(); - map.put(bsonDocument.getNumber("Id").intValue(), bsonDocument.getString("BlockType").getValue()); - } - - LEGACY_BLOCK_ID_MAP = Collections.unmodifiableMap(map); - } - } catch (IOException var14) { - this.getLogger().at(Level.SEVERE).withCause(var14).log("Failed to delete blockIdMap.json"); - } - - if (Options.getOptionSet().has(Options.BARE)) { - this.universeReady = CompletableFuture.completedFuture(null); - HytaleServer.get().getEventBus().dispatch(AllWorldsLoadedEvent.class); - } else { - ObjectArrayList> loadingWorlds = new ObjectArrayList<>(); - - try { - Path worldsPath = this.path.resolve("worlds"); - Files.createDirectories(worldsPath); - - try (DirectoryStream stream = Files.newDirectoryStream(worldsPath)) { - for (Path file : stream) { - if (HytaleServer.get().isShuttingDown()) { - return; - } - - if (!file.equals(worldsPath) && Files.isDirectory(file)) { - String name = file.getFileName().toString(); - if (this.getWorld(name) == null) { - loadingWorlds.add(this.loadWorldFromStart(file, name).exceptionally(throwable -> { - this.getLogger().at(Level.SEVERE).withCause(throwable).log("Failed to load world: %s", name); - return null; - })); - } else { - this.getLogger().at(Level.SEVERE).log("Skipping loading world '%s' because it already exists!", name); - } - } - } - } - - this.universeReady = CompletableFutureUtil._catch( - CompletableFuture.allOf(loadingWorlds.toArray(CompletableFuture[]::new)) - .thenCompose( - v -> { - String worldName = config.getDefaults().getWorld(); - return worldName != null && !this.worlds.containsKey(worldName.toLowerCase()) - ? CompletableFutureUtil._catch(this.addWorld(worldName)) - : CompletableFuture.completedFuture(null); - } - ) - .thenRun(() -> HytaleServer.get().getEventBus().dispatch(AllWorldsLoadedEvent.class)) - ); - } catch (IOException var13) { - throw new RuntimeException("Failed to load Worlds", var13); - } - } - } - } - - @Override - protected void shutdown() { - this.disconnectAllPLayers(); - this.shutdownAllWorlds(); - } - - public void disconnectAllPLayers() { - this.players.values().forEach(player -> player.getPacketHandler().disconnect("Stopping server!")); - } - - public void shutdownAllWorlds() { - Iterator iterator = this.worlds.values().iterator(); - - while (iterator.hasNext()) { - World world = iterator.next(); - world.stop(); - iterator.remove(); - } - } - - @Nonnull - @Override - public MetricResults toMetricResults() { - return METRICS_REGISTRY.toMetricResults(this); - } - - public CompletableFuture getUniverseReady() { - return this.universeReady; - } - - public ResourceType getIndexedStorageCacheResourceType() { - return this.indexedStorageCacheResourceType; - } - - public boolean isWorldLoadable(@Nonnull String name) { - Path savePath = this.path.resolve("worlds").resolve(name); - return Files.isDirectory(savePath) && (Files.exists(savePath.resolve("config.bson")) || Files.exists(savePath.resolve("config.json"))); - } - - @Nonnull - @CheckReturnValue - public CompletableFuture addWorld(@Nonnull String name) { - return this.addWorld(name, null, null); - } - - @Nonnull - @Deprecated - @CheckReturnValue - public CompletableFuture addWorld(@Nonnull String name, @Nullable String generatorType, @Nullable String chunkStorageType) { - if (this.worlds.containsKey(name)) { - throw new IllegalArgumentException("World " + name + " already exists!"); - } else if (this.isWorldLoadable(name)) { - throw new IllegalArgumentException("World " + name + " already exists on disk!"); - } else { - Path savePath = this.path.resolve("worlds").resolve(name); - return this.worldConfigProvider.load(savePath, name).thenCompose(worldConfig -> { - if (generatorType != null && !"default".equals(generatorType)) { - BuilderCodec providerCodec = IWorldGenProvider.CODEC.getCodecFor(generatorType); - if (providerCodec == null) { - throw new IllegalArgumentException("Unknown generatorType '" + generatorType + "'"); - } - - IWorldGenProvider provider = providerCodec.getDefaultValue(); - worldConfig.setWorldGenProvider(provider); - worldConfig.markChanged(); - } - - if (chunkStorageType != null && !"default".equals(chunkStorageType)) { - BuilderCodec providerCodec = IChunkStorageProvider.CODEC.getCodecFor(chunkStorageType); - if (providerCodec == null) { - throw new IllegalArgumentException("Unknown chunkStorageType '" + chunkStorageType + "'"); - } - - IChunkStorageProvider provider = providerCodec.getDefaultValue(); - worldConfig.setChunkStorageProvider(provider); - worldConfig.markChanged(); - } - - return this.makeWorld(name, savePath, worldConfig); - }); - } - } - - @Nonnull - @CheckReturnValue - public CompletableFuture makeWorld(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig) { - return this.makeWorld(name, savePath, worldConfig, true); - } - - @Nonnull - @CheckReturnValue - public CompletableFuture makeWorld(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig, boolean start) { - Map map = worldConfig.getRequiredPlugins(); - if (map != null) { - PluginManager pluginManager = PluginManager.get(); - - for (Entry entry : map.entrySet()) { - if (!pluginManager.hasPlugin(entry.getKey(), entry.getValue())) { - this.getLogger().at(Level.SEVERE).log("Failed to load world! Missing plugin: %s, Version: %s", entry.getKey(), entry.getValue()); - throw new IllegalStateException("Missing plugin"); - } - } - } - - if (this.worlds.containsKey(name)) { - throw new IllegalArgumentException("World " + name + " already exists!"); - } else { - return CompletableFuture.supplyAsync( - SneakyThrow.sneakySupplier( - () -> { - World world = new World(name, savePath, worldConfig); - AddWorldEvent event = HytaleServer.get().getEventBus().dispatchFor(AddWorldEvent.class, name).dispatch(new AddWorldEvent(world)); - if (!event.isCancelled() && !HytaleServer.get().isShuttingDown()) { - World oldWorldByName = this.worlds.putIfAbsent(name.toLowerCase(), world); - if (oldWorldByName != null) { - throw new ConcurrentModificationException( - "World with name " + name + " already exists but didn't before! Looks like you have a race condition." - ); - } else { - World oldWorldByUuid = this.worldsByUuid.putIfAbsent(worldConfig.getUuid(), world); - if (oldWorldByUuid != null) { - throw new ConcurrentModificationException( - "World with UUID " + worldConfig.getUuid() + " already exists but didn't before! Looks like you have a race condition." - ); - } else { - return world; - } - } - } else { - throw new WorldLoadCancelledException(); - } - } - ) - ) - .thenCompose(World::init) - .thenCompose( - world -> !Options.getOptionSet().has(Options.MIGRATIONS) && start - ? world.start().thenApply(v -> world) - : CompletableFuture.completedFuture(world) - ) - .whenComplete((world, throwable) -> { - if (throwable != null) { - String nameLower = name.toLowerCase(); - if (this.worlds.containsKey(nameLower)) { - try { - this.removeWorldExceptionally(name); - } catch (Exception var6x) { - this.getLogger().at(Level.WARNING).withCause(var6x).log("Failed to clean up world '%s' after init failure", name); - } - } - } - }); - } - } - - private CompletableFuture loadWorldFromStart(@Nonnull Path savePath, @Nonnull String name) { - return this.worldConfigProvider - .load(savePath, name) - .thenCompose(worldConfig -> worldConfig.isDeleteOnUniverseStart() ? CompletableFuture.runAsync(() -> { - try { - FileUtil.deleteDirectory(savePath); - this.getLogger().at(Level.INFO).log("Deleted world " + name + " from DeleteOnUniverseStart flag on universe start at " + savePath); - } catch (Throwable var4) { - throw new RuntimeException("Error deleting world directory on universe start", var4); - } - }) : this.makeWorld(name, savePath, worldConfig).thenApply(x -> null)); - } - - @Nonnull - @CheckReturnValue - public CompletableFuture loadWorld(@Nonnull String name) { - if (this.worlds.containsKey(name)) { - throw new IllegalArgumentException("World " + name + " already loaded!"); - } else { - Path savePath = this.path.resolve("worlds").resolve(name); - if (!Files.isDirectory(savePath)) { - throw new IllegalArgumentException("World " + name + " does not exist!"); - } else { - return this.worldConfigProvider.load(savePath, name).thenCompose(worldConfig -> this.makeWorld(name, savePath, worldConfig)); - } - } - } - - @Nullable - public World getWorld(@Nullable String worldName) { - return worldName == null ? null : this.worlds.get(worldName.toLowerCase()); - } - - @Nullable - public World getWorld(@Nonnull UUID uuid) { - return this.worldsByUuid.get(uuid); - } - - @Nullable - public World getDefaultWorld() { - HytaleServerConfig config = HytaleServer.get().getConfig(); - if (config == null) { - return null; - } else { - String worldName = config.getDefaults().getWorld(); - return worldName != null ? this.getWorld(worldName) : null; - } - } - - public boolean removeWorld(@Nonnull String name) { - Objects.requireNonNull(name, "Name can't be null!"); - String nameLower = name.toLowerCase(); - World world = this.worlds.get(nameLower); - if (world == null) { - throw new NullPointerException("World " + name + " doesn't exist!"); - } else { - RemoveWorldEvent event = HytaleServer.get() - .getEventBus() - .dispatchFor(RemoveWorldEvent.class, name) - .dispatch(new RemoveWorldEvent(world, RemoveWorldEvent.RemovalReason.GENERAL)); - if (event.isCancelled()) { - return false; - } else { - this.worlds.remove(nameLower); - this.worldsByUuid.remove(world.getWorldConfig().getUuid()); - if (world.isAlive()) { - world.stopIndividualWorld(); - } - - world.validateDeleteOnRemove(); - return true; - } - } - } - - public void removeWorldExceptionally(@Nonnull String name) { - Objects.requireNonNull(name, "Name can't be null!"); - this.getLogger().at(Level.INFO).log("Removing world exceptionally: %s", name); - String nameLower = name.toLowerCase(); - World world = this.worlds.get(nameLower); - if (world == null) { - throw new NullPointerException("World " + name + " doesn't exist!"); - } else { - HytaleServer.get() - .getEventBus() - .dispatchFor(RemoveWorldEvent.class, name) - .dispatch(new RemoveWorldEvent(world, RemoveWorldEvent.RemovalReason.EXCEPTIONAL)); - this.worlds.remove(nameLower); - this.worldsByUuid.remove(world.getWorldConfig().getUuid()); - if (world.isAlive()) { - world.stopIndividualWorld(); - } - - world.validateDeleteOnRemove(); - - // HyFix: Shut down server if default world crashes exceptionally - // This prevents players from being stuck in a broken state - String defaultWorldName = HytaleServer.get().getConfig().getDefaults().getWorld(); - if (defaultWorldName != null && defaultWorldName.equalsIgnoreCase(name)) { - this.getLogger().at(Level.SEVERE).log("Default world '%s' crashed! Shutting down server.", name); - HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> { - HytaleServer.get().shutdownServer(); - }, 1, TimeUnit.SECONDS); - } - } - } - - @Nonnull - public Path getPath() { - return this.path; - } - - @Nonnull - public Map getWorlds() { - return this.unmodifiableWorlds; - } - - @Nonnull - public List getPlayers() { - return new ObjectArrayList<>(this.players.values()); - } - - @Nullable - public PlayerRef getPlayer(@Nonnull UUID uuid) { - return this.players.get(uuid); - } - - @Nullable - public PlayerRef getPlayer(@Nonnull String value, @Nonnull NameMatching matching) { - return matching.find(this.players.values(), value, v -> v.getComponent(PlayerRef.getComponentType()).getUsername()); - } - - @Nullable - public PlayerRef getPlayer(@Nonnull String value, @Nonnull Comparator comparator, @Nonnull BiPredicate equality) { - return NameMatching.find(this.players.values(), value, v -> v.getComponent(PlayerRef.getComponentType()).getUsername(), comparator, equality); - } - - @Nullable - public PlayerRef getPlayerByUsername(@Nonnull String value, @Nonnull NameMatching matching) { - return matching.find(this.players.values(), value, PlayerRef::getUsername); - } - - @Nullable - public PlayerRef getPlayerByUsername(@Nonnull String value, @Nonnull Comparator comparator, @Nonnull BiPredicate equality) { - return NameMatching.find(this.players.values(), value, PlayerRef::getUsername, comparator, equality); - } - - public int getPlayerCount() { - return this.players.size(); - } - - @Nonnull - public CompletableFuture addPlayer( - @Nonnull Channel channel, - @Nonnull String language, - @Nonnull ProtocolVersion protocolVersion, - @Nonnull UUID uuid, - @Nonnull String username, - @Nonnull PlayerAuthentication auth, - int clientViewRadiusChunks, - @Nullable PlayerSkin skin - ) { - GamePacketHandler playerConnection = new GamePacketHandler(channel, protocolVersion, auth); - playerConnection.setQueuePackets(false); - this.getLogger().at(Level.INFO).log("Adding player '%s (%s)", username, uuid); - return this.playerStorage - .load(uuid) - .exceptionally(throwable -> { - throw new RuntimeException("Exception when adding player to universe:", throwable); - }) - .thenCompose( - holder -> { - ChunkTracker chunkTrackerComponent = new ChunkTracker(); - PlayerRef playerRefComponent = new PlayerRef((Holder) holder, uuid, username, language, playerConnection, chunkTrackerComponent); - chunkTrackerComponent.setDefaultMaxChunksPerSecond(playerRefComponent); - holder.putComponent(PlayerRef.getComponentType(), playerRefComponent); - holder.putComponent(ChunkTracker.getComponentType(), chunkTrackerComponent); - holder.putComponent(UUIDComponent.getComponentType(), new UUIDComponent(uuid)); - holder.ensureComponent(PositionDataComponent.getComponentType()); - holder.ensureComponent(MovementAudioComponent.getComponentType()); - Player playerComponent = holder.ensureAndGetComponent(Player.getComponentType()); - playerComponent.init(uuid, playerRefComponent); - PlayerConfigData playerConfig = playerComponent.getPlayerConfigData(); - playerConfig.cleanup(this); - PacketHandler.logConnectionTimings(channel, "Load Player Config", Level.FINEST); - if (skin != null) { - holder.putComponent(PlayerSkinComponent.getComponentType(), new PlayerSkinComponent(skin)); - holder.putComponent(ModelComponent.getComponentType(), new ModelComponent(CosmeticsModule.get().createModel(skin))); - } - - playerConnection.setPlayerRef(playerRefComponent, playerComponent); - NettyUtil.setChannelHandler(channel, playerConnection); - playerComponent.setClientViewRadius(clientViewRadiusChunks); - EntityTrackerSystems.EntityViewer entityViewerComponent = holder.getComponent(EntityTrackerSystems.EntityViewer.getComponentType()); - if (entityViewerComponent != null) { - entityViewerComponent.viewRadiusBlocks = playerComponent.getViewRadius() * 32; - } else { - entityViewerComponent = new EntityTrackerSystems.EntityViewer(playerComponent.getViewRadius() * 32, playerConnection); - holder.addComponent(EntityTrackerSystems.EntityViewer.getComponentType(), entityViewerComponent); - } - - PlayerRef existingPlayer = this.players.putIfAbsent(uuid, playerRefComponent); - if (existingPlayer != null) { - this.getLogger().at(Level.WARNING).log("Player '%s' (%s) already joining from another connection, rejecting duplicate", username, uuid); - playerConnection.disconnect("A connection with this account is already in progress"); - return CompletableFuture.completedFuture(null); - } else { - String lastWorldName = playerConfig.getWorld(); - World lastWorld = this.getWorld(lastWorldName); - PlayerConnectEvent event = HytaleServer.get() - .getEventBus() - .dispatchFor(PlayerConnectEvent.class) - .dispatch(new PlayerConnectEvent((Holder) holder, playerRefComponent, lastWorld != null ? lastWorld : this.getDefaultWorld())); - if (!channel.isActive()) { - this.players.remove(uuid, playerRefComponent); - this.getLogger().at(Level.INFO).log("Player '%s' (%s) disconnected during PlayerConnectEvent, cleaned up", username, uuid); - return CompletableFuture.completedFuture(null); - } else { - World world = event.getWorld() != null ? event.getWorld() : this.getDefaultWorld(); - if (world == null) { - this.players.remove(uuid, playerRefComponent); - playerConnection.disconnect("No world available to join"); - this.getLogger().at(Level.SEVERE).log("Player '%s' (%s) could not join - no default world configured", username, uuid); - return CompletableFuture.completedFuture(null); - } else { - if (lastWorldName != null && lastWorld == null) { - playerComponent.sendMessage( - Message.translation("server.universe.failedToFindWorld").param("lastWorldName", lastWorldName).param("name", world.getName()) - ); - } - - PacketHandler.logConnectionTimings(channel, "Processed Referral", Level.FINEST); - playerRefComponent.getPacketHandler().write(new ServerTags(AssetRegistry.getClientTags())); - CompletableFuture addPlayerFuture = world.addPlayer(playerRefComponent, null, false, false); - if (addPlayerFuture == null) { - this.players.remove(uuid, playerRefComponent); - this.getLogger().at(Level.INFO).log("Player '%s' (%s) disconnected before world addition, cleaned up", username, uuid); - return CompletableFuture.completedFuture(null); - } else { - return addPlayerFuture.thenApply( - p -> { - PacketHandler.logConnectionTimings(channel, "Add to World", Level.FINEST); - if (!channel.isActive()) { - if (p != null) { - playerComponent.remove(); - } - - this.players.remove(uuid, playerRefComponent); - this.getLogger() - .at(Level.WARNING) - .log("Player '%s' (%s) disconnected during world join, cleaned up from universe", username, uuid); - return null; - } else if (playerComponent.wasRemoved()) { - this.players.remove(uuid, playerRefComponent); - return null; - } else { - return (PlayerRef) p; - } - } - ) - .exceptionally(throwable -> { - this.players.remove(uuid, playerRefComponent); - playerComponent.remove(); - throw new RuntimeException("Exception when adding player to universe:", throwable); - }); - } - } - } - } - } - ); - } - - public void removePlayer(@Nonnull PlayerRef playerRef) { - this.getLogger().at(Level.INFO).log("Removing player '" + playerRef.getUsername() + "' (" + playerRef.getUuid() + ")"); - IEventDispatcher eventDispatcher = HytaleServer.get() - .getEventBus() - .dispatchFor(PlayerDisconnectEvent.class); - if (eventDispatcher.hasListener()) { - eventDispatcher.dispatch(new PlayerDisconnectEvent(playerRef)); - } - - Ref ref = playerRef.getReference(); - if (ref == null) { - this.finalizePlayerRemoval(playerRef); - } else { - World world = ref.getStore().getExternalData().getWorld(); - if (world.isInThread()) { - // HyFix: Wrap in try-catch for IllegalStateException with fallback cleanup - try { - Player playerComponent = ref.getStore().getComponent(ref, Player.getComponentType()); - if (playerComponent != null) { - playerComponent.remove(); - } - } catch (IllegalStateException e) { - System.err.println("[HyFix] Player ref invalid during removal - performing fallback cleanup"); - try { - playerRef.getChunkTracker().clear(); - System.err.println("[HyFix] ChunkTracker cleared - memory leak prevented"); - } catch (Exception cleanupEx) { - System.err.println("[HyFix] Fallback cleanup failed - memory may leak"); - } - } - - this.finalizePlayerRemoval(playerRef); - } else { - CompletableFuture removedFuture = new CompletableFuture<>(); - CompletableFuture.runAsync(() -> { - // HyFix: Wrap in try-catch for IllegalStateException with fallback cleanup - try { - Player playerComponent = ref.getStore().getComponent(ref, Player.getComponentType()); - if (playerComponent != null) { - playerComponent.remove(); - } - } catch (IllegalStateException e) { - System.err.println("[HyFix] Player ref invalid during async removal - performing fallback cleanup"); - try { - playerRef.getChunkTracker().clear(); - System.err.println("[HyFix] ChunkTracker cleared - memory leak prevented"); - } catch (Exception cleanupEx) { - System.err.println("[HyFix] Fallback cleanup failed - memory may leak"); - } - } - }, world).whenComplete((unused, throwable) -> { - if (throwable != null) { - removedFuture.completeExceptionally(throwable); - } else { - removedFuture.complete(unused); - } - }); - removedFuture.orTimeout(5L, TimeUnit.SECONDS) - .whenComplete( - (result, error) -> { - if (error != null) { - this.getLogger() - .at(Level.WARNING) - .withCause(error) - .log("Timeout or error waiting for player '%s' removal from world store", playerRef.getUsername()); - // HyFix: Perform fallback cleanup on timeout - try { - playerRef.getChunkTracker().clear(); - System.err.println("[HyFix] ChunkTracker cleared on timeout - memory leak prevented"); - } catch (Exception cleanupEx) { - System.err.println("[HyFix] Fallback cleanup on timeout failed - memory may leak"); - } - } - - this.finalizePlayerRemoval(playerRef); - } - ); - } - } - } - - private void finalizePlayerRemoval(@Nonnull PlayerRef playerRef) { - this.players.remove(playerRef.getUuid()); - if (Constants.SINGLEPLAYER) { - if (this.players.isEmpty()) { - this.getLogger().at(Level.INFO).log("No players left on singleplayer server shutting down!"); - HytaleServer.get().shutdownServer(); - } else if (SingleplayerModule.isOwner(playerRef)) { - this.getLogger().at(Level.INFO).log("Owner left the singleplayer server shutting down!"); - this.getPlayers().forEach(p -> p.getPacketHandler().disconnect(playerRef.getUsername() + " left! Shutting down singleplayer world!")); - HytaleServer.get().shutdownServer(); - } - } - } - - @Nonnull - public CompletableFuture resetPlayer(@Nonnull PlayerRef oldPlayer) { - return this.playerStorage.load(oldPlayer.getUuid()).exceptionally(throwable -> { - throw new RuntimeException("Exception when adding player to universe:", throwable); - }).thenCompose(holder -> this.resetPlayer(oldPlayer, (Holder) holder)); - } - - @Nonnull - public CompletableFuture resetPlayer(@Nonnull PlayerRef oldPlayer, @Nonnull Holder holder) { - return this.resetPlayer(oldPlayer, holder, null, null); - } - - @Nonnull - public CompletableFuture resetPlayer( - @Nonnull PlayerRef playerRef, @Nonnull Holder holder, @Nullable World world, @Nullable Transform transform - ) { - UUID uuid = playerRef.getUuid(); - Player oldPlayer = playerRef.getComponent(Player.getComponentType()); - World targetWorld; - if (world == null) { - targetWorld = oldPlayer.getWorld(); - } else { - targetWorld = world; - } - - this.getLogger() - .at(Level.INFO) - .log( - "Resetting player '%s', moving to world '%s' at location %s (%s)", - playerRef.getUsername(), - world != null ? world.getName() : null, - transform, - playerRef.getUuid() - ); - GamePacketHandler playerConnection = (GamePacketHandler) playerRef.getPacketHandler(); - Player newPlayer = holder.ensureAndGetComponent(Player.getComponentType()); - newPlayer.init(uuid, playerRef); - CompletableFuture leaveWorld = new CompletableFuture<>(); - if (oldPlayer.getWorld() != null) { - oldPlayer.getWorld().execute(() -> { - playerRef.removeFromStore(); - leaveWorld.complete(null); - }); - } else { - leaveWorld.complete(null); - } - - return leaveWorld.thenAccept(v -> { - oldPlayer.resetManagers(holder); - newPlayer.copyFrom(oldPlayer); - EntityTrackerSystems.EntityViewer viewer = holder.getComponent(EntityTrackerSystems.EntityViewer.getComponentType()); - if (viewer != null) { - viewer.viewRadiusBlocks = newPlayer.getViewRadius() * 32; - } else { - viewer = new EntityTrackerSystems.EntityViewer(newPlayer.getViewRadius() * 32, playerConnection); - holder.addComponent(EntityTrackerSystems.EntityViewer.getComponentType(), viewer); - } - - playerConnection.setPlayerRef(playerRef, newPlayer); - playerRef.replaceHolder(holder); - holder.putComponent(PlayerRef.getComponentType(), playerRef); - }).thenCompose(v -> targetWorld.addPlayer(playerRef, transform)); - } - - @Override - public void sendMessage(@Nonnull Message message) { - for (PlayerRef ref : this.players.values()) { - ref.sendMessage(message); - } - } - - public void broadcastPacket(@Nonnull Packet packet) { - for (PlayerRef player : this.players.values()) { - player.getPacketHandler().write(packet); - } - } - - public void broadcastPacketNoCache(@Nonnull Packet packet) { - for (PlayerRef player : this.players.values()) { - player.getPacketHandler().writeNoCache(packet); - } - } - - public void broadcastPacket(@Nonnull Packet... packets) { - for (PlayerRef player : this.players.values()) { - player.getPacketHandler().write(packets); - } - } - - public PlayerStorage getPlayerStorage() { - return this.playerStorage; - } - - public void setPlayerStorage(@Nonnull PlayerStorage playerStorage) { - this.playerStorage = playerStorage; - } - - public WorldConfigProvider getWorldConfigProvider() { - return this.worldConfigProvider; - } - - @Nonnull - public ComponentType getPlayerRefComponentType() { - return this.playerRefComponentType; - } - - @Nonnull - @Deprecated - public static Map getLegacyBlockIdMap() { - return LEGACY_BLOCK_ID_MAP; - } - - public static Path getWorldGenPath() { - OptionSet optionSet = Options.getOptionSet(); - Path worldGenPath; - if (optionSet.has(Options.WORLD_GEN_DIRECTORY)) { - worldGenPath = optionSet.valueOf(Options.WORLD_GEN_DIRECTORY); - } else { - worldGenPath = AssetUtil.getHytaleAssetsPath().resolve("Server").resolve("World"); - } - - return worldGenPath; - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/World.java b/src/com/hypixel/hytale/server/core/universe/world/World.java deleted file mode 100644 index 2ab5a402..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/World.java +++ /dev/null @@ -1,1247 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world; - -import com.hypixel.hytale.assetstore.AssetRegistry; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.common.util.FormatUtil; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.IResourceStorage; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.data.unknown.UnknownComponents; -import com.hypixel.hytale.event.EventRegistry; -import com.hypixel.hytale.event.IEventDispatcher; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.logger.sentry.SkipSentryException; -import com.hypixel.hytale.math.block.BlockUtil; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.math.vector.Transform; -import com.hypixel.hytale.math.vector.Vector3d; -import com.hypixel.hytale.math.vector.Vector3f; -import com.hypixel.hytale.metrics.ExecutorMetricsRegistry; -import com.hypixel.hytale.metrics.metric.HistoricMetric; -import com.hypixel.hytale.protocol.packets.entities.SetEntitySeed; -import com.hypixel.hytale.protocol.packets.player.JoinWorld; -import com.hypixel.hytale.protocol.packets.player.SetClientId; -import com.hypixel.hytale.protocol.packets.setup.ClientFeature; -import com.hypixel.hytale.protocol.packets.setup.SetTimeDilation; -import com.hypixel.hytale.protocol.packets.setup.SetUpdateRate; -import com.hypixel.hytale.protocol.packets.setup.UpdateFeatures; -import com.hypixel.hytale.protocol.packets.setup.ViewRadius; -import com.hypixel.hytale.protocol.packets.world.ServerSetPaused; -import com.hypixel.hytale.server.core.HytaleServer; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.ShutdownReason; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -import com.hypixel.hytale.server.core.asset.type.gameplay.CombatConfig; -import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; -import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; -import com.hypixel.hytale.server.core.asset.type.gameplay.PlayerConfig; -import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; -import com.hypixel.hytale.server.core.console.ConsoleModule; -import com.hypixel.hytale.server.core.entity.Entity; -import com.hypixel.hytale.server.core.entity.EntityUtils; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; -import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; -import com.hypixel.hytale.server.core.event.events.player.DrainPlayerFromWorldEvent; -import com.hypixel.hytale.server.core.io.PacketHandler; -import com.hypixel.hytale.server.core.modules.entity.EntityModule; -import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; -import com.hypixel.hytale.server.core.modules.entity.tracker.LegacyEntityTrackerSystems; -import com.hypixel.hytale.server.core.modules.time.TimeResource; -import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; -import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; -import com.hypixel.hytale.server.core.receiver.IMessageReceiver; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.Universe; -import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; -import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; -import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; -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.events.StartWorldEvent; -import com.hypixel.hytale.server.core.universe.world.lighting.ChunkLightingManager; -import com.hypixel.hytale.server.core.universe.world.path.WorldPathConfig; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.storage.resources.DiskResourceStorageProvider; -import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; -import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; -import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; -import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapLoadException; -import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; -import com.hypixel.hytale.server.core.util.FillerBlockUtil; -import com.hypixel.hytale.server.core.util.MessageUtil; -import com.hypixel.hytale.server.core.util.io.FileUtil; -import com.hypixel.hytale.server.core.util.thread.TickingThread; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.IntUnaryOperator; -import java.util.logging.Level; - -public class World extends TickingThread implements Executor, ExecutorMetricsRegistry.ExecutorMetric, ChunkAccessor, IWorldChunks, IMessageReceiver { - public static final float SAVE_INTERVAL = 10.0F; - public static final String DEFAULT = "default"; - @Nonnull - public static final ExecutorMetricsRegistry METRICS_REGISTRY = new ExecutorMetricsRegistry() - .register("Name", world -> world.name, Codec.STRING) - .register("Alive", world -> world.alive.get(), Codec.BOOLEAN) - .register("TickLength", TickingThread::getBufferedTickLengthMetricSet, HistoricMetric.METRICS_CODEC) - .register("EntityStore", World::getEntityStore, EntityStore.METRICS_REGISTRY) - .register("ChunkStore", World::getChunkStore, ChunkStore.METRICS_REGISTRY); - @Nonnull - private final HytaleLogger logger; - @Nonnull - private final String name; - @Nonnull - private final Path savePath; - @Nonnull - private final WorldConfig worldConfig; - @Nonnull - private final ChunkStore chunkStore = new ChunkStore(this); - @Nonnull - private final EntityStore entityStore = new EntityStore(this); - @Nonnull - private final ChunkLightingManager chunkLighting; - @Nonnull - private final WorldMapManager worldMapManager; - private WorldPathConfig worldPathConfig; - private final AtomicBoolean acceptingTasks = new AtomicBoolean(true); - @Nonnull - private final Deque taskQueue = new LinkedBlockingDeque<>(); - @Nonnull - private final AtomicBoolean alive = new AtomicBoolean(true); - @Nonnull - private final EventRegistry eventRegistry = new EventRegistry(new CopyOnWriteArrayList<>(), () -> true, null, HytaleServer.get().getEventBus()); - @Nonnull - private final WorldNotificationHandler notificationHandler = new WorldNotificationHandler(this); - private boolean isTicking; - private boolean isPaused; - private long tick; - @Nonnull - private final Random random = new Random(); - @Nonnull - private final AtomicInteger entitySeed = new AtomicInteger(); - @Nonnull - private final Map players = new ConcurrentHashMap<>(); - @Nonnull - private final Collection playerRefs = Collections.unmodifiableCollection(this.players.values()); - @Nonnull - private final Map features = Collections.synchronizedMap(new EnumMap<>(ClientFeature.class)); - private volatile boolean gcHasRun; - - public World(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig) throws IOException { - super("WorldThread - " + name); - this.name = name; - this.logger = HytaleLogger.get("World|" + name); - this.savePath = savePath; - this.worldConfig = worldConfig; - this.logger - .at(Level.INFO) - .log( - "Loading world '%s' with generator type: '%s' and chunk storage: '%s'...", - name, - worldConfig.getWorldGenProvider(), - worldConfig.getChunkStorageProvider() - ); - this.worldMapManager = new WorldMapManager(this); - this.chunkLighting = new ChunkLightingManager(this); - this.isTicking = worldConfig.isTicking(); - - for (ClientFeature feature : ClientFeature.VALUES) { - this.features.put(feature, true); - } - - GameplayConfig gameplayConfig = this.getGameplayConfig(); - CombatConfig combatConfig = gameplayConfig.getCombatConfig(); - this.features.put(ClientFeature.DisplayHealthBars, combatConfig.isDisplayHealthBars()); - this.features.put(ClientFeature.DisplayCombatText, combatConfig.isDisplayCombatText()); - PlayerConfig.ArmorVisibilityOption armorVisibilityOption = gameplayConfig.getPlayerConfig().getArmorVisibilityOption(); - this.features.put(ClientFeature.CanHideHelmet, armorVisibilityOption.canHideHelmet()); - this.features.put(ClientFeature.CanHideCuirass, armorVisibilityOption.canHideCuirass()); - this.features.put(ClientFeature.CanHideGauntlets, armorVisibilityOption.canHideGauntlets()); - this.features.put(ClientFeature.CanHidePants, armorVisibilityOption.canHidePants()); - this.logger.at(Level.INFO).log("Added world '%s' - Seed: %s, GameTime: %s", name, Long.toString(worldConfig.getSeed()), worldConfig.getGameTime()); - } - - @Nonnull - public CompletableFuture init() { - CompletableFuture savingFuture; - if (this.worldConfig.isSavingConfig()) { - savingFuture = Universe.get().getWorldConfigProvider().save(this.savePath, this.worldConfig, this); - } else { - savingFuture = CompletableFuture.completedFuture(null); - } - - CompletableFuture loadWorldGen = CompletableFuture.supplyAsync(() -> { - try { - IWorldGen worldGen = this.worldConfig.getWorldGenProvider().getGenerator(); - this.chunkStore.setGenerator(worldGen); - this.worldConfig.setDefaultSpawnProvider(worldGen); - IWorldMap worldMap = this.worldConfig.getWorldMapProvider().getGenerator(this); - this.worldMapManager.setGenerator(worldMap); - return this; - } catch (WorldGenLoadException var3x) { - if (this.name.equals(HytaleServer.get().getConfig().getDefaults().getWorld())) { - HytaleServer.get().shutdownServer(ShutdownReason.WORLD_GEN.withMessage(var3x.getTraceMessage("\n"))); - } - - throw new SkipSentryException("Failed to load WorldGen!", var3x); - } catch (WorldMapLoadException var4) { - if (this.name.equals(HytaleServer.get().getConfig().getDefaults().getWorld())) { - HytaleServer.get().shutdownServer(ShutdownReason.WORLD_GEN.withMessage(var4.getTraceMessage("\n"))); - } - - throw new SkipSentryException("Failed to load WorldGen!", var4); - } - }); - CompletableFuture loadPaths = WorldPathConfig.load(this).thenAccept(config -> this.worldPathConfig = config); - return this.worldConfig.getSpawnProvider() != null - ? CompletableFuture.allOf(savingFuture, loadPaths).thenApply(v -> this) - : CompletableFuture.allOf(savingFuture, loadPaths).thenCompose(v -> loadWorldGen); - } - - @Override - protected void onStart() { - DiskResourceStorageProvider.migrateFiles(this); - IResourceStorage resourceStorage = this.worldConfig.getResourceStorageProvider().getResourceStorage(this); - this.chunkStore.start(resourceStorage); - this.entityStore.start(resourceStorage); - this.chunkLighting.start(); - this.worldMapManager.updateTickingState(this.worldMapManager.isStarted()); - Path rffPath = this.savePath.resolve("rff"); - if (Files.exists(rffPath)) { - throw new RuntimeException(rffPath + " directory exists but this version of the server doesn't support migrating RFF worlds!"); - } else { - IEventDispatcher dispatcher = HytaleServer.get().getEventBus().dispatchFor(StartWorldEvent.class, this.name); - if (dispatcher.hasListener()) { - dispatcher.dispatch(new StartWorldEvent(this)); - } - } - } - - public void stopIndividualWorld() { - this.logger.at(Level.INFO).log("Removing individual world: %s", this.name); - World defaultWorld = Universe.get().getDefaultWorld(); - if (defaultWorld != null) { - this.drainPlayersTo(defaultWorld).join(); - } else { - for (PlayerRef playerRef : this.players.values()) { - playerRef.getPacketHandler().disconnect("The world you were in was shutdown and there was no default world to move you to!"); - } - } - - if (this.alive.getAndSet(false)) { - try { - super.stop(); - } catch (Throwable var4) { - this.logger.at(Level.SEVERE).withCause(var4).log("Exception while shutting down world:"); - } - } - } - - public void validateDeleteOnRemove() { - if (this.worldConfig.isDeleteOnRemove()) { - try { - FileUtil.deleteDirectory(this.getSavePath()); - } catch (Throwable var2) { - this.logger.at(Level.SEVERE).withCause(var2).log("Exception while deleting world on remove:"); - } - } - } - - @Override - protected boolean isIdle() { - return this.players.isEmpty(); - } - - @Override - protected void tick(float dt) { - if (this.alive.get()) { - TimeResource worldTimeResource = this.entityStore.getStore().getResource(TimeResource.getResourceType()); - dt *= worldTimeResource.getTimeDilationModifier(); - AssetRegistry.ASSET_LOCK.readLock().lock(); - - try { - this.consumeTaskQueue(); - if (!this.isPaused) { - this.entityStore.getStore().tick(dt); - } else { - this.entityStore.getStore().pausedTick(dt); - } - - if (this.isTicking && !this.isPaused) { - this.chunkStore.getStore().tick(dt); - } else { - this.chunkStore.getStore().pausedTick(dt); - } - - this.consumeTaskQueue(); - } finally { - AssetRegistry.ASSET_LOCK.readLock().unlock(); - } - - this.tick++; - } - } - - @Override - protected void onShutdown() { - this.logger.at(Level.INFO).log("Stopping world %s...", this.name); - this.logger.at(Level.INFO).log("Stopping background threads..."); - long start = System.nanoTime(); - - while (this.chunkLighting.interrupt() || this.worldMapManager.interrupt()) { - this.consumeTaskQueue(); - if (System.nanoTime() - start > 5000000000L) { - break; - } - } - - this.chunkLighting.stop(); - this.worldMapManager.stop(); - this.logger.at(Level.INFO).log("Removing players..."); - - for (PlayerRef playerRef : this.playerRefs) { - if (playerRef.getReference() != null) { - playerRef.removeFromStore(); - } - } - - this.consumeTaskQueue(); - this.logger.at(Level.INFO).log("Waiting for loading chunks..."); - this.chunkStore.waitForLoadingChunks(); - - try { - this.logger.at(Level.INFO).log("Shutting down stores..."); - HytaleServer.get().reportSingleplayerStatus("Saving world '" + this.name + "'"); - this.chunkStore.shutdown(); - this.consumeTaskQueue(); - this.entityStore.shutdown(); - this.consumeTaskQueue(); - } finally { - this.logger.at(Level.INFO).log("Saving Config..."); - if (this.worldConfig.isSavingConfig()) { - try { - Universe.get().getWorldConfigProvider().save(this.savePath, this.worldConfig, this).join(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - this.acceptingTasks.set(false); - if (this.alive.getAndSet(false)) { - Universe.get().removeWorldExceptionally(this.name); - } - - HytaleServer.get().reportSingleplayerStatus("Closing world '" + this.name + "'"); - } - - @Override - public void setTps(int tps) { - super.setTps(tps); - SetUpdateRate setUpdateRatePacket = new SetUpdateRate(tps); - this.entityStore - .getStore() - .forEachEntityParallel( - PlayerRef.getComponentType(), - (index, archetypeChunk, commandBuffer) -> archetypeChunk.getComponent(index, PlayerRef.getComponentType()) - .getPacketHandler() - .writeNoCache(setUpdateRatePacket) - ); - } - - public static void setTimeDilation(float timeDilationModifier, @Nonnull ComponentAccessor componentAccessor) { - World world = componentAccessor.getExternalData().getWorld(); - if (!(timeDilationModifier <= 0.01) && !(timeDilationModifier > 4.0F)) { - TimeResource worldTimeResource = componentAccessor.getResource(TimeResource.getResourceType()); - worldTimeResource.setTimeDilationModifier(timeDilationModifier); - SetTimeDilation setTimeDilationPacket = new SetTimeDilation(timeDilationModifier); - - for (PlayerRef playerRef : world.playerRefs) { - playerRef.getPacketHandler().writeNoCache(setTimeDilationPacket); - } - } else { - throw new IllegalArgumentException("TimeDilation is out of bounds (<=0.01 or >4)"); - } - } - - @Nonnull - public String getName() { - return this.name; - } - - public boolean isAlive() { - return this.alive.get(); - } - - @Nonnull - public WorldConfig getWorldConfig() { - return this.worldConfig; - } - - @Nonnull - public DeathConfig getDeathConfig() { - DeathConfig override = this.worldConfig.getDeathConfigOverride(); - return override != null ? override : this.getGameplayConfig().getDeathConfig(); - } - - public int getDaytimeDurationSeconds() { - Integer override = this.worldConfig.getDaytimeDurationSecondsOverride(); - return override != null ? override : this.getGameplayConfig().getWorldConfig().getDaytimeDurationSeconds(); - } - - public int getNighttimeDurationSeconds() { - Integer override = this.worldConfig.getNighttimeDurationSecondsOverride(); - return override != null ? override : this.getGameplayConfig().getWorldConfig().getNighttimeDurationSeconds(); - } - - public boolean isTicking() { - return this.isTicking; - } - - public void setTicking(boolean ticking) { - this.isTicking = ticking; - this.worldConfig.setTicking(ticking); - this.worldConfig.markChanged(); - } - - public boolean isPaused() { - return this.isPaused; - } - - public void setPaused(boolean paused) { - if (this.isPaused != paused) { - this.isPaused = paused; - ServerSetPaused setPaused = new ServerSetPaused(paused); - PlayerUtil.broadcastPacketToPlayersNoCache(this.entityStore.getStore(), setPaused); - } - } - - public long getTick() { - return this.tick; - } - - @Nonnull - public HytaleLogger getLogger() { - return this.logger; - } - - public boolean isCompassUpdating() { - return this.worldConfig.isCompassUpdating(); - } - - public void setCompassUpdating(boolean compassUpdating) { - boolean before = this.worldMapManager.shouldTick(); - this.worldConfig.setCompassUpdating(compassUpdating); - this.worldConfig.markChanged(); - this.worldMapManager.updateTickingState(before); - } - - public void getBlockBulkRelative( - @Nonnull Long2ObjectMap blocks, - @Nonnull IntUnaryOperator xConvert, - @Nonnull IntUnaryOperator yConvert, - @Nonnull IntUnaryOperator zConvert, - @Nonnull World.GenericBlockBulkUpdater consumer - ) { - Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); - blocks.forEach((a, b) -> { - int localX = BlockUtil.unpackX(a); - int localY = BlockUtil.unpackY(a); - int localZ = BlockUtil.unpackZ(a); - int x = xConvert.applyAsInt(localX); - int y = yConvert.applyAsInt(localY); - int z = zConvert.applyAsInt(localZ); - long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z); - WorldChunk chunk = chunks.get(chunkIndex); - if (chunk == null) { - chunk = this.getNonTickingChunk(chunkIndex); - chunks.put(chunkIndex, chunk); - } - - consumer.apply(this, (T) b, chunkIndex, chunk, x, y, z, localX, localY, localZ); - }); - } - - @Nullable - public WorldChunk loadChunkIfInMemory(long index) { - if (!this.isInThread()) { - return CompletableFuture.supplyAsync(() -> this.loadChunkIfInMemory(index), this).join(); - } else { - Ref reference = this.chunkStore.getChunkReference(index); - if (reference == null) { - return null; - } else { - WorldChunk worldChunkComponent = this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - worldChunkComponent.setFlag(ChunkFlag.TICKING, true); - return worldChunkComponent; - } - } - } - - @Nullable - public WorldChunk getChunkIfInMemory(long index) { - Ref reference = this.chunkStore.getChunkReference(index); - if (reference == null) { - return null; - } else { - return !this.isInThread() - ? CompletableFuture.supplyAsync(() -> this.getChunkIfInMemory(index), this).join() - : this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); - } - } - - @Nullable - public WorldChunk getChunkIfLoaded(long index) { - if (!this.isInThread()) { - return CompletableFuture.supplyAsync(() -> this.getChunkIfLoaded(index), this).join(); - } else { - Ref reference = this.chunkStore.getChunkReference(index); - if (reference == null) { - return null; - } else { - WorldChunk worldChunkComponent = this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - return worldChunkComponent.is(ChunkFlag.TICKING) ? worldChunkComponent : null; - } - } - } - - @Nullable - public WorldChunk getChunkIfNonTicking(long index) { - if (!this.isInThread()) { - return CompletableFuture.supplyAsync(() -> this.getChunkIfNonTicking(index), this).join(); - } else { - Ref reference = this.chunkStore.getChunkReference(index); - if (reference == null) { - return null; - } else { - WorldChunk worldChunkComponent = this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - return worldChunkComponent.is(ChunkFlag.TICKING) ? null : worldChunkComponent; - } - } - } - - @Nonnull - @Override - public CompletableFuture getChunkAsync(long index) { - return this.chunkStore - .getChunkReferenceAsync(index, 4) - .thenApplyAsync( - reference -> reference == null ? null : this.chunkStore.getStore().getComponent((Ref) reference, WorldChunk.getComponentType()), this - ); - } - - @Nonnull - @Override - public CompletableFuture getNonTickingChunkAsync(long index) { - return this.chunkStore - .getChunkReferenceAsync(index) - .thenApplyAsync( - reference -> reference == null ? null : this.chunkStore.getStore().getComponent((Ref) reference, WorldChunk.getComponentType()), this - ); - } - - @Deprecated(forRemoval = true) - public List getPlayers() { - if (!this.isInThread()) { - return !this.isStarted() ? Collections.emptyList() : CompletableFuture.supplyAsync(this::getPlayers, this).join(); - } else { - ObjectArrayList players = new ObjectArrayList<>(32); - this.entityStore.getStore().forEachChunk(Player.getComponentType(), (archetypeChunk, commandBuffer) -> { - players.ensureCapacity(players.size() + archetypeChunk.size()); - - for (int index = 0; index < archetypeChunk.size(); index++) { - players.add(archetypeChunk.getComponent(index, Player.getComponentType())); - } - }); - return players; - } - } - - @Nullable - @Deprecated - public Entity getEntity(@Nonnull UUID uuid) { - if (!this.isInThread()) { - return CompletableFuture.supplyAsync(() -> this.getEntity(uuid), this).join(); - } else { - Ref ref = this.entityStore.getRefFromUUID(uuid); - return EntityUtils.getEntity(ref, this.entityStore.getStore()); - } - } - - @Nullable - public Ref getEntityRef(@Nonnull UUID uuid) { - return !this.isInThread() - ? CompletableFuture.>supplyAsync(() -> this.getEntityRef(uuid), this).join() - : this.entityStore.getRefFromUUID(uuid); - } - - public int getPlayerCount() { - return this.players.size(); - } - - @Nonnull - public Collection getPlayerRefs() { - return this.playerRefs; - } - - public void trackPlayerRef(@Nonnull PlayerRef playerRef) { - this.players.put(playerRef.getUuid(), playerRef); - } - - public void untrackPlayerRef(@Nonnull PlayerRef playerRef) { - this.players.remove(playerRef.getUuid(), playerRef); - } - - @Deprecated - @Nullable - public T spawnEntity(T entity, @Nonnull Vector3d position, Vector3f rotation) { - return this.addEntity(entity, position, rotation, AddReason.SPAWN); - } - - @Deprecated - @Nullable - public T addEntity(T entity, @Nonnull Vector3d position, @Nullable Vector3f rotation, @Nonnull AddReason reason) { - if (!EntityModule.get().isKnown(entity)) { - throw new IllegalArgumentException("Unknown entity"); - } else if (entity instanceof Player) { - throw new IllegalArgumentException("Entity can't be a Player!"); - } else if (entity.getNetworkId() == -1) { - throw new IllegalArgumentException("Entity id can't be Entity.UNASSIGNED_ID (-1)!"); - } else if (!this.equals(entity.getWorld())) { - throw new IllegalStateException("Expected entity to already have its world set to " + this.getName() + " but it has " + entity.getWorld()); - } else if (entity.getReference() != null && entity.getReference().isValid()) { - throw new IllegalArgumentException("Entity already has a valid EntityReference: " + entity.getReference()); - } else if (position.getY() < -32.0) { - throw new IllegalArgumentException("Unable to spawn entity below the world! -32 < " + position); - } else if (!this.isInThread()) { - this.logger.at(Level.WARNING).withCause(new SkipSentryException()).log("Warning addEntity was called off thread!"); - this.execute(() -> this.addEntity(entity, position, rotation, reason)); - return entity; - } else { - entity.unloadFromWorld(); - Holder holder = entity.toHolder(); - HeadRotation headRotation = holder.ensureAndGetComponent(HeadRotation.getComponentType()); - if (rotation != null) { - headRotation.teleportRotation(rotation); - } - - holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position, rotation)); - holder.ensureComponent(UUIDComponent.getComponentType()); - this.entityStore.getStore().addEntity(holder, reason); - return entity; - } - } - - @Override - public void sendMessage(@Nonnull Message message) { - if (!this.isInThread()) { - this.execute(() -> this.sendMessage(message)); - } else { - this.entityStore.getStore().forEachEntityParallel(PlayerRef.getComponentType(), (index, archetypeChunk, commandBuffer) -> { - PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - playerRefComponent.sendMessage(message); - }); - this.logger.at(Level.INFO).log("[Broadcast] [Message] %s", MessageUtil.toAnsiString(message).toAnsi(ConsoleModule.get().getTerminal())); - } - } - - @Override - public void execute(@Nonnull Runnable command) { - if (!this.acceptingTasks.get()) { - throw new SkipSentryException(new IllegalThreadStateException("World thread is not accepting tasks: " + this.name + ", " + this.getThread())); - } else { - this.taskQueue.offer(command); - } - } - - @Override - public void consumeTaskQueue() { - this.debugAssertInTickingThread(); - int tickStepNanos = this.getTickStepNanos(); - - Runnable runnable; - while ((runnable = this.taskQueue.poll()) != null) { - try { - long before = System.nanoTime(); - runnable.run(); - long after = System.nanoTime(); - long diff = after - before; - if (diff > tickStepNanos) { - this.logger.at(Level.WARNING).log("Task took %s ns: %s", FormatUtil.nanosToString(diff), runnable); - } - } catch (Exception var9) { - this.logger.at(Level.SEVERE).withCause(var9).log("Failed to run task!"); - } - } - } - - @Nonnull - public ChunkStore getChunkStore() { - return this.chunkStore; - } - - @Nonnull - public EntityStore getEntityStore() { - return this.entityStore; - } - - @Nonnull - public ChunkLightingManager getChunkLighting() { - return this.chunkLighting; - } - - @Nonnull - public WorldMapManager getWorldMapManager() { - return this.worldMapManager; - } - - public WorldPathConfig getWorldPathConfig() { - return this.worldPathConfig; - } - - @Nonnull - public WorldNotificationHandler getNotificationHandler() { - return this.notificationHandler; - } - - @Nonnull - public EventRegistry getEventRegistry() { - return this.eventRegistry; - } - - @Nullable - public CompletableFuture addPlayer(@Nonnull PlayerRef playerRef) { - return this.addPlayer(playerRef, null); - } - - @Nullable - public CompletableFuture addPlayer(@Nonnull PlayerRef playerRef, @Nullable Transform transform) { - return this.addPlayer(playerRef, transform, null, null); - } - - @Nullable - public CompletableFuture addPlayer( - @Nonnull PlayerRef playerRef, - @Deprecated(forRemoval = true) @Nullable Transform transform, - @Nullable Boolean clearWorldOverride, - @Nullable Boolean fadeInOutOverride - ) { - if (!this.alive.get()) { - return CompletableFuture.failedFuture(new IllegalStateException("This world has already been shutdown!")); - } - - // HyFix: Retry loop for race condition when player is being drained from old world - if (playerRef.getReference() != null) { - for (int retry = 0; retry < 5; retry++) { - java.util.concurrent.locks.LockSupport.parkNanos(20_000_000L); // 20ms - if (playerRef.getReference() == null) { - break; - } - } - if (playerRef.getReference() != null) { - throw new IllegalStateException("Player is already in a world"); - } - } - - { - PacketHandler packetHandler = playerRef.getPacketHandler(); - if (!packetHandler.stillActive()) { - return null; - } else { - Holder holder = playerRef.getHolder(); - - assert holder != null; - - TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType()); - if (transformComponent == null && transform == null) { - transformComponent = SpawnUtil.applyFirstSpawnTransform(holder, this, this.worldConfig, playerRef.getUuid()); - if (transformComponent == null) { - return CompletableFuture.failedFuture(new IllegalStateException("Spawn provider cannot be null for positioning new entities!")); - } - } - - assert transformComponent != null; - - Player playerComponent = holder.getComponent(Player.getComponentType()); - - assert playerComponent != null; - - boolean firstSpawn = !playerComponent.getPlayerConfigData().getPerWorldData().containsKey(this.name); - playerComponent.setFirstSpawn(firstSpawn); - if (transform != null) { - SpawnUtil.applyTransform(holder, transform); - } - - AddPlayerToWorldEvent event = HytaleServer.get() - .getEventBus() - .dispatchFor(AddPlayerToWorldEvent.class, this.name) - .dispatch(new AddPlayerToWorldEvent(holder, this)); - ChunkTracker chunkTrackerComponent = holder.getComponent(ChunkTracker.getComponentType()); - boolean clearWorld = clearWorldOverride != null ? clearWorldOverride : true; - boolean fadeInOut = fadeInOutOverride != null ? fadeInOutOverride : true; - if (chunkTrackerComponent != null && (clearWorld || fadeInOut)) { - chunkTrackerComponent.setReadyForChunks(false); - } - - Vector3d spawnPosition = transformComponent.getPosition(); - long chunkIndex = ChunkUtil.indexChunkFromBlock(spawnPosition.getX(), spawnPosition.getZ()); - CompletableFuture loadTargetChunkFuture = this.chunkStore - .getChunkReferenceAsync(chunkIndex) - .thenAccept(v -> playerComponent.startClientReadyTimeout()); - CompletableFuture clientReadyFuture = new CompletableFuture<>(); - packetHandler.setClientReadyForChunksFuture(clientReadyFuture); - CompletableFuture setupPlayerFuture = CompletableFuture.runAsync( - () -> this.onSetupPlayerJoining(holder, playerComponent, playerRef, packetHandler, transform, clearWorld, fadeInOut) - ); - CompletableFuture playerReadyFuture = clientReadyFuture.orTimeout(30L, TimeUnit.SECONDS); - return CompletableFuture.allOf(setupPlayerFuture, playerReadyFuture, loadTargetChunkFuture) - .thenApplyAsync(aVoid -> this.onFinishPlayerJoining(playerComponent, playerRef, packetHandler, event.shouldBroadcastJoinMessage()), this) - .exceptionally(throwable -> { - this.logger.at(Level.WARNING).withCause(throwable).log("Exception when adding player to world!"); - playerRef.getPacketHandler().disconnect("Exception when adding player to world!"); - throw new RuntimeException("Exception when adding player '" + playerRef.getUsername() + "' to world '" + this.name + "'", throwable); - }); - } - } - } - - @Nonnull - private PlayerRef onFinishPlayerJoining( - @Nonnull Player playerComponent, @Nonnull PlayerRef playerRefComponent, @Nonnull PacketHandler packetHandler, boolean broadcastJoin - ) { - TimeResource timeResource = this.entityStore.getStore().getResource(TimeResource.getResourceType()); - float timeDilationModifier = timeResource.getTimeDilationModifier(); - int maxViewRadius = HytaleServer.get().getConfig().getMaxViewRadius(); - packetHandler.write( - new ViewRadius(maxViewRadius * 32), - new SetEntitySeed(this.entitySeed.get()), - new SetClientId(playerComponent.getNetworkId()), - new SetTimeDilation(timeDilationModifier) - ); - packetHandler.write(new UpdateFeatures(this.features)); - packetHandler.write(this.worldConfig.getClientEffects().createSunSettingsPacket()); - packetHandler.write(this.worldConfig.getClientEffects().createPostFxSettingsPacket()); - UUID playerUuid = playerRefComponent.getUuid(); - Store store = this.entityStore.getStore(); - WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); - World world = store.getExternalData().getWorld(); - packetHandler.writeNoCache(new SetUpdateRate(this.getTps())); - if (this.isPaused) { - this.setPaused(false); - } - - Ref ref = playerRefComponent.addToStore(store); - if (ref != null && ref.isValid()) { - worldTimeResource.sendTimePackets(playerRefComponent); - WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); - worldMapTracker.clear(); - worldMapTracker.sendSettings(world); - if (broadcastJoin) { - Message message = Message.translation("server.general.playerJoinedWorld") - .param("username", playerRefComponent.getUsername()) - .param("world", this.worldConfig.getDisplayName() != null ? this.worldConfig.getDisplayName() : WorldConfig.formatDisplayName(this.name)); - PlayerUtil.broadcastMessageToPlayers(playerUuid, message, store); - } - - TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); - - assert transformComponent != null; - - String position = transformComponent.getPosition().toString(); - this.logger.at(Level.INFO).log("Player '%s' joined world '%s' at location %s (%s)", playerRefComponent.getUsername(), this.name, position, playerUuid); - HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); - - assert headRotationComponent != null; - - return playerRefComponent; - } else { - throw new IllegalStateException("Failed to add player ref of joining player to the world store"); - } - } - - private void onSetupPlayerJoining( - @Nonnull Holder holder, - @Nonnull Player playerComponent, - @Nonnull PlayerRef playerRefComponent, - @Nonnull PacketHandler packetHandler, - @Nullable Transform transform, - boolean clearWorld, - boolean fadeInOut - ) { - UUID playerUuid = playerRefComponent.getUuid(); - this.logger - .at(Level.INFO) - .log("Adding player '%s' to world '%s' at location %s (%s)", playerRefComponent.getUsername(), this.name, transform, playerUuid); - int entityId = this.entityStore.takeNextNetworkId(); - playerComponent.setNetworkId(entityId); - PlayerConfigData configData = playerComponent.getPlayerConfigData(); - configData.setWorld(this.name); - if (clearWorld) { - LegacyEntityTrackerSystems.clear(playerComponent, holder); - ChunkTracker chunkTrackerComponent = holder.getComponent(ChunkTracker.getComponentType()); - if (chunkTrackerComponent != null) { - chunkTrackerComponent.clear(); - } - } - - playerComponent.getPageManager().clearCustomPageAcknowledgements(); - JoinWorld packet = new JoinWorld(clearWorld, fadeInOut, this.worldConfig.getUuid()); - packetHandler.write(packet); - packetHandler.tryFlush(); - HytaleLogger.getLogger().at(Level.INFO).log("%s: Sent %s", packetHandler.getIdentifier(), packet); - packetHandler.setQueuePackets(true); - } - - @Nonnull - public CompletableFuture drainPlayersTo(@Nonnull World fallbackTargetWorld) { - return CompletableFuture.completedFuture((Void) null) - .thenComposeAsync( - aVoid -> { - ObjectArrayList> futures = new ObjectArrayList<>(); - - for (PlayerRef playerRef : this.playerRefs) { - Holder holder = playerRef.removeFromStore(); - DrainPlayerFromWorldEvent event = HytaleServer.get() - .getEventBus() - .dispatchFor(DrainPlayerFromWorldEvent.class, this.name) - .dispatch(new DrainPlayerFromWorldEvent(holder, fallbackTargetWorld, null)); - futures.add(event.getWorld().addPlayer(playerRef, event.getTransform())); - } - - return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); - }, - this - ); - } - - @Nonnull - public GameplayConfig getGameplayConfig() { - String gameplayConfigId = this.worldConfig.getGameplayConfig(); - GameplayConfig gameplayConfig = GameplayConfig.getAssetMap().getAsset(gameplayConfigId); - if (gameplayConfig == null) { - gameplayConfig = GameplayConfig.DEFAULT; - } - - return gameplayConfig; - } - - @Nonnull - public Map getFeatures() { - return Collections.unmodifiableMap(this.features); - } - - public boolean isFeatureEnabled(@Nonnull ClientFeature feature) { - return this.features.getOrDefault(feature, false); - } - - public void registerFeature(@Nonnull ClientFeature feature, boolean enabled) { - this.features.put(feature, enabled); - this.broadcastFeatures(); - } - - public void broadcastFeatures() { - UpdateFeatures packet = new UpdateFeatures(this.features); - - for (PlayerRef playerRef : this.playerRefs) { - playerRef.getPacketHandler().write(packet); - } - } - - @Nonnull - public Path getSavePath() { - return this.savePath; - } - - public void updateEntitySeed(@Nonnull Store store) { - int newEntitySeed = this.random.nextInt(); - this.entitySeed.set(newEntitySeed); - PlayerUtil.broadcastPacketToPlayers(store, new SetEntitySeed(newEntitySeed)); - } - - public void markGCHasRun() { - this.gcHasRun = true; - } - - public boolean consumeGCHasRun() { - boolean gcHasRun = this.gcHasRun; - this.gcHasRun = false; - return gcHasRun; - } - - @Override - public int hashCode() { - return this.name != null ? this.name.hashCode() : 0; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } else if (o != null && this.getClass() == o.getClass()) { - World world = (World) o; - return this.name.equals(world.name); - } else { - return false; - } - } - - @Nonnull - @Override - public String toString() { - return "World{name='" - + this.name - + "', alive=" - + this.alive.get() - + ", loadedChunksCount=" - + this.chunkStore.getLoadedChunksCount() - + ", totalLoadedChunksCount=" - + this.chunkStore.getTotalLoadedChunksCount() - + ", totalGeneratedChunksCount=" - + this.chunkStore.getTotalGeneratedChunksCount() - + ", entityCount=" - + this.entityStore.getStore().getEntityCount() - + "}"; - } - - public void validate(@Nonnull StringBuilder errors, @Nonnull IPrefabBuffer.RawBlockConsumer blockValidator, @Nonnull EnumSet options) throws IOException { - this.setThread(Thread.currentThread()); - this.onStart(); - Store store = this.chunkStore.getStore(); - StringBuilder tempBuilder = new StringBuilder(); - - for (long index : this.chunkStore.getLoader().getIndexes()) { - int chunkX = ChunkUtil.xOfChunkIndex(index); - int chunkZ = ChunkUtil.zOfChunkIndex(index); - - try { - CompletableFuture> future = this.chunkStore.getChunkReferenceAsync(index, 2); - - while (!future.isDone()) { - this.consumeTaskQueue(); - } - - Ref reference = future.join(); - if (reference != null && reference.isValid()) { - WorldChunk chunk = store.getComponent(reference, WorldChunk.getComponentType()); - ChunkColumn chunkColumn = store.getComponent(reference, ChunkColumn.getComponentType()); - if (chunkColumn != null) { - for (Ref section : chunkColumn.getSections()) { - final ChunkSection sectionInfo = store.getComponent(section, ChunkSection.getComponentType()); - BlockSection blockSection = store.getComponent(section, BlockSection.getComponentType()); - if (blockSection != null) { - BlockPhysics blockPhys = store.getComponent(section, BlockPhysics.getComponentType()); - - for (int y = 0; y < 32; y++) { - int worldY = (sectionInfo.getY() << 5) + y; - - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { - int blockId = blockSection.get(x, y, z); - int filler = blockSection.getFiller(x, y, z); - int rotation = blockSection.getRotationIndex(x, y, z); - Holder holder = chunk.getBlockComponentHolder(x, worldY, z); - int worldX = ChunkUtil.minBlock(chunk.getX()) + x; - int worldZ = ChunkUtil.minBlock(chunk.getZ()) + z; - blockValidator.accept( - worldX, worldY, worldZ, blockId, 0, 1.0F, holder, blockPhys != null ? blockPhys.get(x, y, z) : 0, rotation, filler, null - ); - if (options.contains(ValidationOption.BLOCK_FILLER)) { - var fetcher = new FillerBlockUtil.FillerFetcher() { - public int getBlock(BlockSection blockSection, ChunkStore chunkStore, int xx, int yx, int zx) { - if (xx >= 0 && yx >= 0 && zx >= 0 && xx < 32 && yx < 32 && zx < 32) { - return blockSection.get(xx, yx, zx); - } else { - int nx = sectionInfo.getX() + ChunkUtil.chunkCoordinate(xx); - int ny = sectionInfo.getY() + ChunkUtil.chunkCoordinate(yx); - int nz = sectionInfo.getZ() + ChunkUtil.chunkCoordinate(zx); - CompletableFuture> refFuture = chunkStore.getChunkSectionReferenceAsync(nx, ny, nz); - - while (!refFuture.isDone()) { - World.this.consumeTaskQueue(); - } - - Ref ref = refFuture.join(); - BlockSection blocks = chunkStore.getStore().getComponent(ref, BlockSection.getComponentType()); - return blocks == null ? Integer.MIN_VALUE : blocks.get(xx, yx, zx); - } - } - - public int getFiller(BlockSection blockSection, ChunkStore chunkStore, int xx, int yx, int zx) { - if (xx >= 0 && yx >= 0 && zx >= 0 && xx < 32 && yx < 32 && zx < 32) { - return blockSection.getFiller(xx, yx, zx); - } else { - int nx = sectionInfo.getX() + ChunkUtil.chunkCoordinate(xx); - int ny = sectionInfo.getY() + ChunkUtil.chunkCoordinate(yx); - int nz = sectionInfo.getZ() + ChunkUtil.chunkCoordinate(zx); - CompletableFuture> refFuture = chunkStore.getChunkSectionReferenceAsync(nx, ny, nz); - - while (!refFuture.isDone()) { - World.this.consumeTaskQueue(); - } - - Ref ref = refFuture.join(); - BlockSection blocks = chunkStore.getStore().getComponent(ref, BlockSection.getComponentType()); - return blocks == null ? Integer.MIN_VALUE : blocks.getFiller(xx, yx, zx); - } - } - - public int getRotationIndex(BlockSection blockSection, ChunkStore chunkStore, int xx, int yx, int zx) { - if (xx >= 0 && yx >= 0 && zx >= 0 && xx < 32 && yx < 32 && zx < 32) { - return blockSection.getFiller(xx, yx, zx); - } else { - int nx = sectionInfo.getX() + ChunkUtil.chunkCoordinate(xx); - int ny = sectionInfo.getY() + ChunkUtil.chunkCoordinate(yx); - int nz = sectionInfo.getZ() + ChunkUtil.chunkCoordinate(zx); - CompletableFuture> refFuture = chunkStore.getChunkSectionReferenceAsync(nx, ny, nz); - - while (!refFuture.isDone()) { - World.this.consumeTaskQueue(); - } - - Ref ref = refFuture.join(); - BlockSection blocks = chunkStore.getStore().getComponent(ref, BlockSection.getComponentType()); - return blocks == null ? Integer.MIN_VALUE : blocks.getRotationIndex(xx, yx, zx); - } - } - }; - FillerBlockUtil.ValidationResult fillerResult = FillerBlockUtil.validateBlock( - x, y, z, blockId, rotation, filler, blockSection, this.chunkStore, fetcher - ); - switch (fillerResult) { - case OK: - default: - break; - case INVALID_BLOCK: { - BlockType blockType = BlockType.getAssetMap().getAsset(blockId); - tempBuilder.append("\tBlock ") - .append(blockType != null ? blockType.getId() : "") - .append(" at ") - .append(x) - .append(", ") - .append(y) - .append(", ") - .append(z) - .append(" is not valid filler") - .append('\n'); - break; - } - case INVALID_FILLER: { - BlockType blockType = BlockType.getAssetMap().getAsset(blockId); - tempBuilder.append("\tBlock ") - .append(blockType != null ? blockType.getId() : "") - .append(" at ") - .append(x) - .append(", ") - .append(y) - .append(", ") - .append(z) - .append(" has invalid/missing filler blocks") - .append('\n'); - } - } - } - } - } - } - - if (!tempBuilder.isEmpty()) { - errors.append("\tChunk ") - .append(sectionInfo.getX()) - .append(", ") - .append(sectionInfo.getY()) - .append(", ") - .append(sectionInfo.getZ()) - .append(" validation errors:") - .append((CharSequence) tempBuilder); - tempBuilder.setLength(0); - } - } - } - - if (options.contains(ValidationOption.ENTITIES)) { - ComponentType> unknownComponentType = EntityStore.REGISTRY.getUnknownComponentType(); - - for (Holder entityHolder : chunk.getEntityChunk().getEntityHolders()) { - UnknownComponents unknownComponents = entityHolder.getComponent(unknownComponentType); - if (unknownComponents != null && !unknownComponents.getUnknownComponents().isEmpty()) { - errors.append("\tUnknown Entity Components: ").append(unknownComponents.getUnknownComponents()).append("\n"); - } - } - } - - store.tick(1.0F); - } - } - } catch (CompletionException var35) { - this.getLogger().at(Level.SEVERE).withCause(var35).log("Failed to validate chunk: %d, %d", chunkX, chunkZ); - errors.append('\t') - .append("Exception validating chunk: ") - .append(chunkX) - .append(", ") - .append(chunkZ) - .append('\n') - .append("\t\t") - .append(var35.getCause().getMessage()) - .append('\n'); - } - } - - if (this.alive.getAndSet(false)) { - this.onShutdown(); - } - - this.setThread(null); - } - - @FunctionalInterface - public interface GenericBlockBulkUpdater { - void apply(World var1, T var2, long var3, WorldChunk var5, int var6, int var7, int var8, int var9, int var10, int var11); - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java b/src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java deleted file mode 100644 index 8d2b1d30..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java +++ /dev/null @@ -1,660 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world; - -import com.hypixel.hytale.common.fastutil.HLongOpenHashSet; -import com.hypixel.hytale.common.fastutil.HLongSet; -import com.hypixel.hytale.common.thread.ticking.Tickable; -import com.hypixel.hytale.common.util.CompletableFutureUtil; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.iterator.CircleSpiralIterator; -import com.hypixel.hytale.math.shape.Box2D; -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.protocol.SoundCategory; -import com.hypixel.hytale.protocol.packets.worldmap.ClearWorldMap; -import com.hypixel.hytale.protocol.packets.worldmap.MapChunk; -import com.hypixel.hytale.protocol.packets.worldmap.MapImage; -import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; -import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMap; -import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapSettings; -import com.hypixel.hytale.server.core.Message; -import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.event.events.ecs.DiscoverZoneEvent; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; -import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapSettings; -import com.hypixel.hytale.server.core.universe.world.worldmap.markers.MapMarkerTracker; -import com.hypixel.hytale.server.core.util.EventTitleUtil; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongIterator; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Predicate; -import java.util.logging.Level; - -public class WorldMapTracker implements Tickable { - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - public static final float UPDATE_SPEED = 1.0F; - public static final int RADIUS_MAX = 512; - public static final int EMPTY_UPDATE_WORLD_MAP_SIZE = 13; - private static final int EMPTY_MAP_CHUNK_SIZE = 10; - private static final int FULL_MAP_CHUNK_SIZE = 23; - public static final int MAX_IMAGE_GENERATION = 20; - public static final int MAX_FRAME = 2621440; - private final Player player; - private final CircleSpiralIterator spiralIterator = new CircleSpiralIterator(); - private final ReentrantReadWriteLock loadedLock = new ReentrantReadWriteLock(); - private final HLongSet loaded = new HLongOpenHashSet(); - private final HLongSet pendingReloadChunks = new HLongOpenHashSet(); - private final Long2ObjectOpenHashMap> pendingReloadFutures = new Long2ObjectOpenHashMap<>(); - private final MapMarkerTracker markerTracker; - private float updateTimer; - private Integer viewRadiusOverride; - private boolean started; - private int sentViewRadius; - private int lastChunkX; - private int lastChunkZ; - @Nullable - private String currentBiomeName; - @Nullable - private WorldMapTracker.ZoneDiscoveryInfo currentZone; - private boolean clientHasWorldMapVisible; - @Nullable - private TransformComponent transformComponent; - - public WorldMapTracker(@Nonnull Player player) { - this.player = player; - this.markerTracker = new MapMarkerTracker(this); - } - - @Override - public void tick(float dt) { - if (!this.started) { - this.started = true; - LOGGER.at(Level.INFO).log("Started Generating Map!"); - } - - World world = this.player.getWorld(); - if (world != null) { - if (this.transformComponent == null) { - this.transformComponent = this.player.getTransformComponent(); - if (this.transformComponent == null) { - return; - } - } - - WorldMapManager worldMapManager = world.getWorldMapManager(); - WorldMapSettings worldMapSettings = worldMapManager.getWorldMapSettings(); - int viewRadius; - if (this.viewRadiusOverride != null) { - viewRadius = this.viewRadiusOverride; - } else { - viewRadius = worldMapSettings.getViewRadius(this.player.getViewRadius()); - } - - Vector3d position = this.transformComponent.getPosition(); - int playerX = MathUtil.floor(position.getX()); - int playerZ = MathUtil.floor(position.getZ()); - int playerChunkX = playerX >> 5; - int playerChunkZ = playerZ >> 5; - if (world.isCompassUpdating()) { - this.markerTracker.updatePointsOfInterest(dt, world, viewRadius, playerChunkX, playerChunkZ); - } - - if (worldMapManager.isWorldMapEnabled()) { - this.updateWorldMap(world, dt, worldMapSettings, viewRadius, playerChunkX, playerChunkZ); - } - } - } - - public void updateCurrentZoneAndBiome( - @Nonnull Ref ref, - @Nullable WorldMapTracker.ZoneDiscoveryInfo zoneDiscoveryInfo, - @Nullable String biomeName, - @Nonnull ComponentAccessor componentAccessor - ) { - this.currentBiomeName = biomeName; - this.currentZone = zoneDiscoveryInfo; - Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - if (!playerComponent.isWaitingForClientReady()) { - World world = componentAccessor.getExternalData().getWorld(); - if (zoneDiscoveryInfo != null && this.discoverZone(world, zoneDiscoveryInfo.regionName())) { - this.onZoneDiscovered(ref, zoneDiscoveryInfo, componentAccessor); - } - } - } - - private void onZoneDiscovered( - @Nonnull Ref ref, @Nonnull WorldMapTracker.ZoneDiscoveryInfo zoneDiscoveryInfo, @Nonnull ComponentAccessor componentAccessor - ) { - WorldMapTracker.ZoneDiscoveryInfo discoverZoneEventInfo = zoneDiscoveryInfo.clone(); - DiscoverZoneEvent.Display discoverZoneEvent = new DiscoverZoneEvent.Display(discoverZoneEventInfo); - componentAccessor.invoke(ref, discoverZoneEvent); - if (!discoverZoneEvent.isCancelled() && discoverZoneEventInfo.display()) { - PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - EventTitleUtil.showEventTitleToPlayer( - playerRefComponent, - Message.translation(String.format("server.map.region.%s", discoverZoneEventInfo.regionName())), - Message.translation(String.format("server.map.zone.%s", discoverZoneEventInfo.zoneName())), - discoverZoneEventInfo.major(), - discoverZoneEventInfo.icon(), - discoverZoneEventInfo.duration(), - discoverZoneEventInfo.fadeInDuration(), - discoverZoneEventInfo.fadeOutDuration() - ); - String discoverySoundEventId = discoverZoneEventInfo.discoverySoundEventId(); - if (discoverySoundEventId != null) { - int assetIndex = SoundEvent.getAssetMap().getIndex(discoverySoundEventId); - if (assetIndex != Integer.MIN_VALUE) { - SoundUtil.playSoundEvent2d(ref, assetIndex, SoundCategory.UI, componentAccessor); - } - } - } - } - - private void updateWorldMap( - @Nonnull World world, float dt, @Nonnull WorldMapSettings worldMapSettings, int chunkViewRadius, int playerChunkX, int playerChunkZ - ) { - this.processPendingReloadChunks(world); - Box2D worldMapArea = worldMapSettings.getWorldMapArea(); - if (worldMapArea == null) { - int xDiff = Math.abs(this.lastChunkX - playerChunkX); - int zDiff = Math.abs(this.lastChunkZ - playerChunkZ); - int chunkMoveDistance = xDiff <= 0 && zDiff <= 0 ? 0 : (int) Math.ceil(Math.sqrt(xDiff * xDiff + zDiff * zDiff)); - this.sentViewRadius = Math.max(0, this.sentViewRadius - chunkMoveDistance); - this.lastChunkX = playerChunkX; - this.lastChunkZ = playerChunkZ; - this.updateTimer -= dt; - if (this.updateTimer > 0.0F) { - return; - } - - if (this.sentViewRadius != chunkViewRadius) { - if (this.sentViewRadius > chunkViewRadius) { - this.sentViewRadius = chunkViewRadius; - } - - this.unloadImages(chunkViewRadius, playerChunkX, playerChunkZ); - if (this.sentViewRadius < chunkViewRadius) { - this.loadImages(world, chunkViewRadius, playerChunkX, playerChunkZ, 20); - } - } else { - this.updateTimer = 1.0F; - } - } else { - this.updateTimer -= dt; - if (this.updateTimer > 0.0F) { - return; - } - - this.loadWorldMap(world, worldMapArea, 20); - } - } - - private void unloadImages(int chunkViewRadius, int playerChunkX, int playerChunkZ) { - // HyFix #16: Wrap in try-catch for iterator corruption NPE under high load - try { - List currentUnloadList = null; - List> allUnloadLists = null; - this.loadedLock.writeLock().lock(); - - try { - int packetSize = 2621427; - LongIterator iterator = this.loaded.iterator(); - - while (iterator.hasNext()) { - long chunkCoordinates = iterator.nextLong(); - int mapChunkX = ChunkUtil.xOfChunkIndex(chunkCoordinates); - int mapChunkZ = ChunkUtil.zOfChunkIndex(chunkCoordinates); - if (!shouldBeVisible(chunkViewRadius, playerChunkX, playerChunkZ, mapChunkX, mapChunkZ)) { - if (currentUnloadList == null) { - currentUnloadList = new ObjectArrayList<>(packetSize / 10); - } - - currentUnloadList.add(new MapChunk(mapChunkX, mapChunkZ, null)); - packetSize -= 10; - iterator.remove(); - if (packetSize < 10) { - packetSize = 2621427; - if (allUnloadLists == null) { - allUnloadLists = new ObjectArrayList<>(this.loaded.size() / (packetSize / 10)); - } - - allUnloadLists.add(currentUnloadList); - currentUnloadList = new ObjectArrayList<>(packetSize / 10); - } - } - } - - if (allUnloadLists != null) { - for (List unloadList : allUnloadLists) { - this.writeUpdatePacket(unloadList); - } - } - - this.writeUpdatePacket(currentUnloadList); - } finally { - this.loadedLock.writeLock().unlock(); - } - } catch (NullPointerException e) { - System.out.println("[HyFix] WARNING: Iterator corruption in WorldMapTracker.unloadImages() - recovered gracefully (Issue #16)"); - } - } - - private void processPendingReloadChunks(@Nonnull World world) { - List chunksToSend = null; - this.loadedLock.writeLock().lock(); - - try { - if (!this.pendingReloadChunks.isEmpty()) { - int imageSize = MathUtil.fastFloor(32.0F * world.getWorldMapManager().getWorldMapSettings().getImageScale()); - int fullMapChunkSize = 23 + 4 * imageSize * imageSize; - int packetSize = 2621427; - LongIterator iterator = this.pendingReloadChunks.iterator(); - - while (iterator.hasNext()) { - long chunkCoordinates = iterator.nextLong(); - CompletableFuture future = this.pendingReloadFutures.get(chunkCoordinates); - if (future == null) { - future = world.getWorldMapManager().getImageAsync(chunkCoordinates); - this.pendingReloadFutures.put(chunkCoordinates, future); - } - - if (future.isDone()) { - iterator.remove(); - this.pendingReloadFutures.remove(chunkCoordinates); - if (chunksToSend == null) { - chunksToSend = new ObjectArrayList<>(packetSize / fullMapChunkSize); - } - - int mapChunkX = ChunkUtil.xOfChunkIndex(chunkCoordinates); - int mapChunkZ = ChunkUtil.zOfChunkIndex(chunkCoordinates); - chunksToSend.add(new MapChunk(mapChunkX, mapChunkZ, future.getNow(null))); - this.loaded.add(chunkCoordinates); - packetSize -= fullMapChunkSize; - if (packetSize < fullMapChunkSize) { - this.writeUpdatePacket(chunksToSend); - chunksToSend = new ObjectArrayList<>(2621440 - 13 / fullMapChunkSize); - packetSize = 2621427; - } - } - } - - this.writeUpdatePacket(chunksToSend); - return; - } - } finally { - this.loadedLock.writeLock().unlock(); - } - } - - private int loadImages(@Nonnull World world, int chunkViewRadius, int playerChunkX, int playerChunkZ, int maxGeneration) { - List currentLoadList = null; - List> allLoadLists = null; - this.loadedLock.writeLock().lock(); - - try { - int packetSize = 2621427; - int imageSize = MathUtil.fastFloor(32.0F * world.getWorldMapManager().getWorldMapSettings().getImageScale()); - int fullMapChunkSize = 23 + 4 * imageSize * imageSize; - boolean areAllLoaded = true; - this.spiralIterator.init(playerChunkX, playerChunkZ, this.sentViewRadius, chunkViewRadius); - - while (maxGeneration > 0 && this.spiralIterator.hasNext()) { - long chunkCoordinates = this.spiralIterator.next(); - if (!this.loaded.contains(chunkCoordinates)) { - areAllLoaded = false; - CompletableFuture future = world.getWorldMapManager().getImageAsync(chunkCoordinates); - if (!future.isDone()) { - maxGeneration--; - } else if (this.loaded.add(chunkCoordinates)) { - if (currentLoadList == null) { - currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); - } - - int mapChunkX = ChunkUtil.xOfChunkIndex(chunkCoordinates); - int mapChunkZ = ChunkUtil.zOfChunkIndex(chunkCoordinates); - currentLoadList.add(new MapChunk(mapChunkX, mapChunkZ, future.getNow(null))); - packetSize -= fullMapChunkSize; - if (packetSize < fullMapChunkSize) { - packetSize = 2621427; - if (allLoadLists == null) { - allLoadLists = new ObjectArrayList<>(); - } - - allLoadLists.add(currentLoadList); - currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); - } - } - } else if (areAllLoaded) { - this.sentViewRadius = this.spiralIterator.getCompletedRadius(); - } - } - - if (areAllLoaded) { - this.sentViewRadius = this.spiralIterator.getCompletedRadius(); - } - - if (allLoadLists != null) { - for (List unloadList : allLoadLists) { - this.writeUpdatePacket(unloadList); - } - } - - this.writeUpdatePacket(currentLoadList); - } finally { - this.loadedLock.writeLock().unlock(); - } - - return maxGeneration; - } - - private int loadWorldMap(@Nonnull World world, @Nonnull Box2D worldMapArea, int maxGeneration) { - List currentLoadList = null; - List> allLoadLists = null; - this.loadedLock.writeLock().lock(); - - try { - int packetSize = 2621427; - int imageSize = MathUtil.fastFloor(32.0F * world.getWorldMapManager().getWorldMapSettings().getImageScale()); - int fullMapChunkSize = 23 + 4 * imageSize * imageSize; - - for (int mapChunkX = MathUtil.floor(worldMapArea.min.x); mapChunkX < MathUtil.ceil(worldMapArea.max.x) && maxGeneration > 0; mapChunkX++) { - for (int mapChunkZ = MathUtil.floor(worldMapArea.min.y); mapChunkZ < MathUtil.ceil(worldMapArea.max.y) && maxGeneration > 0; mapChunkZ++) { - long chunkCoordinates = ChunkUtil.indexChunk(mapChunkX, mapChunkZ); - if (!this.loaded.contains(chunkCoordinates)) { - CompletableFuture future = CompletableFutureUtil._catch(world.getWorldMapManager().getImageAsync(chunkCoordinates)); - if (!future.isDone()) { - maxGeneration--; - } else { - if (currentLoadList == null) { - currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); - } - - currentLoadList.add(new MapChunk(mapChunkX, mapChunkZ, future.getNow(null))); - this.loaded.add(chunkCoordinates); - packetSize -= fullMapChunkSize; - if (packetSize < fullMapChunkSize) { - packetSize = 2621427; - if (allLoadLists == null) { - allLoadLists = new ObjectArrayList<>(Math.max(packetSize / fullMapChunkSize, 1)); - } - - allLoadLists.add(currentLoadList); - currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); - } - } - } - } - } - } finally { - this.loadedLock.writeLock().unlock(); - } - - if (allLoadLists != null) { - for (List unloadList : allLoadLists) { - this.writeUpdatePacket(unloadList); - } - } - - this.writeUpdatePacket(currentLoadList); - return maxGeneration; - } - - private void writeUpdatePacket(@Nullable List list) { - if (list != null) { - UpdateWorldMap packet = new UpdateWorldMap(list.toArray(MapChunk[]::new), null, null); - LOGGER.at(Level.FINE).log("Sending world map update to %s - %d chunks", this.player.getUuid(), list.size()); - this.player.getPlayerConnection().write(packet); - } - } - - @Nonnull - public Map getSentMarkers() { - return this.markerTracker.getSentMarkers(); - } - - @Nonnull - public Player getPlayer() { - return this.player; - } - - @Nullable - public TransformComponent getTransformComponent() { - return this.transformComponent; - } - - public void clear() { - this.loadedLock.writeLock().lock(); - - try { - this.loaded.clear(); - this.sentViewRadius = 0; - this.markerTracker.getSentMarkers().clear(); - } finally { - this.loadedLock.writeLock().unlock(); - } - - this.player.getPlayerConnection().write(new ClearWorldMap()); - } - - public void clearChunks(@Nonnull LongSet chunkIndices) { - this.loadedLock.writeLock().lock(); - - try { - chunkIndices.forEach(index -> { - this.loaded.remove(index); - this.pendingReloadChunks.add(index); - this.pendingReloadFutures.remove(index); - }); - } finally { - this.loadedLock.writeLock().unlock(); - } - - this.updateTimer = 0.0F; - } - - public void sendSettings(@Nonnull World world) { - UpdateWorldMapSettings worldMapSettingsPacket = new UpdateWorldMapSettings(world.getWorldMapManager().getWorldMapSettings().getSettingsPacket()); - world.execute(() -> { - Store store = world.getEntityStore().getStore(); - Ref ref = this.player.getReference(); - if (ref != null) { - Player playerComponent = store.getComponent(ref, Player.getComponentType()); - - assert playerComponent != null; - - PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); - - assert playerRefComponent != null; - - worldMapSettingsPacket.allowTeleportToCoordinates = this.isAllowTeleportToCoordinates(); - worldMapSettingsPacket.allowTeleportToMarkers = this.isAllowTeleportToMarkers(); - playerRefComponent.getPacketHandler().write(worldMapSettingsPacket); - } - }); - } - - private boolean hasDiscoveredZone(@Nonnull String zoneName) { - return this.player.getPlayerConfigData().getDiscoveredZones().contains(zoneName); - } - - public boolean discoverZone(@Nonnull World world, @Nonnull String zoneName) { - Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); - if (!discoveredZones.contains(zoneName)) { - Set var4 = new HashSet<>(discoveredZones); - var4.add(zoneName); - this.player.getPlayerConfigData().setDiscoveredZones(var4); - this.sendSettings(world); - return true; - } else { - return false; - } - } - - public boolean undiscoverZone(@Nonnull World world, @Nonnull String zoneName) { - Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); - if (discoveredZones.contains(zoneName)) { - Set var4 = new HashSet<>(discoveredZones); - var4.remove(zoneName); - this.player.getPlayerConfigData().setDiscoveredZones(var4); - this.sendSettings(world); - return true; - } else { - return false; - } - } - - public boolean discoverZones(@Nonnull World world, @Nonnull Set zoneNames) { - Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); - if (!discoveredZones.containsAll(zoneNames)) { - Set var4 = new HashSet<>(discoveredZones); - var4.addAll(zoneNames); - this.player.getPlayerConfigData().setDiscoveredZones(var4); - this.sendSettings(world); - return true; - } else { - return false; - } - } - - public boolean undiscoverZones(@Nonnull World world, @Nonnull Set zoneNames) { - Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); - if (discoveredZones.containsAll(zoneNames)) { - Set var4 = new HashSet<>(discoveredZones); - var4.removeAll(zoneNames); - this.player.getPlayerConfigData().setDiscoveredZones(var4); - this.sendSettings(world); - return true; - } else { - return false; - } - } - - public boolean isAllowTeleportToCoordinates() { - return this.player.hasPermission("hytale.world_map.teleport.coordinate"); - } - - public boolean isAllowTeleportToMarkers() { - return this.player.hasPermission("hytale.world_map.teleport.marker"); - } - - public void setPlayerMapFilter(Predicate playerMapFilter) { - this.markerTracker.setPlayerMapFilter(playerMapFilter); - } - - public void setClientHasWorldMapVisible(boolean visible) { - this.clientHasWorldMapVisible = visible; - } - - @Nullable - public Integer getViewRadiusOverride() { - return this.viewRadiusOverride; - } - - @Nullable - public String getCurrentBiomeName() { - return this.currentBiomeName; - } - - @Nullable - public WorldMapTracker.ZoneDiscoveryInfo getCurrentZone() { - return this.currentZone; - } - - public void setViewRadiusOverride(@Nullable Integer viewRadiusOverride) { - this.viewRadiusOverride = viewRadiusOverride; - this.clear(); - } - - public int getEffectiveViewRadius(@Nonnull World world) { - return this.viewRadiusOverride != null - ? this.viewRadiusOverride - : world.getWorldMapManager().getWorldMapSettings().getViewRadius(this.player.getViewRadius()); - } - - public boolean shouldBeVisible(int chunkViewRadius, long chunkCoordinates) { - if (this.player != null && this.transformComponent != null) { - Vector3d position = this.transformComponent.getPosition(); - int chunkX = MathUtil.floor(position.getX()) >> 5; - int chunkZ = MathUtil.floor(position.getZ()) >> 5; - int x = ChunkUtil.xOfChunkIndex(chunkCoordinates); - int z = ChunkUtil.zOfChunkIndex(chunkCoordinates); - return shouldBeVisible(chunkViewRadius, chunkX, chunkZ, x, z); - } else { - return false; - } - } - - public void copyFrom(@Nonnull WorldMapTracker worldMapTracker) { - this.loadedLock.writeLock().lock(); - - try { - worldMapTracker.loadedLock.readLock().lock(); - - try { - this.loaded.addAll(worldMapTracker.loaded); - this.markerTracker.copyFrom(worldMapTracker.markerTracker); - } finally { - worldMapTracker.loadedLock.readLock().unlock(); - } - } finally { - this.loadedLock.writeLock().unlock(); - } - } - - public static boolean shouldBeVisible(int chunkViewRadius, int chunkX, int chunkZ, int x, int z) { - int xDiff = Math.abs(x - chunkX); - int zDiff = Math.abs(z - chunkZ); - int distanceSq = xDiff * xDiff + zDiff * zDiff; - return distanceSq <= chunkViewRadius * chunkViewRadius; - } - - public record ZoneDiscoveryInfo( - @Nonnull String zoneName, - @Nonnull String regionName, - boolean display, - @Nullable String discoverySoundEventId, - @Nullable String icon, - boolean major, - float duration, - float fadeInDuration, - float fadeOutDuration - ) { - @Nonnull - public WorldMapTracker.ZoneDiscoveryInfo clone() { - return new WorldMapTracker.ZoneDiscoveryInfo( - this.zoneName, - this.regionName, - this.display, - this.discoverySoundEventId, - this.icon, - this.major, - this.duration, - this.fadeInDuration, - this.fadeOutDuration - ); - } - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java deleted file mode 100644 index 425ee11e..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java +++ /dev/null @@ -1,429 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world.chunk; - -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.map.Int2ObjectMapCodec; -import com.hypixel.hytale.codec.store.StoredCodec; -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.Component; -import com.hypixel.hytale.component.ComponentRegistry; -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.query.Query; -import com.hypixel.hytale.component.system.RefChangeSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.math.vector.Vector3i; -import com.hypixel.hytale.protocol.Packet; -import com.hypixel.hytale.server.core.modules.LegacyModule; -import com.hypixel.hytale.server.core.modules.block.BlockModule; -import com.hypixel.hytale.server.core.universe.PlayerRef; -import com.hypixel.hytale.server.core.universe.world.meta.BlockState; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; -import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectCollection; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Objects; -import java.util.logging.Level; - -public class BlockComponentChunk implements Component { - public static final BuilderCodec CODEC = BuilderCodec.builder(BlockComponentChunk.class, BlockComponentChunk::new) - .addField( - new KeyedCodec<>("BlockComponents", new Int2ObjectMapCodec<>(new StoredCodec<>(ChunkStore.HOLDER_CODEC_KEY), Int2ObjectOpenHashMap::new)), - (entityChunk, map) -> { - entityChunk.entityHolders.clear(); - entityChunk.entityHolders.putAll(map); - }, - entityChunk -> { - if (entityChunk.entityReferences.isEmpty()) { - return entityChunk.entityHolders; - } else { - Int2ObjectMap> map = new Int2ObjectOpenHashMap<>(entityChunk.entityHolders.size() + entityChunk.entityReferences.size()); - map.putAll(entityChunk.entityHolders); - - for (Entry> entry : entityChunk.entityReferences.int2ObjectEntrySet()) { - Ref reference = entry.getValue(); - Store store = reference.getStore(); - if (store.getArchetype(reference).hasSerializableComponents(store.getRegistry().getData())) { - map.put(entry.getIntKey(), store.copySerializableEntity(reference)); - } - } - - return map; - } - } - ) - .build(); - @Nonnull - private final Int2ObjectMap> entityHolders; - @Nonnull - private final Int2ObjectMap> entityReferences; - @Nonnull - private final Int2ObjectMap> entityHoldersUnmodifiable; - @Nonnull - private final Int2ObjectMap> entityReferencesUnmodifiable; - private boolean needsSaving; - - public static ComponentType getComponentType() { - return LegacyModule.get().getBlockComponentChunkComponentType(); - } - - public BlockComponentChunk() { - this.entityHolders = new Int2ObjectOpenHashMap<>(); - this.entityReferences = new Int2ObjectOpenHashMap<>(); - this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable(this.entityHolders); - this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable(this.entityReferences); - } - - public BlockComponentChunk(@Nonnull Int2ObjectMap> entityHolders, @Nonnull Int2ObjectMap> entityReferences) { - this.entityHolders = entityHolders; - this.entityReferences = entityReferences; - this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable(entityHolders); - this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable(entityReferences); - } - - @Nonnull - @Override - public Component clone() { - Int2ObjectOpenHashMap> entityHoldersClone = new Int2ObjectOpenHashMap<>(this.entityHolders.size() + this.entityReferences.size()); - - for (Entry> entry : this.entityHolders.int2ObjectEntrySet()) { - entityHoldersClone.put(entry.getIntKey(), entry.getValue().clone()); - } - - for (Entry> entry : this.entityReferences.int2ObjectEntrySet()) { - Ref reference = entry.getValue(); - entityHoldersClone.put(entry.getIntKey(), reference.getStore().copyEntity(reference)); - } - - return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<>()); - } - - @Nonnull - @Override - public Component cloneSerializable() { - ComponentRegistry.Data data = ChunkStore.REGISTRY.getData(); - Int2ObjectOpenHashMap> entityHoldersClone = new Int2ObjectOpenHashMap<>(this.entityHolders.size() + this.entityReferences.size()); - - for (Entry> entry : this.entityHolders.int2ObjectEntrySet()) { - Holder holder = entry.getValue(); - if (holder.getArchetype().hasSerializableComponents(data)) { - entityHoldersClone.put(entry.getIntKey(), holder.cloneSerializable(data)); - } - } - - for (Entry> entryx : this.entityReferences.int2ObjectEntrySet()) { - Ref reference = entryx.getValue(); - Store store = reference.getStore(); - if (store.getArchetype(reference).hasSerializableComponents(data)) { - entityHoldersClone.put(entryx.getIntKey(), store.copySerializableEntity(reference)); - } - } - - return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<>()); - } - - @Nonnull - public Int2ObjectMap> getEntityHolders() { - return this.entityHoldersUnmodifiable; - } - - @Nullable - public Holder getEntityHolder(int index) { - return this.entityHolders.get(index); - } - - public void addEntityHolder(int index, @Nonnull Holder holder) { - if (this.entityReferences.containsKey(index)) { - throw new IllegalArgumentException("Duplicate block components at: " + index); - } else if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) { - throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index); - } else { - this.markNeedsSaving(); - } - } - - public void storeEntityHolder(int index, @Nonnull Holder holder) { - if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) { - throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index); - } - } - - @Nullable - public Holder removeEntityHolder(int index) { - Holder reference = this.entityHolders.remove(index); - if (reference != null) { - this.markNeedsSaving(); - } - - return reference; - } - - @Nonnull - public Int2ObjectMap> getEntityReferences() { - return this.entityReferencesUnmodifiable; - } - - @Nullable - public Ref getEntityReference(int index) { - return this.entityReferences.get(index); - } - - public void addEntityReference(int index, @Nonnull Ref reference) { - reference.validate(); - // HyFix #8: Handle duplicate block components gracefully instead of throwing - if (this.entityHolders.containsKey(index)) { - System.out.println("[HyFix] WARNING: Duplicate block component detected at index " + index + " - ignoring (teleporter fix)"); - return; - } else if (this.entityReferences.putIfAbsent(index, Objects.requireNonNull(reference)) != null) { - System.out.println("[HyFix] WARNING: Duplicate block component (entity reference) detected at index " + index + " - ignoring (teleporter fix)"); - return; - } else { - this.markNeedsSaving(); - } - } - - public void loadEntityReference(int index, @Nonnull Ref reference) { - reference.validate(); - if (this.entityHolders.containsKey(index)) { - throw new IllegalArgumentException("Duplicate block components at: " + index); - } else if (this.entityReferences.putIfAbsent(index, Objects.requireNonNull(reference)) != null) { - throw new IllegalArgumentException("Duplicate block components (entity reference) at: " + index); - } - } - - public void removeEntityReference(int index, Ref reference) { - if (this.entityReferences.remove(index, reference)) { - this.markNeedsSaving(); - } - } - - public void unloadEntityReference(int index, Ref reference) { - this.entityReferences.remove(index, reference); - } - - @Nullable - public Int2ObjectMap> takeEntityHolders() { - if (this.entityHolders.isEmpty()) { - return null; - } else { - Int2ObjectOpenHashMap> holders = new Int2ObjectOpenHashMap<>(this.entityHolders); - this.entityHolders.clear(); - return holders; - } - } - - @Nullable - public Int2ObjectMap> takeEntityReferences() { - if (this.entityReferences.isEmpty()) { - return null; - } else { - Int2ObjectOpenHashMap> holders = new Int2ObjectOpenHashMap<>(this.entityReferences); - this.entityReferences.clear(); - return holders; - } - } - - @Nullable - public > T getComponent(int index, @Nonnull ComponentType componentType) { - Ref reference = this.entityReferences.get(index); - if (reference != null) { - return reference.getStore().getComponent(reference, componentType); - } else { - Holder holder = this.entityHolders.get(index); - return holder != null ? holder.getComponent(componentType) : null; - } - } - - public boolean hasComponents(int index) { - return this.entityReferences.containsKey(index) || this.entityHolders.containsKey(index); - } - - public boolean getNeedsSaving() { - return this.needsSaving; - } - - public void markNeedsSaving() { - this.needsSaving = true; - } - - public boolean consumeNeedsSaving() { - boolean out = this.needsSaving; - this.needsSaving = false; - return out; - } - - public static class BlockComponentChunkLoadingSystem extends RefChangeSystem> { - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - private final Archetype archetype = Archetype.of(WorldChunk.getComponentType(), BlockComponentChunk.getComponentType()); - - public BlockComponentChunkLoadingSystem() { - } - - @Override - public Query getQuery() { - return this.archetype; - } - - @Nonnull - @Override - public ComponentType> componentType() { - return ChunkStore.REGISTRY.getNonTickingComponentType(); - } - - public void onComponentAdded( - @Nonnull Ref ref, - @Nonnull NonTicking component, - @Nonnull Store store, - @Nonnull CommandBuffer commandBuffer - ) { - BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType()); - Int2ObjectMap> entityReferences = blockComponentChunk.takeEntityReferences(); - if (entityReferences != null) { - int size = entityReferences.size(); - int[] indexes = new int[size]; - Ref[] references = new Ref[size]; - int j = 0; - - for (Entry> entry : entityReferences.int2ObjectEntrySet()) { - indexes[j] = entry.getIntKey(); - references[j] = entry.getValue(); - j++; - } - - ComponentRegistry.Data data = ChunkStore.REGISTRY.getData(); - - for (int i = 0; i < size; i++) { - if (store.getArchetype(references[i]).hasSerializableComponents(data)) { - Holder holder = ChunkStore.REGISTRY.newHolder(); - commandBuffer.removeEntity(references[i], holder, RemoveReason.UNLOAD); - blockComponentChunk.storeEntityHolder(indexes[i], holder); - } else { - commandBuffer.removeEntity(references[i], RemoveReason.UNLOAD); - } - } - } - } - - public void onComponentSet( - @Nonnull Ref ref, - 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 - ) { - WorldChunk chunk = store.getComponent(ref, WorldChunk.getComponentType()); - BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType()); - Int2ObjectMap> entityHolders = blockComponentChunk.takeEntityHolders(); - if (entityHolders != null) { - int holderCount = entityHolders.size(); - int[] indexes = new int[holderCount]; - Holder[] holders = new Holder[holderCount]; - int j = 0; - - for (Entry> entry : entityHolders.int2ObjectEntrySet()) { - indexes[j] = entry.getIntKey(); - holders[j] = entry.getValue(); - j++; - } - - for (int i = holderCount - 1; i >= 0; i--) { - Holder holder = holders[i]; - if (holder.getArchetype().isEmpty()) { - LOGGER.at(Level.SEVERE).log("Empty archetype entity holder: %s (#%d)", holder, i); - holders[i] = holders[--holderCount]; - holders[holderCount] = holder; - chunk.markNeedsSaving(); - } else { - int index = indexes[i]; - int x = ChunkUtil.xFromBlockInColumn(index); - int y = ChunkUtil.yFromBlockInColumn(index); - int z = ChunkUtil.zFromBlockInColumn(index); - holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, ref)); - BlockState state = BlockState.getBlockState(holder); - if (state != null) { - state.setPosition(chunk, new Vector3i(x, y, z)); - } - } - } - - commandBuffer.addEntities(holders, AddReason.LOAD); - } - } - } - - public static class LoadBlockComponentPacketSystem extends ChunkStore.LoadPacketDataQuerySystem { - private final ComponentType componentType; - - public LoadBlockComponentPacketSystem(ComponentType blockComponentChunkComponentType) { - this.componentType = blockComponentChunkComponentType; - } - - @Override - public Query getQuery() { - return this.componentType; - } - - public void fetch( - int index, - @Nonnull ArchetypeChunk archetypeChunk, - @Nonnull Store store, - CommandBuffer commandBuffer, - PlayerRef player, - @Nonnull List results - ) { - BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType); - ObjectCollection> references = component.entityReferences.values(); - Store componentStore = store.getExternalData().getWorld().getChunkStore().getStore(); - componentStore.fetch(references, ChunkStore.LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results); - } - } - - public static class UnloadBlockComponentPacketSystem extends ChunkStore.UnloadPacketDataQuerySystem { - private final ComponentType componentType; - - public UnloadBlockComponentPacketSystem(ComponentType blockComponentChunkComponentType) { - this.componentType = blockComponentChunkComponentType; - } - - @Override - public Query getQuery() { - return this.componentType; - } - - public void fetch( - int index, - @Nonnull ArchetypeChunk archetypeChunk, - @Nonnull Store store, - CommandBuffer commandBuffer, - PlayerRef player, - @Nonnull List results - ) { - BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType); - ObjectCollection> references = component.entityReferences.values(); - Store componentStore = store.getExternalData().getWorld().getChunkStore().getStore(); - componentStore.fetch(references, ChunkStore.UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results); - } - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java b/src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java deleted file mode 100644 index 811b98c7..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world.lighting; - -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.math.vector.Vector3i; -import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; -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.chunk.section.BlockSection; -import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; - -import javax.annotation.Nonnull; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; -import java.util.logging.Level; - -public class ChunkLightingManager implements Runnable { - @Nonnull - private final HytaleLogger logger; - @Nonnull - private final Thread thread; - @Nonnull - private final World world; - private final Semaphore semaphore = new Semaphore(1); - private final Set set = ConcurrentHashMap.newKeySet(); - private final ObjectArrayFIFOQueue queue = new ObjectArrayFIFOQueue<>(); - private LightCalculation lightCalculation; - - // HyFix: Periodic chunk lighting invalidation to prevent memory leaks - private static final long PERIODIC_INVALIDATION_INTERVAL_MS = 30000; // 30 seconds - private long lastPeriodicInvalidation = 0; - - public ChunkLightingManager(@Nonnull World world) { - this.logger = HytaleLogger.get("World|" + world.getName() + "|L"); - this.thread = new Thread(this, "ChunkLighting - " + world.getName()); - this.thread.setDaemon(true); - this.world = world; - this.lightCalculation = new FloodLightCalculation(this); - } - - @Nonnull - protected HytaleLogger getLogger() { - return this.logger; - } - - @Nonnull - public World getWorld() { - return this.world; - } - - public void setLightCalculation(LightCalculation lightCalculation) { - this.lightCalculation = lightCalculation; - } - - public LightCalculation getLightCalculation() { - return this.lightCalculation; - } - - public void start() { - this.thread.start(); - } - - @Override - public void run() { - try { - int lastSize = 0; - int count = 0; - this.lastPeriodicInvalidation = System.currentTimeMillis(); - - while (!this.thread.isInterrupted()) { - this.semaphore.drainPermits(); - Vector3i pos; - synchronized (this.queue) { - pos = this.queue.isEmpty() ? null : this.queue.dequeue(); - } - - if (pos != null) { - this.process(pos); - } - - // HyFix: Periodic chunk lighting invalidation to prevent memory leaks and stale lighting - long now = System.currentTimeMillis(); - if (now - this.lastPeriodicInvalidation >= PERIODIC_INVALIDATION_INTERVAL_MS) { - this.lastPeriodicInvalidation = now; - try { - this.invalidateLoadedChunks(); - } catch (Exception e) { - // Silently handle errors - may occur during world transitions - } - } - - Thread.yield(); - int currentSize; - synchronized (this.queue) { - currentSize = this.queue.size(); - } - - if (currentSize != lastSize) { - count = 0; - lastSize = currentSize; - } else if (count <= currentSize) { - count++; - } else { - this.semaphore.acquire(); - } - } - } catch (InterruptedException var9) { - Thread.currentThread().interrupt(); - } - } - - private void process(Vector3i chunkPosition) { - try { - switch (this.lightCalculation.calculateLight(chunkPosition)) { - case NOT_LOADED: - case WAITING_FOR_NEIGHBOUR: - case DONE: - this.set.remove(chunkPosition); - break; - case INVALIDATED: - synchronized (this.queue) { - this.queue.enqueue(chunkPosition); - } - } - } catch (Exception var5) { - this.logger.at(Level.WARNING).withCause(var5).log("Failed to calculate lighting for: %s", chunkPosition); - this.set.remove(chunkPosition); - } - } - - public boolean interrupt() { - if (this.thread.isAlive()) { - this.thread.interrupt(); - return true; - } else { - return false; - } - } - - public void stop() { - try { - int i = 0; - - while (this.thread.isAlive()) { - this.thread.interrupt(); - this.thread.join(this.world.getTickStepNanos() / 1000000); - i += this.world.getTickStepNanos() / 1000000; - if (i > 5000) { - StringBuilder sb = new StringBuilder(); - - for (StackTraceElement traceElement : this.thread.getStackTrace()) { - sb.append("\tat ").append(traceElement).append('\n'); - } - - HytaleLogger.getLogger().at(Level.SEVERE).log("Forcing ChunkLighting Thread %s to stop:\n%s", this.thread, sb.toString()); - this.thread.stop(); - break; - } - } - } catch (InterruptedException var7) { - Thread.currentThread().interrupt(); - } - } - - public void init(WorldChunk worldChunk) { - this.lightCalculation.init(worldChunk); - } - - public void addToQueue(Vector3i chunkPosition) { - if (this.set.add(chunkPosition)) { - synchronized (this.queue) { - this.queue.enqueue(chunkPosition); - } - - this.semaphore.release(1); - } - } - - public boolean isQueued(int chunkX, int chunkZ) { - Vector3i chunkPos = new Vector3i(chunkX, 0, chunkZ); - - for (int chunkY = 0; chunkY < 10; chunkY++) { - chunkPos.setY(chunkY); - if (this.isQueued(chunkPos)) { - return true; - } - } - - return false; - } - - public boolean isQueued(Vector3i chunkPosition) { - return this.set.contains(chunkPosition); - } - - public int getQueueSize() { - synchronized (this.queue) { - return this.queue.size(); - } - } - - public boolean invalidateLightAtBlock(WorldChunk worldChunk, int blockX, int blockY, int blockZ, BlockType blockType, int oldHeight, int newHeight) { - return this.lightCalculation.invalidateLightAtBlock(worldChunk, blockX, blockY, blockZ, blockType, oldHeight, newHeight); - } - - public boolean invalidateLightInChunk(WorldChunk worldChunk) { - return this.lightCalculation.invalidateLightInChunkSections(worldChunk, 0, 10); - } - - public boolean invalidateLightInChunkSection(WorldChunk worldChunk, int sectionIndex) { - return this.lightCalculation.invalidateLightInChunkSections(worldChunk, sectionIndex, sectionIndex + 1); - } - - public boolean invalidateLightInChunkSections(WorldChunk worldChunk, int sectionIndexFrom, int sectionIndexTo) { - return this.lightCalculation.invalidateLightInChunkSections(worldChunk, sectionIndexFrom, sectionIndexTo); - } - - public void invalidateLoadedChunks() { - this.world.getChunkStore().getStore().forEachEntityParallel(WorldChunk.getComponentType(), (index, archetypeChunk, storeCommandBuffer) -> { - WorldChunk chunk = archetypeChunk.getComponent(index, WorldChunk.getComponentType()); - - for (int y = 0; y < 10; y++) { - BlockSection section = chunk.getBlockChunk().getSectionAtIndex(y); - section.invalidateLocalLight(); - if (BlockChunk.SEND_LOCAL_LIGHTING_DATA || BlockChunk.SEND_GLOBAL_LIGHTING_DATA) { - chunk.getBlockChunk().invalidateChunkSection(y); - } - } - }); - this.world.getChunkStore().getChunkIndexes().forEach(index -> { - int x = ChunkUtil.xOfChunkIndex(index); - int z = ChunkUtil.zOfChunkIndex(index); - - for (int y = 0; y < 10; y++) { - this.addToQueue(new Vector3i(x, y, z)); - } - }); - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java b/src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java deleted file mode 100644 index 55e7cf0b..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java +++ /dev/null @@ -1,923 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world.storage; - -import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.store.CodecKey; -import com.hypixel.hytale.codec.store.CodecStore; -import com.hypixel.hytale.common.util.FormatUtil; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.IResourceStorage; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.SystemType; -import com.hypixel.hytale.component.system.StoreSystem; -import com.hypixel.hytale.component.system.data.EntityDataSystem; -import com.hypixel.hytale.event.IEventDispatcher; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.metrics.MetricProvider; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.protocol.Packet; -import com.hypixel.hytale.server.core.HytaleServer; -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.WorldProvider; -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.ChunkFlag; -import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; -import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; -import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; -import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkUnloadingSystem; -import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; -import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; -import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; -import com.hypixel.hytale.sneakythrow.SneakyThrow; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.longs.LongSets; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.StampedLock; -import java.util.logging.Level; - -public class ChunkStore implements WorldProvider { - @Nonnull - public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register("Store", ChunkStore::getStore, Store.METRICS_REGISTRY) - .register("ChunkLoader", MetricProvider.maybe(ChunkStore::getLoader)) - .register("ChunkSaver", MetricProvider.maybe(ChunkStore::getSaver)) - .register("WorldGen", MetricProvider.maybe(ChunkStore::getGenerator)) - .register("TotalGeneratedChunkCount", chunkComponentStore -> (long) chunkComponentStore.totalGeneratedChunksCount.get(), Codec.LONG) - .register("TotalLoadedChunkCount", chunkComponentStore -> (long) chunkComponentStore.totalLoadedChunksCount.get(), Codec.LONG); - public static final long MAX_FAILURE_BACKOFF_NANOS = TimeUnit.SECONDS.toNanos(10L); - public static final long FAILURE_BACKOFF_NANOS = TimeUnit.MILLISECONDS.toNanos(1L); - public static final ComponentRegistry REGISTRY = new ComponentRegistry<>(); - public static final CodecKey> HOLDER_CODEC_KEY = new CodecKey<>("ChunkHolder"); - @Nonnull - public static final SystemType LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType( - ChunkStore.LoadPacketDataQuerySystem.class - ); - @Nonnull - public static final SystemType LOAD_FUTURE_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType( - ChunkStore.LoadFuturePacketDataQuerySystem.class - ); - @Nonnull - public static final SystemType UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType( - ChunkStore.UnloadPacketDataQuerySystem.class - ); - @Nonnull - public static final ResourceType UNLOAD_RESOURCE = REGISTRY.registerResource( - ChunkUnloadingSystem.Data.class, ChunkUnloadingSystem.Data::new - ); - @Nonnull - public static final ResourceType SAVE_RESOURCE = REGISTRY.registerResource( - ChunkSavingSystems.Data.class, ChunkSavingSystems.Data::new - ); - public static final SystemGroup INIT_GROUP = REGISTRY.registerSystemGroup(); - @Nonnull - private final World world; - @Nonnull - private final Long2ObjectConcurrentHashMap chunks = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND); - private Store store; - @Nullable - private IChunkLoader loader; - @Nullable - private IChunkSaver saver; - @Nullable - private IWorldGen generator; - @Nonnull - private CompletableFuture generatorLoaded = new CompletableFuture<>(); - private final StampedLock generatorLock = new StampedLock(); - private final AtomicInteger totalGeneratedChunksCount = new AtomicInteger(); - private final AtomicInteger totalLoadedChunksCount = new AtomicInteger(); - - public ChunkStore(@Nonnull World world) { - this.world = world; - } - - @Nonnull - @Override - public World getWorld() { - return this.world; - } - - @Nonnull - public Store getStore() { - return this.store; - } - - @Nullable - public IChunkLoader getLoader() { - return this.loader; - } - - @Nullable - public IChunkSaver getSaver() { - return this.saver; - } - - @Nullable - public IWorldGen getGenerator() { - long readStamp = this.generatorLock.readLock(); - - IWorldGen var3; - try { - var3 = this.generator; - } finally { - this.generatorLock.unlockRead(readStamp); - } - - return var3; - } - - public void shutdownGenerator() { - this.setGenerator(null); - } - - public void setGenerator(@Nullable IWorldGen generator) { - long writeStamp = this.generatorLock.writeLock(); - - try { - if (this.generator != null) { - this.generator.shutdown(); - } - - this.totalGeneratedChunksCount.set(0); - this.generator = generator; - if (generator != null) { - this.generatorLoaded.complete(null); - this.generatorLoaded = new CompletableFuture<>(); - } - } finally { - this.generatorLock.unlockWrite(writeStamp); - } - } - - @Nonnull - public LongSet getChunkIndexes() { - return LongSets.unmodifiable(this.chunks.keySet()); - } - - public int getLoadedChunksCount() { - return this.chunks.size(); - } - - public int getTotalGeneratedChunksCount() { - return this.totalGeneratedChunksCount.get(); - } - - public int getTotalLoadedChunksCount() { - return this.totalLoadedChunksCount.get(); - } - - public void start(@Nonnull IResourceStorage resourceStorage) { - this.store = REGISTRY.addStore(this, resourceStorage, store -> this.store = store); - } - - public void waitForLoadingChunks() { - long start = System.nanoTime(); - - boolean hasLoadingChunks; - do { - this.world.consumeTaskQueue(); - Thread.yield(); - hasLoadingChunks = false; - - for (Entry entry : this.chunks.long2ObjectEntrySet()) { - ChunkStore.ChunkLoadState chunkState = entry.getValue(); - long stamp = chunkState.lock.readLock(); - - try { - CompletableFuture> future = chunkState.future; - if (future != null && !future.isDone()) { - hasLoadingChunks = true; - break; - } - } finally { - chunkState.lock.unlockRead(stamp); - } - } - } while (hasLoadingChunks && System.nanoTime() - start <= 5000000000L); - - this.world.consumeTaskQueue(); - } - - public void shutdown() { - this.store.shutdown(); - this.chunks.clear(); - } - - @Nonnull - private Ref add(@Nonnull Holder holder) { - this.world.debugAssertInTickingThread(); - WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - ChunkStore.ChunkLoadState chunkState = this.chunks.get(worldChunkComponent.getIndex()); - if (chunkState == null) { - throw new IllegalStateException("Expected the ChunkLoadState to exist!"); - } else { - Ref oldReference = null; - long stamp = chunkState.lock.writeLock(); - - try { - if (chunkState.future == null) { - throw new IllegalStateException("Expected the ChunkLoadState to have a future!"); - } - - if (chunkState.reference != null) { - oldReference = chunkState.reference; - chunkState.reference = null; - } - } finally { - chunkState.lock.unlockWrite(stamp); - } - - if (oldReference != null) { - WorldChunk oldWorldChunkComponent = this.store.getComponent(oldReference, WorldChunk.getComponentType()); - - assert oldWorldChunkComponent != null; - - oldWorldChunkComponent.setFlag(ChunkFlag.TICKING, false); - this.store.removeEntity(oldReference, RemoveReason.REMOVE); - this.world.getNotificationHandler().updateChunk(worldChunkComponent.getIndex()); - } - - oldReference = this.store.addEntity(holder, AddReason.SPAWN); - if (oldReference == null) { - throw new UnsupportedOperationException("Unable to add the chunk to the world!"); - } else { - worldChunkComponent.setReference(oldReference); - stamp = chunkState.lock.writeLock(); - - Ref var17; - try { - chunkState.reference = oldReference; - chunkState.flags = 0; - chunkState.future = null; - chunkState.throwable = null; - chunkState.failedWhen = 0L; - chunkState.failedCounter = 0; - var17 = oldReference; - } finally { - chunkState.lock.unlockWrite(stamp); - } - - return var17; - } - } - } - - public void remove(@Nonnull Ref reference, @Nonnull RemoveReason reason) { - this.world.debugAssertInTickingThread(); - WorldChunk worldChunkComponent = this.store.getComponent(reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - long index = worldChunkComponent.getIndex(); - ChunkStore.ChunkLoadState chunkState = this.chunks.get(index); - long stamp = chunkState.lock.readLock(); - - try { - worldChunkComponent.setFlag(ChunkFlag.TICKING, false); - this.store.removeEntity(reference, reason); - if (chunkState.future != null) { - chunkState.reference = null; - } else { - this.chunks.remove(index, chunkState); - } - } finally { - chunkState.lock.unlockRead(stamp); - } - } - - @Nullable - public Ref getChunkReference(long index) { - ChunkStore.ChunkLoadState chunkState = this.chunks.get(index); - if (chunkState == null) { - return null; - } else { - long stamp = chunkState.lock.tryOptimisticRead(); - Ref reference = chunkState.reference; - if (chunkState.lock.validate(stamp)) { - return reference; - } else { - stamp = chunkState.lock.readLock(); - - Ref var7; - try { - var7 = chunkState.reference; - } finally { - chunkState.lock.unlockRead(stamp); - } - - return var7; - } - } - } - - @Nullable - public Ref getChunkSectionReference(int x, int y, int z) { - Ref ref = this.getChunkReference(ChunkUtil.indexChunk(x, z)); - if (ref == null) { - return null; - } else { - ChunkColumn chunkColumnComponent = this.store.getComponent(ref, ChunkColumn.getComponentType()); - return chunkColumnComponent == null ? null : chunkColumnComponent.getSection(y); - } - } - - @Nullable - public Ref getChunkSectionReference(@Nonnull ComponentAccessor commandBuffer, int x, int y, int z) { - Ref ref = this.getChunkReference(ChunkUtil.indexChunk(x, z)); - if (ref == null) { - return null; - } else { - ChunkColumn chunkColumnComponent = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); - return chunkColumnComponent == null ? null : chunkColumnComponent.getSection(y); - } - } - - @Nonnull - public CompletableFuture> getChunkSectionReferenceAsync(int x, int y, int z) { - return y >= 0 && y < 10 ? this.getChunkReferenceAsync(ChunkUtil.indexChunk(x, z)).thenApplyAsync(ref -> { - if (ref != null && ref.isValid()) { - Store store = ref.getStore(); - ChunkColumn chunkColumnComponent = store.getComponent((Ref) ref, ChunkColumn.getComponentType()); - return chunkColumnComponent == null ? null : chunkColumnComponent.getSection(y); - } else { - return null; - } - }, this.store.getExternalData().getWorld()) : CompletableFuture.failedFuture(new IndexOutOfBoundsException("Invalid y: " + y)); - } - - @Nullable - public > T getChunkComponent(long index, @Nonnull ComponentType componentType) { - Ref reference = this.getChunkReference(index); - return reference != null && reference.isValid() ? this.store.getComponent(reference, componentType) : null; - } - - @Nonnull - public CompletableFuture> getChunkReferenceAsync(long index) { - return this.getChunkReferenceAsync(index, 0); - } - - @Nonnull - public CompletableFuture> getChunkReferenceAsync(long index, int flags) { - if (this.store.isShutdown()) { - return CompletableFuture.completedFuture(null); - } else { - ChunkStore.ChunkLoadState chunkState; - if ((flags & 3) == 3) { - chunkState = this.chunks.get(index); - if (chunkState == null) { - return CompletableFuture.completedFuture(null); - } - - long stamp = chunkState.lock.readLock(); - - try { - if ((flags & 4) == 0 || (chunkState.flags & 4) != 0) { - if (chunkState.reference != null) { - return CompletableFuture.completedFuture(chunkState.reference); - } - - if (chunkState.future != null) { - return chunkState.future; - } - - return CompletableFuture.completedFuture(null); - } - } finally { - chunkState.lock.unlockRead(stamp); - } - } else { - chunkState = this.chunks.computeIfAbsent(index, l -> new ChunkStore.ChunkLoadState()); - } - - long stamp = chunkState.lock.writeLock(); - if (chunkState.future == null && chunkState.reference != null && (flags & 8) == 0) { - Ref reference = chunkState.reference; - if ((flags & 4) == 0) { - chunkState.lock.unlockWrite(stamp); - return CompletableFuture.completedFuture(reference); - } else if (this.world.isInThread() && (flags & -2147483648) == 0) { - chunkState.lock.unlockWrite(stamp); - WorldChunk worldChunkComponent = this.store.getComponent(reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - worldChunkComponent.setFlag(ChunkFlag.TICKING, true); - return CompletableFuture.completedFuture(reference); - } else { - chunkState.lock.unlockWrite(stamp); - return CompletableFuture.supplyAsync(() -> { - WorldChunk worldChunkComponent = this.store.getComponent(reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - worldChunkComponent.setFlag(ChunkFlag.TICKING, true); - return reference; - }, this.world); - } - } else { - try { - if (chunkState.throwable != null) { - long nanosSince = System.nanoTime() - chunkState.failedWhen; - int count = chunkState.failedCounter; - if (nanosSince < Math.min(MAX_FAILURE_BACKOFF_NANOS, count * count * FAILURE_BACKOFF_NANOS)) { - return CompletableFuture.failedFuture(new RuntimeException("Chunk failure backoff", chunkState.throwable)); - } - - chunkState.throwable = null; - chunkState.failedWhen = 0L; - } - - boolean isNew = chunkState.future == null; - if (isNew) { - chunkState.flags = flags; - } - - int x = ChunkUtil.xOfChunkIndex(index); - int z = ChunkUtil.zOfChunkIndex(index); - int seed = (int) this.world.getWorldConfig().getSeed(); - if ((isNew || (chunkState.flags & 1) != 0) && (flags & 1) == 0) { - if (chunkState.future == null) { - chunkState.future = this.loader.loadHolder(x, z).thenApplyAsync(holder -> { - if (holder != null && !this.store.isShutdown()) { - this.totalLoadedChunksCount.getAndIncrement(); - return this.preLoadChunkAsync(index, (Holder) holder, false); - } else { - return null; - } - }).exceptionallyCompose(throwable -> { - // Corruption detected during load - recover by regenerating - return this.handleCorruptionAndRegenerate(index, x, z, seed, throwable); - }).thenApplyAsync(this::postLoadChunk, this.world); - } else { - chunkState.flags &= -2; - chunkState.future = chunkState.future - .thenCompose( - reference -> reference != null - ? CompletableFuture.completedFuture((Ref) reference) - : this.loader.loadHolder(x, z).thenApplyAsync(holder -> { - if (holder != null && !this.store.isShutdown()) { - this.totalLoadedChunksCount.getAndIncrement(); - return this.preLoadChunkAsync(index, (Holder) holder, false); - } else { - return null; - } - }).exceptionallyCompose(throwable -> { - // Corruption detected during load - recover by regenerating - return this.handleCorruptionAndRegenerate(index, x, z, seed, throwable); - }).thenApplyAsync(this::postLoadChunk, this.world) - ); - } - } - - if ((isNew || (chunkState.flags & 2) != 0) && (flags & 2) == 0) { - if (chunkState.future == null) { - long readStamp = this.generatorLock.readLock(); - - CompletableFuture future; - try { - if (this.generator == null) { - future = this.generatorLoaded - .thenCompose(aVoid -> this.generator.generate(seed, index, x, z, (flags & 16) != 0 ? this::isChunkStillNeeded : null)); - } else { - future = this.generator.generate(seed, index, x, z, (flags & 16) != 0 ? this::isChunkStillNeeded : null); - } - } finally { - this.generatorLock.unlockRead(readStamp); - } - - chunkState.future = future.>thenApplyAsync(generatedChunk -> { - if (generatedChunk != null && !this.store.isShutdown()) { - this.totalGeneratedChunksCount.getAndIncrement(); - return this.preLoadChunkAsync(index, generatedChunk.toHolder(this.world), true); - } else { - return null; - } - }).thenApplyAsync(this::postLoadChunk, this.world).exceptionally(throwable -> { - LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to generate chunk! %s, %s", x, z); - chunkState.fail(throwable); - throw SneakyThrow.sneakyThrow(throwable); - }); - } else { - chunkState.flags &= -3; - chunkState.future = chunkState.future.thenCompose(reference -> { - if (reference != null) { - return CompletableFuture.completedFuture((Ref) reference); - } else { - long readStampx = this.generatorLock.readLock(); - - CompletableFuture future; - try { - if (this.generator == null) { - future = this.generatorLoaded.thenCompose(aVoid -> this.generator.generate(seed, index, x, z, null)); - } else { - future = this.generator.generate(seed, index, x, z, null); - } - } finally { - this.generatorLock.unlockRead(readStampx); - } - - return future.>thenApplyAsync(generatedChunk -> { - if (generatedChunk != null && !this.store.isShutdown()) { - this.totalGeneratedChunksCount.getAndIncrement(); - return this.preLoadChunkAsync(index, generatedChunk.toHolder(this.world), true); - } else { - return null; - } - }).thenApplyAsync(this::postLoadChunk, this.world).exceptionally(throwable -> { - LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to generate chunk! %s, %s", x, z); - chunkState.fail(throwable); - throw SneakyThrow.sneakyThrow(throwable); - }); - } - }); - } - } - - if ((isNew || (chunkState.flags & 4) == 0) && (flags & 4) != 0) { - chunkState.flags |= 4; - if (chunkState.future != null) { - chunkState.future = chunkState.future.>thenApplyAsync(reference -> { - if (reference != null) { - WorldChunk worldChunkComponent = this.store.getComponent((Ref) reference, WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - worldChunkComponent.setFlag(ChunkFlag.TICKING, true); - } - - return reference; - }, this.world).exceptionally(throwable -> { - LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to set chunk ticking! %s, %s", x, z); - chunkState.fail(throwable); - throw SneakyThrow.sneakyThrow(throwable); - }); - } - } - - return chunkState.future != null ? chunkState.future : CompletableFuture.completedFuture(null); - } finally { - chunkState.lock.unlockWrite(stamp); - } - } - } - } - - private boolean isChunkStillNeeded(long index) { - for (PlayerRef playerRef : this.world.getPlayerRefs()) { - if (playerRef.getChunkTracker().shouldBeVisible(index)) { - return true; - } - } - - return false; - } - - /** - * Handles corruption recovery by logging the corruption, marking the region file as corrupted, - * and triggering regeneration. - */ - @Nonnull - private CompletableFuture> handleCorruptionAndRegenerate( - long index, int x, int z, int seed, Throwable corruptionCause) { - - // Log corruption to file - CorruptedChunkLogger.logCorruption(this.world.getName(), x, z, corruptionCause); - - LOGGER.at(Level.WARNING).log( - "Corrupted chunk detected at (%d, %d) in world '%s' - marking region as corrupted and regenerating. See logs/corrupted_chunks.log for details.", - x, z, this.world.getName() - ); - - // Mark the entire region file as corrupted (renames to .corrupted suffix) - CompletableFuture removeFuture; - if (this.saver != null) { - removeFuture = this.saver.deleteRegionFile(x, z).exceptionally(removeError -> { - LOGGER.at(Level.WARNING).withCause(removeError).log( - "Failed to mark region as corrupted for chunk (%d, %d)", x, z - ); - return null; - }); - } else { - removeFuture = CompletableFuture.completedFuture(null); - } - - // After removal, regenerate the chunk - return removeFuture.thenCompose(ignored -> { - long readStamp = this.generatorLock.readLock(); - try { - CompletableFuture genFuture; - if (this.generator == null) { - genFuture = this.generatorLoaded.thenCompose( - aVoid -> this.generator.generate(seed, index, x, z, null) - ); - } else { - genFuture = this.generator.generate(seed, index, x, z, null); - } - return genFuture.thenApplyAsync(generatedChunk -> { - if (generatedChunk != null && !this.store.isShutdown()) { - this.totalGeneratedChunksCount.getAndIncrement(); - return this.preLoadChunkAsync(index, generatedChunk.toHolder(this.world), true); - } else { - return null; - } - }); - } finally { - this.generatorLock.unlockRead(readStamp); - } - }); - } - - public boolean isChunkOnBackoff(long index, long maxFailureBackoffNanos) { - ChunkStore.ChunkLoadState chunkState = this.chunks.get(index); - if (chunkState == null) { - return false; - } else { - long stamp = chunkState.lock.readLock(); - - boolean nanosSince; - try { - if (chunkState.throwable != null) { - long nanosSincex = System.nanoTime() - chunkState.failedWhen; - int count = chunkState.failedCounter; - return nanosSincex < Math.min(maxFailureBackoffNanos, count * count * FAILURE_BACKOFF_NANOS); - } - - nanosSince = false; - } finally { - chunkState.lock.unlockRead(stamp); - } - - return nanosSince; - } - } - - @Nonnull - private Holder preLoadChunkAsync(long index, @Nonnull Holder holder, boolean newlyGenerated) { - WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); - if (worldChunkComponent == null) { - throw new IllegalStateException( - String.format("Holder missing WorldChunk component! (%d, %d)", ChunkUtil.xOfChunkIndex(index), ChunkUtil.zOfChunkIndex(index)) - ); - } else if (worldChunkComponent.getIndex() != index) { - throw new IllegalStateException( - String.format( - "Incorrect chunk index! Got (%d, %d) expected (%d, %d)", - worldChunkComponent.getX(), - worldChunkComponent.getZ(), - ChunkUtil.xOfChunkIndex(index), - ChunkUtil.zOfChunkIndex(index) - ) - ); - } else { - BlockChunk blockChunk = holder.getComponent(BlockChunk.getComponentType()); - if (blockChunk == null) { - throw new IllegalStateException( - String.format("Holder missing BlockChunk component! (%d, %d)", ChunkUtil.xOfChunkIndex(index), ChunkUtil.zOfChunkIndex(index)) - ); - } else { - blockChunk.loadFromHolder(holder); - worldChunkComponent.setFlag(ChunkFlag.NEWLY_GENERATED, newlyGenerated); - worldChunkComponent.setLightingUpdatesEnabled(false); - if (newlyGenerated && this.world.getWorldConfig().shouldSaveNewChunks()) { - worldChunkComponent.markNeedsSaving(); - } - - try { - long start = System.nanoTime(); - IEventDispatcher dispatcher = HytaleServer.get() - .getEventBus() - .dispatchFor(ChunkPreLoadProcessEvent.class, this.world.getName()); - if (dispatcher.hasListener()) { - ChunkPreLoadProcessEvent event = dispatcher.dispatch(new ChunkPreLoadProcessEvent(holder, worldChunkComponent, newlyGenerated, start)); - if (!event.didLog()) { - long end = System.nanoTime(); - long diff = end - start; - if (diff > this.world.getTickStepNanos()) { - LOGGER.at(Level.SEVERE) - .log( - "Took too long to pre-load process chunk: %s > TICK_STEP, Has GC Run: %s, %s", - FormatUtil.nanosToString(diff), - this.world.consumeGCHasRun(), - worldChunkComponent - ); - } - } - } - } finally { - worldChunkComponent.setLightingUpdatesEnabled(true); - } - - return holder; - } - } - } - - @Nullable - private Ref postLoadChunk(@Nullable Holder holder) { - this.world.debugAssertInTickingThread(); - if (holder != null && !this.store.isShutdown()) { - long start = System.nanoTime(); - WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); - - assert worldChunkComponent != null; - - worldChunkComponent.setFlag(ChunkFlag.START_INIT, true); - if (worldChunkComponent.is(ChunkFlag.TICKING)) { - holder.tryRemoveComponent(REGISTRY.getNonTickingComponentType()); - } else { - holder.ensureComponent(REGISTRY.getNonTickingComponentType()); - } - - Ref reference = this.add(holder); - worldChunkComponent.initFlags(); - this.world.getChunkLighting().init(worldChunkComponent); - long end = System.nanoTime(); - long diff = end - start; - if (diff > this.world.getTickStepNanos()) { - LOGGER.at(Level.SEVERE) - .log( - "Took too long to post-load process chunk: %s > TICK_STEP, Has GC Run: %s, %s", - FormatUtil.nanosToString(diff), - this.world.consumeGCHasRun(), - worldChunkComponent - ); - } - - return reference; - } else { - return null; - } - } - - static { - CodecStore.STATIC.putCodecSupplier(HOLDER_CODEC_KEY, REGISTRY::getEntityCodec); - REGISTRY.registerSystem(new ChunkStore.ChunkLoaderSaverSetupSystem()); - REGISTRY.registerSystem(new ChunkUnloadingSystem()); - REGISTRY.registerSystem(new ChunkSavingSystems.WorldRemoved()); - REGISTRY.registerSystem(new ChunkSavingSystems.Ticking()); - } - - private static class ChunkLoadState { - private final StampedLock lock = new StampedLock(); - private int flags = 0; - @Nullable - private CompletableFuture> future; - @Nullable - private Ref reference; - @Nullable - private Throwable throwable; - private long failedWhen; - private int failedCounter; - - private ChunkLoadState() { - } - - private void fail(Throwable throwable) { - long stamp = this.lock.writeLock(); - - try { - this.flags = 0; - this.future = null; - this.throwable = throwable; - this.failedWhen = System.nanoTime(); - this.failedCounter++; - } finally { - this.lock.unlockWrite(stamp); - } - } - } - - /** - * Utility class for logging corrupted chunk information to logs/corrupted_chunks.log - */ - private static class CorruptedChunkLogger { - private static final Path LOG_PATH = Paths.get("logs", "corrupted_chunks.log"); - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS") - .withZone(ZoneId.systemDefault()); - private static final Object WRITE_LOCK = new Object(); - - static void logCorruption(String worldName, int chunkX, int chunkZ, Throwable cause) { - try { - synchronized (WRITE_LOCK) { - Files.createDirectories(LOG_PATH.getParent()); - try (BufferedWriter writer = Files.newBufferedWriter(LOG_PATH, - StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { - String timestamp = FORMATTER.format(Instant.now()); - writer.write(String.format("[%s] CORRUPTED CHUNK DETECTED - Regenerating%n", timestamp)); - writer.write(String.format(" World: %s%n", worldName)); - writer.write(String.format(" Chunk: (%d, %d)%n", chunkX, chunkZ)); - writer.write(String.format(" Block coords: (%d, %d) to (%d, %d)%n", - chunkX * 16, chunkZ * 16, chunkX * 16 + 15, chunkZ * 16 + 15)); - writer.write(String.format(" Cause: %s%n", cause.getClass().getSimpleName())); - writer.write(String.format(" Message: %s%n", cause.getMessage())); - - // Write stack trace - StringWriter sw = new StringWriter(); - cause.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - // Indent stack trace - for (String line : stackTrace.split("\n")) { - writer.write(String.format(" %s%n", line)); - } - writer.write(String.format("---%n%n")); - } - } - } catch (IOException e) { - LOGGER.at(Level.WARNING).withCause(e).log("Failed to write to corrupted_chunks.log"); - } - } - } - - public static class ChunkLoaderSaverSetupSystem extends StoreSystem { - public ChunkLoaderSaverSetupSystem() { - } - - @Nullable - @Override - public SystemGroup getGroup() { - return ChunkStore.INIT_GROUP; - } - - @Override - public void onSystemAddedToStore(@Nonnull Store store) { - ChunkStore data = store.getExternalData(); - World world = data.getWorld(); - IChunkStorageProvider chunkStorageProvider = world.getWorldConfig().getChunkStorageProvider(); - - try { - data.loader = chunkStorageProvider.getLoader(store); - data.saver = chunkStorageProvider.getSaver(store); - } catch (IOException var6) { - throw SneakyThrow.sneakyThrow(var6); - } - } - - @Override - public void onSystemRemovedFromStore(@Nonnull Store store) { - ChunkStore data = store.getExternalData(); - - try { - if (data.loader != null) { - IChunkLoader oldLoader = data.loader; - data.loader = null; - oldLoader.close(); - } - - if (data.saver != null) { - IChunkSaver oldSaver = data.saver; - data.saver = null; - oldSaver.close(); - } - } catch (IOException var4) { - ChunkStore.LOGGER.at(Level.SEVERE).withCause(var4).log("Failed to close storage!"); - } - } - } - - public abstract static class LoadFuturePacketDataQuerySystem extends EntityDataSystem> { - public LoadFuturePacketDataQuerySystem() { - } - } - - public abstract static class LoadPacketDataQuerySystem extends EntityDataSystem { - public LoadPacketDataQuerySystem() { - } - } - - public abstract static class UnloadPacketDataQuerySystem extends EntityDataSystem { - public UnloadPacketDataQuerySystem() { - } - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java b/src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java deleted file mode 100644 index 27f3d420..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world.storage; - -import com.hypixel.hytale.codec.store.CodecKey; -import com.hypixel.hytale.codec.store.CodecStore; -import com.hypixel.hytale.component.AddReason; -import com.hypixel.hytale.component.CommandBuffer; -import com.hypixel.hytale.component.ComponentRegistry; -import com.hypixel.hytale.component.Holder; -import com.hypixel.hytale.component.IResourceStorage; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.query.Query; -import com.hypixel.hytale.component.system.RefSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.WorldProvider; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; - -public class EntityStore implements WorldProvider { - @Nonnull - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register("Store", EntityStore::getStore, Store.METRICS_REGISTRY); - @Nonnull - public static final ComponentRegistry REGISTRY = new ComponentRegistry<>(); - @Nonnull - public static final CodecKey> HOLDER_CODEC_KEY = new CodecKey<>("EntityHolder"); - @Nonnull - public static final SystemGroup SEND_PACKET_GROUP = REGISTRY.registerSystemGroup(); - @Nonnull - private final AtomicInteger networkIdCounter = new AtomicInteger(1); - @Nonnull - private final World world; - private Store store; - @Nonnull - private final Map> entitiesByUuid = new ConcurrentHashMap<>(); - @Nonnull - private final Int2ObjectMap> networkIdToRef = new Int2ObjectOpenHashMap<>(); - - public EntityStore(@Nonnull World world) { - this.world = world; - } - - public void start(@Nonnull IResourceStorage resourceStorage) { - this.store = REGISTRY.addStore(this, resourceStorage, store -> this.store = store); - } - - public void shutdown() { - this.store.shutdown(); - this.entitiesByUuid.clear(); - } - - public Store getStore() { - return this.store; - } - - @Nullable - public Ref getRefFromUUID(@Nonnull UUID uuid) { - return this.entitiesByUuid.get(uuid); - } - - @Nullable - public Ref getRefFromNetworkId(int networkId) { - return this.networkIdToRef.get(networkId); - } - - public int takeNextNetworkId() { - return this.networkIdCounter.getAndIncrement(); - } - - @Nonnull - @Override - public World getWorld() { - return this.world; - } - - static { - CodecStore.STATIC.putCodecSupplier(HOLDER_CODEC_KEY, REGISTRY::getEntityCodec); - } - - public static class NetworkIdSystem extends RefSystem { - public NetworkIdSystem() { - } - - @Nonnull - @Override - public Query getQuery() { - return NetworkId.getComponentType(); - } - - @Override - public void onEntityAdded( - @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - EntityStore entityStore = store.getExternalData(); - NetworkId networkIdComponent = commandBuffer.getComponent(ref, NetworkId.getComponentType()); - - assert networkIdComponent != null; - - int networkId = networkIdComponent.getId(); - if (entityStore.networkIdToRef.putIfAbsent(networkId, ref) != null) { - networkId = entityStore.takeNextNetworkId(); - commandBuffer.putComponent(ref, NetworkId.getComponentType(), new NetworkId(networkId)); - entityStore.networkIdToRef.put(networkId, ref); - } - } - - @Override - public void onEntityRemove( - @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - EntityStore entityStore = store.getExternalData(); - NetworkId networkIdComponent = commandBuffer.getComponent(ref, NetworkId.getComponentType()); - - assert networkIdComponent != null; - - entityStore.networkIdToRef.remove(networkIdComponent.getId(), ref); - } - } - - public static class UUIDSystem extends RefSystem { - @Nonnull - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - - public UUIDSystem() { - } - - @Nonnull - @Override - public Query getQuery() { - return UUIDComponent.getComponentType(); - } - - @Override - public void onEntityAdded( - @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); - - assert uuidComponent != null; - - Ref currentRef = store.getExternalData().entitiesByUuid.putIfAbsent(uuidComponent.getUuid(), ref); - if (currentRef != null) { - LOGGER.at(Level.WARNING).log("Removing duplicate entity with UUID: %s", uuidComponent.getUuid()); - commandBuffer.removeEntity(ref, RemoveReason.REMOVE); - } - } - - @Override - public void onEntityRemove( - @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); - - // HyFix: Null check for uuidComponent and uuid to prevent NPE on entity remove - // Entities can be removed before UUID component is fully initialized - if (uuidComponent == null) { - LOGGER.at(Level.WARNING).log("Null UUIDComponent during entity remove - skipping UUID cleanup"); - return; - } - UUID uuid = uuidComponent.getUuid(); - if (uuid == null) { - LOGGER.at(Level.WARNING).log("Null UUID in UUIDComponent during entity remove - skipping UUID cleanup"); - return; - } - - store.getExternalData().entitiesByUuid.remove(uuid, ref); - } - } -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java b/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java deleted file mode 100644 index d45ac2a8..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world.storage; - -import com.hypixel.hytale.component.Holder; -import it.unimi.dsi.fastutil.longs.LongSet; - -import javax.annotation.Nonnull; -import java.io.Closeable; -import java.io.IOException; -import java.util.concurrent.CompletableFuture; - -public interface IChunkSaver extends Closeable { - @Nonnull - CompletableFuture saveHolder(int var1, int var2, @Nonnull Holder var3); - - @Nonnull - CompletableFuture removeHolder(int var1, int var2); - - /** - * Deletes the entire region file containing the specified chunk coordinates. - * Used for corruption recovery when the region file is unrecoverable. - */ - @Nonnull - default CompletableFuture deleteRegionFile(int x, int z) { - return CompletableFuture.completedFuture(null); - } - - @Nonnull - LongSet getIndexes() throws IOException; - - void flush() throws IOException; -} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java deleted file mode 100644 index 6693e761..00000000 --- a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java +++ /dev/null @@ -1,682 +0,0 @@ -package com.hypixel.hytale.server.core.universe.world.storage.provider; - -import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.component.Resource; -import com.hypixel.hytale.component.ResourceType; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.SystemGroup; -import com.hypixel.hytale.component.system.StoreSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.math.util.ChunkUtil; -import com.hypixel.hytale.metrics.MetricProvider; -import com.hypixel.hytale.metrics.MetricResults; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.server.core.universe.Universe; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.BufferChunkLoader; -import com.hypixel.hytale.server.core.universe.world.storage.BufferChunkSaver; -import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; -import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; -import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; -import com.hypixel.hytale.sneakythrow.SneakyThrow; -import com.hypixel.hytale.storage.IndexedStorageFile; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.ints.IntListIterator; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.longs.LongSets; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.Closeable; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.stream.Stream; - -public class IndexedStorageChunkStorageProvider implements IChunkStorageProvider { - public static final String ID = "IndexedStorage"; - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder( - IndexedStorageChunkStorageProvider.class, IndexedStorageChunkStorageProvider::new - ) - .documentation("Uses the indexed storage file format to store chunks.") - .appendInherited( - new KeyedCodec<>("FlushOnWrite", Codec.BOOLEAN), (o, i) -> o.flushOnWrite = i, o -> o.flushOnWrite, (o, p) -> o.flushOnWrite = p.flushOnWrite - ) - .documentation( - "Controls whether the indexed storage flushes during writes.\nRecommended to be enabled to prevent corruption of chunks during unclean shutdowns." - ) - .add() - .build(); - private boolean flushOnWrite = false; - - public IndexedStorageChunkStorageProvider() { - } - - @Nonnull - @Override - public IChunkLoader getLoader(@Nonnull Store store) { - return new IndexedStorageChunkStorageProvider.IndexedStorageChunkLoader(store, this.flushOnWrite); - } - - @Nonnull - @Override - public IChunkSaver getSaver(@Nonnull Store store) { - return new IndexedStorageChunkStorageProvider.IndexedStorageChunkSaver(store, this.flushOnWrite); - } - - @Nonnull - @Override - public String toString() { - return "IndexedStorageChunkStorageProvider{}"; - } - - @Nonnull - private static String toFileName(int regionX, int regionZ) { - return regionX + "." + regionZ + ".region.bin"; - } - - private static long fromFileName(@Nonnull String fileName) { - String[] split = fileName.split("\\."); - if (split.length != 4) { - throw new IllegalArgumentException("Unexpected file name format!"); - } else if (!"region".equals(split[2])) { - throw new IllegalArgumentException("Unexpected file name format!"); - } else if (!"bin".equals(split[3])) { - throw new IllegalArgumentException("Unexpected file extension!"); - } else { - int regionX = Integer.parseInt(split[0]); - int regionZ = Integer.parseInt(split[1]); - return ChunkUtil.indexChunk(regionX, regionZ); - } - } - - public static class IndexedStorageCache implements Closeable, MetricProvider, Resource { - @Nonnull - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register( - "Files", - cache -> cache.cache - .long2ObjectEntrySet() - .stream() - .map(IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData::new) - .toArray(IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData[]::new), - new ArrayCodec<>( - IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData.CODEC, - IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData[]::new - ) - ); - - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - - /** - * How long a region file can be idle before being closed (in milliseconds) - */ - private static final long IDLE_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(5); - /** - * How often to check for idle region files (in milliseconds) - */ - private static final long CLEANUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1); - - /** - * Wrapper for IndexedStorageFile that tracks usage for async-safe cleanup. - */ - private static class CachedFile { - final IndexedStorageFile file; - final AtomicInteger activeOps = new AtomicInteger(0); - volatile long lastAccessTime; - volatile boolean markedForClose = false; - - CachedFile(IndexedStorageFile file) { - this.file = file; - this.lastAccessTime = System.currentTimeMillis(); - } - - void recordAccess() { - this.lastAccessTime = System.currentTimeMillis(); - } - - /** - * Acquire a reference for an operation. Returns false if file is marked for close. - */ - boolean acquire() { - if (markedForClose) { - return false; - } - activeOps.incrementAndGet(); - // Double-check after increment - if (markedForClose) { - activeOps.decrementAndGet(); - return false; - } - recordAccess(); - return true; - } - - void release() { - activeOps.decrementAndGet(); - } - - boolean isIdle(long threshold) { - return lastAccessTime < threshold && activeOps.get() == 0; - } - } - - private final Long2ObjectConcurrentHashMap cache = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND); - private final Map trackedFiles = new ConcurrentHashMap<>(); - private Path path; - private ScheduledExecutorService cleanupExecutor; - - public IndexedStorageCache() { - } - - private void startCleanupTask() { - if (cleanupExecutor != null) { - return; - } - LOGGER.at(Level.INFO).log("Starting region file cleanup task (idle timeout: %ds, interval: %ds)", - IDLE_TIMEOUT_MS / 1000, CLEANUP_INTERVAL_MS / 1000); - cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> { - Thread t = new Thread(r, "IndexedStorageCache-Cleanup"); - t.setDaemon(true); - return t; - }); - cleanupExecutor.scheduleAtFixedRate(() -> { - try { - cleanupIdleFiles(); - } catch (Exception e) { - LOGGER.at(Level.SEVERE).withCause(e).log("Region cleanup task failed"); - } - }, CLEANUP_INTERVAL_MS, CLEANUP_INTERVAL_MS, TimeUnit.MILLISECONDS); - } - - private void cleanupIdleFiles() { - long now = System.currentTimeMillis(); - long threshold = now - IDLE_TIMEOUT_MS; - int totalFiles = trackedFiles.size(); - int idleCount = 0; - - LOGGER.at(Level.INFO).log("Running region cleanup check: %d tracked files", totalFiles); - - for (Map.Entry entry : trackedFiles.entrySet()) { - long regionKey = entry.getKey(); - CachedFile cached = entry.getValue(); - - long idleTimeMs = now - cached.lastAccessTime; - int activeOps = cached.activeOps.get(); - - if (cached.isIdle(threshold)) { - idleCount++; - // Mark for close first - prevents new acquisitions - cached.markedForClose = true; - - // Double-check no operations started between isIdle check and marking - if (cached.activeOps.get() > 0) { - cached.markedForClose = false; - LOGGER.at(Level.INFO).log("Region cleanup skipped - ops started during close"); - continue; - } - - // Safe to close now - cache.remove(regionKey); - trackedFiles.remove(regionKey); - - int regionX = ChunkUtil.xOfChunkIndex(regionKey); - int regionZ = ChunkUtil.zOfChunkIndex(regionKey); - try { - cached.file.close(); - LOGGER.at(Level.INFO).log("%d,%d region unloaded", regionX, regionZ); - } catch (IOException e) { - LOGGER.at(Level.WARNING).withCause(e).log("Failed to close idle region file %d.%d", regionX, regionZ); - } - } else { - LOGGER.at(Level.FINE).log("Region %d not idle: lastAccess=%dms ago, activeOps=%d, threshold=%dms", - regionKey, idleTimeMs, activeOps, IDLE_TIMEOUT_MS); - } - } - - if (totalFiles > 0) { - LOGGER.at(Level.INFO).log("Region cleanup complete: %d/%d files were idle", idleCount, totalFiles); - } - } - - private void trackFile(long regionKey, IndexedStorageFile file) { - trackedFiles.computeIfAbsent(regionKey, k -> new CachedFile(file)); - } - - private void recordAccess(long regionKey) { - CachedFile cached = trackedFiles.get(regionKey); - if (cached != null) { - cached.recordAccess(); - } - } - - /** - * Acquire a file for use. Must call releaseFile() when done. - * Returns null if file doesn't exist or is being closed. - */ - @Nullable - public IndexedStorageFile acquireFile(int regionX, int regionZ, boolean flushOnWrite) { - long regionKey = ChunkUtil.indexChunk(regionX, regionZ); - IndexedStorageFile file = getOrTryOpen(regionX, regionZ, flushOnWrite); - if (file == null) { - return null; - } - - CachedFile cached = trackedFiles.get(regionKey); - if (cached != null && cached.acquire()) { - return file; - } - - // File is being closed, retry to get a fresh one - return getOrTryOpen(regionX, regionZ, flushOnWrite); - } - - /** - * Release a file after use. - */ - public void releaseFile(int regionX, int regionZ) { - long regionKey = ChunkUtil.indexChunk(regionX, regionZ); - CachedFile cached = trackedFiles.get(regionKey); - if (cached != null) { - cached.release(); - } - } - - public static ResourceType getResourceType() { - return Universe.get().getIndexedStorageCacheResourceType(); - } - - @Nonnull - public Long2ObjectConcurrentHashMap getCache() { - return this.cache; - } - - @Override - public void close() throws IOException { - // Shutdown cleanup task first - if (cleanupExecutor != null) { - cleanupExecutor.shutdown(); - try { - cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - cleanupExecutor = null; - } - - IOException exception = null; - Iterator iterator = this.cache.values().iterator(); - - while (iterator.hasNext()) { - try { - iterator.next().close(); - iterator.remove(); - } catch (Exception var4) { - if (exception == null) { - exception = new IOException("Failed to close one or more loaders!"); - } - - exception.addSuppressed(var4); - } - } - - trackedFiles.clear(); - - if (exception != null) { - throw exception; - } - } - - @Nullable - public IndexedStorageFile getOrTryOpen(int regionX, int regionZ, boolean flushOnWrite) { - long regionKey = ChunkUtil.indexChunk(regionX, regionZ); - IndexedStorageFile file = this.cache.computeIfAbsent(regionKey, k -> { - startCleanupTask(); - Path regionFile = this.path.resolve(IndexedStorageChunkStorageProvider.toFileName(regionX, regionZ)); - if (!Files.exists(regionFile)) { - return null; - } else { - try { - IndexedStorageFile open = IndexedStorageFile.open(regionFile, StandardOpenOption.READ, StandardOpenOption.WRITE); - open.setFlushOnWrite(flushOnWrite); - return open; - } catch (FileNotFoundException var8) { - return null; - } catch (Exception var9) { - // Corruption detected - rename file and return null to trigger regeneration - LOGGER.at(Level.SEVERE).withCause(var9).log("Corrupted region file detected: %s", regionFile); - try { - Path corruptedPath = regionFile.resolveSibling(regionFile.getFileName() + ".corrupted"); - Files.move(regionFile, corruptedPath, StandardCopyOption.REPLACE_EXISTING); - LOGGER.at(Level.WARNING).log("Renamed corrupted file to: %s", corruptedPath); - } catch (IOException moveErr) { - LOGGER.at(Level.SEVERE).withCause(moveErr).log("Failed to rename corrupted file: %s", regionFile); - } - return null; - } - } - }); - if (file != null) { - trackFile(regionKey, file); - recordAccess(regionKey); - } - return file; - } - - /** - * Marks a region file as corrupted by renaming it with .corrupted suffix. - * The file will be regenerated on next access. - */ - public void markRegionCorrupted(int regionX, int regionZ) { - long key = ChunkUtil.indexChunk(regionX, regionZ); - IndexedStorageFile file = this.cache.remove(key); - CachedFile cached = trackedFiles.remove(key); - if (cached != null) { - cached.markedForClose = true; - } - if (file != null) { - try { - file.close(); - } catch (IOException e) { - // Ignore close errors - } - } - Path regionFile = this.path.resolve(IndexedStorageChunkStorageProvider.toFileName(regionX, regionZ)); - if (Files.exists(regionFile)) { - try { - Path corruptedPath = regionFile.resolveSibling(regionFile.getFileName() + ".corrupted"); - Files.move(regionFile, corruptedPath, StandardCopyOption.REPLACE_EXISTING); - LOGGER.at(Level.WARNING).log("Marked region %d.%d as corrupted: %s", regionX, regionZ, corruptedPath); - } catch (IOException e) { - LOGGER.at(Level.SEVERE).withCause(e).log("Failed to mark region %d.%d as corrupted", regionX, regionZ); - } - } - } - - @Nonnull - public IndexedStorageFile getOrCreate(int regionX, int regionZ, boolean flushOnWrite) { - long regionKey = ChunkUtil.indexChunk(regionX, regionZ); - IndexedStorageFile file = this.cache.computeIfAbsent(regionKey, k -> { - startCleanupTask(); - try { - if (!Files.exists(this.path)) { - try { - Files.createDirectory(this.path); - } catch (FileAlreadyExistsException var8) { - } - } - - Path regionFile = this.path.resolve(IndexedStorageChunkStorageProvider.toFileName(regionX, regionZ)); - IndexedStorageFile open = IndexedStorageFile.open(regionFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - open.setFlushOnWrite(flushOnWrite); - return open; - } catch (IOException var9) { - throw SneakyThrow.sneakyThrow(var9); - } - }); - trackFile(regionKey, file); - recordAccess(regionKey); - return file; - } - - @Nonnull - public LongSet getIndexes() throws IOException { - if (!Files.exists(this.path)) { - return LongSets.EMPTY_SET; - } else { - LongOpenHashSet chunkIndexes = new LongOpenHashSet(); - - try (Stream stream = Files.list(this.path)) { - stream.forEach(path -> { - if (!Files.isDirectory(path)) { - long regionIndex; - try { - regionIndex = IndexedStorageChunkStorageProvider.fromFileName(path.getFileName().toString()); - } catch (IllegalArgumentException var15) { - return; - } - - int regionX = ChunkUtil.xOfChunkIndex(regionIndex); - int regionZ = ChunkUtil.zOfChunkIndex(regionIndex); - IndexedStorageFile regionFile = this.getOrTryOpen(regionX, regionZ, true); - if (regionFile != null) { - IntList blobIndexes = regionFile.keys(); - IntListIterator iterator = blobIndexes.iterator(); - - while (iterator.hasNext()) { - int blobIndex = iterator.nextInt(); - int localX = ChunkUtil.xFromColumn(blobIndex); - int localZ = ChunkUtil.zFromColumn(blobIndex); - int chunkX = regionX << 5 | localX; - int chunkZ = regionZ << 5 | localZ; - chunkIndexes.add(ChunkUtil.indexChunk(chunkX, chunkZ)); - } - } - } - }); - } - - return chunkIndexes; - } - } - - public void flush() throws IOException { - IOException exception = null; - - for (IndexedStorageFile indexedStorageFile : this.cache.values()) { - try { - indexedStorageFile.force(false); - } catch (Exception var5) { - if (exception == null) { - exception = new IOException("Failed to close one or more loaders!"); - } - - exception.addSuppressed(var5); - } - } - - if (exception != null) { - throw exception; - } - } - - @Nonnull - @Override - public MetricResults toMetricResults() { - return METRICS_REGISTRY.toMetricResults(this); - } - - @Nonnull - @Override - public Resource clone() { - return new IndexedStorageChunkStorageProvider.IndexedStorageCache(); - } - - private static class CacheEntryMetricData { - @Nonnull - private static final Codec CODEC = BuilderCodec.builder( - IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData.class, - IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData::new - ) - .append(new KeyedCodec<>("Key", Codec.LONG), (entry, o) -> entry.key = o, entry -> entry.key) - .add() - .append(new KeyedCodec<>("File", IndexedStorageFile.METRICS_REGISTRY), (entry, o) -> entry.value = o, entry -> entry.value) - .add() - .build(); - private long key; - private IndexedStorageFile value; - - public CacheEntryMetricData() { - } - - public CacheEntryMetricData(@Nonnull Entry entry) { - this.key = entry.getLongKey(); - this.value = entry.getValue(); - } - } - } - - public static class IndexedStorageCacheSetupSystem extends StoreSystem { - public IndexedStorageCacheSetupSystem() { - } - - @Nullable - @Override - public SystemGroup getGroup() { - return ChunkStore.INIT_GROUP; - } - - @Override - public void onSystemAddedToStore(@Nonnull Store store) { - World world = store.getExternalData().getWorld(); - store.getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).path = world.getSavePath().resolve("chunks"); - } - - @Override - public void onSystemRemovedFromStore(@Nonnull Store store) { - } - } - - public static class IndexedStorageChunkLoader extends BufferChunkLoader implements MetricProvider { - private final boolean flushOnWrite; - - public IndexedStorageChunkLoader(@Nonnull Store store, boolean flushOnWrite) { - super(store); - this.flushOnWrite = flushOnWrite; - } - - @Override - public void close() throws IOException { - this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).close(); - } - - @Nonnull - @Override - public CompletableFuture loadBuffer(int x, int z) { - int regionX = x >> 5; - int regionZ = z >> 5; - int localX = x & 31; - int localZ = z & 31; - int index = ChunkUtil.indexColumn(localX, localZ); - IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() - .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); - return CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> { - IndexedStorageFile chunks = indexedStorageCache.getOrTryOpen(regionX, regionZ, this.flushOnWrite); - return chunks == null ? null : chunks.readBlob(index); - })); - } - - @Nonnull - @Override - public LongSet getIndexes() throws IOException { - return this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).getIndexes(); - } - - @Nullable - @Override - public MetricResults toMetricResults() { - return this.getStore().getExternalData().getSaver() instanceof IndexedStorageChunkStorageProvider.IndexedStorageChunkSaver - ? null - : this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).toMetricResults(); - } - } - - public static class IndexedStorageChunkSaver extends BufferChunkSaver implements MetricProvider { - private final boolean flushOnWrite; - - protected IndexedStorageChunkSaver(@Nonnull Store store, boolean flushOnWrite) { - super(store); - this.flushOnWrite = flushOnWrite; - } - - @Override - public void close() throws IOException { - IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() - .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); - indexedStorageCache.close(); - } - - @Nonnull - @Override - public CompletableFuture saveBuffer(int x, int z, @Nonnull ByteBuffer buffer) { - int regionX = x >> 5; - int regionZ = z >> 5; - int localX = x & 31; - int localZ = z & 31; - int index = ChunkUtil.indexColumn(localX, localZ); - IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() - .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); - return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> { - IndexedStorageFile chunks = indexedStorageCache.getOrCreate(regionX, regionZ, this.flushOnWrite); - chunks.writeBlob(index, buffer); - })); - } - - @Nonnull - @Override - public CompletableFuture removeBuffer(int x, int z) { - int regionX = x >> 5; - int regionZ = z >> 5; - int localX = x & 31; - int localZ = z & 31; - int index = ChunkUtil.indexColumn(localX, localZ); - IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() - .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); - return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> { - IndexedStorageFile chunks = indexedStorageCache.getOrTryOpen(regionX, regionZ, this.flushOnWrite); - if (chunks != null) { - chunks.removeBlob(index); - } - })); - } - - @Nonnull - @Override - public CompletableFuture deleteRegionFile(int x, int z) { - int regionX = x >> 5; - int regionZ = z >> 5; - IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() - .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); - return CompletableFuture.runAsync(() -> { - indexedStorageCache.markRegionCorrupted(regionX, regionZ); - }); - } - - @Nonnull - @Override - public LongSet getIndexes() throws IOException { - return this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).getIndexes(); - } - - @Override - public void flush() throws IOException { - this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).flush(); - } - - @Override - public MetricResults toMetricResults() { - return this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).toMetricResults(); - } - } -} diff --git a/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java b/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java index eabc2d20..bc0c9147 100644 --- a/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java +++ b/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java @@ -1,9 +1,8 @@ package com.hypixel.hytale.server.core.util.thread; +import com.hypixel.hytale.common.plugin.PluginIdentifier; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.metrics.metric.HistoricMetric; -import com.hypixel.hytale.server.core.HytaleServer; -import com.hypixel.hytale.server.core.ShutdownReason; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -27,6 +26,10 @@ public abstract class TickingThread implements Runnable { private Thread thread; @Nonnull private CompletableFuture startedFuture = new CompletableFuture<>(); + @Nullable + private PluginIdentifier possibleFailureCause; + @Nullable + private Throwable failureException; public TickingThread(String threadName) { this(threadName, 30, false); @@ -77,8 +80,16 @@ public abstract class TickingThread implements Runnable { } catch (InterruptedException var9) { Thread.currentThread().interrupt(); } catch (Throwable var10) { - HytaleLogger.getLogger().at(Level.SEVERE).withCause(var10).log("Exception in thread %s:", this.thread); - HytaleServer.get().shutdownServer(ShutdownReason.CRASH.withMessage("TickingThread crash: " + var10.getMessage())); + this.failureException = var10; + this.possibleFailureCause = PluginIdentifier.identifyThirdPartyPlugin(var10); + if (this.possibleFailureCause == null) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var10).log("Exception in thread %s:", this.thread); + } else { + HytaleLogger.getLogger() + .at(Level.SEVERE) + .withCause(var10) + .log("Exception in thread %s potentially caused by %s:", this.thread, this.possibleFailureCause); + } } if (this.needsShutdown.getAndSet(false)) { @@ -119,7 +130,6 @@ public abstract class TickingThread implements Runnable { } } - @SuppressWarnings("deprecation") public void stop() { Thread thread = this.thread; if (thread != null) { @@ -138,15 +148,7 @@ public abstract class TickingThread implements Runnable { } HytaleLogger.getLogger().at(Level.SEVERE).log("Forcing TickingThread %s to stop:\n%s", thread, sb.toString()); - - // HyFix #32: Handle Java 21+ where Thread.stop() throws UnsupportedOperationException - try { - thread.stop(); - } catch (UnsupportedOperationException e) { - HytaleLogger.getLogger().at(Level.WARNING).log("[HyFix] Thread.stop() not supported on Java 21+, using interrupt() instead"); - thread.interrupt(); - } - + thread.stop(); Thread var9 = null; if (this.needsShutdown.getAndSet(false)) { this.onShutdown(); @@ -208,6 +210,16 @@ public abstract class TickingThread implements Runnable { return this.thread != null && this.thread.isAlive() && this.needsShutdown.get(); } + @Nullable + public PluginIdentifier getPossibleFailureCause() { + return this.possibleFailureCause; + } + + @Nullable + public Throwable getFailureException() { + return this.failureException; + } + @Deprecated protected void setThread(Thread thread) { this.thread = thread; diff --git a/src/com/hypixel/hytale/server/npc/movement/controllers/MotionControllerBase.java b/src/com/hypixel/hytale/server/npc/movement/controllers/MotionControllerBase.java index d4df3b09..1417de90 100644 --- a/src/com/hypixel/hytale/server/npc/movement/controllers/MotionControllerBase.java +++ b/src/com/hypixel/hytale/server/npc/movement/controllers/MotionControllerBase.java @@ -52,6 +52,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.function.BiPredicate; @@ -1044,10 +1045,6 @@ public abstract class MotionControllerBase implements MotionController { } } - protected boolean isDebugMode(RoleDebugFlags mode) { - return this.getRole() != null && this.getRole().getDebugSupport().getDebugFlags().contains(mode); - } - public boolean isProcessTriggersHasMoved() { return this.processTriggersHasMoved; } @@ -1056,16 +1053,21 @@ public abstract class MotionControllerBase implements MotionController { return !componentAccessor.getArchetype(ref).contains(DeathComponent.getComponentType()); } + @Override + public void onDebugFlagsChanged(EnumSet newFlags) { + MotionController.super.onDebugFlagsChanged(newFlags); + this.debugModeSteer = newFlags.contains(RoleDebugFlags.MotionControllerSteer); + this.debugModeMove = newFlags.contains(RoleDebugFlags.MotionControllerMove); + this.debugModeCollisions = newFlags.contains(RoleDebugFlags.Collisions); + this.debugModeBlockCollisions = newFlags.contains(RoleDebugFlags.BlockCollisions); + this.debugModeProbeBlockCollisions = newFlags.contains(RoleDebugFlags.ProbeBlockCollisions); + this.debugModeValidatePositions = newFlags.contains(RoleDebugFlags.ValidatePositions); + this.debugModeOverlaps = newFlags.contains(RoleDebugFlags.Overlaps); + this.debugModeValidateMath = newFlags.contains(RoleDebugFlags.ValidateMath); + } + @Override public void activate() { - this.debugModeSteer = this.isDebugMode(RoleDebugFlags.MotionControllerSteer); - this.debugModeMove = this.isDebugMode(RoleDebugFlags.MotionControllerMove); - this.debugModeCollisions = this.isDebugMode(RoleDebugFlags.Collisions); - this.debugModeBlockCollisions = this.isDebugMode(RoleDebugFlags.BlockCollisions); - this.debugModeProbeBlockCollisions = this.isDebugMode(RoleDebugFlags.ProbeBlockCollisions); - this.debugModeValidatePositions = this.isDebugMode(RoleDebugFlags.ValidatePositions); - this.debugModeOverlaps = this.isDebugMode(RoleDebugFlags.Overlaps); - this.debugModeValidateMath = this.isDebugMode(RoleDebugFlags.ValidateMath); this.resetObstructedFlags(); this.resetNavState(); } diff --git a/src/com/hypixel/hytale/server/npc/role/Role.java b/src/com/hypixel/hytale/server/npc/role/Role.java index fc2e909c..dbb3fc3a 100644 --- a/src/com/hypixel/hytale/server/npc/role/Role.java +++ b/src/com/hypixel/hytale/server/npc/role/Role.java @@ -57,6 +57,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Supplier; import java.util.logging.Level; @@ -213,11 +214,11 @@ public class Role implements IAnnotatedComponentCollection { this.collisionRadius = builder.getCollisionRadius(); this.collisionViewAngle = builder.getCollisionViewAngle(); this.collisionViewHalfAngleCosine = TrigMathUtil.cos(this.collisionViewAngle / 2.0F); - this.separationDistance = builder.getSeparationDistance(); - this.separationWeight = builder.getSeparationWeight(); - this.separationDistanceTarget = builder.getSeparationDistanceTarget(); - this.separationNearRadiusTarget = builder.getSeparationNearRadiusTarget(); - this.separationFarRadiusTarget = builder.getSeparationFarRadiusTarget(); + this.separationDistance = builder.getSeparationDistance(builderSupport); + this.separationWeight = builder.getSeparationWeight(builderSupport); + this.separationDistanceTarget = builder.getSeparationDistanceTarget(builderSupport); + this.separationNearRadiusTarget = builder.getSeparationNearRadiusTarget(builderSupport); + this.separationFarRadiusTarget = builder.getSeparationFarRadiusTarget(builderSupport); this.applySeparation = builder.isApplySeparation(builderSupport); if (builder.isOverridingHeadPitchAngle(builderSupport)) { this.headPitchAngleRange = builder.getHeadPitchAngleRange(builderSupport); @@ -566,6 +567,11 @@ public class Role implements IAnnotatedComponentCollection { @Nonnull NPCEntity npcComponent, @Nonnull Map motionControllers, @Nullable String initialMotionController ) { this.motionControllers = motionControllers; + + for (Entry entry : this.motionControllers.entrySet()) { + this.debugSupport.registerDebugFlagsListener(entry.getValue()); + } + this.updateMotionControllers(null, null, null, null); if (!this.motionControllers.isEmpty()) { if (initialMotionController != null && this.setActiveMotionController(null, npcComponent, initialMotionController, null)) { @@ -624,6 +630,10 @@ public class Role implements IAnnotatedComponentCollection { protected void computeActionsAndSteering( @Nonnull Ref ref, double tickTime, @Nonnull Steering bodySteering, @Nonnull Steering headSteering, @Nonnull Store store ) { + if (this.debugSupport.isVisSensorRanges()) { + this.debugSupport.beginSensorVisualization(); + } + boolean isDead = store.getArchetype(ref).contains(DeathComponent.getComponentType()); if (isDead) { if (this.deathInstruction != null) { diff --git a/src/com/hypixel/hytale/server/npc/systems/RoleSystems.java b/src/com/hypixel/hytale/server/npc/systems/RoleSystems.java index aba8d37c..4002e1af 100644 --- a/src/com/hypixel/hytale/server/npc/systems/RoleSystems.java +++ b/src/com/hypixel/hytale/server/npc/systems/RoleSystems.java @@ -15,10 +15,17 @@ 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.TickingSystem; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; import com.hypixel.hytale.protocol.GameMode; import com.hypixel.hytale.server.core.entity.Frozen; import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; import com.hypixel.hytale.server.core.modules.entity.component.NewSpawnComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; @@ -29,18 +36,23 @@ import com.hypixel.hytale.server.core.modules.entity.system.TransformSystems; import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; import com.hypixel.hytale.server.npc.NPCPlugin; import com.hypixel.hytale.server.npc.components.StepComponent; import com.hypixel.hytale.server.npc.entities.NPCEntity; import com.hypixel.hytale.server.npc.movement.controllers.MotionController; import com.hypixel.hytale.server.npc.role.Role; import com.hypixel.hytale.server.npc.role.RoleDebugDisplay; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.role.support.DebugSupport; import com.hypixel.hytale.server.npc.role.support.EntitySupport; import com.hypixel.hytale.server.npc.role.support.MarkedEntitySupport; import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; @@ -117,13 +129,14 @@ public class RoleSystems { } try { + Role role2 = npcComponent.getRole(); boolean benchmarking = NPCPlugin.get().isBenchmarkingRole(); if (benchmarking) { long start = System.nanoTime(); - role.tick(entityReference, tickLength, store); - NPCPlugin.get().collectRoleTick(role.getRoleIndex(), System.nanoTime() - start); + role2.tick(entityReference, tickLength, store); + NPCPlugin.get().collectRoleTick(role2.getRoleIndex(), System.nanoTime() - start); } else { - role.tick(entityReference, tickLength, store); + role2.tick(entityReference, tickLength, store); } } catch (IllegalArgumentException | IllegalStateException | NullPointerException var15) { NPCPlugin.get().getLogger().at(Level.SEVERE).withCause(var15).log("Failed to tick NPC: %s", npcComponent.getRoleName()); @@ -329,7 +342,7 @@ public class RoleSystems { Role role = npcComponent.getRole(); role.getStateSupport().activate(); - role.getDebugSupport().activate(); + role.getDebugSupport().notifyDebugFlagsListeners(role.getDebugSupport().getDebugFlags()); ModelComponent modelComponent = holder.getComponent(this.modelComponentType); assert modelComponent != null; @@ -357,6 +370,15 @@ public class RoleSystems { } public static class RoleDebugSystem extends SteppableTickingSystem { + private static final float DEBUG_SHAPE_TIME = 0.1F; + private static final float SENSOR_VIS_OPACITY = 0.4F; + private static final double FULL_CIRCLE_EPSILON = 0.01; + private static final float LEASH_SPHERE_RADIUS = 0.3F; + private static final float LEASH_RING_OUTER_RADIUS = 0.5F; + private static final float LEASH_RING_INNER_RADIUS = 0.4F; + private static final float NPC_RING_THICKNESS = 0.1F; + private static final float NPC_RING_OFFSET = 0.1F; + private static final float LEASH_LINE_THICKNESS = 0.05F; @Nonnull private final ComponentType npcComponentType; @Nonnull @@ -381,7 +403,7 @@ public class RoleSystems { @Nonnull @Override public Query getQuery() { - return this.npcComponentType; + return Query.and(this.npcComponentType, TransformComponent.getComponentType(), BoundingBox.getComponentType()); } @Override @@ -397,10 +419,235 @@ public class RoleSystems { assert npcComponent != null; Role role = npcComponent.getRole(); - RoleDebugDisplay debugDisplay = role.getDebugSupport().getDebugDisplay(); - if (debugDisplay != null) { - debugDisplay.display(role, index, archetypeChunk, commandBuffer); + if (role != null) { + DebugSupport debugSupport = role.getDebugSupport(); + RoleDebugDisplay debugDisplay = debugSupport.getDebugDisplay(); + if (debugDisplay != null) { + debugDisplay.display(role, index, archetypeChunk, commandBuffer); + } + + if (debugSupport.isDebugFlagSet(RoleDebugFlags.VisMarkedTargets)) { + renderMarkedTargetArrows(role, index, archetypeChunk, commandBuffer); + } + + boolean hasSensorVis = debugSupport.hasSensorVisData(); + boolean hasLeashVis = debugSupport.isDebugFlagSet(RoleDebugFlags.VisLeashPosition); + if (hasSensorVis || hasLeashVis) { + Ref npcRef = archetypeChunk.getReferenceTo(index); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + World world = commandBuffer.getExternalData().getWorld(); + if (hasSensorVis) { + renderSensorVisualization(debugSupport, npcRef, transformComponent, boundingBoxComponent, world, commandBuffer); + } + + if (hasLeashVis) { + renderLeashPositionVisualization(npcComponent, npcRef, transformComponent, boundingBoxComponent, world); + } + } } } + + private static void renderMarkedTargetArrows( + @Nonnull Role role, int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull CommandBuffer commandBuffer + ) { + Ref npcRef = archetypeChunk.getReferenceTo(index); + Transform npcLook = TargetUtil.getLook(npcRef, commandBuffer); + Vector3d npcEyePosition = npcLook.getPosition(); + World world = commandBuffer.getExternalData().getWorld(); + MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport(); + Ref[] entityTargets = markedEntitySupport.getEntityTargets(); + + for (int slotIndex = 0; slotIndex < entityTargets.length; slotIndex++) { + Ref targetRef = entityTargets[slotIndex]; + if (targetRef != null && targetRef.isValid()) { + Transform targetLook = TargetUtil.getLook(targetRef, commandBuffer); + Vector3d targetEyePosition = targetLook.getPosition(); + Vector3d direction = new Vector3d( + targetEyePosition.x - npcEyePosition.x, targetEyePosition.y - npcEyePosition.y, targetEyePosition.z - npcEyePosition.z + ); + Vector3f color = DebugUtils.INDEXED_COLORS[slotIndex % DebugUtils.INDEXED_COLORS.length]; + DebugUtils.addArrow(world, npcEyePosition, direction, color, 0.1F, false); + } + } + } + + private static void renderSensorVisualization( + @Nonnull DebugSupport debugSupport, + @Nonnull Ref npcRef, + @Nonnull TransformComponent transformComponent, + @Nonnull BoundingBox boundingBoxComponent, + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer + ) { + List sensorDataList = debugSupport.getSensorVisData(); + if (sensorDataList != null) { + Vector3d npcPosition = transformComponent.getPosition(); + double npcMidHeight = boundingBoxComponent.getBoundingBox().max.y / 2.0; + HeadRotation headRotation = commandBuffer.getComponent(npcRef, HeadRotation.getComponentType()); + double heading = headRotation != null ? headRotation.getRotation().getYaw() : transformComponent.getRotation().getYaw(); + sensorDataList.sort((a, b) -> Double.compare(b.range(), a.range())); + double discStackOffset = 0.1; + + for (int i = 0; i < sensorDataList.size(); i++) { + DebugSupport.SensorVisData sensorData = sensorDataList.get(i); + Vector3f color = DebugUtils.INDEXED_COLORS[sensorData.colorIndex() % DebugUtils.INDEXED_COLORS.length]; + double height = npcPosition.y + npcMidHeight + i * 0.1; + if (sensorData.viewAngle() > 0.0 && sensorData.viewAngle() < 6.273185482025147) { + double sectorHeading = -heading + Math.PI; + DebugUtils.addSector( + world, + npcPosition.x, + height, + npcPosition.z, + sectorHeading, + sensorData.range(), + sensorData.viewAngle(), + sensorData.minRange(), + color, + 0.4F, + 0.1F, + false + ); + } else { + DebugUtils.addDisc(world, npcPosition.x, height, npcPosition.z, sensorData.range(), sensorData.minRange(), color, 0.4F, 0.1F, false); + } + } + + Map, List> entityDataMap = debugSupport.getEntityVisData(); + if (entityDataMap != null) { + double markerOffset = 0.3; + double sphereStackOffset = 0.3; + double defaultEntityHeight = 2.0; + + for (Entry, List> entry : entityDataMap.entrySet()) { + Ref entityRef = entry.getKey(); + List checks = entry.getValue(); + if (!checks.isEmpty() && entityRef.isValid()) { + TransformComponent entityTransform = commandBuffer.getComponent(entityRef, TransformComponent.getComponentType()); + if (entityTransform != null) { + Vector3d entityPosition = entityTransform.getPosition(); + BoundingBox entityBoundingBox = commandBuffer.getComponent(entityRef, BoundingBox.getComponentType()); + double entityHeight = entityBoundingBox != null ? entityBoundingBox.getBoundingBox().max.y : 2.0; + double markerBaseHeight = entityHeight + 0.3; + boolean anyMatched = false; + + for (DebugSupport.EntityVisData check : checks) { + if (check.matched()) { + anyMatched = true; + break; + } + } + + int sphereCount = 0; + + for (DebugSupport.EntityVisData checkx : checks) { + if (checkx.matched()) { + Vector3f sensorColor = DebugUtils.INDEXED_COLORS[checkx.sensorColorIndex() % DebugUtils.INDEXED_COLORS.length]; + double sphereHeight = markerBaseHeight + sphereCount * 0.3; + DebugUtils.addSphere(world, entityPosition.x, entityPosition.y + sphereHeight, entityPosition.z, sensorColor, 0.2, 0.1F); + sphereCount++; + } + } + + if (!anyMatched) { + DebugUtils.addCube(world, entityPosition.x, entityPosition.y + markerBaseHeight, entityPosition.z, DebugUtils.COLOR_GRAY, 0.2, 0.1F); + } + + DebugUtils.addLine( + world, + npcPosition.x, + npcPosition.y + npcMidHeight, + npcPosition.z, + entityPosition.x, + entityPosition.y + markerBaseHeight, + entityPosition.z, + DebugUtils.COLOR_GRAY, + 0.03, + 0.1F, + false + ); + } + } + } + } + + debugSupport.clearSensorVisData(); + } + } + + private static void renderLeashPositionVisualization( + @Nonnull NPCEntity npcComponent, + @Nonnull Ref npcRef, + @Nonnull TransformComponent transformComponent, + @Nonnull BoundingBox boundingBoxComponent, + @Nonnull World world + ) { + if (npcComponent.requiresLeashPosition()) { + Box boundingBox = boundingBoxComponent.getBoundingBox(); + double npcWidth = boundingBox.max.x - boundingBox.min.x; + double npcDepth = boundingBox.max.z - boundingBox.min.z; + double npcRingOuterRadius = Math.max(npcWidth, npcDepth) / 2.0 + 0.1F; + double npcRingInnerRadius = npcRingOuterRadius - 0.1F; + int colorIndex = Math.abs(npcRef.getIndex()) % DebugUtils.INDEXED_COLORS.length; + Vector3f color = DebugUtils.INDEXED_COLORS[colorIndex]; + Vector3d leashPoint = npcComponent.getLeashPoint(); + DebugUtils.addSphere(world, leashPoint, color, 0.3F, 0.1F); + Vector3d npcPosition = transformComponent.getPosition(); + double npcMidHeight = boundingBox.max.y / 2.0; + double npcMidY = npcPosition.y + npcMidHeight; + double dirX = npcPosition.x - leashPoint.x; + double dirZ = npcPosition.z - leashPoint.z; + double horizontalDist = Math.sqrt(dirX * dirX + dirZ * dirZ); + if (horizontalDist > 0.001) { + double verticalDist = npcMidY - leashPoint.y; + double pitchAngle = Math.atan2(verticalDist, horizontalDist); + double yawAngle = Math.atan2(dirZ, dirX); + addChainRing(world, leashPoint.x, leashPoint.y, leashPoint.z, 0.5, 0.4F, yawAngle, -pitchAngle, color); + addChainRing(world, npcPosition.x, npcMidY, npcPosition.z, npcRingOuterRadius, npcRingInnerRadius, yawAngle + Math.PI, pitchAngle, color); + double hDirX = dirX / horizontalDist; + double hDirZ = dirZ / horizontalDist; + double cosPitch = Math.cos(pitchAngle); + double sinPitch = Math.sin(pitchAngle); + double leashEdgeX = leashPoint.x + hDirX * 0.5 * cosPitch; + double leashEdgeY = leashPoint.y + sinPitch * 0.5; + double leashEdgeZ = leashPoint.z + hDirZ * 0.5 * cosPitch; + double npcEdgeX = npcPosition.x - hDirX * npcRingOuterRadius * cosPitch; + double npcEdgeY = npcMidY - sinPitch * npcRingOuterRadius; + double npcEdgeZ = npcPosition.z - hDirZ * npcRingOuterRadius * cosPitch; + DebugUtils.addLine(world, leashEdgeX, leashEdgeY, leashEdgeZ, npcEdgeX, npcEdgeY, npcEdgeZ, color, 0.05F, 0.1F, false); + } else { + DebugUtils.addDisc(world, leashPoint.x, leashPoint.y, leashPoint.z, 0.5, 0.4F, color, 0.8F, 0.1F, false); + DebugUtils.addDisc(world, npcPosition.x, npcMidY, npcPosition.z, npcRingOuterRadius, npcRingInnerRadius, color, 0.8F, 0.1F, false); + DebugUtils.addLine(world, leashPoint.x, leashPoint.y, leashPoint.z, npcPosition.x, npcMidY, npcPosition.z, color, 0.05F, 0.1F, false); + } + } + } + + private static void addChainRing( + @Nonnull World world, + double x, + double y, + double z, + double outerRadius, + double innerRadius, + double yawAngle, + double pitchAngle, + @Nonnull Vector3f color + ) { + Matrix4d matrix = new Matrix4d(); + matrix.identity(); + matrix.translate(x, y, z); + Matrix4d tmp = new Matrix4d(); + matrix.rotateAxis(yawAngle, 0.0, 1.0, 0.0, tmp); + matrix.rotateAxis(pitchAngle, 0.0, 0.0, 1.0, tmp); + DebugUtils.addDisc(world, matrix, outerRadius, innerRadius, color, 0.8F, 0.1F, false); + } } } diff --git a/src/com/hypixel/hytale/server/npc/systems/SpawnReferenceSystems.java b/src/com/hypixel/hytale/server/npc/systems/SpawnReferenceSystems.java deleted file mode 100644 index 11b74827..00000000 --- a/src/com/hypixel/hytale/server/npc/systems/SpawnReferenceSystems.java +++ /dev/null @@ -1,430 +0,0 @@ -package com.hypixel.hytale.server.npc.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.Ref; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.ResourceType; -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.RefSystem; -import com.hypixel.hytale.component.system.tick.EntityTickingSystem; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.entity.reference.InvalidatablePersistentRef; -import com.hypixel.hytale.server.core.modules.entity.component.WorldGenId; -import com.hypixel.hytale.server.core.modules.entity.damage.DeathSystems; -import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.flock.StoredFlock; -import com.hypixel.hytale.server.npc.components.SpawnBeaconReference; -import com.hypixel.hytale.server.npc.components.SpawnMarkerReference; -import com.hypixel.hytale.server.npc.entities.NPCEntity; -import com.hypixel.hytale.server.spawning.SpawningPlugin; -import com.hypixel.hytale.server.spawning.assets.spawnmarker.config.SpawnMarker; -import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity; -import com.hypixel.hytale.server.spawning.controllers.BeaconSpawnController; -import com.hypixel.hytale.server.spawning.spawnmarkers.SpawnMarkerEntity; - -import javax.annotation.Nonnull; -import java.time.Duration; -import java.time.Instant; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; - -public class SpawnReferenceSystems { - public SpawnReferenceSystems() { - } - - public static class BeaconAddRemoveSystem extends RefSystem { - @Nonnull - private final ComponentType spawnReferenceComponentType; - @Nonnull - private final ComponentType legacySpawnBeaconComponent; - @Nonnull - private final ComponentType npcComponentType; - @Nonnull - private final Query query; - - public BeaconAddRemoveSystem( - @Nonnull ComponentType spawnReferenceComponentType, - @Nonnull ComponentType legacySpawnBeaconComponent - ) { - this.spawnReferenceComponentType = spawnReferenceComponentType; - this.legacySpawnBeaconComponent = legacySpawnBeaconComponent; - this.npcComponentType = NPCEntity.getComponentType(); - this.query = Archetype.of(spawnReferenceComponentType, this.npcComponentType); - } - - @Nonnull - @Override - public Query getQuery() { - return this.query; - } - - @Override - public void onEntityAdded( - @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - switch (reason) { - case LOAD: - SpawnBeaconReference spawnReferenceComponent = store.getComponent(ref, this.spawnReferenceComponentType); - - assert spawnReferenceComponent != null; - - Ref markerReference = spawnReferenceComponent.getReference().getEntity(store); - if (markerReference == null) { - return; - } else { - LegacySpawnBeaconEntity legacySpawnBeaconComponent = store.getComponent(markerReference, this.legacySpawnBeaconComponent); - - assert legacySpawnBeaconComponent != null; - - NPCEntity npcComponent = store.getComponent(ref, this.npcComponentType); - - assert npcComponent != null; - - spawnReferenceComponent.getReference().setEntity(markerReference, store); - spawnReferenceComponent.refreshTimeoutCounter(); - BeaconSpawnController spawnController = legacySpawnBeaconComponent.getSpawnController(); - - // HyFix: Add null check for spawnController before calling hasSlots() - if (spawnController == null) { - System.out.println("[HyFix] WARNING: null spawnController in BeaconAddRemoveSystem - despawning NPC (missing beacon type?)"); - npcComponent.setToDespawn(); - return; - } - - if (!spawnController.hasSlots()) { - npcComponent.setToDespawn(); - return; - } else { - spawnController.notifySpawnedEntityExists(markerReference, commandBuffer); - } - } - case SPAWN: - } - } - - @Override - public void onEntityRemove( - @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - switch (reason) { - case REMOVE: - SpawnBeaconReference spawnReference = store.getComponent(ref, this.spawnReferenceComponentType); - if (spawnReference == null) { - return; - } else { - Ref spawnBeaconRef = spawnReference.getReference().getEntity(store); - if (spawnBeaconRef == null) { - return; - } else { - LegacySpawnBeaconEntity legacySpawnBeaconComponent = store.getComponent(spawnBeaconRef, this.legacySpawnBeaconComponent); - if (legacySpawnBeaconComponent == null) { - return; - } else { - legacySpawnBeaconComponent.getSpawnController().notifyNPCRemoval(ref, store); - } - } - } - case UNLOAD: - } - } - } - - public static class MarkerAddRemoveSystem extends RefSystem { - @Nonnull - private final ComponentType spawnReferenceComponentType; - @Nonnull - private final ComponentType spawnMarkerEntityComponentType; - @Nonnull - private final ComponentType npcComponentType; - @Nonnull - private final ComponentType worldGenIdComponentType; - @Nonnull - private final ComponentType uuidComponentComponentType; - @Nonnull - private final ResourceType worldTimeResourceResourceType; - @Nonnull - private final Query query; - - public MarkerAddRemoveSystem( - @Nonnull ComponentType spawnReferenceComponentType, - @Nonnull ComponentType spawnMarkerEntityComponentType - ) { - this.spawnReferenceComponentType = spawnReferenceComponentType; - this.spawnMarkerEntityComponentType = spawnMarkerEntityComponentType; - this.npcComponentType = NPCEntity.getComponentType(); - this.worldGenIdComponentType = WorldGenId.getComponentType(); - this.uuidComponentComponentType = UUIDComponent.getComponentType(); - this.worldTimeResourceResourceType = WorldTimeResource.getResourceType(); - this.query = Archetype.of(spawnReferenceComponentType, this.npcComponentType, this.uuidComponentComponentType); - } - - @Nonnull - @Override - public Query getQuery() { - return this.query; - } - - @Override - public void onEntityAdded( - @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - switch (reason) { - case LOAD: - SpawnMarkerReference spawnReferenceComponent = store.getComponent(ref, this.spawnReferenceComponentType); - - assert spawnReferenceComponent != null; - - Ref markerReference = spawnReferenceComponent.getReference().getEntity(store); - if (markerReference == null) { - return; - } else { - SpawnMarkerEntity markerTypeComponent = store.getComponent(markerReference, this.spawnMarkerEntityComponentType); - - assert markerTypeComponent != null; - - NPCEntity npcComponent = store.getComponent(ref, this.npcComponentType); - - assert npcComponent != null; - - spawnReferenceComponent.getReference().setEntity(markerReference, store); - spawnReferenceComponent.refreshTimeoutCounter(); - markerTypeComponent.refreshTimeout(); - WorldGenId worldGenIdComponent = commandBuffer.getComponent(markerReference, this.worldGenIdComponentType); - int worldGenId = worldGenIdComponent != null ? worldGenIdComponent.getWorldGenId() : 0; - commandBuffer.putComponent(markerReference, WorldGenId.getComponentType(), new WorldGenId(worldGenId)); - HytaleLogger.Api context = SpawningPlugin.get().getLogger().at(Level.FINE); - if (context.isEnabled()) { - UUIDComponent uuidComponent = commandBuffer.getComponent(markerReference, this.uuidComponentComponentType); - - assert uuidComponent != null; - - UUID uuid = uuidComponent.getUuid(); - context.log("%s synced up with marker %s", npcComponent.getRoleName(), uuid); - } - } - case SPAWN: - } - } - - @Override - public void onEntityRemove( - @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer - ) { - switch (reason) { - case REMOVE: - SpawnMarkerReference spawnReferenceComponent = store.getComponent(ref, this.spawnReferenceComponentType); - if (spawnReferenceComponent == null) { - return; - } else { - Ref spawnMarkerRef = spawnReferenceComponent.getReference().getEntity(store); - if (spawnMarkerRef == null) { - return; - } else { - SpawnMarkerEntity spawnMarkerComponent = store.getComponent(spawnMarkerRef, this.spawnMarkerEntityComponentType); - - assert spawnMarkerComponent != null; - - UUIDComponent uuidComponent = store.getComponent(ref, this.uuidComponentComponentType); - - assert uuidComponent != null; - - UUID uuid = uuidComponent.getUuid(); - int spawnCount = spawnMarkerComponent.decrementAndGetSpawnCount(); - SpawnMarker cachedMarker = spawnMarkerComponent.getCachedMarker(); - if (spawnCount > 0 && cachedMarker.getDeactivationDistance() > 0.0) { - InvalidatablePersistentRef[] newReferences = new InvalidatablePersistentRef[spawnCount]; - int pos = 0; - InvalidatablePersistentRef[] npcReferences = spawnMarkerComponent.getNpcReferences(); - - for (InvalidatablePersistentRef npcRef : npcReferences) { - if (!npcRef.getUuid().equals(uuid)) { - newReferences[pos++] = npcRef; - } - } - - spawnMarkerComponent.setNpcReferences(newReferences); - } - - if (spawnCount <= 0 && !cachedMarker.isRealtimeRespawn()) { - Instant instant = store.getResource(this.worldTimeResourceResourceType).getGameTime(); - Duration gameTimeRespawn = spawnMarkerComponent.pollGameTimeRespawn(); - if (gameTimeRespawn != null) { - instant = instant.plus(gameTimeRespawn); - } - - spawnMarkerComponent.setSpawnAfter(instant); - spawnMarkerComponent.setNpcReferences(null); - StoredFlock storedFlock = spawnMarkerComponent.getStoredFlock(); - if (storedFlock != null) { - storedFlock.clear(); - } - } - } - } - case UNLOAD: - } - } - } - - public static class TickingSpawnBeaconSystem extends EntityTickingSystem { - @Nonnull - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, NPCPreTickSystem.class), new SystemDependency<>(Order.BEFORE, DeathSystems.CorpseRemoval.class) - ); - @Nonnull - private final ComponentType spawnReferenceComponentType; - @Nonnull - private final ComponentType npcEntityComponentType; - @Nonnull - private final Query query; - - public TickingSpawnBeaconSystem(@Nonnull ComponentType spawnReferenceComponentType) { - this.spawnReferenceComponentType = spawnReferenceComponentType; - this.npcEntityComponentType = NPCEntity.getComponentType(); - this.query = Archetype.of(spawnReferenceComponentType, this.npcEntityComponentType); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - @Nonnull - @Override - public Query getQuery() { - return this.query; - } - - @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 - ) { - NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcEntityComponentType); - - assert npcComponent != null; - - if (!npcComponent.isDespawning() && !npcComponent.isPlayingDespawnAnim()) { - SpawnBeaconReference spawnReferenceComponent = archetypeChunk.getComponent(index, this.spawnReferenceComponentType); - - assert spawnReferenceComponent != null; - - if (spawnReferenceComponent.tickMarkerLostTimeoutCounter(dt)) { - Ref spawnBeaconRef = spawnReferenceComponent.getReference().getEntity(commandBuffer); - if (spawnBeaconRef != null) { - spawnReferenceComponent.refreshTimeoutCounter(); - } else if (npcComponent.getRole().getStateSupport().isInBusyState()) { - spawnReferenceComponent.refreshTimeoutCounter(); - } else { - npcComponent.setToDespawn(); - HytaleLogger.Api context = SpawningPlugin.get().getLogger().at(Level.WARNING); - if (context.isEnabled()) { - context.log("NPCEntity despawning due to lost marker: %s", archetypeChunk.getReferenceTo(index)); - } - } - } - } - } - } - - public static class TickingSpawnMarkerSystem extends EntityTickingSystem { - @Nonnull - private static final Set> DEPENDENCIES = Set.of( - new SystemDependency<>(Order.AFTER, NPCPreTickSystem.class), new SystemDependency<>(Order.BEFORE, DeathSystems.CorpseRemoval.class) - ); - @Nonnull - private final ComponentType spawnReferenceComponentType; - @Nonnull - private final ComponentType markerTypeComponentType; - @Nonnull - private final ComponentType npcEntityComponentType; - @Nonnull - private final Query query; - - public TickingSpawnMarkerSystem( - @Nonnull ComponentType spawnReferenceComponentType, - @Nonnull ComponentType markerTypeComponentType - ) { - this.spawnReferenceComponentType = spawnReferenceComponentType; - this.markerTypeComponentType = markerTypeComponentType; - this.npcEntityComponentType = NPCEntity.getComponentType(); - this.query = Archetype.of(spawnReferenceComponentType, this.npcEntityComponentType); - } - - @Nonnull - @Override - public Set> getDependencies() { - return DEPENDENCIES; - } - - @Nonnull - @Override - public Query getQuery() { - return this.query; - } - - @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 - ) { - NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcEntityComponentType); - - assert npcComponent != null; - - if (!npcComponent.isDespawning() && !npcComponent.isPlayingDespawnAnim()) { - SpawnMarkerReference spawnReferenceComponent = archetypeChunk.getComponent(index, this.spawnReferenceComponentType); - - assert spawnReferenceComponent != null; - - if (spawnReferenceComponent.tickMarkerLostTimeoutCounter(dt)) { - Ref spawnMarkerRef = spawnReferenceComponent.getReference().getEntity(commandBuffer); - if (spawnMarkerRef != null) { - SpawnMarkerEntity spawnMarkerComponent = commandBuffer.getComponent(spawnMarkerRef, this.markerTypeComponentType); - - assert spawnMarkerComponent != null; - - spawnReferenceComponent.refreshTimeoutCounter(); - spawnMarkerComponent.refreshTimeout(); - } else if (npcComponent.getRole().getStateSupport().isInBusyState()) { - spawnReferenceComponent.refreshTimeoutCounter(); - } else { - npcComponent.setToDespawn(); - HytaleLogger.Api context = SpawningPlugin.get().getLogger().at(Level.WARNING); - if (context.isEnabled()) { - context.log("NPCEntity despawning due to lost marker: %s", archetypeChunk.getReferenceTo(index)); - } - } - } - } - } - } -} diff --git a/src/com/hypixel/hytale/server/spawning/controllers/BeaconSpawnController.java b/src/com/hypixel/hytale/server/spawning/controllers/BeaconSpawnController.java deleted file mode 100644 index aab2f15b..00000000 --- a/src/com/hypixel/hytale/server/spawning/controllers/BeaconSpawnController.java +++ /dev/null @@ -1,262 +0,0 @@ -package com.hypixel.hytale.server.spawning.controllers; - -import com.hypixel.hytale.component.ComponentAccessor; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -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.storage.EntityStore; -import com.hypixel.hytale.server.npc.NPCPlugin; -import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn; -import com.hypixel.hytale.server.spawning.assets.spawns.config.RoleSpawnParameters; -import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity; -import com.hypixel.hytale.server.spawning.jobs.NPCBeaconSpawnJob; -import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; -import it.unimi.dsi.fastutil.objects.Object2DoubleMap; -import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.time.Duration; -import java.util.Comparator; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.logging.Level; - -public class BeaconSpawnController extends SpawnController { - @Nonnull - private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); - public static final int MAX_ATTEMPTS_PER_TICK = 5; - public static final double ROUNDING_BREAK_POINT = 0.25; - @Nonnull - private final Ref ownerRef; - private final List> spawnedEntities = new ObjectArrayList<>(); - private final List playersInRegion = new ObjectArrayList<>(); - private int nextPlayerIndex = 0; - private final Object2IntMap entitiesPerPlayer = new Object2IntOpenHashMap<>(); - private final Object2DoubleMap> entityTimeoutCounter = new Object2DoubleOpenHashMap<>(); - private final IntSet unspawnableRoles = new IntOpenHashSet(); - private final Comparator threatComparator = Comparator.comparingInt(playerRef -> this.entitiesPerPlayer.getOrDefault(playerRef.getUuid(), 0)); - private int baseMaxTotalSpawns; - private int currentScaledMaxTotalSpawns; - private int[] baseMaxConcurrentSpawns; - private int currentScaledMaxConcurrentSpawns; - private int spawnsThisRound; - private int remainingSpawns; - private boolean roundStart = true; - private double beaconRadiusSquared; - private double spawnRadiusSquared; - private double despawnNPCAfterTimeout; - private Duration despawnBeaconAfterTimeout; - private boolean despawnNPCsIfIdle; - - public BeaconSpawnController(@Nonnull World world, @Nonnull Ref ownerRef) { - super(world); - this.ownerRef = ownerRef; - } - - @Override - public int getMaxActiveJobs() { - return Math.min(this.remainingSpawns, this.baseMaxActiveJobs); - } - - @Nullable - public NPCBeaconSpawnJob createRandomSpawnJob(@Nonnull ComponentAccessor componentAccessor) { - LegacySpawnBeaconEntity legacySpawnBeaconComponent = componentAccessor.getComponent(this.ownerRef, LegacySpawnBeaconEntity.getComponentType()); - - assert legacySpawnBeaconComponent != null; - - BeaconSpawnWrapper wrapper = legacySpawnBeaconComponent.getSpawnWrapper(); - RoleSpawnParameters spawn = wrapper.pickRole(ThreadLocalRandom.current()); - - // HyFix: Null check for spawn parameter - prevents NPE when beacon has misconfigured spawn types - if (spawn == null) { - System.out.println("[HyFix] WARNING: null spawn from pickRole() - returning null (missing spawn config in beacon?)"); - return null; - } else { - String spawnId = spawn.getId(); - int roleIndex = NPCPlugin.get().getIndex(spawnId); - if (roleIndex >= 0 && !this.unspawnableRoles.contains(roleIndex)) { - NPCBeaconSpawnJob job = null; - int predictedTotal = this.spawnedEntities.size() + this.activeJobs.size(); - if (this.activeJobs.size() < this.getMaxActiveJobs() - && this.nextPlayerIndex < this.playersInRegion.size() - && predictedTotal < this.currentScaledMaxTotalSpawns) { - job = this.idleJobs.isEmpty() ? new NPCBeaconSpawnJob() : this.idleJobs.pop(); - job.beginProbing(this.playersInRegion.get(this.nextPlayerIndex++), this.currentScaledMaxConcurrentSpawns, roleIndex, spawn.getFlockDefinition()); - this.activeJobs.add(job); - if (this.nextPlayerIndex >= this.playersInRegion.size()) { - this.nextPlayerIndex = 0; - } - } - - return job; - } else { - return null; - } - } - } - - public void initialise(@Nonnull BeaconSpawnWrapper spawnWrapper) { - BeaconNPCSpawn spawn = spawnWrapper.getSpawn(); - this.baseMaxTotalSpawns = spawn.getMaxSpawnedNpcs(); - this.baseMaxConcurrentSpawns = spawn.getConcurrentSpawnsRange(); - double beaconRadius = spawn.getBeaconRadius(); - this.beaconRadiusSquared = beaconRadius * beaconRadius; - double spawnRadius = spawn.getSpawnRadius(); - this.spawnRadiusSquared = spawnRadius * spawnRadius; - this.despawnNPCAfterTimeout = spawn.getNpcIdleDespawnTimeSeconds(); - this.despawnBeaconAfterTimeout = spawn.getBeaconVacantDespawnTime(); - this.despawnNPCsIfIdle = spawn.getNpcSpawnState() != null; - } - - public int getSpawnsThisRound() { - return this.spawnsThisRound; - } - - public void setRemainingSpawns(int remainingSpawns) { - this.remainingSpawns = remainingSpawns; - } - - public void addRoundSpawn() { - this.spawnsThisRound++; - this.remainingSpawns--; - } - - public boolean isRoundStart() { - return this.roundStart; - } - - public void setRoundStart(boolean roundStart) { - this.roundStart = roundStart; - } - - public Ref getOwnerRef() { - return this.ownerRef; - } - - public int[] getBaseMaxConcurrentSpawns() { - return this.baseMaxConcurrentSpawns; - } - - public List getPlayersInRegion() { - return this.playersInRegion; - } - - public int getCurrentScaledMaxConcurrentSpawns() { - return this.currentScaledMaxConcurrentSpawns; - } - - public void setCurrentScaledMaxConcurrentSpawns(int currentScaledMaxConcurrentSpawns) { - this.currentScaledMaxConcurrentSpawns = currentScaledMaxConcurrentSpawns; - } - - public Duration getDespawnBeaconAfterTimeout() { - return this.despawnBeaconAfterTimeout; - } - - public double getSpawnRadiusSquared() { - return this.spawnRadiusSquared; - } - - public double getBeaconRadiusSquared() { - return this.beaconRadiusSquared; - } - - public int getBaseMaxTotalSpawns() { - return this.baseMaxTotalSpawns; - } - - public void setCurrentScaledMaxTotalSpawns(int currentScaledMaxTotalSpawns) { - this.currentScaledMaxTotalSpawns = currentScaledMaxTotalSpawns; - } - - public List> getSpawnedEntities() { - return this.spawnedEntities; - } - - public void setNextPlayerIndex(int nextPlayerIndex) { - this.nextPlayerIndex = nextPlayerIndex; - } - - public Object2DoubleMap> getEntityTimeoutCounter() { - return this.entityTimeoutCounter; - } - - public Object2IntMap getEntitiesPerPlayer() { - return this.entitiesPerPlayer; - } - - public boolean isDespawnNPCsIfIdle() { - return this.despawnNPCsIfIdle; - } - - public double getDespawnNPCAfterTimeout() { - return this.despawnNPCAfterTimeout; - } - - public Comparator getThreatComparator() { - return this.threatComparator; - } - - public void notifySpawnedEntityExists(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { - this.spawnedEntities.add(ref); - HytaleLogger.Api context = LOGGER.at(Level.FINE); - if (context.isEnabled()) { - UUIDComponent ownerUuidComponent = componentAccessor.getComponent(this.ownerRef, UUIDComponent.getComponentType()); - - assert ownerUuidComponent != null; - - context.log("Registering NPC with reference %s with Spawn Beacon %s", ref, ownerUuidComponent.getUuid()); - } - } - - public void onJobFinished(@Nonnull ComponentAccessor componentAccessor) { - if (++this.spawnsThisRound >= this.currentScaledMaxConcurrentSpawns) { - this.onAllConcurrentSpawned(componentAccessor); - } - } - - public void notifyNPCRemoval(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { - this.spawnedEntities.remove(ref); - this.entityTimeoutCounter.removeDouble(ref); - if (this.spawnedEntities.size() == this.currentScaledMaxTotalSpawns - 1) { - LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor); - } - - HytaleLogger.Api context = LOGGER.at(Level.FINE); - if (context.isEnabled()) { - UUIDComponent ownerUuidComponent = componentAccessor.getComponent(this.ownerRef, UUIDComponent.getComponentType()); - - assert ownerUuidComponent != null; - - context.log("Removing NPC with reference %s from Spawn Beacon %s", ref, ownerUuidComponent.getUuid()); - } - } - - public boolean hasSlots() { - return this.spawnedEntities.size() < this.currentScaledMaxTotalSpawns; - } - - public void markNPCUnspawnable(int roleIndex) { - this.unspawnableRoles.add(roleIndex); - } - - public void clearUnspawnableNPCs() { - this.unspawnableRoles.clear(); - } - - public void onAllConcurrentSpawned(@Nonnull ComponentAccessor componentAccessor) { - this.spawnsThisRound = 0; - this.remainingSpawns = 0; - LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor); - this.roundStart = true; - } -} diff --git a/src/com/hypixel/hytale/server/spawning/spawnmarkers/SpawnMarkerEntity.java b/src/com/hypixel/hytale/server/spawning/spawnmarkers/SpawnMarkerEntity.java deleted file mode 100644 index 2cf10374..00000000 --- a/src/com/hypixel/hytale/server/spawning/spawnmarkers/SpawnMarkerEntity.java +++ /dev/null @@ -1,547 +0,0 @@ -package com.hypixel.hytale.server.spawning.spawnmarkers; - -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.codec.KeyedCodec; -import com.hypixel.hytale.codec.builder.BuilderCodec; -import com.hypixel.hytale.codec.codecs.array.ArrayCodec; -import com.hypixel.hytale.component.Component; -import com.hypixel.hytale.component.ComponentType; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.RemoveReason; -import com.hypixel.hytale.component.Store; -import com.hypixel.hytale.component.spatial.SpatialResource; -import com.hypixel.hytale.function.consumer.TriConsumer; -import com.hypixel.hytale.math.vector.Vector3d; -import com.hypixel.hytale.math.vector.Vector3f; -import com.hypixel.hytale.server.core.asset.type.model.config.Model; -import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; -import com.hypixel.hytale.server.core.entity.UUIDComponent; -import com.hypixel.hytale.server.core.entity.group.EntityGroup; -import com.hypixel.hytale.server.core.entity.reference.InvalidatablePersistentRef; -import com.hypixel.hytale.server.core.modules.entity.EntityModule; -import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; -import com.hypixel.hytale.server.core.modules.entity.component.WorldGenId; -import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; -import com.hypixel.hytale.server.flock.FlockPlugin; -import com.hypixel.hytale.server.flock.StoredFlock; -import com.hypixel.hytale.server.npc.NPCPlugin; -import com.hypixel.hytale.server.npc.asset.builder.Builder; -import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; -import com.hypixel.hytale.server.npc.components.SpawnMarkerReference; -import com.hypixel.hytale.server.npc.entities.NPCEntity; -import com.hypixel.hytale.server.spawning.ISpawnableWithModel; -import com.hypixel.hytale.server.spawning.SpawnTestResult; -import com.hypixel.hytale.server.spawning.SpawningContext; -import com.hypixel.hytale.server.spawning.SpawningPlugin; -import com.hypixel.hytale.server.spawning.assets.spawnmarker.config.SpawnMarker; -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.time.Duration; -import java.time.Instant; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.logging.Level; - -public class SpawnMarkerEntity implements Component { - private static final double SPAWN_LOST_TIMEOUT = 35.0; - @Nonnull - private static final InvalidatablePersistentRef[] EMPTY_REFERENCES = new InvalidatablePersistentRef[0]; - public static final ArrayCodec NPC_REFERENCES_CODEC = new ArrayCodec<>( - InvalidatablePersistentRef.CODEC, InvalidatablePersistentRef[]::new - ); - @Nonnull - public static final BuilderCodec CODEC = BuilderCodec.builder(SpawnMarkerEntity.class, SpawnMarkerEntity::new) - .addField( - new KeyedCodec<>("SpawnMarker", Codec.STRING), - (spawnMarkerEntity, s) -> spawnMarkerEntity.spawnMarkerId = s, - spawnMarkerEntity -> spawnMarkerEntity.spawnMarkerId - ) - .addField( - new KeyedCodec<>("RespawnTime", Codec.DOUBLE), - (spawnMarkerEntity, d) -> spawnMarkerEntity.respawnCounter = d, - spawnMarkerEntity -> spawnMarkerEntity.respawnCounter - ) - .addField( - new KeyedCodec<>("SpawnCount", Codec.INTEGER), - (spawnMarkerEntity, i) -> spawnMarkerEntity.spawnCount = i, - spawnMarkerEntity -> spawnMarkerEntity.spawnCount - ) - .addField( - new KeyedCodec<>("GameTimeRespawn", Codec.DURATION), - (spawnMarkerEntity, duration) -> spawnMarkerEntity.gameTimeRespawn = duration, - spawnMarkerEntity -> spawnMarkerEntity.gameTimeRespawn - ) - .addField( - new KeyedCodec<>("SpawnAfter", Codec.INSTANT), - (spawnMarkerEntity, instant) -> spawnMarkerEntity.spawnAfter = instant, - spawnMarkerEntity -> spawnMarkerEntity.spawnAfter - ) - .addField( - new KeyedCodec<>("NPCReferences", NPC_REFERENCES_CODEC), - (spawnMarkerEntity, array) -> spawnMarkerEntity.npcReferences = array, - spawnMarkerEntity -> spawnMarkerEntity.npcReferences - ) - .addField( - new KeyedCodec<>("PersistedFlock", StoredFlock.CODEC), - (spawnMarkerEntity, o) -> spawnMarkerEntity.storedFlock = o, - spawnMarkerEntity -> spawnMarkerEntity.storedFlock - ) - .addField( - new KeyedCodec<>("SpawnPosition", Vector3d.CODEC), - (spawnMarkerEntity, v) -> spawnMarkerEntity.spawnPosition.assign(v), - spawnMarkerEntity -> spawnMarkerEntity.storedFlock == null ? null : spawnMarkerEntity.spawnPosition - ) - .build(); - private static final int MAX_FAILED_SPAWNS = 5; - private String spawnMarkerId; - private SpawnMarker cachedMarker; - private double respawnCounter; - @Nullable - private Duration gameTimeRespawn; - @Nullable - private Instant spawnAfter; - private int spawnCount; - @Nullable - private Set suppressedBy; - private int failedSpawns; - @Nonnull - private final SpawningContext context; - private final Vector3d spawnPosition = new Vector3d(); - private InvalidatablePersistentRef[] npcReferences; - @Nullable - private StoredFlock storedFlock; - @Nullable - private List, NPCEntity>> tempStorageList; - private double timeToDeactivation; - private boolean despawnStarted; - private double spawnLostTimeoutCounter; - - public static ComponentType getComponentType() { - return SpawningPlugin.get().getSpawnMarkerComponentType(); - } - - public SpawnMarkerEntity() { - this.context = new SpawningContext(); - this.npcReferences = EMPTY_REFERENCES; - } - - public SpawnMarker getCachedMarker() { - return this.cachedMarker; - } - - public void setCachedMarker(@Nonnull SpawnMarker marker) { - this.cachedMarker = marker; - } - - public int getSpawnCount() { - return this.spawnCount; - } - - public void setSpawnCount(int spawnCount) { - this.spawnCount = spawnCount; - } - - public void setRespawnCounter(double respawnCounter) { - this.respawnCounter = respawnCounter; - } - - public void setSpawnAfter(@Nullable Instant spawnAfter) { - this.spawnAfter = spawnAfter; - } - - @Nullable - public Instant getSpawnAfter() { - return this.spawnAfter; - } - - public void setGameTimeRespawn(@Nullable Duration gameTimeRespawn) { - this.gameTimeRespawn = gameTimeRespawn; - } - - @Nullable - public Duration pollGameTimeRespawn() { - Duration ret = this.gameTimeRespawn; - this.gameTimeRespawn = null; - return ret; - } - - public boolean tickRespawnTimer(float dt) { - return (this.respawnCounter -= dt) <= 0.0; - } - - @Nullable - public Set getSuppressedBy() { - return this.suppressedBy; - } - - public void setStoredFlock(@Nonnull StoredFlock storedFlock) { - this.storedFlock = storedFlock; - } - - @Nullable - public StoredFlock getStoredFlock() { - return this.storedFlock; - } - - public double getTimeToDeactivation() { - return this.timeToDeactivation; - } - - public void setTimeToDeactivation(double timeToDeactivation) { - this.timeToDeactivation = timeToDeactivation; - } - - public boolean tickTimeToDeactivation(float dt) { - return (this.timeToDeactivation -= dt) <= 0.0; - } - - public boolean tickSpawnLostTimeout(float dt) { - return (this.spawnLostTimeoutCounter -= dt) <= 0.0; - } - - @Nonnull - public Vector3d getSpawnPosition() { - return this.spawnPosition; - } - - public InvalidatablePersistentRef[] getNpcReferences() { - return this.npcReferences; - } - - public void setNpcReferences(@Nullable InvalidatablePersistentRef[] npcReferences) { - this.npcReferences = npcReferences != null ? npcReferences : EMPTY_REFERENCES; - } - - @Nullable - public List, NPCEntity>> getTempStorageList() { - return this.tempStorageList; - } - - public void setTempStorageList(@Nonnull List, NPCEntity>> tempStorageList) { - this.tempStorageList = tempStorageList; - } - - public boolean isDespawnStarted() { - return this.despawnStarted; - } - - public void setDespawnStarted(boolean despawnStarted) { - this.despawnStarted = despawnStarted; - } - - public void refreshTimeout() { - this.spawnLostTimeoutCounter = 35.0; - } - - public boolean spawnNPC(@Nonnull Ref ref, @Nonnull SpawnMarker marker, @Nonnull Store store) { - SpawnMarker.SpawnConfiguration spawn = marker.getWeightedConfigurations().get(ThreadLocalRandom.current()); - if (spawn == null) { - SpawningPlugin.get().getLogger().at(Level.SEVERE).log("Marker %s has no spawn configuration to spawn", ref); - this.refreshTimeout(); - return false; - } else { - boolean realtime = marker.isRealtimeRespawn(); - if (realtime) { - this.respawnCounter = spawn.getRealtimeRespawnTime(); - } else { - this.spawnAfter = null; - this.gameTimeRespawn = spawn.getSpawnAfterGameTime(); - } - - UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); - - assert uuidComponent != null; - - UUID uuid = uuidComponent.getUuid(); - String roleName = spawn.getNpc(); - if (roleName != null && !roleName.isEmpty()) { - NPCPlugin npcModule = NPCPlugin.get(); - int roleIndex = npcModule.getIndex(roleName); - TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); - - assert transformComponent != null; - - Vector3d position = transformComponent.getPosition(); - BuilderInfo builderInfo = npcModule.getRoleBuilderInfo(roleIndex); - if (builderInfo == null) { - SpawningPlugin.get().getLogger().at(Level.SEVERE).log("Marker %s attempted to spawn non-existent NPC role '%s'", uuid, roleName); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.NONEXISTENT_ROLE); - return false; - } else { - Builder role = builderInfo.isValid() ? builderInfo.getBuilder() : null; - if (role == null) { - SpawningPlugin.get().getLogger().at(Level.SEVERE).log("Marker %s attempted to spawn invalid NPC role '%s'", uuid, roleName); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.INVALID_ROLE); - return false; - } else if (!role.isSpawnable()) { - SpawningPlugin.get().getLogger().at(Level.SEVERE).log("Marker %s attempted to spawn a non-spawnable (abstract) role '%s'", uuid, roleName); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.INVALID_ROLE); - return false; - } else if (!this.context.setSpawnable((ISpawnableWithModel) role)) { - SpawningPlugin.get() - .getLogger() - .at(Level.SEVERE) - .log("Marker %s failed to spawn NPC role '%s' due to failed role validation", uuid, roleName); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.FAILED_ROLE_VALIDATION); - return false; - } else { - ObjectList> results = SpatialResource.getThreadLocalReferenceList(); - SpatialResource, EntityStore> spatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); - spatialResource.getSpatialStructure().collect(position, marker.getExclusionRadius(), results); - boolean hasPlayersInRange = !results.isEmpty(); - if (hasPlayersInRange) { - this.refreshTimeout(); - return false; - } else { - World world = store.getExternalData().getWorld(); - if (!this.context.set(world, position.x, position.y, position.z)) { - SpawningPlugin.get() - .getLogger() - .at(Level.FINE) - .log("Marker %s attempted to spawn NPC '%s' at %s but could not fit", uuid, roleName, position); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.NO_ROOM); - return false; - } else { - SpawnTestResult testResult = this.context.canSpawn(true, false); - if (testResult != SpawnTestResult.TEST_OK) { - SpawningPlugin.get() - .getLogger() - .at(Level.FINE) - .log("Marker %s attempted to spawn NPC '%s' at %s but could not fit: %s", uuid, roleName, position, testResult); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.NO_ROOM); - return false; - } else { - this.spawnPosition.assign(this.context.xSpawn, this.context.ySpawn, this.context.zSpawn); - if (this.spawnPosition.distanceSquaredTo(position) > marker.getMaxDropHeightSquared()) { - SpawningPlugin.get() - .getLogger() - .at(Level.FINE) - .log("Marker %s attempted to spawn NPC '%s' but was offset too far from the ground at %s", uuid, roleName, position); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.TOO_HIGH); - return false; - } else { - TriConsumer, Store> postSpawn = (_entity, _ref, _store) -> { - SpawnMarkerReference spawnMarkerReference = _store.ensureAndGetComponent(_ref, SpawnMarkerReference.getComponentType()); - spawnMarkerReference.getReference().setEntity(ref, _store); - spawnMarkerReference.refreshTimeoutCounter(); - WorldGenId worldGenIdComponent = _store.getComponent(ref, WorldGenId.getComponentType()); - int worldGenId = worldGenIdComponent != null ? worldGenIdComponent.getWorldGenId() : 0; - _store.putComponent(_ref, WorldGenId.getComponentType(), new WorldGenId(worldGenId)); - }; - Vector3f rotation = transformComponent.getRotation(); - Pair, NPCEntity> npcPair = npcModule.spawnEntity(store, roleIndex, this.spawnPosition, rotation, null, postSpawn); - if (npcPair == null) { - SpawningPlugin.get() - .getLogger() - .at(Level.SEVERE) - .log("Marker %s failed to spawn NPC role '%s' due to an internal error", uuid, roleName); - this.fail(ref, uuid, roleName, position, store, SpawnMarkerEntity.FailReason.INVALID_ROLE); - return false; - } else { - Ref npcRef = npcPair.first(); - NPCEntity npcComponent = npcPair.second(); - Ref flockReference = FlockPlugin.trySpawnFlock( - npcRef, npcComponent, store, roleIndex, this.spawnPosition, rotation, spawn.getFlockDefinition(), postSpawn - ); - EntityGroup group = flockReference == null ? null : store.getComponent(flockReference, EntityGroup.getComponentType()); - this.spawnCount = group != null ? group.size() : 1; - if (this.storedFlock != null) { - this.despawnStarted = false; - this.npcReferences = new InvalidatablePersistentRef[this.spawnCount]; - if (group != null) { - group.forEachMember((index, member, referenceArray) -> { - InvalidatablePersistentRef referencex = new InvalidatablePersistentRef(); - referencex.setEntity(member, store); - referenceArray[index] = referencex; - }, this.npcReferences); - } else { - InvalidatablePersistentRef reference = new InvalidatablePersistentRef(); - reference.setEntity(npcRef, store); - this.npcReferences[0] = reference; - } - - this.storedFlock.clear(); - } - - SpawningPlugin.get() - .getLogger() - .at(Level.FINE) - .log( - "Marker %s spawned %s and set respawn to %s", - uuid, - npcComponent.getRoleName(), - realtime ? this.respawnCounter : this.gameTimeRespawn - ); - this.refreshTimeout(); - return true; - } - } - } - } - } - } - } - } else { - SpawningPlugin.get() - .getLogger() - .at(Level.FINE) - .log("Marker %s performed noop spawn and set repawn to %s", uuid, realtime ? this.respawnCounter : this.gameTimeRespawn); - this.refreshTimeout(); - return true; - } - } - } - - private void fail( - @Nonnull Ref self, - @Nonnull UUID uuid, - @Nonnull String role, - @Nonnull Vector3d position, - @Nonnull Store store, - @Nonnull SpawnMarkerEntity.FailReason reason - ) { - if (++this.failedSpawns >= 5) { - SpawningPlugin.get() - .getLogger() - .at(Level.WARNING) - .log("Marker %s at %s removed due to repeated spawning fails of %s with reason: %s", uuid, position, role, reason); - store.removeEntity(self, RemoveReason.REMOVE); - } else { - this.refreshTimeout(); - } - } - - public void setSpawnMarker(@Nonnull SpawnMarker marker) { - this.spawnMarkerId = marker.getId(); - this.cachedMarker = marker; - if (this.cachedMarker.getDeactivationDistance() > 0.0) { - this.storedFlock = new StoredFlock(); - this.tempStorageList = new ObjectArrayList<>(); - } else { - this.storedFlock = null; - this.tempStorageList = null; - } - } - - public int decrementAndGetSpawnCount() { - return --this.spawnCount; - } - - public String getSpawnMarkerId() { - return this.spawnMarkerId; - } - - public boolean isManualTrigger() { - return this.cachedMarker.isManualTrigger(); - } - - public boolean trigger(@Nonnull Ref markerRef, @Nonnull Store store) { - return this.cachedMarker.isManualTrigger() && this.spawnCount <= 0 ? this.spawnNPC(markerRef, this.cachedMarker, store) : false; - } - - public void suppress(@Nonnull UUID suppressor) { - if (this.suppressedBy == null) { - this.suppressedBy = new HashSet<>(); - } - - this.suppressedBy.add(suppressor); - } - - public void releaseSuppression(@Nonnull UUID suppressor) { - if (this.suppressedBy != null) { - this.suppressedBy.remove(suppressor); - } - } - - public void clearAllSuppressions() { - if (this.suppressedBy != null) { - this.suppressedBy.clear(); - } - } - - @Nonnull - @Override - public Component clone() { - SpawnMarkerEntity spawnMarker = new SpawnMarkerEntity(); - spawnMarker.spawnMarkerId = this.spawnMarkerId; - spawnMarker.cachedMarker = this.cachedMarker; - spawnMarker.respawnCounter = this.respawnCounter; - spawnMarker.gameTimeRespawn = this.gameTimeRespawn; - spawnMarker.spawnAfter = this.spawnAfter; - spawnMarker.spawnCount = this.spawnCount; - spawnMarker.suppressedBy = this.suppressedBy != null ? new HashSet<>(this.suppressedBy) : null; - spawnMarker.failedSpawns = this.failedSpawns; - spawnMarker.spawnPosition.assign(this.spawnPosition); - spawnMarker.npcReferences = this.npcReferences; - spawnMarker.storedFlock = this.storedFlock != null ? this.storedFlock.clone() : null; - spawnMarker.timeToDeactivation = this.timeToDeactivation; - spawnMarker.despawnStarted = this.despawnStarted; - spawnMarker.spawnLostTimeoutCounter = this.spawnLostTimeoutCounter; - return spawnMarker; - } - - @Nonnull - @Override - public String toString() { - return "SpawnMarkerEntity{spawnMarkerId='" - + this.spawnMarkerId - + "', cachedMarker=" - + this.cachedMarker - + ", respawnCounter=" - + this.respawnCounter - + ", gameTimeRespawn=" - + this.gameTimeRespawn - + ", spawnAfter=" - + this.spawnAfter - + ", spawnCount=" - + this.spawnCount - + ", spawnLostTimeoutCounter=" - + this.spawnLostTimeoutCounter - + ", failedSpawns=" - + this.failedSpawns - + ", context=" - + this.context - + ", spawnPosition=" - + this.spawnPosition - + ", storedFlock=" - + this.storedFlock - + "} " - + super.toString(); - } - - public static Model getModel(@Nonnull SpawnMarker marker) { - String modelName = marker.getModel(); - ModelAsset modelAsset = null; - if (modelName != null && !modelName.isEmpty()) { - modelAsset = ModelAsset.getAssetMap().getAsset(modelName); - } - - Model model; - if (modelAsset == null) { - model = SpawningPlugin.get().getSpawnMarkerModel(); - } else { - model = Model.createUnitScaleModel(modelAsset); - } - - return model; - } - - private static enum FailReason { - INVALID_ROLE, - NONEXISTENT_ROLE, - FAILED_ROLE_VALIDATION, - NO_ROOM, - TOO_HIGH; - - private FailReason() { - } - } -} diff --git a/src/com/hypixel/hytale/storage/IndexedStorageFile.java b/src/com/hypixel/hytale/storage/IndexedStorageFile.java deleted file mode 100644 index 77e4b6e9..00000000 --- a/src/com/hypixel/hytale/storage/IndexedStorageFile.java +++ /dev/null @@ -1,912 +0,0 @@ -package com.hypixel.hytale.storage; - -import com.github.luben.zstd.Zstd; -import com.hypixel.hytale.codec.Codec; -import com.hypixel.hytale.metrics.MetricsRegistry; -import com.hypixel.hytale.unsafe.UnsafeUtil; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.MapMode; -import java.nio.channels.FileLock; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.FileAttribute; -import java.util.Arrays; -import java.util.BitSet; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.locks.StampedLock; - -public class IndexedStorageFile implements Closeable { - public static final StampedLock[] EMPTY_STAMPED_LOCKS = new StampedLock[0]; - public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() - .register("Size", file -> { - try { - return file.size(); - } catch (IOException var2) { - return -1L; - } - }, Codec.LONG) - .register("CompressionLevel", file -> file.getCompressionLevel(), Codec.INTEGER) - .register("BlobCount", file -> file.getBlobCount(), Codec.INTEGER) - .register("UsedBlobCount", file -> file.keys().size(), Codec.INTEGER) - .register("SegmentSize", file -> file.segmentSize(), Codec.INTEGER) - .register("SegmentCount", file -> file.segmentCount(), Codec.INTEGER); - public static final String MAGIC_STRING = "HytaleIndexedStorage"; - public static final int VERSION = 1; - public static final int DEFAULT_BLOB_COUNT = 1024; - public static final int DEFAULT_SEGMENT_SIZE = 4096; - public static final int DEFAULT_COMPRESSION_LEVEL = 3; - static final IndexedStorageFile.OffsetHelper HOH = new IndexedStorageFile.OffsetHelper(); - public static final int MAGIC_LENGTH = 20; - public static final int MAGIC_OFFSET = HOH.next(20); - public static final int VERSION_OFFSET = HOH.next(4); - public static final int BLOB_COUNT_OFFSET = HOH.next(4); - public static final int SEGMENT_SIZE_OFFSET = HOH.next(4); - public static final int HEADER_LENGTH = HOH.length(); - static final IndexedStorageFile.OffsetHelper BOH = new IndexedStorageFile.OffsetHelper(); - public static final int SRC_LENGTH_OFFSET = BOH.next(4); - public static final int COMPRESSED_LENGTH_OFFSET = BOH.next(4); - public static final int BLOB_HEADER_LENGTH = BOH.length(); - public static final int INDEX_SIZE = 4; - public static final int UNASSIGNED_INDEX = 0; - public static final int FIRST_SEGMENT_INDEX = 1; - public static final FileAttribute[] NO_ATTRIBUTES = new FileAttribute[0]; - static final byte[] MAGIC_BYTES = "HytaleIndexedStorage".getBytes(StandardCharsets.UTF_8); - private static final ByteBuffer MAGIC_BUFFER = ByteBuffer.wrap(MAGIC_BYTES); - private static final ThreadLocal CACHED_TEMP_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(HEADER_LENGTH)); - @Nonnull - private final Path path; - private final FileChannel fileChannel; - private boolean flushOnWrite = false; - private int compressionLevel = 3; - private int version; - private int blobCount; - private int segmentSize; - private StampedLock[] indexLocks; - private MappedByteBuffer mappedBlobIndexes; - private final StampedLock segmentLocksLock = new StampedLock(); - private volatile StampedLock[] segmentLocks = EMPTY_STAMPED_LOCKS; - private final StampedLock usedSegmentsLock = new StampedLock(); - private final BitSet usedSegments = new BitSet(); - - @Nonnull - private static ByteBuffer getTempBuffer(int length) { - ByteBuffer buffer = CACHED_TEMP_BUFFER.get(); - buffer.position(0); - buffer.limit(length); - return buffer; - } - - @Nonnull - private static ByteBuffer allocateDirect(int length) { - return ByteBuffer.allocateDirect(length); - } - - @Nonnull - public static IndexedStorageFile open(@Nonnull Path path, OpenOption... options) throws IOException { - return open(path, 1024, 4096, Set.of(options), NO_ATTRIBUTES); - } - - @Nonnull - public static IndexedStorageFile open(@Nonnull Path path, @Nonnull Set options, FileAttribute... attrs) throws IOException { - return open(path, 1024, 4096, options, attrs); - } - - @Nonnull - public static IndexedStorageFile open(@Nonnull Path path, int blobCount, int segmentSize, OpenOption... options) throws IOException { - return open(path, blobCount, segmentSize, Set.of(options), NO_ATTRIBUTES); - } - - @Nonnull - public static IndexedStorageFile open( - @Nonnull Path path, int blobCount, int segmentSize, @Nonnull Set options, FileAttribute... attrs - ) throws IOException { - IndexedStorageFile storageFile = new IndexedStorageFile(path, FileChannel.open(path, options, attrs)); - if (options.contains(StandardOpenOption.CREATE_NEW)) { - storageFile.create(blobCount, segmentSize); - return storageFile; - } else { - if (options.contains(StandardOpenOption.CREATE) && storageFile.fileChannel.size() == 0L) { - storageFile.create(blobCount, segmentSize); - } else { - if (storageFile.fileChannel.size() == 0L) { - throw new IOException("file channel is empty"); - } - - try { - storageFile.readHeader(); - storageFile.memoryMapBlobIndexes(); - if (storageFile.version == 0) { - storageFile = migrateV0(path, blobCount, segmentSize, options, attrs, storageFile); - } else { - storageFile.readUsedSegments(); - } - } catch (CorruptedStorageException e) { - // Corruption detected - delete and recreate the file - System.err.println("[IndexedStorageFile] Corruption detected in " + path + ": " + e.getMessage() + " - deleting and recreating"); - storageFile.close(); - Files.delete(path); - storageFile = new IndexedStorageFile(path, FileChannel.open(path, options, attrs)); - storageFile.create(blobCount, segmentSize); - } - } - - return storageFile; - } - } - - /** - * Exception thrown when storage file corruption is detected. - */ - public static class CorruptedStorageException extends IOException { - public CorruptedStorageException(String message) { - super(message); - } - } - - private static IndexedStorageFile migrateV0( - Path path, int blobCount, int segmentSize, Set options, FileAttribute[] attrs, IndexedStorageFile storageFile - ) throws IOException { - storageFile.close(); - Path tempFile = path.resolveSibling(path.getFileName().toString() + ".old"); - Path tempPath = Files.move(path, tempFile, StandardCopyOption.REPLACE_EXISTING); - HashSet newOptions = new HashSet<>(options); - newOptions.add(StandardOpenOption.CREATE); - storageFile = new IndexedStorageFile(path, FileChannel.open(path, newOptions, attrs)); - storageFile.create(blobCount, segmentSize); - - try (IndexedStorageFile_v0 oldStorageFile = new IndexedStorageFile_v0(tempPath, FileChannel.open(tempPath, options, attrs))) { - oldStorageFile.open(); - - for (int blobIndex = 0; blobIndex < blobCount; blobIndex++) { - ByteBuffer blob = oldStorageFile.readBlob(blobIndex); - if (blob != null) { - storageFile.writeBlob(blobIndex, blob); - } - } - } finally { - Files.delete(tempFile); - } - - return storageFile; - } - - private IndexedStorageFile(@Nonnull Path path, @Nonnull FileChannel fileChannel) { - this.path = path; - this.fileChannel = fileChannel; - } - - @Nonnull - public Path getPath() { - return this.path; - } - - public int getBlobCount() { - return this.blobCount; - } - - public int getSegmentSize() { - return this.segmentSize; - } - - public int getCompressionLevel() { - return this.compressionLevel; - } - - public void setFlushOnWrite(boolean flushOnWrite) { - this.flushOnWrite = flushOnWrite; - } - - public void setCompressionLevel(int compressionLevel) { - this.compressionLevel = compressionLevel; - } - - @Nonnull - protected IndexedStorageFile create(int blobCount, int segmentSize) throws IOException { - if (blobCount <= 0) { - throw new IllegalArgumentException("blobCount must be > 0"); - } else if (segmentSize <= 0) { - throw new IllegalArgumentException("segmentSize must be > 0"); - } else { - this.blobCount = blobCount; - this.segmentSize = segmentSize; - if (this.fileChannel.size() != 0L) { - throw new IOException("file channel is not empty"); - } else { - this.writeHeader(blobCount, segmentSize); - this.memoryMapBlobIndexes(); - return this; - } - } - } - - protected void writeHeader(int blobCount, int segmentSize) throws IOException { - ByteBuffer header = getTempBuffer(HEADER_LENGTH); - header.put(MAGIC_BYTES); - header.putInt(VERSION_OFFSET, 1); - header.putInt(BLOB_COUNT_OFFSET, blobCount); - header.putInt(SEGMENT_SIZE_OFFSET, segmentSize); - header.position(0); - if (this.fileChannel.write(header, 0L) != HEADER_LENGTH) { - throw new IllegalStateException(); - } - } - - protected void readHeader() throws IOException { - ByteBuffer header = getTempBuffer(HEADER_LENGTH); - if (this.fileChannel.read(header, 0L) != HEADER_LENGTH) { - throw new IllegalStateException(); - } else { - header.position(0); - header.limit(20); - if (!MAGIC_BUFFER.equals(header)) { - header.position(0); - byte[] dst = new byte[20]; - header.get(dst); - throw new IOException("Invalid MAGIC! " + header + ", " + Arrays.toString(dst) + " expected " + Arrays.toString(MAGIC_BYTES)); - } else { - header.limit(HEADER_LENGTH); - this.version = header.getInt(VERSION_OFFSET); - if (this.version >= 0 && this.version <= 1) { - this.blobCount = header.getInt(BLOB_COUNT_OFFSET); - this.segmentSize = header.getInt(SEGMENT_SIZE_OFFSET); - } else { - throw new IOException("Invalid version! " + this.version); - } - } - } - } - - protected void memoryMapBlobIndexes() throws IOException { - this.indexLocks = new StampedLock[this.blobCount]; - - for (int i = 0; i < this.blobCount; i++) { - this.indexLocks[i] = new StampedLock(); - } - - this.mappedBlobIndexes = this.fileChannel.map(MapMode.READ_WRITE, HEADER_LENGTH, this.blobCount * 4L); - } - - private static final int MAX_COMPRESSED_LENGTH_LIMIT = 256 * 1024 * 1024; // 256MB - private static final int MAX_SEGMENT_INDEX = 10_000_000; // ~40GB at 4KB segments - - protected void readUsedSegments() throws IOException { - long stamp = this.usedSegmentsLock.writeLock(); - - try { - for (int blobIndex = 0; blobIndex < this.blobCount; blobIndex++) { - int indexPos = blobIndex * 4; - long segmentStamp = this.indexLocks[blobIndex].readLock(); - - int firstSegmentIndex; - int compressedLength; - try { - firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (firstSegmentIndex == 0) { - compressedLength = 0; - } else if (firstSegmentIndex < 0 || firstSegmentIndex > MAX_SEGMENT_INDEX) { - // Corrupted segment index - file is corrupt - throw new CorruptedStorageException( - "Invalid segment index " + firstSegmentIndex + " for blob " + blobIndex); - } else { - ByteBuffer blobHeaderBuffer = this.readBlobHeader(firstSegmentIndex); - compressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET); - if (compressedLength < 0 || compressedLength > MAX_COMPRESSED_LENGTH_LIMIT) { - throw new CorruptedStorageException( - "Invalid compressed length " + compressedLength + " for blob " + blobIndex); - } - } - } finally { - this.indexLocks[blobIndex].unlockRead(segmentStamp); - } - - if (compressedLength > 0 && firstSegmentIndex > 0) { - int segmentsCount = this.requiredSegments(BLOB_HEADER_LENGTH + compressedLength); - int toIndex = firstSegmentIndex + segmentsCount; - if (segmentsCount > 0 && toIndex > firstSegmentIndex) { - this.usedSegments.set(firstSegmentIndex, toIndex); - } - } - } - } finally { - this.usedSegmentsLock.unlockWrite(stamp); - } - } - - public long size() throws IOException { - return this.fileChannel.size(); - } - - public int segmentSize() { - try { - return this.requiredSegments(this.fileChannel.size() - this.segmentsBase()) + 1; - } catch (IOException var2) { - return -1; - } - } - - public int segmentCount() { - long stamp = this.usedSegmentsLock.tryOptimisticRead(); - int count = this.usedSegments.cardinality(); - if (this.usedSegmentsLock.validate(stamp)) { - return count; - } else { - stamp = this.usedSegmentsLock.readLock(); - - int var4; - try { - var4 = this.usedSegments.cardinality(); - } finally { - this.usedSegmentsLock.unlockRead(stamp); - } - - return var4; - } - } - - @Nonnull - public IntList keys() { - IntArrayList list = new IntArrayList(this.blobCount); - - for (int blobIndex = 0; blobIndex < this.blobCount; blobIndex++) { - int indexPos = blobIndex * 4; - StampedLock lock = this.indexLocks[blobIndex]; - long stamp = lock.tryOptimisticRead(); - int segmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (lock.validate(stamp)) { - if (segmentIndex != 0) { - list.add(blobIndex); - } - } else { - stamp = lock.readLock(); - - try { - if (this.mappedBlobIndexes.getInt(indexPos) != 0) { - list.add(blobIndex); - } - } finally { - lock.unlockRead(stamp); - } - } - } - - return list; - } - - public int readBlobLength(int blobIndex) throws IOException { - if (blobIndex >= 0 && blobIndex < this.blobCount) { - int indexPos = blobIndex * 4; - long stamp = this.indexLocks[blobIndex].readLock(); - - byte blobHeaderBuffer; - try { - int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (firstSegmentIndex != 0) { - ByteBuffer blobHeaderBufferx = this.readBlobHeader(firstSegmentIndex); - return blobHeaderBufferx.getInt(SRC_LENGTH_OFFSET); - } - - blobHeaderBuffer = 0; - } finally { - this.indexLocks[blobIndex].unlockRead(stamp); - } - - return blobHeaderBuffer; - } else { - throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount); - } - } - - public int readBlobCompressedLength(int blobIndex) throws IOException { - if (blobIndex >= 0 && blobIndex < this.blobCount) { - int indexPos = blobIndex * 4; - long stamp = this.indexLocks[blobIndex].readLock(); - - byte blobHeaderBuffer; - try { - int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (firstSegmentIndex != 0) { - ByteBuffer blobHeaderBufferx = this.readBlobHeader(firstSegmentIndex); - return blobHeaderBufferx.getInt(COMPRESSED_LENGTH_OFFSET); - } - - blobHeaderBuffer = 0; - } finally { - this.indexLocks[blobIndex].unlockRead(stamp); - } - - return blobHeaderBuffer; - } else { - throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount); - } - } - - @Nullable - public ByteBuffer readBlob(int blobIndex) throws IOException { - if (blobIndex >= 0 && blobIndex < this.blobCount) { - int indexPos = blobIndex * 4; - long stamp = this.indexLocks[blobIndex].readLock(); - - ByteBuffer src; - int srcLength; - label43: - { - ByteBuffer blobHeaderBuffer; - try { - int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (firstSegmentIndex > 0) { // Changed from != 0 to > 0 to reject negative indices - blobHeaderBuffer = this.readBlobHeader(firstSegmentIndex); - srcLength = blobHeaderBuffer.getInt(SRC_LENGTH_OFFSET); - int compressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET); - // Handle empty chunks (newly generated with no data) - if (compressedLength == 0 && srcLength == 0) { - return allocateDirect(0); - } - src = this.readSegments(firstSegmentIndex, compressedLength); - break label43; - } - - blobHeaderBuffer = null; - } finally { - this.indexLocks[blobIndex].unlockRead(stamp); - } - - return blobHeaderBuffer; - } - - src.position(0); - return Zstd.decompress(src, srcLength); - } else { - throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount); - } - } - - public void readBlob(int blobIndex, @Nonnull ByteBuffer dest) throws IOException { - if (blobIndex >= 0 && blobIndex < this.blobCount) { - int indexPos = blobIndex * 4; - long stamp = this.indexLocks[blobIndex].readLock(); - - ByteBuffer src; - int srcLength; - try { - int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (firstSegmentIndex <= 0) { // Changed from == 0 to <= 0 to reject negative indices - return; - } - - ByteBuffer blobHeaderBuffer = this.readBlobHeader(firstSegmentIndex); - srcLength = blobHeaderBuffer.getInt(SRC_LENGTH_OFFSET); - int compressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET); - // Handle empty chunks (newly generated with no data) - if (compressedLength == 0 && srcLength == 0) { - return; - } - if (srcLength > dest.remaining()) { - throw new IllegalArgumentException("dest buffer is not large enough! required dest.remaining() >= " + srcLength); - } - - src = this.readSegments(firstSegmentIndex, compressedLength); - } finally { - this.indexLocks[blobIndex].unlockRead(stamp); - } - - src.position(0); - if (dest.isDirect()) { - Zstd.decompress(dest, src); - } else { - ByteBuffer tempDest = allocateDirect(srcLength); - - try { - Zstd.decompress(tempDest, src); - tempDest.position(0); - dest.put(tempDest); - } finally { - if (UnsafeUtil.UNSAFE != null) { - UnsafeUtil.UNSAFE.invokeCleaner(tempDest); - } - } - } - } else { - throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount); - } - } - - @Nonnull - protected ByteBuffer readBlobHeader(int firstSegmentIndex) throws IOException { - if (firstSegmentIndex == 0) { - throw new IllegalArgumentException("Invalid segment index!"); - } else { - ByteBuffer blobHeaderBuffer = getTempBuffer(BLOB_HEADER_LENGTH); - if (this.fileChannel.read(blobHeaderBuffer, this.segmentPosition(firstSegmentIndex)) != BLOB_HEADER_LENGTH) { - throw new IllegalStateException(); - } else { - return blobHeaderBuffer; - } - } - } - - @Nonnull - protected ByteBuffer readSegments(int firstSegmentIndex, int compressedLength) throws IOException { - if (compressedLength <= 0 || compressedLength > MAX_COMPRESSED_LENGTH_LIMIT) { - throw new IOException("Invalid compressed length: " + compressedLength); - } - if (firstSegmentIndex <= 0) { - throw new IOException("Invalid segment index: " + firstSegmentIndex); - } - ByteBuffer buffer = allocateDirect(compressedLength); - long segmentPosition = this.segmentPosition(firstSegmentIndex); - if (this.fileChannel.read(buffer, segmentPosition + BLOB_HEADER_LENGTH) != compressedLength) { - throw new IllegalStateException(); - } else if (buffer.remaining() != 0) { - throw new IOException("Failed to read segments: " + firstSegmentIndex + ", " + compressedLength + ", " + buffer); - } else { - return buffer; - } - } - - public void writeBlob(int blobIndex, @Nonnull ByteBuffer src) throws IOException { - if (blobIndex >= 0 && blobIndex < this.blobCount) { - int srcLength = src.remaining(); - int maxCompressedLength = (int) Zstd.compressBound(srcLength); - ByteBuffer dest = allocateDirect(BLOB_HEADER_LENGTH + maxCompressedLength); - dest.putInt(SRC_LENGTH_OFFSET, srcLength); - dest.position(BLOB_HEADER_LENGTH); - int compressedLength; - if (src.isDirect()) { - compressedLength = Zstd.compress(dest, src, this.compressionLevel); - } else { - ByteBuffer tempSrc = allocateDirect(srcLength); - - try { - tempSrc.put(src); - tempSrc.position(0); - compressedLength = Zstd.compress(dest, tempSrc, this.compressionLevel); - } finally { - if (UnsafeUtil.UNSAFE != null) { - UnsafeUtil.UNSAFE.invokeCleaner(tempSrc); - } - } - } - - dest.putInt(COMPRESSED_LENGTH_OFFSET, compressedLength); - dest.limit(dest.position()); - dest.position(0); - int indexPos = blobIndex * 4; - long stamp = this.indexLocks[blobIndex].writeLock(); - - try { - int oldSegmentLength = 0; - int oldFirstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (oldFirstSegmentIndex != 0) { - try { - ByteBuffer blobHeaderBuffer = this.readBlobHeader(oldFirstSegmentIndex); - int oldCompressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET); - // Validate to prevent corruption from causing invalid segment ranges - if (oldCompressedLength >= 0) { - oldSegmentLength = this.requiredSegments(BLOB_HEADER_LENGTH + oldCompressedLength); - } - } catch (Exception e) { - // Old blob header is corrupted/unreadable - skip clearing old segments - // This leaks segments but prevents corruption from propagating - oldSegmentLength = 0; - } - } - - int firstSegmentIndex = this.writeSegments(dest); - if (this.flushOnWrite) { - this.fileChannel.force(false); - } - - this.mappedBlobIndexes.putInt(indexPos, firstSegmentIndex); - if (this.flushOnWrite) { - this.mappedBlobIndexes.force(indexPos, 4); - } - - if (oldSegmentLength > 0 && oldFirstSegmentIndex > 0) { - int toIndex = oldFirstSegmentIndex + oldSegmentLength; - // Validate range to prevent IndexOutOfBoundsException - if (toIndex > oldFirstSegmentIndex) { - long usedSegmentsStamp = this.usedSegmentsLock.writeLock(); - - try { - this.usedSegments.clear(oldFirstSegmentIndex, toIndex); - } finally { - this.usedSegmentsLock.unlockWrite(usedSegmentsStamp); - } - } - } - } finally { - this.indexLocks[blobIndex].unlockWrite(stamp); - } - } else { - throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount); - } - } - - public void removeBlob(int blobIndex) throws IOException { - if (blobIndex >= 0 && blobIndex < this.blobCount) { - int indexPos = blobIndex * 4; - long stamp = this.indexLocks[blobIndex].writeLock(); - - try { - int oldFirstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos); - if (oldFirstSegmentIndex != 0) { - // Clear the index first - this ensures the blob is marked as removed - // even if we can't clear its segments due to corruption - this.mappedBlobIndexes.putInt(indexPos, 0); - if (this.flushOnWrite) { - this.mappedBlobIndexes.force(indexPos, 4); - } - - // Try to clear the segments, but handle corrupted blob headers gracefully - try { - ByteBuffer blobHeaderBuffer = this.readBlobHeader(oldFirstSegmentIndex); - int oldCompressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET); - - // Validate values to prevent IndexOutOfBoundsException from corrupted data - if (oldCompressedLength >= 0 && oldFirstSegmentIndex > 0) { - int oldSegmentLength = this.requiredSegments(BLOB_HEADER_LENGTH + oldCompressedLength); - int toIndex = oldFirstSegmentIndex + oldSegmentLength; - - // Only clear if the range is valid - if (oldSegmentLength > 0 && toIndex > oldFirstSegmentIndex) { - long usedSegmentsStamp = this.usedSegmentsLock.writeLock(); - try { - this.usedSegments.clear(oldFirstSegmentIndex, toIndex); - } finally { - this.usedSegmentsLock.unlockWrite(usedSegmentsStamp); - } - } - } - // If values are invalid (corrupted blob header), we skip clearing segments - // The segments will be leaked but the blob index is cleared, preventing further errors - } catch (Exception e) { - // Blob header is unreadable/corrupted - segments will be leaked but - // the blob index has already been cleared, so the corrupted data won't cause issues - } - } - } finally { - this.indexLocks[blobIndex].unlockWrite(stamp); - } - } else { - throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount); - } - } - - protected int writeSegments(@Nonnull ByteBuffer data) throws IOException { - int dataRemaining = data.remaining(); - int segmentsCount = this.requiredSegments(dataRemaining); - IndexedStorageFile.SegmentRangeWriteLock segmentLock = this.findFreeSegment(segmentsCount); - - int var8; - try { - int firstSegmentIndex = segmentLock.segmentIndex; - if (this.fileChannel.write(data, this.segmentPosition(firstSegmentIndex)) != dataRemaining) { - throw new IllegalStateException(); - } - - long stamp = this.usedSegmentsLock.writeLock(); - - try { - this.usedSegments.set(firstSegmentIndex, firstSegmentIndex + segmentsCount); - } finally { - this.usedSegmentsLock.unlockWrite(stamp); - } - - var8 = firstSegmentIndex; - } finally { - segmentLock.unlock(); - } - - return var8; - } - - @Nonnull - private IndexedStorageFile.SegmentRangeWriteLock findFreeSegment(int count) { - long[] stamps = new long[count]; - int index = 1; - - label98: - while (true) { - long indexesStamp = this.usedSegmentsLock.readLock(); - - try { - int start = 0; - int found = 0; - - while (found < count) { - int nextUsedIndex = this.usedSegments.nextSetBit(index); - if (nextUsedIndex < 0) { - start = index; - break; - } - - if (index == nextUsedIndex) { - start = this.usedSegments.nextClearBit(index); - nextUsedIndex = this.usedSegments.nextSetBit(start + 1); - if (nextUsedIndex < 0) { - break; - } - - found = nextUsedIndex - start; - index = nextUsedIndex + 1; - } else { - start = index; - found = nextUsedIndex - index; - index = nextUsedIndex + 1; - } - } - - for (int i = count - 1; i >= 0; i--) { - stamps[i] = this.getSegmentLock(start + i).tryWriteLock(); - if (stamps[i] == 0L) { - for (int j = count - 1; j > i; j--) { - this.getSegmentLock(start + j).unlockWrite(stamps[j]); - } - - index = start + i + 1; - continue label98; - } - } - - return new IndexedStorageFile.SegmentRangeWriteLock(start, count, stamps); - } finally { - this.usedSegmentsLock.unlockRead(indexesStamp); - } - } - } - - protected StampedLock getSegmentLock(int segmentIndex) { - // First try optimistic read - no locking overhead in common case - StampedLock[] locks = this.segmentLocks; - if (segmentIndex < locks.length) { - return locks[segmentIndex]; - } - - // Need to expand array - acquire write lock - long stamp = this.segmentLocksLock.writeLock(); - try { - // Re-check under lock - another thread may have expanded - if (segmentIndex < this.segmentLocks.length) { - return this.segmentLocks[segmentIndex]; - } - - int newLength = segmentIndex + 1; - StampedLock[] newArray = Arrays.copyOf(this.segmentLocks, newLength); - - for (int i = this.segmentLocks.length; i < newLength; i++) { - newArray[i] = new StampedLock(); - } - - this.segmentLocks = newArray; - return newArray[segmentIndex]; - } finally { - this.segmentLocksLock.unlockWrite(stamp); - } - } - - protected long segmentsBase() { - return HEADER_LENGTH + this.blobCount * 4L; - } - - protected long segmentOffset(int segmentIndex) { - if (segmentIndex == 0) { - throw new IllegalArgumentException("Invalid segment index!"); - } else { - return (long) (segmentIndex - 1) * this.segmentSize; - } - } - - protected long segmentPosition(int segmentIndex) { - return this.segmentOffset(segmentIndex) + this.segmentsBase(); - } - - protected int positionToSegment(long position) { - long segmentOffset = position - this.segmentsBase(); - if (segmentOffset < 0L) { - throw new IllegalArgumentException("position is before the segments start"); - } else { - return (int) (segmentOffset / this.segmentSize) + 1; - } - } - - protected int requiredSegments(long dataLength) { - return (int) ((dataLength + this.segmentSize - 1L) / this.segmentSize); - } - - public FileLock lock() throws IOException { - return this.fileChannel.lock(); - } - - public void force(boolean metaData) throws IOException { - this.fileChannel.force(metaData); - this.mappedBlobIndexes.force(); - } - - @Override - public void close() throws IOException { - this.fileChannel.close(); - if (UnsafeUtil.UNSAFE != null) { - UnsafeUtil.UNSAFE.invokeCleaner(this.mappedBlobIndexes); - } - - this.mappedBlobIndexes = null; - } - - @Nonnull - @Override - public String toString() { - return "IndexedStorageFile{fileChannel=" - + this.fileChannel - + ", compressionLevel=" - + this.compressionLevel - + ", blobCount=" - + this.blobCount - + ", segmentSize=" - + this.segmentSize - + ", mappedBlobIndexes=" - + this.mappedBlobIndexes - + ", usedSegments=" - + this.usedSegments - + "}"; - } - - static { - MAGIC_BUFFER.position(0); - } - - static class OffsetHelper { - private int index; - - OffsetHelper() { - } - - public int next(int len) { - int cur = this.index; - this.index += len; - return cur; - } - - public int length() { - return this.index; - } - } - - protected class SegmentRangeWriteLock { - private final int segmentIndex; - private final int count; - private final long[] stamps; - - public SegmentRangeWriteLock(int segmentIndex, int count, long[] stamps) { - if (segmentIndex == 0) { - throw new IllegalArgumentException("Invalid segment index!"); - } else if (count == 0) { - throw new IllegalArgumentException("Invalid count!"); - } else { - this.segmentIndex = segmentIndex; - this.count = count; - this.stamps = stamps; - } - } - - protected void unlock() { - for (int i = 0; i < this.count; i++) { - IndexedStorageFile.this.getSegmentLock(this.segmentIndex + i).unlockWrite(this.stamps[i]); - this.stamps[i] = 0L; - } - } - } -}