This commit is contained in:
luk
2026-02-17 17:37:51 +00:00
parent 1c2a87c680
commit 4e0b86e085
36 changed files with 585 additions and 14549 deletions

View File

@@ -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 {

View File

@@ -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<SetMemoriesCapacityInteraction> CODEC = BuilderCodec.builder(
SetMemoriesCapacityInteraction.class, SetMemoriesCapacityInteraction::new, SimpleInstantInteraction.CODEC
)
.documentation("Sets how many memories a player can store.")
.<Integer>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<EntityStore> ref = context.getEntity();
CommandBuffer<EntityStore> 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();
}
}

View File

@@ -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<GatherObjectiveTask> 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<EntityStore> store) {
Set<UUID> 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<EntityStore> 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<UUID> 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<UUID> participatingPlayers, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
int count = 0;
BlockTagOrItemIdField blockTypeOrSet = this.getAsset().getBlockTagOrItemIdField();
for (UUID playerUUID : participatingPlayers) {
PlayerRef playerRefComponent = Universe.get().getPlayer(playerUUID);
if (playerRefComponent != null) {
Ref<EntityStore> 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();
}
}

View File

@@ -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<ChunkStore> {
private static final ComponentType<ChunkStore, BlockChunk> COMPONENT_TYPE = BlockChunk.getComponentType();
public PreTick() {
}
@Override
public Query<ChunkStore> 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<ChunkStore> archetypeChunk,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> 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<ChunkStore> {
private static final ComponentType<ChunkStore, WorldChunk> COMPONENT_TYPE = WorldChunk.getComponentType();
private static final Set<Dependency<ChunkStore>> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, ChunkBlockTickSystem.PreTick.class));
public Ticking() {
}
@Override
public Query<ChunkStore> getQuery() {
return COMPONENT_TYPE;
}
@Nonnull
@Override
public Set<Dependency<ChunkStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public void tick(
float dt,
int index,
@Nonnull ArchetypeChunk<ChunkStore> archetypeChunk,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
Ref<ChunkStore> 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<ChunkStore> 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;
}
}
}
}

View File

@@ -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<EntityStore> {
@Nonnull
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
@Nonnull
private final BlockingQueue<CraftingManager.CraftingJob> 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<EntityStore, CraftingManager> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref,
@Nonnull ComponentAccessor<EntityStore> 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<PlayerCraftEvent, PlayerCraftEvent> 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<EntityStore> ref,
@Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
LOGGER.at(Level.FINE).log("Cancel Crafting!");
ObjectList<CraftingManager.CraftingJob> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<ItemStack> 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<MaterialQuantity> materialsToRemove = getInputMaterials(craftingRecipe);
if (materialsToRemove.isEmpty()) {
return true;
} else {
LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", job, materialsToRemove);
ObjectList<ItemStack> itemStackList = new ObjectArrayList<>();
boolean succeeded = switch (job.inputRemovalType) {
case NORMAL -> {
ListTransaction<MaterialTransaction> 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<MaterialSlotTransaction> 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<MaterialQuantity> materialsToRemove = getInputMaterials(craftingRecipe, quantity);
if (materialsToRemove.isEmpty()) {
return true;
} else {
LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", craftingRecipe, materialsToRemove);
ListTransaction<MaterialTransaction> materialTransactions = itemContainer.removeMaterials(materialsToRemove, true, true, true);
return materialTransactions.succeeded();
}
}
private static void refundInputToInventory(
@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor, @Nonnull CraftingManager.CraftingJob job, int currentItemId
) {
Objects.requireNonNull(job, "Job can't be null!");
List<ItemStack> 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<ItemStack> getOutputItemStacks(@Nonnull CraftingRecipe recipe) {
return getOutputItemStacks(recipe, 1);
}
@Nonnull
public static List<ItemStack> getOutputItemStacks(@Nonnull CraftingRecipe recipe, int quantity) {
Objects.requireNonNull(recipe);
MaterialQuantity[] output = recipe.getOutputs();
if (output == null) {
return List.of();
} else {
ObjectList<ItemStack> 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<MaterialQuantity> getInputMaterials(@Nonnull CraftingRecipe recipe) {
return getInputMaterials(recipe, 1);
}
@Nonnull
private static List<MaterialQuantity> getInputMaterials(@Nonnull MaterialQuantity[] input) {
return getInputMaterials(input, 1);
}
@Nonnull
public static List<MaterialQuantity> getInputMaterials(@Nonnull CraftingRecipe recipe, int quantity) {
Objects.requireNonNull(recipe);
return recipe.getInput() == null ? Collections.emptyList() : getInputMaterials(recipe.getInput(), quantity);
}
@Nonnull
private static List<MaterialQuantity> getInputMaterials(@Nonnull MaterialQuantity[] input, int quantity) {
ObjectList<MaterialQuantity> 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<CraftingRecipe> 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<CraftingRecipe> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<MaterialQuantity> 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<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<MaterialQuantity> 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<MaterialTransaction> materialTransactions = combined.removeMaterials(input);
if (materialTransactions.succeeded()) {
List<ItemStack> 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<EntityStore> 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<ItemContainer> chests = result.containers;
List<ItemContainerState> chestStates = result.states;
ItemContainer itemContainer = EmptyItemContainer.INSTANCE;
if (!chests.isEmpty()) {
itemContainer = new CombinedItemContainer(chests.stream().map(container -> {
DelegateItemContainer<ItemContainer> delegate = new DelegateItemContainer<>(container);
delegate.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY);
return delegate;
}).toArray(ItemContainer[]::new));
}
Map<String, ItemQuantity> 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<ItemContainer> containers = new ObjectArrayList<>();
List<ItemContainerState> states = new ObjectArrayList<>();
List<ItemContainerState> spatialResults = new ObjectArrayList<>();
List<ItemContainerState> filteredOut = new ObjectArrayList<>();
World world = benchState.getChunk().getWorld();
Store<ChunkStore> 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<Ref<ChunkStore>, ChunkStore> blockStateSpatialStructure = store.getResource(BlockStateModule.get().getItemContainerSpatialResourceType());
ObjectList<Ref<ChunkStore>> 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<ChunkStore> 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<EntityStore> 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<ItemContainer> containers,
List<ItemContainerState> states,
List<ItemContainerState> spatialResults,
List<ItemContainerState> 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<List<ItemStack>> 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() {
}
}
}

View File

@@ -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<ProcessingBenchState> 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<Short> processingSlots = new HashSet<>();
private final Set<Short> 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<ItemStack> 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<EntityStore> store = world.getEntityStore().getStore();
Holder<EntityStore>[] 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<ItemStack> 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<MaterialQuantity> validIngredients = new ObjectArrayList<>();
for (CraftingRecipe recipe : CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId())) {
if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), tierLevel)) {
List<MaterialQuantity> 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<EntityStore> store = world.getEntityStore().getStore();
Holder<EntityStore>[] 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<ChunkStore> archetypeChunk, @Nonnull Store<ChunkStore> store, CommandBuffer<ChunkStore> commandBuffer) {
World world = store.getExternalData().getWorld();
Store<EntityStore> entityStore = world.getEntityStore().getStore();
BlockType blockType = this.getBlockType();
String currentState = BlockAccessor.getCurrentInteractionState(blockType);
List<ItemStack> outputItemStacks = null;
List<MaterialQuantity> 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<TestRemoveItemSlotResult> 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<ItemStack> extraItemStacks = new ObjectArrayList<>(extra.getOutputs().length);
for (MaterialQuantity e : extra.getOutputs()) {
extraItemStacks.add(e.toItemStack());
}
ListTransaction<ItemStackTransaction> addTransaction = this.outputContainer.addItemStacks(extraItemStacks, false, false, false);
List<ItemStack> 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<EntityStore>[] 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<ItemStack> 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<ItemStackTransaction> 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<EntityStore>[] itemEntityHolders = this.ejectItems(entityStore, remainderItems);
entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
}
return;
}
}
List<ItemStack> remainderItems = new ObjectArrayList<>();
ListTransaction<MaterialTransaction> 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<ItemStackTransaction> 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<EntityStore>[] 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<EntityStore>[] ejectItems(@Nonnull ComponentAccessor<EntityStore> accessor, @Nonnull List<ItemStack> 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<Holder<EntityStore>> 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<EntityStore> 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<ItemStack> itemStacks = this.combinedItemContainer.dropAllItemStacks();
this.dropFuelItems(itemStacks);
World world = this.getChunk().getWorld();
Store<EntityStore> entityStore = world.getEntityStore().getStore();
Vector3d dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5);
Holder<EntityStore>[] 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<CraftingRecipe> recipes = CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId());
if (recipes.isEmpty()) {
this.clearRecipe();
} else {
List<CraftingRecipe> 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<ItemStack> 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<EntityStore> playerRef,
@Nonnull String blockTypeKey,
@Nonnull BlockState blockState,
@Nonnull ComponentAccessor<EntityStore> 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<EntityStore> 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();
}
}

View File

@@ -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<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.and(ChunkSection.getComponentType(), Query.not(FluidSection.getComponentType()));
public EnsureFluidSection() {
}
@Override
public void onEntityAdd(@Nonnull Holder<ChunkStore> holder, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store) {
holder.addComponent(FluidSection.getComponentType(), new FluidSection());
}
@Override
public void onEntityRemoved(@Nonnull Holder<ChunkStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store) {
}
@Nonnull
@Override
public Query<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
@Override
public Set<Dependency<ChunkStore>> getDependencies() {
return RootDependency.firstSet();
}
}
public static class LoadPacketGenerator extends ChunkStore.LoadFuturePacketDataQuerySystem {
public LoadPacketGenerator() {
}
public void fetch(
int index,
@Nonnull ArchetypeChunk<ChunkStore> archetypeChunk,
Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> commandBuffer,
PlayerRef query,
@Nonnull List<CompletableFuture<Packet>> results
) {
ChunkColumn chunkColumnComponent = archetypeChunk.getComponent(index, ChunkColumn.getComponentType());
assert chunkColumnComponent != null;
for (Ref<ChunkStore> 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<ChunkStore> getQuery() {
return ChunkColumn.getComponentType();
}
}
public static class MigrateFromColumn extends ChunkColumnMigrationSystem {
@Nonnull
private final Query<ChunkStore> QUERY = Query.and(ChunkColumn.getComponentType(), BlockChunk.getComponentType());
@Nonnull
private final Set<Dependency<ChunkStore>> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, LegacyModule.MigrateLegacySections.class));
public MigrateFromColumn() {
}
@Override
public void onEntityAdd(@Nonnull Holder<ChunkStore> holder, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store) {
ChunkColumn chunkColumnComponent = holder.getComponent(ChunkColumn.getComponentType());
assert chunkColumnComponent != null;
BlockChunk blockChunkComponent = holder.getComponent(BlockChunk.getComponentType());
assert blockChunkComponent != null;
Holder<ChunkStore>[] sections = chunkColumnComponent.getSectionHolders();
BlockSection[] legacySections = blockChunkComponent.getMigratedSections();
if (legacySections != null) {
for (int i = 0; i < sections.length; i++) {
Holder<ChunkStore> section = sections[i];
BlockSection paletteSection = legacySections[i];
if (section != null && paletteSection != null) {
FluidSection fluid = paletteSection.takeMigratedFluid();
if (fluid != null) {
section.putComponent(FluidSection.getComponentType(), fluid);
blockChunkComponent.markNeedsSaving();
}
}
}
}
}
@Override
public void onEntityRemoved(@Nonnull Holder<ChunkStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store) {
}
@Nonnull
@Override
public Query<ChunkStore> getQuery() {
return this.QUERY;
}
@Nonnull
@Override
public Set<Dependency<ChunkStore>> getDependencies() {
return this.DEPENDENCIES;
}
}
public static class ReplicateChanges extends EntityTickingSystem<ChunkStore> implements RunWhenPausedSystem<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> 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<ChunkStore> archetypeChunk,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> 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<PlayerRef> playerRefs = store.getExternalData().getWorld().getPlayerRefs();
if (playerRefs.isEmpty()) {
changes.clear();
} else {
long chunkIndex = ChunkUtil.indexChunk(fluidSectionComponent.getX(), fluidSectionComponent.getZ());
if (changes.size() >= 1024) {
ObjectArrayList<PlayerRef> playersCopy = new ObjectArrayList<>(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<EntityStore> 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<EntityStore> ref = playerRef.getReference();
if (ref != null && ref.isValid()) {
ChunkTracker tracker = playerRef.getChunkTracker();
if (tracker.isLoaded(chunkIndex)) {
playerRef.getPacketHandler().writeNoCache(packet);
}
}
}
} else {
SetFluidCmd[] cmds = new SetFluidCmd[changes.size()];
IntIterator iter = changes.intIterator();
int 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<EntityStore> 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<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
@Override
public Set<Dependency<ChunkStore>> getDependencies() {
return RootDependency.lastSet();
}
}
public static class SetupSection extends HolderSystem<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.and(ChunkSection.getComponentType(), FluidSection.getComponentType());
@Nonnull
private static final Set<Dependency<ChunkStore>> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, FluidSystems.MigrateFromColumn.class));
public SetupSection() {
}
@Override
public void onEntityAdd(@Nonnull Holder<ChunkStore> holder, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> 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<ChunkStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store) {
}
@Nonnull
@Override
public Query<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
@Override
public Set<Dependency<ChunkStore>> getDependencies() {
return DEPENDENCIES;
}
}
public static class Ticking extends EntityTickingSystem<ChunkStore> {
@Nonnull
private static final Query<ChunkStore> QUERY = Query.and(FluidSection.getComponentType(), ChunkSection.getComponentType());
@Nonnull
private static final Set<Dependency<ChunkStore>> 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<ChunkStore> archetypeChunk,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType());
assert chunkSectionComponent != null;
FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType());
assert fluidSectionComponent != null;
Ref<ChunkStore> 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<ChunkStore> getQuery() {
return QUERY;
}
@Nonnull
@Override
public Set<Dependency<ChunkStore>> getDependencies() {
return DEPENDENCIES;
}
}
}

View File

@@ -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<ChunkStore, InstanceDataResource> instanceDataResourceType;
private ComponentType<EntityStore, InstanceEntityConfig> instanceEntityConfigComponentType;
private ComponentType<ChunkStore, InstanceBlock> instanceBlockComponentType;
private ComponentType<ChunkStore, ConfigurableInstanceBlock> 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<ChunkStore> 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<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
holder.ensureComponent(ConfigurableInstanceBlock.getComponentType());
return holder;
}
);
this.getCodecRegistry(WorldConfig.PLUGIN_CODEC).register(InstanceWorldConfig.class, "Instance", InstanceWorldConfig.CODEC);
}
@Nonnull
public CompletableFuture<World> spawnInstance(@Nonnull String name, @Nonnull World forWorld, @Nonnull Transform returnPoint) {
return this.spawnInstance(name, null, forWorld, returnPoint);
}
@Nonnull
public CompletableFuture<World> 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<Path> 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<EntityStore> entityRef,
@Nonnull ComponentAccessor<EntityStore> componentAccessor,
@Nonnull CompletableFuture<World> 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<EntityStore> playerRef,
@Nonnull ComponentAccessor<EntityStore> 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<EntityStore> targetRef, @Nonnull ComponentAccessor<EntityStore> 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> 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<World> 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<String> getInstanceAssets() {
final List<String> 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<Path>() {
@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<EntityStore> 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<EntityStore> 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<UUID> discoveredInstances = new HashSet<>(playerConfigData.getDiscoveredInstances());
discoveredInstances.add(instanceUuid);
playerConfigData.setDiscoveredInstances(discoveredInstances);
Ref<EntityStore> playerRef = event.getPlayerRef();
if (playerRef.isValid()) {
world.execute(() -> {
Store<EntityStore> store = world.getEntityStore().getStore();
showInstanceDiscovery(playerRef, store, instanceUuid, discoveryConfig);
});
}
}
}
}
}
}
private static void showInstanceDiscovery(
@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> 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<String, Schema> 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<ValidationOption> 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<ChunkStore, InstanceDataResource> getInstanceDataResourceType() {
return this.instanceDataResourceType;
}
@Nonnull
public ComponentType<EntityStore, InstanceEntityConfig> getInstanceEntityConfigComponentType() {
return this.instanceEntityConfigComponentType;
}
@Nonnull
public ComponentType<ChunkStore, InstanceBlock> getInstanceBlockComponentType() {
return this.instanceBlockComponentType;
}
@Nonnull
public ComponentType<ChunkStore, ConfigurableInstanceBlock> getConfigurableInstanceBlockComponentType() {
return this.configurableInstanceBlockComponentType;
}
}

View File

@@ -69,17 +69,11 @@ public class ArchetypeChunk<ECS_TYPE> {
@Nullable
public <T extends Component<ECS_TYPE>> T getComponent(int index, @Nonnull ComponentType<ECS_TYPE, T> 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<ECS_TYPE> {
@Nonnull
public Holder<ECS_TYPE> copySerializableEntity(@Nonnull ComponentRegistry.Data<ECS_TYPE> data, int entityIndex, @Nonnull Holder<ECS_TYPE> target) {
// HyFix #29: Handle IndexOutOfBoundsException during chunk saving gracefully
try {
if (entityIndex >= this.entitiesSize) {
throw new IndexOutOfBoundsException(entityIndex);
} else {
Archetype<ECS_TYPE> serializableArchetype = this.archetype.getSerializableArchetype(data);
Component<ECS_TYPE>[] entityComponents = target.ensureComponentsSize(serializableArchetype.length());
if (entityIndex >= this.entitiesSize) {
throw new IndexOutOfBoundsException(entityIndex);
} else {
Archetype<ECS_TYPE> serializableArchetype = this.archetype.getSerializableArchetype(data);
Component<ECS_TYPE>[] entityComponents = target.ensureComponentsSize(serializableArchetype.length());
for (int i = serializableArchetype.getMinIndex(); i < serializableArchetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) serializableArchetype.get(
i
);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
Component<ECS_TYPE> component = this.components[componentTypeIndex][entityIndex];
entityComponents[componentTypeIndex] = component.cloneSerializable();
}
for (int i = serializableArchetype.getMinIndex(); i < serializableArchetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) serializableArchetype.get(
i
);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
Component<ECS_TYPE> 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;
}
}

View File

@@ -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

View File

@@ -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<HytaleServer> METRICS_REGISTRY = new MetricsRegistry<HytaleServer>()
.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<ShutdownReason> 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<String, Object> 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<String, Object> 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<String, Object> pluginsContext = new HashMap<>();
for (PluginBase plugin : this.pluginManager.getPlugins()) {
PluginManifest manifestxx = plugin.getManifest();
HashMap<String, Object> 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<String, Object> packsContext = new HashMap<>();
for (AssetPack pack : assetModule.getAssetPacks()) {
HashMap<String, Object> 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<String, Object> 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()
.<Void, LoadAssetEvent>dispatchFor(LoadAssetEvent.class)
.dispatch(new LoadAssetEvent(this.bootStart));
if (this.isShuttingDown()) {
return;
}
if (loadAssetEvent.isShouldShutdown()) {
List<String> 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<String> 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<String> 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<PluginBase> 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;
}
}

View File

@@ -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<InteractionChain> forkedChains = new Long2ObjectOpenHashMap<>();
@Nonnull
private final Long2ObjectMap<InteractionChain.TempChain> tempForkedChainData = new Long2ObjectOpenHashMap<>();
@Nonnull
private final Long2LongMap forkedChainsMap = new Long2LongOpenHashMap();
@Nonnull
private final List<InteractionChain> newForks = new ObjectArrayList<>();
@Nonnull
private final RootInteraction initialRootInteraction;
private RootInteraction rootInteraction;
private int operationCounter = 0;
@Nonnull
private final List<InteractionChain.CallState> 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<InteractionEntry> interactions = new ObjectArrayList<>();
@Nonnull
private final List<InteractionSyncData> 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<Entry<InteractionChain.TempChain>> it = Long2ObjectMaps.fastIterator(this.getTempForkedChainData());
while (it.hasNext()) {
Entry<InteractionChain.TempChain> 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<EntityStore> 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<InteractionChain> getForkedChains() {
return this.forkedChains;
}
@Nonnull
public Long2ObjectMap<InteractionChain.TempChain> 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<InteractionChain> 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<InteractionChain.TempChain> tempForkedChainData = new Long2ObjectOpenHashMap<>();
final List<InteractionSyncData> 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<EntityStore> 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 + "}";
}
}
}

View File

@@ -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<Long> 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<Void> 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<Void> 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<Void> getClientReadyForChunksFuture() {
return this.clientReadyForChunksFuture;
}
public static void logConnectionTimings(@Nonnull Channel channel, @Nonnull String message, @Nonnull Level level) {
Attribute<Long> 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<PacketHandler.PingInfo> METRICS_REGISTRY = new MetricsRegistry<PacketHandler.PingInfo>()
.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();
}
}
}
}

View File

@@ -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<X509Certificate> CLIENT_CERTIFICATE_ATTR = AttributeKey.valueOf("CLIENT_CERTIFICATE");
public static final AttributeKey<Integer> ALPN_REJECT_ERROR_CODE_ATTR = AttributeKey.valueOf("ALPN_REJECT_ERROR_CODE");
public static final AttributeKey<String> 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<String, QuicSslContext>) 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<? extends DatagramChannel> 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);

View File

@@ -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<EntityStore> FIND_VISIBLE_ENTITIES_GROUP = EntityStore.REGISTRY.registerSystemGroup();
@Nonnull
public static final SystemGroup<EntityStore> QUEUE_UPDATE_GROUP = EntityStore.REGISTRY.registerSystemGroup();
public EntityTrackerSystems() {
}
public static boolean despawnAll(@Nonnull Ref<EntityStore> viewerRef, @Nonnull Store<EntityStore> 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<EntityStore> viewerRef, @Nonnull Store<EntityStore> store) {
EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType());
if (viewer == null) {
if (!viewerRef.isValid()) {
return false;
} else {
for (Ref<EntityStore> 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<EntityStore> 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<EntityStore> {
@Nonnull
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Collections.singleton(
new SystemDependency<>(Order.AFTER, EntityTrackerSystems.EnsureVisibleComponent.class)
);
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public AddToVisible(
ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType,
ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType
@Nonnull ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType,
@Nonnull ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType
) {
this.entityViewerComponentType = entityViewerComponentType;
this.visibleComponentType = visibleComponentType;
@@ -132,23 +148,32 @@ public class EntityTrackerSystems {
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
Ref<EntityStore> viewerRef = archetypeChunk.getReferenceTo(index);
EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.entityViewerComponentType);
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType);
for (Ref<EntityStore> ref : viewer.visible) {
commandBuffer.getComponent(ref, this.visibleComponentType).addViewerParallel(viewerRef, viewer);
assert entityViewerComponent != null;
for (Ref<EntityStore> 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<EntityStore> {
@Nonnull
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Collections.singleton(
new SystemGroupDependency<>(Order.BEFORE, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP)
);
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
public ClearEntityViewers(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType) {
this.componentType = componentType;
public ClearEntityViewers(@Nonnull ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType) {
this.entityViewerComponentType = entityViewerComponentType;
}
@Nonnull
@@ -159,7 +184,7 @@ public class EntityTrackerSystems {
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
return this.entityViewerComponentType;
}
@Override
@@ -175,22 +200,27 @@ public class EntityTrackerSystems {
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> 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<EntityStore> {
@Nonnull
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Set.of(
new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearEntityViewers.class),
new SystemGroupDependency<EntityStore>(Order.AFTER, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP)
);
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public ClearPreviouslyVisible(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
public ClearPreviouslyVisible(@Nonnull ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType) {
this.visibleComponentType = visibleComponentType;
}
@Nonnull
@@ -201,7 +231,7 @@ public class EntityTrackerSystems {
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
return this.visibleComponentType;
}
@Override
@@ -217,24 +247,29 @@ public class EntityTrackerSystems {
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType);
Map<Ref<EntityStore>, 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<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> oldVisibleTo = visibleComponent.previousVisibleTo;
visibleComponent.previousVisibleTo = visibleComponent.visibleTo;
visibleComponent.visibleTo = oldVisibleTo;
visibleComponent.visibleTo.clear();
visibleComponent.newlyVisibleTo.clear();
}
}
public static class CollectVisible extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public CollectVisible(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType) {
this.componentType = componentType;
this.query = Archetype.of(componentType, TransformComponent.getComponentType());
public CollectVisible(@Nonnull ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> 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<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> 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<Ref<EntityStore>> spatialStructure = store.getResource(EntityModule.get().getNetworkSendableSpatialResourceType())
.getSpatialStructure();
ObjectList<Ref<EntityStore>> 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<EntityStore> entityRef = archetypeChunk.getReferenceTo(index);
EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, this.effectControllerComponentType);
assert effectControllerComponent != null;
Ref<EntityStore> 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<Ref<EntityStore>, 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<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> visibleTo,
@Nonnull Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> exclude
) {
ComponentUpdate update = new ComponentUpdate();
update.type = ComponentUpdateType.EntityEffects;
EntityEffectsUpdate update = new EntityEffectsUpdate();
update.entityEffectUpdates = effectControllerComponent.consumeChanges();
if (!exclude.isEmpty()) {
for (Entry<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) {
@@ -377,15 +416,18 @@ public class EntityTrackerSystems {
}
public static class EnsureVisibleComponent extends EntityTickingSystem<EntityStore> {
@Nonnull
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Collections.singleton(
new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearPreviouslyVisible.class)
);
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public EnsureVisibleComponent(
ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType,
ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType
@Nonnull ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType,
@Nonnull ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType
) {
this.entityViewerComponentType = entityViewerComponentType;
this.visibleComponentType = visibleComponentType;
@@ -415,9 +457,13 @@ public class EntityTrackerSystems {
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
for (Ref<EntityStore> 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<EntityStore> 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<Ref<EntityStore>> visible;
@Nonnull
public Map<Ref<EntityStore>, EntityTrackerSystems.EntityUpdate> updates;
@Nonnull
public Object2IntMap<Ref<EntityStore>> 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<EntityStore> ref, ComponentUpdateType type) {
public void queueRemove(@Nonnull Ref<EntityStore> 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<EntityStore> ref, ComponentUpdate update) {
public void queueUpdate(@Nonnull Ref<EntityStore> 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<EntityStore> {
@Nonnull
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Set.of(
new SystemDependency<>(Order.AFTER, EntityTrackerSystems.AddToVisible.class),
new SystemGroupDependency<EntityStore>(Order.BEFORE, EntityTrackerSystems.QUEUE_UPDATE_GROUP)
);
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public RemoveEmptyVisibleComponent(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
public RemoveEmptyVisibleComponent(@Nonnull ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType) {
this.visibleComponentType = visibleComponentType;
}
@Nonnull
@@ -585,7 +637,7 @@ public class EntityTrackerSystems {
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
return this.visibleComponentType;
}
@Override
@@ -601,22 +653,27 @@ public class EntityTrackerSystems {
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> 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<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public RemoveVisibleComponent(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
public RemoveVisibleComponent(@Nonnull ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType) {
this.visibleComponentType = visibleComponentType;
}
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
return this.visibleComponentType;
}
@Override
@@ -625,18 +682,22 @@ public class EntityTrackerSystems {
@Override
public void onEntityRemoved(@Nonnull Holder<EntityStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store) {
holder.removeComponent(this.componentType);
holder.removeComponent(this.visibleComponentType);
}
}
public static class SendPackets extends EntityTickingSystem<EntityStore> {
@Nonnull
public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
@Nonnull
public static final ThreadLocal<IntList> INT_LIST_THREAD_LOCAL = ThreadLocal.withInitial(IntArrayList::new);
@Nonnull
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Set.of(new SystemGroupDependency<>(Order.AFTER, EntityTrackerSystems.QUEUE_UPDATE_GROUP));
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType;
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
public SendPackets(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType) {
this.componentType = componentType;
public SendPackets(@Nonnull ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType) {
this.entityViewerComponentType = entityViewerComponentType;
}
@Nullable
@@ -653,7 +714,7 @@ public class EntityTrackerSystems {
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
return this.entityViewerComponentType;
}
@Override
@@ -669,61 +730,70 @@ public class EntityTrackerSystems {
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> 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<it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<Ref<EntityStore>>> iterator = viewer.sent.object2IntEntrySet().iterator();
ObjectIterator<it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<Ref<EntityStore>>> iterator = entityViewerComponent.sent
.object2IntEntrySet()
.iterator();
while (iterator.hasNext()) {
it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<Ref<EntityStore>> entry = iterator.next();
Ref<EntityStore> 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<Ref<EntityStore>> iteratorx = viewer.updates.keySet().iterator();
if (!removedEntities.isEmpty() || !entityViewerComponent.updates.isEmpty()) {
Iterator<Ref<EntityStore>> iteratorx = entityViewerComponent.updates.keySet().iterator();
while (iteratorx.hasNext()) {
Ref<EntityStore> 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<Ref<EntityStore>, EntityTrackerSystems.EntityUpdate> entry : viewer.updates.entrySet()) {
for (Entry<Ref<EntityStore>, 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<EntityStore> ref, EntityTrackerSystems.EntityViewer entityViewer) {
public void addViewerParallel(@Nonnull Ref<EntityStore> 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);

View File

@@ -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<EntityStore> 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<EntityStore> ref, @Nullable ModelComponent model, float entityScale, @Nonnull Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> visibleTo
Ref<EntityStore> ref,
@Nullable ModelComponent model,
float entityScale,
boolean isProp,
@Nonnull Map<Ref<EntityStore>, 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<EntityStore> ref, @Nonnull PlayerSkinComponent component, @Nonnull Map<Ref<EntityStore>, 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<EntityStore> ref, @Nonnull LivingEntity entity, @Nonnull Map<Ref<EntityStore>, 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<EntityStore> 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);

View File

@@ -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<ChunkStore> {
public static final BuilderCodec<TrackedPlacement> 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<ChunkStore, TrackedPlacement> getComponentType() {
return InteractionModule.get().getTrackedPlacementComponentType();
}
public TrackedPlacement() {
}
public TrackedPlacement(String blockName) {
this.blockName = blockName;
}
@Nullable
@Override
public Component<ChunkStore> clone() {
return new TrackedPlacement(this.blockName);
}
public static class OnAddRemove extends RefSystem<ChunkStore> {
private static final ComponentType<ChunkStore, TrackedPlacement> COMPONENT_TYPE = TrackedPlacement.getComponentType();
private static final ResourceType<ChunkStore, BlockCounter> BLOCK_COUNTER_RESOURCE_TYPE = BlockCounter.getResourceType();
public OnAddRemove() {
}
@Override
public void onEntityAdded(
@Nonnull Ref<ChunkStore> ref, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
if (reason == AddReason.SPAWN) {
BlockModule.BlockStateInfo blockInfo = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType());
assert blockInfo != null;
Ref<ChunkStore> 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<ChunkStore> ref, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> 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<ChunkStore> getQuery() {
return COMPONENT_TYPE;
}
}
}

View File

@@ -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());

View File

@@ -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<Integer, String> LEGACY_BLOCK_ID_MAP = Collections.emptyMap();
@Nonnull
public static final MetricsRegistry<Universe> METRICS_REGISTRY = new MetricsRegistry<Universe>()
.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<EntityStore, PlayerRef> playerRefComponentType;
@Nonnull
private final Path path = Constants.UNIVERSE_PATH;
@Nonnull
private final Map<UUID, PlayerRef> players = new ConcurrentHashMap<>();
@Nonnull
private final Map<String, World> worlds = new ConcurrentHashMap<>();
@Nonnull
private final Map<UUID, World> worldsByUuid = new ConcurrentHashMap<>();
@Nonnull
private final Map<String, World> unmodifiableWorlds = Collections.unmodifiableMap(this.worlds);
private PlayerStorage playerStorage;
private WorldConfigProvider worldConfigProvider;
private ResourceType<ChunkStore, IndexedStorageChunkStorageProvider.IndexedStorageCache> indexedStorageCacheResourceType;
private CompletableFuture<Void> 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<Void> runBackup() {
return CompletableFuture.allOf(this.worlds.values().stream().map(world -> CompletableFuture.<ChunkSavingSystems.Data>supplyAsync(() -> {
Store<ChunkStore> 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<ChunkStore> 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<ChunkStore> chunkStoreRegistry = this.getChunkStoreRegistry();
ComponentRegistryProxy<EntityStore> 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()
.<Void, PrepareUniverseEvent>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<Integer, String> 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<CompletableFuture<?>> loadingWorlds = new ObjectArrayList<>();
try {
Path worldsPath = this.path.resolve("worlds");
Files.createDirectories(worldsPath);
try (DirectoryStream<Path> 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<World> 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<Void> getUniverseReady() {
return this.universeReady;
}
public ResourceType<ChunkStore, IndexedStorageChunkStorageProvider.IndexedStorageCache> 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<World> addWorld(@Nonnull String name) {
return this.addWorld(name, null, null);
}
@Nonnull
@Deprecated
@CheckReturnValue
public CompletableFuture<World> 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<? extends IWorldGenProvider> 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<? extends IChunkStorageProvider> 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<World> makeWorld(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig) {
return this.makeWorld(name, savePath, worldConfig, true);
}
@Nonnull
@CheckReturnValue
public CompletableFuture<World> makeWorld(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig, boolean start) {
Map<PluginIdentifier, SemverRange> map = worldConfig.getRequiredPlugins();
if (map != null) {
PluginManager pluginManager = PluginManager.get();
for (Entry<PluginIdentifier, SemverRange> 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<Void> 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<World> 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<String, World> getWorlds() {
return this.unmodifiableWorlds;
}
@Nonnull
public List<PlayerRef> 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<String> comparator, @Nonnull BiPredicate<String, String> 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<String> comparator, @Nonnull BiPredicate<String, String> equality) {
return NameMatching.find(this.players.values(), value, PlayerRef::getUsername, comparator, equality);
}
public int getPlayerCount() {
return this.players.size();
}
@Nonnull
public CompletableFuture<PlayerRef> 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<EntityStore>) 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()
.<Void, PlayerConnectEvent>dispatchFor(PlayerConnectEvent.class)
.dispatch(new PlayerConnectEvent((Holder<EntityStore>) 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<PlayerRef> 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.<PlayerRef>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<PlayerDisconnectEvent, PlayerDisconnectEvent> eventDispatcher = HytaleServer.get()
.getEventBus()
.dispatchFor(PlayerDisconnectEvent.class);
if (eventDispatcher.hasListener()) {
eventDispatcher.dispatch(new PlayerDisconnectEvent(playerRef));
}
Ref<EntityStore> 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<Void> 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<PlayerRef> 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<EntityStore>) holder));
}
@Nonnull
public CompletableFuture<PlayerRef> resetPlayer(@Nonnull PlayerRef oldPlayer, @Nonnull Holder<EntityStore> holder) {
return this.resetPlayer(oldPlayer, holder, null, null);
}
@Nonnull
public CompletableFuture<PlayerRef> resetPlayer(
@Nonnull PlayerRef playerRef, @Nonnull Holder<EntityStore> 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<Void> 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<EntityStore, PlayerRef> getPlayerRefComponentType() {
return this.playerRefComponentType;
}
@Nonnull
@Deprecated
public static Map<Integer, String> 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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<CompletableFuture<MapImage>> 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<EntityStore> ref,
@Nullable WorldMapTracker.ZoneDiscoveryInfo zoneDiscoveryInfo,
@Nullable String biomeName,
@Nonnull ComponentAccessor<EntityStore> 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<EntityStore> ref, @Nonnull WorldMapTracker.ZoneDiscoveryInfo zoneDiscoveryInfo, @Nonnull ComponentAccessor<EntityStore> 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<MapChunk> currentUnloadList = null;
List<List<MapChunk>> 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<MapChunk> 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<MapChunk> 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<MapImage> 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<MapChunk> currentLoadList = null;
List<List<MapChunk>> 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<MapImage> 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<MapChunk> 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<MapChunk> currentLoadList = null;
List<List<MapChunk>> 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<MapImage> 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<MapChunk> unloadList : allLoadLists) {
this.writeUpdatePacket(unloadList);
}
}
this.writeUpdatePacket(currentLoadList);
return maxGeneration;
}
private void writeUpdatePacket(@Nullable List<MapChunk> 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<String, MapMarker> 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<EntityStore> store = world.getEntityStore().getStore();
Ref<EntityStore> 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<String> discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones();
if (!discoveredZones.contains(zoneName)) {
Set<String> 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<String> discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones();
if (discoveredZones.contains(zoneName)) {
Set<String> 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<String> zoneNames) {
Set<String> discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones();
if (!discoveredZones.containsAll(zoneNames)) {
Set<String> 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<String> zoneNames) {
Set<String> discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones();
if (discoveredZones.containsAll(zoneNames)) {
Set<String> 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<PlayerRef> 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
);
}
}
}

View File

@@ -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<ChunkStore> {
public static final BuilderCodec<BlockComponentChunk> 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<Holder<ChunkStore>> map = new Int2ObjectOpenHashMap<>(entityChunk.entityHolders.size() + entityChunk.entityReferences.size());
map.putAll(entityChunk.entityHolders);
for (Entry<Ref<ChunkStore>> entry : entityChunk.entityReferences.int2ObjectEntrySet()) {
Ref<ChunkStore> reference = entry.getValue();
Store<ChunkStore> 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<Holder<ChunkStore>> entityHolders;
@Nonnull
private final Int2ObjectMap<Ref<ChunkStore>> entityReferences;
@Nonnull
private final Int2ObjectMap<Holder<ChunkStore>> entityHoldersUnmodifiable;
@Nonnull
private final Int2ObjectMap<Ref<ChunkStore>> entityReferencesUnmodifiable;
private boolean needsSaving;
public static ComponentType<ChunkStore, BlockComponentChunk> 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<Holder<ChunkStore>> entityHolders, @Nonnull Int2ObjectMap<Ref<ChunkStore>> entityReferences) {
this.entityHolders = entityHolders;
this.entityReferences = entityReferences;
this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable(entityHolders);
this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable(entityReferences);
}
@Nonnull
@Override
public Component<ChunkStore> clone() {
Int2ObjectOpenHashMap<Holder<ChunkStore>> entityHoldersClone = new Int2ObjectOpenHashMap<>(this.entityHolders.size() + this.entityReferences.size());
for (Entry<Holder<ChunkStore>> entry : this.entityHolders.int2ObjectEntrySet()) {
entityHoldersClone.put(entry.getIntKey(), entry.getValue().clone());
}
for (Entry<Ref<ChunkStore>> entry : this.entityReferences.int2ObjectEntrySet()) {
Ref<ChunkStore> reference = entry.getValue();
entityHoldersClone.put(entry.getIntKey(), reference.getStore().copyEntity(reference));
}
return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<>());
}
@Nonnull
@Override
public Component<ChunkStore> cloneSerializable() {
ComponentRegistry.Data<ChunkStore> data = ChunkStore.REGISTRY.getData();
Int2ObjectOpenHashMap<Holder<ChunkStore>> entityHoldersClone = new Int2ObjectOpenHashMap<>(this.entityHolders.size() + this.entityReferences.size());
for (Entry<Holder<ChunkStore>> entry : this.entityHolders.int2ObjectEntrySet()) {
Holder<ChunkStore> holder = entry.getValue();
if (holder.getArchetype().hasSerializableComponents(data)) {
entityHoldersClone.put(entry.getIntKey(), holder.cloneSerializable(data));
}
}
for (Entry<Ref<ChunkStore>> entryx : this.entityReferences.int2ObjectEntrySet()) {
Ref<ChunkStore> reference = entryx.getValue();
Store<ChunkStore> 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<Holder<ChunkStore>> getEntityHolders() {
return this.entityHoldersUnmodifiable;
}
@Nullable
public Holder<ChunkStore> getEntityHolder(int index) {
return this.entityHolders.get(index);
}
public void addEntityHolder(int index, @Nonnull Holder<ChunkStore> 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<ChunkStore> holder) {
if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) {
throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index);
}
}
@Nullable
public Holder<ChunkStore> removeEntityHolder(int index) {
Holder<ChunkStore> reference = this.entityHolders.remove(index);
if (reference != null) {
this.markNeedsSaving();
}
return reference;
}
@Nonnull
public Int2ObjectMap<Ref<ChunkStore>> getEntityReferences() {
return this.entityReferencesUnmodifiable;
}
@Nullable
public Ref<ChunkStore> getEntityReference(int index) {
return this.entityReferences.get(index);
}
public void addEntityReference(int index, @Nonnull Ref<ChunkStore> 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<ChunkStore> 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<ChunkStore> reference) {
if (this.entityReferences.remove(index, reference)) {
this.markNeedsSaving();
}
}
public void unloadEntityReference(int index, Ref<ChunkStore> reference) {
this.entityReferences.remove(index, reference);
}
@Nullable
public Int2ObjectMap<Holder<ChunkStore>> takeEntityHolders() {
if (this.entityHolders.isEmpty()) {
return null;
} else {
Int2ObjectOpenHashMap<Holder<ChunkStore>> holders = new Int2ObjectOpenHashMap<>(this.entityHolders);
this.entityHolders.clear();
return holders;
}
}
@Nullable
public Int2ObjectMap<Ref<ChunkStore>> takeEntityReferences() {
if (this.entityReferences.isEmpty()) {
return null;
} else {
Int2ObjectOpenHashMap<Ref<ChunkStore>> holders = new Int2ObjectOpenHashMap<>(this.entityReferences);
this.entityReferences.clear();
return holders;
}
}
@Nullable
public <T extends Component<ChunkStore>> T getComponent(int index, @Nonnull ComponentType<ChunkStore, T> componentType) {
Ref<ChunkStore> reference = this.entityReferences.get(index);
if (reference != null) {
return reference.getStore().getComponent(reference, componentType);
} else {
Holder<ChunkStore> 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<ChunkStore, NonTicking<ChunkStore>> {
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
private final Archetype<ChunkStore> archetype = Archetype.of(WorldChunk.getComponentType(), BlockComponentChunk.getComponentType());
public BlockComponentChunkLoadingSystem() {
}
@Override
public Query<ChunkStore> getQuery() {
return this.archetype;
}
@Nonnull
@Override
public ComponentType<ChunkStore, NonTicking<ChunkStore>> componentType() {
return ChunkStore.REGISTRY.getNonTickingComponentType();
}
public void onComponentAdded(
@Nonnull Ref<ChunkStore> ref,
@Nonnull NonTicking<ChunkStore> component,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType());
Int2ObjectMap<Ref<ChunkStore>> entityReferences = blockComponentChunk.takeEntityReferences();
if (entityReferences != null) {
int size = entityReferences.size();
int[] indexes = new int[size];
Ref<ChunkStore>[] references = new Ref[size];
int j = 0;
for (Entry<Ref<ChunkStore>> entry : entityReferences.int2ObjectEntrySet()) {
indexes[j] = entry.getIntKey();
references[j] = entry.getValue();
j++;
}
ComponentRegistry.Data<ChunkStore> data = ChunkStore.REGISTRY.getData();
for (int i = 0; i < size; i++) {
if (store.getArchetype(references[i]).hasSerializableComponents(data)) {
Holder<ChunkStore> 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<ChunkStore> ref,
NonTicking<ChunkStore> oldComponent,
@Nonnull NonTicking<ChunkStore> newComponent,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
}
public void onComponentRemoved(
@Nonnull Ref<ChunkStore> ref,
@Nonnull NonTicking<ChunkStore> component,
@Nonnull Store<ChunkStore> store,
@Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
WorldChunk chunk = store.getComponent(ref, WorldChunk.getComponentType());
BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType());
Int2ObjectMap<Holder<ChunkStore>> entityHolders = blockComponentChunk.takeEntityHolders();
if (entityHolders != null) {
int holderCount = entityHolders.size();
int[] indexes = new int[holderCount];
Holder<ChunkStore>[] holders = new Holder[holderCount];
int j = 0;
for (Entry<Holder<ChunkStore>> entry : entityHolders.int2ObjectEntrySet()) {
indexes[j] = entry.getIntKey();
holders[j] = entry.getValue();
j++;
}
for (int i = holderCount - 1; i >= 0; i--) {
Holder<ChunkStore> 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<ChunkStore, BlockComponentChunk> componentType;
public LoadBlockComponentPacketSystem(ComponentType<ChunkStore, BlockComponentChunk> blockComponentChunkComponentType) {
this.componentType = blockComponentChunkComponentType;
}
@Override
public Query<ChunkStore> getQuery() {
return this.componentType;
}
public void fetch(
int index,
@Nonnull ArchetypeChunk<ChunkStore> archetypeChunk,
@Nonnull Store<ChunkStore> store,
CommandBuffer<ChunkStore> commandBuffer,
PlayerRef player,
@Nonnull List<Packet> results
) {
BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType);
ObjectCollection<Ref<ChunkStore>> references = component.entityReferences.values();
Store<ChunkStore> 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<ChunkStore, BlockComponentChunk> componentType;
public UnloadBlockComponentPacketSystem(ComponentType<ChunkStore, BlockComponentChunk> blockComponentChunkComponentType) {
this.componentType = blockComponentChunkComponentType;
}
@Override
public Query<ChunkStore> getQuery() {
return this.componentType;
}
public void fetch(
int index,
@Nonnull ArchetypeChunk<ChunkStore> archetypeChunk,
@Nonnull Store<ChunkStore> store,
CommandBuffer<ChunkStore> commandBuffer,
PlayerRef player,
@Nonnull List<Packet> results
) {
BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType);
ObjectCollection<Ref<ChunkStore>> references = component.entityReferences.values();
Store<ChunkStore> componentStore = store.getExternalData().getWorld().getChunkStore().getStore();
componentStore.fetch(references, ChunkStore.UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results);
}
}
}

View File

@@ -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<Vector3i> set = ConcurrentHashMap.newKeySet();
private final ObjectArrayFIFOQueue<Vector3i> 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));
}
});
}
}

View File

@@ -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<ChunkStore> METRICS_REGISTRY = new MetricsRegistry<ChunkStore>()
.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<ChunkStore> REGISTRY = new ComponentRegistry<>();
public static final CodecKey<Holder<ChunkStore>> HOLDER_CODEC_KEY = new CodecKey<>("ChunkHolder");
@Nonnull
public static final SystemType<ChunkStore, ChunkStore.LoadPacketDataQuerySystem> LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType(
ChunkStore.LoadPacketDataQuerySystem.class
);
@Nonnull
public static final SystemType<ChunkStore, ChunkStore.LoadFuturePacketDataQuerySystem> LOAD_FUTURE_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType(
ChunkStore.LoadFuturePacketDataQuerySystem.class
);
@Nonnull
public static final SystemType<ChunkStore, ChunkStore.UnloadPacketDataQuerySystem> UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType(
ChunkStore.UnloadPacketDataQuerySystem.class
);
@Nonnull
public static final ResourceType<ChunkStore, ChunkUnloadingSystem.Data> UNLOAD_RESOURCE = REGISTRY.registerResource(
ChunkUnloadingSystem.Data.class, ChunkUnloadingSystem.Data::new
);
@Nonnull
public static final ResourceType<ChunkStore, ChunkSavingSystems.Data> SAVE_RESOURCE = REGISTRY.registerResource(
ChunkSavingSystems.Data.class, ChunkSavingSystems.Data::new
);
public static final SystemGroup<ChunkStore> INIT_GROUP = REGISTRY.registerSystemGroup();
@Nonnull
private final World world;
@Nonnull
private final Long2ObjectConcurrentHashMap<ChunkStore.ChunkLoadState> chunks = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND);
private Store<ChunkStore> store;
@Nullable
private IChunkLoader loader;
@Nullable
private IChunkSaver saver;
@Nullable
private IWorldGen generator;
@Nonnull
private CompletableFuture<Void> 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<ChunkStore> 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<ChunkStore.ChunkLoadState> entry : this.chunks.long2ObjectEntrySet()) {
ChunkStore.ChunkLoadState chunkState = entry.getValue();
long stamp = chunkState.lock.readLock();
try {
CompletableFuture<Ref<ChunkStore>> 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<ChunkStore> add(@Nonnull Holder<ChunkStore> 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<ChunkStore> 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<ChunkStore> 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<ChunkStore> getChunkReference(long index) {
ChunkStore.ChunkLoadState chunkState = this.chunks.get(index);
if (chunkState == null) {
return null;
} else {
long stamp = chunkState.lock.tryOptimisticRead();
Ref<ChunkStore> 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<ChunkStore> getChunkSectionReference(int x, int y, int z) {
Ref<ChunkStore> 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<ChunkStore> getChunkSectionReference(@Nonnull ComponentAccessor<ChunkStore> commandBuffer, int x, int y, int z) {
Ref<ChunkStore> 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<Ref<ChunkStore>> 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<ChunkStore> store = ref.getStore();
ChunkColumn chunkColumnComponent = store.getComponent((Ref<ChunkStore>) 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 extends Component<ChunkStore>> T getChunkComponent(long index, @Nonnull ComponentType<ChunkStore, T> componentType) {
Ref<ChunkStore> reference = this.getChunkReference(index);
return reference != null && reference.isValid() ? this.store.getComponent(reference, componentType) : null;
}
@Nonnull
public CompletableFuture<Ref<ChunkStore>> getChunkReferenceAsync(long index) {
return this.getChunkReferenceAsync(index, 0);
}
@Nonnull
public CompletableFuture<Ref<ChunkStore>> 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<ChunkStore> 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<ChunkStore>) 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<ChunkStore>) reference)
: this.loader.loadHolder(x, z).thenApplyAsync(holder -> {
if (holder != null && !this.store.isShutdown()) {
this.totalLoadedChunksCount.getAndIncrement();
return this.preLoadChunkAsync(index, (Holder<ChunkStore>) 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<GeneratedChunk> 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.<Holder<ChunkStore>>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<ChunkStore>) reference);
} else {
long readStampx = this.generatorLock.readLock();
CompletableFuture<GeneratedChunk> 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.<Holder<ChunkStore>>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.<Ref<ChunkStore>>thenApplyAsync(reference -> {
if (reference != null) {
WorldChunk worldChunkComponent = this.store.getComponent((Ref<ChunkStore>) 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<Holder<ChunkStore>> 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<Void> 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<GeneratedChunk> 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<ChunkStore> preLoadChunkAsync(long index, @Nonnull Holder<ChunkStore> 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<ChunkPreLoadProcessEvent, ChunkPreLoadProcessEvent> 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<ChunkStore> postLoadChunk(@Nullable Holder<ChunkStore> 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<ChunkStore> 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<Ref<ChunkStore>> future;
@Nullable
private Ref<ChunkStore> 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<ChunkStore> {
public ChunkLoaderSaverSetupSystem() {
}
@Nullable
@Override
public SystemGroup<ChunkStore> getGroup() {
return ChunkStore.INIT_GROUP;
}
@Override
public void onSystemAddedToStore(@Nonnull Store<ChunkStore> 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<ChunkStore> 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<ChunkStore, PlayerRef, CompletableFuture<Packet>> {
public LoadFuturePacketDataQuerySystem() {
}
}
public abstract static class LoadPacketDataQuerySystem extends EntityDataSystem<ChunkStore, PlayerRef, Packet> {
public LoadPacketDataQuerySystem() {
}
}
public abstract static class UnloadPacketDataQuerySystem extends EntityDataSystem<ChunkStore, PlayerRef, Packet> {
public UnloadPacketDataQuerySystem() {
}
}
}

View File

@@ -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<EntityStore> METRICS_REGISTRY = new MetricsRegistry<EntityStore>()
.register("Store", EntityStore::getStore, Store.METRICS_REGISTRY);
@Nonnull
public static final ComponentRegistry<EntityStore> REGISTRY = new ComponentRegistry<>();
@Nonnull
public static final CodecKey<Holder<EntityStore>> HOLDER_CODEC_KEY = new CodecKey<>("EntityHolder");
@Nonnull
public static final SystemGroup<EntityStore> SEND_PACKET_GROUP = REGISTRY.registerSystemGroup();
@Nonnull
private final AtomicInteger networkIdCounter = new AtomicInteger(1);
@Nonnull
private final World world;
private Store<EntityStore> store;
@Nonnull
private final Map<UUID, Ref<EntityStore>> entitiesByUuid = new ConcurrentHashMap<>();
@Nonnull
private final Int2ObjectMap<Ref<EntityStore>> 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<EntityStore> getStore() {
return this.store;
}
@Nullable
public Ref<EntityStore> getRefFromUUID(@Nonnull UUID uuid) {
return this.entitiesByUuid.get(uuid);
}
@Nullable
public Ref<EntityStore> 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<EntityStore> {
public NetworkIdSystem() {
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return NetworkId.getComponentType();
}
@Override
public void onEntityAdded(
@Nonnull Ref<EntityStore> ref, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> 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<EntityStore> ref, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> 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<EntityStore> {
@Nonnull
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
public UUIDSystem() {
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return UUIDComponent.getComponentType();
}
@Override
public void onEntityAdded(
@Nonnull Ref<EntityStore> ref, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer
) {
UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType());
assert uuidComponent != null;
Ref<EntityStore> 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<EntityStore> ref, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> 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);
}
}
}

View File

@@ -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<Void> saveHolder(int var1, int var2, @Nonnull Holder<ChunkStore> var3);
@Nonnull
CompletableFuture<Void> 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<Void> deleteRegionFile(int x, int z) {
return CompletableFuture.completedFuture(null);
}
@Nonnull
LongSet getIndexes() throws IOException;
void flush() throws IOException;
}

View File

@@ -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<IndexedStorageChunkStorageProvider> CODEC = BuilderCodec.builder(
IndexedStorageChunkStorageProvider.class, IndexedStorageChunkStorageProvider::new
)
.documentation("Uses the indexed storage file format to store chunks.")
.<Boolean>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<ChunkStore> store) {
return new IndexedStorageChunkStorageProvider.IndexedStorageChunkLoader(store, this.flushOnWrite);
}
@Nonnull
@Override
public IChunkSaver getSaver(@Nonnull Store<ChunkStore> 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<ChunkStore> {
@Nonnull
public static final MetricsRegistry<IndexedStorageChunkStorageProvider.IndexedStorageCache> METRICS_REGISTRY = new MetricsRegistry<IndexedStorageChunkStorageProvider.IndexedStorageCache>()
.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<IndexedStorageFile> cache = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND);
private final Map<Long, CachedFile> 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<Long, CachedFile> 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<ChunkStore, IndexedStorageChunkStorageProvider.IndexedStorageCache> getResourceType() {
return Universe.get().getIndexedStorageCacheResourceType();
}
@Nonnull
public Long2ObjectConcurrentHashMap<IndexedStorageFile> 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<IndexedStorageFile> 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<Path> 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<ChunkStore> clone() {
return new IndexedStorageChunkStorageProvider.IndexedStorageCache();
}
private static class CacheEntryMetricData {
@Nonnull
private static final Codec<IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData> 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<IndexedStorageFile> entry) {
this.key = entry.getLongKey();
this.value = entry.getValue();
}
}
}
public static class IndexedStorageCacheSetupSystem extends StoreSystem<ChunkStore> {
public IndexedStorageCacheSetupSystem() {
}
@Nullable
@Override
public SystemGroup<ChunkStore> getGroup() {
return ChunkStore.INIT_GROUP;
}
@Override
public void onSystemAddedToStore(@Nonnull Store<ChunkStore> store) {
World world = store.getExternalData().getWorld();
store.getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).path = world.getSavePath().resolve("chunks");
}
@Override
public void onSystemRemovedFromStore(@Nonnull Store<ChunkStore> store) {
}
}
public static class IndexedStorageChunkLoader extends BufferChunkLoader implements MetricProvider {
private final boolean flushOnWrite;
public IndexedStorageChunkLoader(@Nonnull Store<ChunkStore> 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<ByteBuffer> 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<ChunkStore> 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<Void> 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<Void> 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<Void> 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();
}
}
}

View File

@@ -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<Void> 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;

View File

@@ -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<RoleDebugFlags> 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();
}

View File

@@ -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<String, MotionController> motionControllers, @Nullable String initialMotionController
) {
this.motionControllers = motionControllers;
for (Entry<String, MotionController> 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<EntityStore> ref, double tickTime, @Nonnull Steering bodySteering, @Nonnull Steering headSteering, @Nonnull Store<EntityStore> store
) {
if (this.debugSupport.isVisSensorRanges()) {
this.debugSupport.beginSensorVisualization();
}
boolean isDead = store.getArchetype(ref).contains(DeathComponent.getComponentType());
if (isDead) {
if (this.deathInstruction != null) {

View File

@@ -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<EntityStore, NPCEntity> npcComponentType;
@Nonnull
@@ -381,7 +403,7 @@ public class RoleSystems {
@Nonnull
@Override
public Query<EntityStore> 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<EntityStore> 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<EntityStore> archetypeChunk, @Nonnull CommandBuffer<EntityStore> commandBuffer
) {
Ref<EntityStore> npcRef = archetypeChunk.getReferenceTo(index);
Transform npcLook = TargetUtil.getLook(npcRef, commandBuffer);
Vector3d npcEyePosition = npcLook.getPosition();
World world = commandBuffer.getExternalData().getWorld();
MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport();
Ref<EntityStore>[] entityTargets = markedEntitySupport.getEntityTargets();
for (int slotIndex = 0; slotIndex < entityTargets.length; slotIndex++) {
Ref<EntityStore> 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<EntityStore> npcRef,
@Nonnull TransformComponent transformComponent,
@Nonnull BoundingBox boundingBoxComponent,
@Nonnull World world,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
List<DebugSupport.SensorVisData> 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<Ref<EntityStore>, List<DebugSupport.EntityVisData>> entityDataMap = debugSupport.getEntityVisData();
if (entityDataMap != null) {
double markerOffset = 0.3;
double sphereStackOffset = 0.3;
double defaultEntityHeight = 2.0;
for (Entry<Ref<EntityStore>, List<DebugSupport.EntityVisData>> entry : entityDataMap.entrySet()) {
Ref<EntityStore> entityRef = entry.getKey();
List<DebugSupport.EntityVisData> 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<EntityStore> 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);
}
}
}

View File

@@ -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<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, SpawnBeaconReference> spawnReferenceComponentType;
@Nonnull
private final ComponentType<EntityStore, LegacySpawnBeaconEntity> legacySpawnBeaconComponent;
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final Query<EntityStore> query;
public BeaconAddRemoveSystem(
@Nonnull ComponentType<EntityStore, SpawnBeaconReference> spawnReferenceComponentType,
@Nonnull ComponentType<EntityStore, LegacySpawnBeaconEntity> legacySpawnBeaconComponent
) {
this.spawnReferenceComponentType = spawnReferenceComponentType;
this.legacySpawnBeaconComponent = legacySpawnBeaconComponent;
this.npcComponentType = NPCEntity.getComponentType();
this.query = Archetype.of(spawnReferenceComponentType, this.npcComponentType);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.query;
}
@Override
public void onEntityAdded(
@Nonnull Ref<EntityStore> ref, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer
) {
switch (reason) {
case LOAD:
SpawnBeaconReference spawnReferenceComponent = store.getComponent(ref, this.spawnReferenceComponentType);
assert spawnReferenceComponent != null;
Ref<EntityStore> 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<EntityStore> ref, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer
) {
switch (reason) {
case REMOVE:
SpawnBeaconReference spawnReference = store.getComponent(ref, this.spawnReferenceComponentType);
if (spawnReference == null) {
return;
} else {
Ref<EntityStore> 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<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, SpawnMarkerReference> spawnReferenceComponentType;
@Nonnull
private final ComponentType<EntityStore, SpawnMarkerEntity> spawnMarkerEntityComponentType;
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, WorldGenId> worldGenIdComponentType;
@Nonnull
private final ComponentType<EntityStore, UUIDComponent> uuidComponentComponentType;
@Nonnull
private final ResourceType<EntityStore, WorldTimeResource> worldTimeResourceResourceType;
@Nonnull
private final Query<EntityStore> query;
public MarkerAddRemoveSystem(
@Nonnull ComponentType<EntityStore, SpawnMarkerReference> spawnReferenceComponentType,
@Nonnull ComponentType<EntityStore, SpawnMarkerEntity> 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<EntityStore> getQuery() {
return this.query;
}
@Override
public void onEntityAdded(
@Nonnull Ref<EntityStore> ref, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer
) {
switch (reason) {
case LOAD:
SpawnMarkerReference spawnReferenceComponent = store.getComponent(ref, this.spawnReferenceComponentType);
assert spawnReferenceComponent != null;
Ref<EntityStore> 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<EntityStore> ref, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer
) {
switch (reason) {
case REMOVE:
SpawnMarkerReference spawnReferenceComponent = store.getComponent(ref, this.spawnReferenceComponentType);
if (spawnReferenceComponent == null) {
return;
} else {
Ref<EntityStore> 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<EntityStore> {
@Nonnull
private static final Set<Dependency<EntityStore>> DEPENDENCIES = Set.of(
new SystemDependency<>(Order.AFTER, NPCPreTickSystem.class), new SystemDependency<>(Order.BEFORE, DeathSystems.CorpseRemoval.class)
);
@Nonnull
private final ComponentType<EntityStore, SpawnBeaconReference> spawnReferenceComponentType;
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcEntityComponentType;
@Nonnull
private final Query<EntityStore> query;
public TickingSpawnBeaconSystem(@Nonnull ComponentType<EntityStore, SpawnBeaconReference> spawnReferenceComponentType) {
this.spawnReferenceComponentType = spawnReferenceComponentType;
this.npcEntityComponentType = NPCEntity.getComponentType();
this.query = Archetype.of(spawnReferenceComponentType, this.npcEntityComponentType);
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Nonnull
@Override
public Query<EntityStore> 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<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> 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<EntityStore> 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<EntityStore> {
@Nonnull
private static final Set<Dependency<EntityStore>> DEPENDENCIES = Set.of(
new SystemDependency<>(Order.AFTER, NPCPreTickSystem.class), new SystemDependency<>(Order.BEFORE, DeathSystems.CorpseRemoval.class)
);
@Nonnull
private final ComponentType<EntityStore, SpawnMarkerReference> spawnReferenceComponentType;
@Nonnull
private final ComponentType<EntityStore, SpawnMarkerEntity> markerTypeComponentType;
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcEntityComponentType;
@Nonnull
private final Query<EntityStore> query;
public TickingSpawnMarkerSystem(
@Nonnull ComponentType<EntityStore, SpawnMarkerReference> spawnReferenceComponentType,
@Nonnull ComponentType<EntityStore, SpawnMarkerEntity> markerTypeComponentType
) {
this.spawnReferenceComponentType = spawnReferenceComponentType;
this.markerTypeComponentType = markerTypeComponentType;
this.npcEntityComponentType = NPCEntity.getComponentType();
this.query = Archetype.of(spawnReferenceComponentType, this.npcEntityComponentType);
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Nonnull
@Override
public Query<EntityStore> 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<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> 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<EntityStore> 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));
}
}
}
}
}
}
}

View File

@@ -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<NPCBeaconSpawnJob> {
@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<EntityStore> ownerRef;
private final List<Ref<EntityStore>> spawnedEntities = new ObjectArrayList<>();
private final List<PlayerRef> playersInRegion = new ObjectArrayList<>();
private int nextPlayerIndex = 0;
private final Object2IntMap<UUID> entitiesPerPlayer = new Object2IntOpenHashMap<>();
private final Object2DoubleMap<Ref<EntityStore>> entityTimeoutCounter = new Object2DoubleOpenHashMap<>();
private final IntSet unspawnableRoles = new IntOpenHashSet();
private final Comparator<PlayerRef> 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<EntityStore> ownerRef) {
super(world);
this.ownerRef = ownerRef;
}
@Override
public int getMaxActiveJobs() {
return Math.min(this.remainingSpawns, this.baseMaxActiveJobs);
}
@Nullable
public NPCBeaconSpawnJob createRandomSpawnJob(@Nonnull ComponentAccessor<EntityStore> 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<EntityStore> getOwnerRef() {
return this.ownerRef;
}
public int[] getBaseMaxConcurrentSpawns() {
return this.baseMaxConcurrentSpawns;
}
public List<PlayerRef> 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<Ref<EntityStore>> getSpawnedEntities() {
return this.spawnedEntities;
}
public void setNextPlayerIndex(int nextPlayerIndex) {
this.nextPlayerIndex = nextPlayerIndex;
}
public Object2DoubleMap<Ref<EntityStore>> getEntityTimeoutCounter() {
return this.entityTimeoutCounter;
}
public Object2IntMap<UUID> getEntitiesPerPlayer() {
return this.entitiesPerPlayer;
}
public boolean isDespawnNPCsIfIdle() {
return this.despawnNPCsIfIdle;
}
public double getDespawnNPCAfterTimeout() {
return this.despawnNPCAfterTimeout;
}
public Comparator<PlayerRef> getThreatComparator() {
return this.threatComparator;
}
public void notifySpawnedEntityExists(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> componentAccessor) {
if (++this.spawnsThisRound >= this.currentScaledMaxConcurrentSpawns) {
this.onAllConcurrentSpawned(componentAccessor);
}
}
public void notifyNPCRemoval(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> 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<EntityStore> componentAccessor) {
this.spawnsThisRound = 0;
this.remainingSpawns = 0;
LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor);
this.roundStart = true;
}
}

View File

@@ -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<EntityStore> {
private static final double SPAWN_LOST_TIMEOUT = 35.0;
@Nonnull
private static final InvalidatablePersistentRef[] EMPTY_REFERENCES = new InvalidatablePersistentRef[0];
public static final ArrayCodec<InvalidatablePersistentRef> NPC_REFERENCES_CODEC = new ArrayCodec<>(
InvalidatablePersistentRef.CODEC, InvalidatablePersistentRef[]::new
);
@Nonnull
public static final BuilderCodec<SpawnMarkerEntity> 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<UUID> 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<Pair<Ref<EntityStore>, NPCEntity>> tempStorageList;
private double timeToDeactivation;
private boolean despawnStarted;
private double spawnLostTimeoutCounter;
public static ComponentType<EntityStore, SpawnMarkerEntity> 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<UUID> 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<Pair<Ref<EntityStore>, NPCEntity>> getTempStorageList() {
return this.tempStorageList;
}
public void setTempStorageList(@Nonnull List<Pair<Ref<EntityStore>, 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<EntityStore> ref, @Nonnull SpawnMarker marker, @Nonnull Store<EntityStore> 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<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
SpatialResource<Ref<EntityStore>, 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<NPCEntity, Ref<EntityStore>, Store<EntityStore>> 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<Ref<EntityStore>, 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<EntityStore> npcRef = npcPair.first();
NPCEntity npcComponent = npcPair.second();
Ref<EntityStore> 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<EntityStore> self,
@Nonnull UUID uuid,
@Nonnull String role,
@Nonnull Vector3d position,
@Nonnull Store<EntityStore> 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<EntityStore> markerRef, @Nonnull Store<EntityStore> 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<EntityStore> 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() {
}
}
}

View File

@@ -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<IndexedStorageFile> METRICS_REGISTRY = new MetricsRegistry<IndexedStorageFile>()
.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<ByteBuffer> 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<? extends OpenOption> 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<? extends OpenOption> 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<? extends OpenOption> 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<OpenOption> 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;
}
}
}
}