Initial commit

This commit is contained in:
luk
2026-01-25 21:02:19 +00:00
commit 0ad4b55303
43 changed files with 19721 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.gradle/
build/
repo/
patcher
Assets
.hytale-downloader-credentials.json

150
build.gradle.kts Normal file
View File

@@ -0,0 +1,150 @@
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
// Text file extensions for LF normalization
val textExtensions = setOf(".kt", ".java", ".properties", ".json", ".toml", ".xml", ".txt", ".MF")
fun isTextFile(name: String) = textExtensions.any { name.endsWith(it) } || name.startsWith("META-INF/services/")
fun normalizeContent(name: String, content: ByteArray): ByteArray {
if (!isTextFile(name)) return content
return content.toString(Charsets.UTF_8)
.replace("\r\n", "\n").replace("\r", "\n")
.toByteArray(Charsets.UTF_8)
}
fun writeStoredEntry(output: ZipOutputStream, name: String, content: ByteArray) {
val normalized = normalizeContent(name, content)
val entry = ZipEntry(name).apply {
time = 0
size = normalized.size.toLong()
compressedSize = normalized.size.toLong()
crc = CRC32().apply { update(normalized) }.value
method = ZipEntry.STORED
}
output.putNextEntry(entry)
output.write(normalized)
output.closeEntry()
}
plugins {
id("java")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
repositories {
mavenCentral()
maven {
name = "hytale-release"
url = uri("https://maven.hytale.com/release")
}
maven {
name = "hytale-pre-release"
url = uri("https://maven.hytale.com/pre-release")
}
}
val hytaleServer: Configuration by configurations.creating
val patchedJar = rootDir.resolve("repo/applications/HytaleServerPatched.jar")
dependencies {
hytaleServer("com.hypixel.hytale:Server:2026.01.22-6f8bdbdc4")
}
configurations {
compileOnly.get().extendsFrom(hytaleServer)
}
tasks.withType<JavaCompile> {
options.compilerArgs.addAll(listOf(
"--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"
))
}
sourceSets {
main {
java {
// Patch files go in runtime/hytale-server/src/
// Use the same package structure as the original JAR
// e.g., src/com/hypixel/hytale/server/SomeClass.java
srcDir("src")
}
}
}
// Task to create the patched JAR by overlaying compiled classes on top of original
tasks.register("patchJar") {
group = "coldfusion"
description = "Creates a patched HytaleServer JAR with compiled modifications"
dependsOn(tasks.compileJava)
inputs.files(hytaleServer)
inputs.files(tasks.compileJava.get().outputs.files).optional()
outputs.file(patchedJar)
doLast {
val compiledClassesDir = tasks.compileJava.get().destinationDirectory.get().asFile
val originalJar = hytaleServer.singleFile
// Collect all compiled class files with their relative paths
val patchedFiles = mutableMapOf<String, ByteArray>()
if (compiledClassesDir.exists()) {
compiledClassesDir.walkTopDown()
.filter { it.isFile }
.forEach { file ->
val relativePath = file.relativeTo(compiledClassesDir).path.replace(File.separatorChar, '/')
patchedFiles[relativePath] = file.readBytes()
}
}
logger.lifecycle("Patching JAR with ${patchedFiles.size} compiled files")
// Collect all entries: patched files override original
val allEntries = mutableMapOf<String, ByteArray?>() // null = directory
ZipFile(originalJar).use { original ->
original.entries().asSequence().forEach { entry ->
allEntries[entry.name] = if (entry.isDirectory) null else original.getInputStream(entry).readBytes()
}
}
// Override with patched files
patchedFiles.forEach { (path, content) ->
logger.lifecycle(" Patching: $path")
allEntries[path] = content
}
// Write sorted entries with STORED compression and LF normalization
patchedJar.outputStream().buffered().let { ZipOutputStream(it) }.use { output ->
output.setMethod(ZipOutputStream.STORED)
allEntries.keys.sorted().forEach { name ->
val content = allEntries[name]
if (content == null) {
// Directory
val entry = ZipEntry(name).apply {
time = 0
size = 0
compressedSize = 0
crc = 0
method = ZipEntry.STORED
}
output.putNextEntry(entry)
output.closeEntry()
} else {
writeStoredEntry(output, name, content)
}
}
}
logger.lifecycle("Created patched JAR: $patchedJar")
}
}

Binary file not shown.

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Pull files from repo/hytale-server/src into vendor/hytale-server/src
# Only overrides files that exist in both locations
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HYTALE_SERVER_ROOT="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$(dirname "$HYTALE_SERVER_ROOT")")"
SRC_DIR="$HYTALE_SERVER_ROOT/src"
REPO_SRC="$PROJECT_ROOT/repo/hytale-server/src"
if [ ! -d "$SRC_DIR" ]; then
echo "Error: src does not exist"
exit 1
fi
if [ ! -d "$REPO_SRC" ]; then
echo "Error: repo/hytale-server/src does not exist"
exit 1
fi
count=0
# Find all files in src
while IFS= read -r -d '' file; do
# Get relative path from SRC_DIR
rel_path="${file#$SRC_DIR/}"
# Check if corresponding file exists in repo
repo_file="$REPO_SRC/$rel_path"
if [ -f "$repo_file" ]; then
cp "$repo_file" "$file"
echo "Updated: $rel_path"
count=$((count + 1))
fi
done < <(find "$SRC_DIR" -type f -print0)
echo ""
echo "Done. Updated $count file(s)."

38
scripts/update-server.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HYTALE_SERVER_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_ROOT="$(cd "$HYTALE_SERVER_ROOT/../.." && pwd)"
DOWNLOADER="$SCRIPT_DIR/hytale-downloader-linux-amd64"
OUTPUT_DIR="$PROJECT_ROOT/repo/hytale-server"
if [ ! -f "$DOWNLOADER" ]; then
echo "Error: Downloader not found at $DOWNLOADER"
exit 1
fi
echo "[update-server] Creating output directory..."
mkdir -p "$OUTPUT_DIR"
echo "[update-server] Downloading Hytale server files..."
cd "$SCRIPT_DIR"
"$DOWNLOADER" -download-path "$OUTPUT_DIR/game.zip"
echo "[update-server] Extracting server files..."
unzip -o -q "$OUTPUT_DIR/game.zip" -d "$OUTPUT_DIR"
rm -f "$OUTPUT_DIR/game.zip"
echo "[update-server] Verifying server files..."
if [ ! -f "$OUTPUT_DIR/Server/HytaleServer.jar" ]; then
echo "Error: HytaleServer.jar not found"
exit 1
fi
if [ ! -f "$OUTPUT_DIR/Assets.zip" ]; then
echo "Error: Assets.zip not found"
exit 1
fi
echo "[update-server] Server update completed successfully"
echo "[update-server] Files located at: $OUTPUT_DIR"

View File

@@ -0,0 +1,295 @@
package coldfusion.hytaleserver;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Guards against inventory sharing between players (Issue #45).
* <p>
* This class is called by the patched LivingEntity.setInventory() method
* to validate that an Inventory isn't being shared between multiple players.
* <p>
* If a shared reference is detected, the inventory is deep-cloned to break
* the shared reference before assignment.
*/
public class InventoryOwnershipGuard {
private static final Logger LOGGER = Logger.getLogger("HyFixes");
// Track inventory -> owner UUID
private static final Map<Integer, UUID> inventoryOwners = new ConcurrentHashMap<>();
private static final Map<Integer, UUID> containerOwners = new ConcurrentHashMap<>();
// Statistics
private static volatile int sharedReferencesDetected = 0;
private static volatile int inventoriesCloned = 0;
private static volatile int validationCalls = 0;
// Cached reflection objects
private static Method getPlayerRefMethod;
private static Method getUuidMethod;
private static Class<?> playerClass;
private static boolean reflectionInitialized = false;
private static boolean reflectionFailed = false;
/**
* Called by patched LivingEntity.setInventory() BEFORE inventory assignment.
*/
public static Object validateAndClone(Object livingEntity, Object inventory) {
if (inventory == null || livingEntity == null) {
return inventory;
}
validationCalls++;
try {
if (!reflectionInitialized && !reflectionFailed) {
initializeReflection();
}
if (reflectionFailed) {
return inventory;
}
// Only validate for Player entities
if (!playerClass.isInstance(livingEntity)) {
return inventory;
}
UUID newOwnerUuid = getEntityUuid(livingEntity);
if (newOwnerUuid == null) {
return inventory;
}
int inventoryId = System.identityHashCode(inventory);
UUID currentOwner = inventoryOwners.get(inventoryId);
if (currentOwner != null && !currentOwner.equals(newOwnerUuid)) {
// SHARED REFERENCE DETECTED!
sharedReferencesDetected++;
LOGGER.log(Level.WARNING,
"[HyFix #45] INVENTORY SHARING DETECTED!\n" +
" Inventory @{0} owned by {1} being assigned to {2}\n" +
" Forcing deep clone to prevent inventory sync.",
new Object[]{
Integer.toHexString(inventoryId),
currentOwner.toString().substring(0, 8) + "...",
newOwnerUuid.toString().substring(0, 8) + "..."
}
);
Object clonedInventory = cloneInventory(inventory);
if (clonedInventory != null) {
inventoriesCloned++;
int clonedId = System.identityHashCode(clonedInventory);
inventoryOwners.put(clonedId, newOwnerUuid);
registerContainers(clonedInventory, newOwnerUuid);
return clonedInventory;
} else {
LOGGER.log(Level.SEVERE,
"[HyFix #45] CRITICAL: Failed to clone inventory!");
}
} else {
inventoryOwners.put(inventoryId, newOwnerUuid);
registerContainers(inventory, newOwnerUuid);
}
} catch (Exception e) {
if (validationCalls < 5) {
LOGGER.log(Level.WARNING,
"[HyFix] Error in inventory validation: " + e.getMessage());
}
}
return inventory;
}
private static synchronized void initializeReflection() {
if (reflectionInitialized || reflectionFailed) {
return;
}
try {
playerClass = Class.forName("com.hypixel.hytale.server.core.entity.entities.Player");
getPlayerRefMethod = playerClass.getMethod("getPlayerRef");
Class<?> playerRefClass = Class.forName("com.hypixel.hytale.server.core.universe.PlayerRef");
getUuidMethod = playerRefClass.getMethod("getUuid");
reflectionInitialized = true;
LOGGER.log(Level.INFO, "[HyFix] InventoryOwnershipGuard initialized");
} catch (Exception e) {
reflectionFailed = true;
LOGGER.log(Level.WARNING,
"[HyFix] Failed to initialize InventoryOwnershipGuard: " + e.getMessage());
}
}
private static UUID getEntityUuid(Object livingEntity) {
try {
Object playerRef = getPlayerRefMethod.invoke(livingEntity);
if (playerRef == null) {
return null;
}
return (UUID) getUuidMethod.invoke(playerRef);
} catch (Exception e) {
return null;
}
}
private static void registerContainers(Object inventory, UUID ownerUuid) {
try {
String[] containerGetters = {"getStorage", "getArmor", "getHotbar", "getUtility", "getTools", "getBackpack"};
for (String getter : containerGetters) {
try {
Method method = inventory.getClass().getMethod(getter);
Object container = method.invoke(inventory);
if (container != null) {
containerOwners.put(System.identityHashCode(container), ownerUuid);
}
} catch (NoSuchMethodException ignored) {
}
}
} catch (Exception e) {
}
}
private static Object cloneInventory(Object inventory) {
Object cloned = cloneViaCodec(inventory);
if (cloned != null) {
return cloned;
}
cloned = cloneViaContainers(inventory);
if (cloned != null) {
return cloned;
}
LOGGER.log(Level.SEVERE, "[HyFix] All inventory clone methods failed!");
return null;
}
private static Object cloneViaCodec(Object inventory) {
try {
Class<?> inventoryClass = inventory.getClass();
Field codecField = inventoryClass.getField("CODEC");
Object codec = codecField.get(null);
Class<?> extraInfoClass = Class.forName("com.hypixel.hytale.codec.ExtraInfo");
Field threadLocalField = extraInfoClass.getField("THREAD_LOCAL");
ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
Object extraInfo = threadLocal.get();
if (extraInfo == null) {
return null;
}
Method encodeMethod = findMethod(codec.getClass(), "encode", Object.class, extraInfoClass);
if (encodeMethod == null) {
return null;
}
Object bsonValue = encodeMethod.invoke(codec, inventory, extraInfo);
Constructor<?> defaultConstructor = inventoryClass.getDeclaredConstructor();
defaultConstructor.setAccessible(true);
Object newInventory = defaultConstructor.newInstance();
Class<?> bsonValueClass = Class.forName("org.bson.BsonValue");
Method decodeMethod = findMethod(codec.getClass(), "decode", bsonValueClass, Object.class, extraInfoClass);
if (decodeMethod == null) {
return null;
}
decodeMethod.invoke(codec, bsonValue, newInventory, extraInfo);
return newInventory;
} catch (Exception e) {
return null;
}
}
private static Object cloneViaContainers(Object inventory) {
try {
Class<?> inventoryClass = inventory.getClass();
Object storage = invokeGetter(inventory, "getStorage");
Object armor = invokeGetter(inventory, "getArmor");
Object hotbar = invokeGetter(inventory, "getHotbar");
Object utility = invokeGetter(inventory, "getUtility");
Object tools = invokeGetter(inventory, "getTools");
Object backpack = invokeGetter(inventory, "getBackpack");
Object clonedStorage = cloneContainer(storage);
Object clonedArmor = cloneContainer(armor);
Object clonedHotbar = cloneContainer(hotbar);
Object clonedUtility = cloneContainer(utility);
Object clonedTools = cloneContainer(tools);
Object clonedBackpack = cloneContainer(backpack);
Class<?> containerClass = Class.forName("com.hypixel.hytale.server.core.inventory.container.ItemContainer");
Constructor<?> constructor = inventoryClass.getConstructor(
containerClass, containerClass, containerClass,
containerClass, containerClass, containerClass
);
return constructor.newInstance(
clonedStorage, clonedArmor, clonedHotbar,
clonedUtility, clonedTools, clonedBackpack
);
} catch (Exception e) {
return null;
}
}
private static Object cloneContainer(Object container) {
if (container == null) {
return null;
}
try {
Method cloneMethod = container.getClass().getMethod("clone");
return cloneMethod.invoke(container);
} catch (Exception e) {
return container;
}
}
private static Object invokeGetter(Object obj, String methodName) {
try {
Method method = obj.getClass().getMethod(methodName);
return method.invoke(obj);
} catch (Exception e) {
return null;
}
}
private static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
try {
return clazz.getMethod(name, paramTypes);
} catch (NoSuchMethodException e) {
for (Method m : clazz.getMethods()) {
if (m.getName().equals(name) && m.getParameterCount() == paramTypes.length) {
return m;
}
}
return null;
}
}
public static void onPlayerDisconnect(UUID playerUuid) {
if (playerUuid == null) {
return;
}
inventoryOwners.entrySet().removeIf(entry -> playerUuid.equals(entry.getValue()));
containerOwners.entrySet().removeIf(entry -> playerUuid.equals(entry.getValue()));
}
}

View File

@@ -0,0 +1,86 @@
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

@@ -0,0 +1,114 @@
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

@@ -0,0 +1,982 @@
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

@@ -0,0 +1,840 @@
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

@@ -0,0 +1,644 @@
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

@@ -0,0 +1,356 @@
package com.hypixel.hytale.component;
import com.hypixel.hytale.common.util.ArrayUtil;
import com.hypixel.hytale.function.consumer.IntObjectConsumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
public class ArchetypeChunk<ECS_TYPE> {
@Nonnull
private static final ArchetypeChunk[] EMPTY_ARRAY = new ArchetypeChunk[0];
@Nonnull
protected final Store<ECS_TYPE> store;
@Nonnull
protected final Archetype<ECS_TYPE> archetype;
protected int entitiesSize;
@Nonnull
protected Ref<ECS_TYPE>[] refs = new Ref[16];
protected Component<ECS_TYPE>[][] components;
public static <ECS_TYPE> ArchetypeChunk<ECS_TYPE>[] emptyArray() {
return EMPTY_ARRAY;
}
public ArchetypeChunk(@Nonnull Store<ECS_TYPE> store, @Nonnull Archetype<ECS_TYPE> archetype) {
this.store = store;
this.archetype = archetype;
this.components = new Component[archetype.length()][];
for (int i = archetype.getMinIndex(); i < archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) archetype.get(i);
if (componentType != null) {
this.components[componentType.getIndex()] = new Component[16];
}
}
}
@Nonnull
public Archetype<ECS_TYPE> getArchetype() {
return this.archetype;
}
public int size() {
return this.entitiesSize;
}
@Nonnull
public Ref<ECS_TYPE> getReferenceTo(int index) {
if (index >= 0 && index < this.entitiesSize) {
return this.refs[index];
} else {
throw new IndexOutOfBoundsException(index);
}
}
public <T extends Component<ECS_TYPE>> void setComponent(int index, @Nonnull ComponentType<ECS_TYPE, T> componentType, @Nonnull T component) {
componentType.validateRegistry(this.store.getRegistry());
if (index < 0 || index >= this.entitiesSize) {
throw new IndexOutOfBoundsException(index);
} else if (!this.archetype.contains(componentType)) {
throw new IllegalArgumentException("Entity doesn't have component type " + componentType);
} else {
this.components[componentType.getIndex()][index] = component;
}
}
@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;
}
}
public int addEntity(@Nonnull Ref<ECS_TYPE> ref, @Nonnull Holder<ECS_TYPE> holder) {
if (!this.archetype.equals(holder.getArchetype())) {
throw new IllegalArgumentException("EntityHolder is not for this archetype chunk!");
} else {
int entityIndex = this.entitiesSize++;
int oldLength = this.refs.length;
if (oldLength <= entityIndex) {
int newLength = ArrayUtil.grow(entityIndex);
this.refs = Arrays.copyOf(this.refs, newLength);
for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(i);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
this.components[componentTypeIndex] = Arrays.copyOf(this.components[componentTypeIndex], newLength);
}
}
}
this.refs[entityIndex] = ref;
for (int ix = this.archetype.getMinIndex(); ix < this.archetype.length(); ix++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(ix);
if (componentType != null) {
this.components[componentType.getIndex()][entityIndex] = holder.getComponent((ComponentType<ECS_TYPE, Component<ECS_TYPE>>) componentType);
}
}
return entityIndex;
}
}
@Nonnull
public Holder<ECS_TYPE> copyEntity(int entityIndex, @Nonnull Holder<ECS_TYPE> target) {
if (entityIndex >= this.entitiesSize) {
throw new IndexOutOfBoundsException(entityIndex);
} else {
Component<ECS_TYPE>[] entityComponents = target.ensureComponentsSize(this.archetype.length());
for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(i);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
Component<ECS_TYPE> component = this.components[componentTypeIndex][entityIndex];
entityComponents[componentTypeIndex] = component.clone();
}
}
target.init(this.archetype, entityComponents);
return target;
}
}
@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());
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)");
return target;
}
}
@Nonnull
public Holder<ECS_TYPE> removeEntity(int entityIndex, @Nonnull Holder<ECS_TYPE> target) {
if (entityIndex >= this.entitiesSize) {
throw new IndexOutOfBoundsException(entityIndex);
} else {
Component<ECS_TYPE>[] entityComponents = target.ensureComponentsSize(this.archetype.length());
for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(i);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
entityComponents[componentTypeIndex] = this.components[componentTypeIndex][entityIndex];
}
}
int lastIndex = this.entitiesSize - 1;
if (entityIndex != lastIndex) {
this.fillEmptyIndex(entityIndex, lastIndex);
}
this.refs[lastIndex] = null;
for (int ix = this.archetype.getMinIndex(); ix < this.archetype.length(); ix++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(ix);
if (componentType != null) {
this.components[componentType.getIndex()][lastIndex] = null;
}
}
this.entitiesSize = lastIndex;
target.init(this.archetype, entityComponents);
return target;
}
}
public void transferTo(
@Nonnull Holder<ECS_TYPE> tempInternalEntityHolder,
@Nonnull ArchetypeChunk<ECS_TYPE> chunk,
@Nonnull Consumer<Holder<ECS_TYPE>> modification,
@Nonnull IntObjectConsumer<Ref<ECS_TYPE>> referenceConsumer
) {
Component<ECS_TYPE>[] entityComponents = new Component[this.archetype.length()];
for (int entityIndex = 0; entityIndex < this.entitiesSize; entityIndex++) {
Ref<ECS_TYPE> ref = this.refs[entityIndex];
this.refs[entityIndex] = null;
for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(i);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
entityComponents[componentTypeIndex] = this.components[componentTypeIndex][entityIndex];
this.components[componentTypeIndex][entityIndex] = null;
}
}
tempInternalEntityHolder._internal_init(this.archetype, entityComponents, this.store.getRegistry().getUnknownComponentType());
modification.accept(tempInternalEntityHolder);
int newEntityIndex = chunk.addEntity(ref, tempInternalEntityHolder);
referenceConsumer.accept(newEntityIndex, ref);
}
this.entitiesSize = 0;
}
public void transferSomeTo(
@Nonnull Holder<ECS_TYPE> tempInternalEntityHolder,
@Nonnull ArchetypeChunk<ECS_TYPE> chunk,
@Nonnull IntPredicate shouldTransfer,
@Nonnull Consumer<Holder<ECS_TYPE>> modification,
@Nonnull IntObjectConsumer<Ref<ECS_TYPE>> referenceConsumer
) {
int firstTransfered = Integer.MIN_VALUE;
int lastTransfered = Integer.MIN_VALUE;
Component<ECS_TYPE>[] entityComponents = new Component[this.archetype.length()];
for (int entityIndex = 0; entityIndex < this.entitiesSize; entityIndex++) {
if (shouldTransfer.test(entityIndex)) {
if (firstTransfered == Integer.MIN_VALUE) {
firstTransfered = entityIndex;
}
lastTransfered = entityIndex;
Ref<ECS_TYPE> ref = this.refs[entityIndex];
this.refs[entityIndex] = null;
for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(i);
if (componentType != null) {
int componentTypeIndex = componentType.getIndex();
entityComponents[componentTypeIndex] = this.components[componentTypeIndex][entityIndex];
this.components[componentTypeIndex][entityIndex] = null;
}
}
tempInternalEntityHolder.init(this.archetype, entityComponents);
modification.accept(tempInternalEntityHolder);
int newEntityIndex = chunk.addEntity(ref, tempInternalEntityHolder);
referenceConsumer.accept(newEntityIndex, ref);
}
}
if (firstTransfered != Integer.MIN_VALUE) {
if (lastTransfered == this.entitiesSize - 1) {
this.entitiesSize = firstTransfered;
return;
}
int newSize = this.entitiesSize - (lastTransfered - firstTransfered + 1);
for (int entityIndexx = firstTransfered; entityIndexx <= lastTransfered; entityIndexx++) {
if (this.refs[entityIndexx] == null) {
int lastIndex = this.entitiesSize - 1;
if (lastIndex == lastTransfered) {
break;
}
if (entityIndexx != lastIndex) {
this.fillEmptyIndex(entityIndexx, lastIndex);
}
this.entitiesSize--;
}
}
this.entitiesSize = newSize;
}
}
protected void fillEmptyIndex(int entityIndex, int lastIndex) {
Ref<ECS_TYPE> ref = this.refs[lastIndex];
this.store.setEntityChunkIndex(ref, entityIndex);
for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype.get(i);
if (componentType != null) {
Component<ECS_TYPE>[] componentArr = this.components[componentType.getIndex()];
componentArr[entityIndex] = componentArr[lastIndex];
}
}
this.refs[entityIndex] = ref;
}
public void appendDump(@Nonnull String prefix, @Nonnull StringBuilder sb) {
sb.append(prefix).append("archetype=").append(this.archetype).append("\n");
sb.append(prefix).append("entitiesSize=").append(this.entitiesSize).append("\n");
for (int i = 0; i < this.entitiesSize; i++) {
sb.append(prefix).append("\t- ").append(this.refs[i]).append("\n");
sb.append(prefix).append("\t").append("components=").append("\n");
for (int x = this.archetype.getMinIndex(); x < this.archetype.length(); x++) {
ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>) this.archetype
.get(x);
if (componentType != null) {
sb.append(prefix)
.append("\t\t- ")
.append(componentType.getIndex())
.append("\t")
.append(this.components[componentType.getIndex()][x])
.append("\n");
}
}
}
}
@Nonnull
@Override
public String toString() {
return "ArchetypeChunk{archetype="
+ this.archetype
+ ", entitiesSize="
+ this.entitiesSize
+ ", entityReferences="
+ Arrays.toString((Object[]) this.refs)
+ ", components="
+ Arrays.toString((Object[]) this.components)
+ "}";
}
}

View File

@@ -0,0 +1,375 @@
package com.hypixel.hytale.component;
import com.hypixel.hytale.component.event.EntityEventType;
import com.hypixel.hytale.component.event.WorldEventType;
import com.hypixel.hytale.component.system.EcsEvent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Consumer;
public class CommandBuffer<ECS_TYPE> implements ComponentAccessor<ECS_TYPE> {
@Nonnull
private final Store<ECS_TYPE> store;
@Nonnull
private final Deque<Consumer<Store<ECS_TYPE>>> queue = new ArrayDeque<>();
@Nullable
private Ref<ECS_TYPE> trackedRef;
private boolean trackedRefRemoved;
@Nullable
private CommandBuffer<ECS_TYPE> parentBuffer;
@Nullable
private Thread thread;
protected CommandBuffer(@Nonnull Store<ECS_TYPE> store) {
this.store = store;
assert this.setThread();
}
@Nonnull
public Store<ECS_TYPE> getStore() {
return this.store;
}
public void run(@Nonnull Consumer<Store<ECS_TYPE>> consumer) {
assert Thread.currentThread() == this.thread;
this.queue.add(consumer);
}
@Override
public <T extends Component<ECS_TYPE>> T getComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType) {
assert Thread.currentThread() == this.thread;
return this.store.__internal_getComponent(ref, componentType);
}
@Nonnull
@Override
public Archetype<ECS_TYPE> getArchetype(@Nonnull Ref<ECS_TYPE> ref) {
assert Thread.currentThread() == this.thread;
return this.store.__internal_getArchetype(ref);
}
@Nonnull
@Override
public <T extends Resource<ECS_TYPE>> T getResource(@Nonnull ResourceType<ECS_TYPE, T> resourceType) {
assert Thread.currentThread() == this.thread;
return this.store.__internal_getResource(resourceType);
}
@Nonnull
@Override
public ECS_TYPE getExternalData() {
return this.store.getExternalData();
}
@Nonnull
@Override
public Ref<ECS_TYPE>[] addEntities(@Nonnull Holder<ECS_TYPE>[] holders, @Nonnull AddReason reason) {
assert Thread.currentThread() == this.thread;
Ref<ECS_TYPE>[] refs = new Ref[holders.length];
for (int i = 0; i < holders.length; i++) {
refs[i] = new Ref<>(this.store);
}
this.queue.add(chunk -> chunk.addEntities(holders, refs, reason));
return refs;
}
@Nonnull
@Override
public Ref<ECS_TYPE> addEntity(@Nonnull Holder<ECS_TYPE> holder, @Nonnull AddReason reason) {
assert Thread.currentThread() == this.thread;
Ref<ECS_TYPE> ref = new Ref<>(this.store);
this.queue.add(chunk -> chunk.addEntity(holder, ref, reason));
return ref;
}
public void addEntities(
@Nonnull Holder<ECS_TYPE>[] holders, int holderStart, @Nonnull Ref<ECS_TYPE>[] refs, int refStart, int length, @Nonnull AddReason reason
) {
assert Thread.currentThread() == this.thread;
for (int i = refStart; i < refStart + length; i++) {
refs[i] = new Ref<>(this.store);
}
this.queue.add(chunk -> chunk.addEntities(holders, holderStart, refs, refStart, length, reason));
}
@Nonnull
public Ref<ECS_TYPE> addEntity(@Nonnull Holder<ECS_TYPE> holder, @Nonnull Ref<ECS_TYPE> ref, @Nonnull AddReason reason) {
if (ref.isValid()) {
throw new IllegalArgumentException("EntityReference is already in use!");
} else if (ref.getStore() != this.store) {
throw new IllegalArgumentException("EntityReference is not for the correct store!");
} else {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> chunk.addEntity(holder, ref, reason));
return ref;
}
}
@Nonnull
public Holder<ECS_TYPE> copyEntity(@Nonnull Ref<ECS_TYPE> ref, @Nonnull Holder<ECS_TYPE> target) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> chunk.copyEntity(ref, target));
return target;
}
public void tryRemoveEntity(@Nonnull Ref<ECS_TYPE> ref, @Nonnull RemoveReason reason) {
assert Thread.currentThread() == this.thread;
Throwable source = new Throwable();
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.removeEntity(ref, chunk.getRegistry().newHolder(), reason, source);
}
});
if (ref.equals(this.trackedRef)) {
this.trackedRefRemoved = true;
}
if (this.parentBuffer != null) {
this.parentBuffer.testRemovedTracked(ref);
}
}
public void removeEntity(@Nonnull Ref<ECS_TYPE> ref, @Nonnull RemoveReason reason) {
assert Thread.currentThread() == this.thread;
Throwable source = new Throwable();
this.queue.add(chunk -> chunk.removeEntity(ref, chunk.getRegistry().newHolder(), reason, source));
if (ref.equals(this.trackedRef)) {
this.trackedRefRemoved = true;
}
if (this.parentBuffer != null) {
this.parentBuffer.testRemovedTracked(ref);
}
}
@Nonnull
@Override
public Holder<ECS_TYPE> removeEntity(@Nonnull Ref<ECS_TYPE> ref, @Nonnull Holder<ECS_TYPE> target, @Nonnull RemoveReason reason) {
assert Thread.currentThread() == this.thread;
Throwable source = new Throwable();
this.queue.add(chunk -> chunk.removeEntity(ref, target, reason, source));
if (ref.equals(this.trackedRef)) {
this.trackedRefRemoved = true;
}
if (this.parentBuffer != null) {
this.parentBuffer.testRemovedTracked(ref);
}
return target;
}
public <T extends Component<ECS_TYPE>> void ensureComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.ensureComponent(ref, componentType);
}
});
}
@Nonnull
@Override
public <T extends Component<ECS_TYPE>> T ensureAndGetComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType) {
assert Thread.currentThread() == this.thread;
T component = this.store.__internal_getComponent(ref, componentType);
if (component != null) {
return component;
} else {
T newComponent = this.store.getRegistry()._internal_getData().createComponent(componentType);
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.addComponent(ref, componentType, newComponent);
}
});
return newComponent;
}
}
@Nonnull
@Override
public <T extends Component<ECS_TYPE>> T addComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType) {
assert Thread.currentThread() == this.thread;
T component = this.store.getRegistry()._internal_getData().createComponent(componentType);
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.addComponent(ref, componentType, component);
}
});
return component;
}
@Override
public <T extends Component<ECS_TYPE>> void addComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType, @Nonnull T component) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.addComponent(ref, componentType, component);
}
});
}
public <T extends Component<ECS_TYPE>> void replaceComponent(
@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType, @Nonnull T component
) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.replaceComponent(ref, componentType, component);
}
});
}
@Override
public <T extends Component<ECS_TYPE>> void removeComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> {
if (ref.isValid()) {
// HyFix #12: Use tryRemoveComponent to prevent race condition when multiple systems queue removal
chunk.tryRemoveComponent(ref, componentType);
}
});
}
@Override
public <T extends Component<ECS_TYPE>> void tryRemoveComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.tryRemoveComponent(ref, componentType);
}
});
}
@Override
public <T extends Component<ECS_TYPE>> void putComponent(@Nonnull Ref<ECS_TYPE> ref, @Nonnull ComponentType<ECS_TYPE, T> componentType, @Nonnull T component) {
assert Thread.currentThread() == this.thread;
this.queue.add(chunk -> {
if (ref.isValid()) {
chunk.putComponent(ref, componentType, component);
}
});
}
@Override
public <Event extends EcsEvent> void invoke(@Nonnull Ref<ECS_TYPE> ref, @Nonnull Event param) {
assert Thread.currentThread() == this.thread;
this.store.internal_invoke(this, ref, param);
}
@Override
public <Event extends EcsEvent> void invoke(@Nonnull EntityEventType<ECS_TYPE, Event> systemType, @Nonnull Ref<ECS_TYPE> ref, @Nonnull Event param) {
assert Thread.currentThread() == this.thread;
this.store.internal_invoke(this, systemType, ref, param);
}
@Override
public <Event extends EcsEvent> void invoke(@Nonnull Event param) {
assert Thread.currentThread() == this.thread;
this.store.internal_invoke(this, param);
}
@Override
public <Event extends EcsEvent> void invoke(@Nonnull WorldEventType<ECS_TYPE, Event> systemType, @Nonnull Event param) {
assert Thread.currentThread() == this.thread;
this.store.internal_invoke(this, systemType, param);
}
void track(@Nonnull Ref<ECS_TYPE> ref) {
this.trackedRef = ref;
}
private void testRemovedTracked(@Nonnull Ref<ECS_TYPE> ref) {
if (ref.equals(this.trackedRef)) {
this.trackedRefRemoved = true;
}
if (this.parentBuffer != null) {
this.parentBuffer.testRemovedTracked(ref);
}
}
boolean consumeWasTrackedRefRemoved() {
if (this.trackedRef == null) {
throw new IllegalStateException("Not tracking any ref!");
} else {
boolean wasRemoved = this.trackedRefRemoved;
this.trackedRefRemoved = false;
return wasRemoved;
}
}
void consume() {
this.trackedRef = null;
this.trackedRefRemoved = false;
assert Thread.currentThread() == this.thread;
while (!this.queue.isEmpty()) {
this.queue.pop().accept(this.store);
}
this.store.storeCommandBuffer(this);
}
@Nonnull
public CommandBuffer<ECS_TYPE> fork() {
CommandBuffer<ECS_TYPE> forkedBuffer = this.store.takeCommandBuffer();
forkedBuffer.parentBuffer = this;
return forkedBuffer;
}
public void mergeParallel(@Nonnull CommandBuffer<ECS_TYPE> commandBuffer) {
this.trackedRef = null;
this.trackedRefRemoved = false;
this.parentBuffer = null;
while (!this.queue.isEmpty()) {
commandBuffer.queue.add(this.queue.pop());
}
this.store.storeCommandBuffer(this);
}
public boolean setThread() {
this.thread = Thread.currentThread();
return true;
}
public void validateEmpty() {
if (!this.queue.isEmpty()) {
throw new AssertionError("CommandBuffer must be empty when returned to store!");
}
}
}

View File

@@ -0,0 +1,107 @@
package com.hypixel.hytale.component;
import com.hypixel.hytale.component.query.Query;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ComponentType<ECS_TYPE, T extends Component<ECS_TYPE>> implements Comparable<ComponentType<ECS_TYPE, ?>>, Query<ECS_TYPE> {
@Nonnull
public static final ComponentType[] EMPTY_ARRAY = new ComponentType[0];
private ComponentRegistry<ECS_TYPE> registry;
private Class<? super T> tClass;
private int index;
private boolean invalid = true;
public ComponentType() {
}
void init(@Nonnull ComponentRegistry<ECS_TYPE> registry, @Nonnull Class<? super T> tClass, int index) {
this.registry = registry;
this.tClass = tClass;
this.index = index;
this.invalid = false;
}
@Nonnull
public ComponentRegistry<ECS_TYPE> getRegistry() {
return this.registry;
}
@Nonnull
public Class<? super T> getTypeClass() {
return this.tClass;
}
public int getIndex() {
return this.index;
}
void invalidate() {
this.invalid = true;
}
public boolean isValid() {
return !this.invalid;
}
@Override
public boolean test(@Nonnull Archetype<ECS_TYPE> archetype) {
return archetype.contains(this);
}
@Override
public boolean requiresComponentType(ComponentType<ECS_TYPE, ?> componentType) {
return this.equals(componentType);
}
@Override
public void validateRegistry(@Nonnull ComponentRegistry<ECS_TYPE> registry) {
if (!this.registry.equals(registry)) {
throw new IllegalArgumentException("ComponentType is for a different registry! " + this);
}
}
@Override
public void validate() {
if (this.invalid) {
throw new IllegalStateException("ComponentType is invalid!");
}
}
public int compareTo(@Nonnull ComponentType<ECS_TYPE, ?> o) {
return Integer.compare(this.index, o.index);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
} else if (o != null && this.getClass() == o.getClass()) {
ComponentType<?, ?> that = (ComponentType<?, ?>) o;
return this.index != that.index ? false : this.registry.equals(that.registry);
} else {
return false;
}
}
@Override
public int hashCode() {
int result = this.registry.hashCode();
return 31 * result + this.index;
}
@Nonnull
@Override
public String toString() {
return "ComponentType{registry="
+ this.registry.getClass()
+ "@"
+ this.registry.hashCode()
+ ", typeClass="
+ this.tClass
+ ", index="
+ this.index
+ "}";
}
}

View File

@@ -0,0 +1,129 @@
package com.hypixel.hytale.component.system.tick;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.task.ParallelRangeTask;
import com.hypixel.hytale.component.task.ParallelTask;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.IntConsumer;
public abstract class EntityTickingSystem<ECS_TYPE> extends ArchetypeTickingSystem<ECS_TYPE> {
public EntityTickingSystem() {
}
protected static boolean maybeUseParallel(int archetypeChunkSize, int taskCount) {
return false;
}
protected static boolean useParallel(int archetypeChunkSize, int taskCount) {
return taskCount > 0 || archetypeChunkSize > ParallelRangeTask.PARALLELISM;
}
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return false;
}
@Override
public void tick(float dt, @Nonnull ArchetypeChunk<ECS_TYPE> archetypeChunk, @Nonnull Store<ECS_TYPE> store, @Nonnull CommandBuffer<ECS_TYPE> commandBuffer) {
doTick(this, dt, archetypeChunk, store, commandBuffer);
}
public abstract void tick(float var1, int var2, @Nonnull ArchetypeChunk<ECS_TYPE> var3, @Nonnull Store<ECS_TYPE> var4, @Nonnull CommandBuffer<ECS_TYPE> var5);
static long lastError = 0;
public static <ECS_TYPE> void doTick(
@Nonnull EntityTickingSystem<ECS_TYPE> system,
float dt,
@Nonnull ArchetypeChunk<ECS_TYPE> archetypeChunk,
@Nonnull Store<ECS_TYPE> store,
@Nonnull CommandBuffer<ECS_TYPE> commandBuffer
) {
try {
int archetypeChunkSize = archetypeChunk.size();
if (archetypeChunkSize != 0) {
ParallelTask<EntityTickingSystem.SystemTaskData<ECS_TYPE>> task = store.getParallelTask();
if (system.isParallel(archetypeChunkSize, task.size())) {
ParallelRangeTask<EntityTickingSystem.SystemTaskData<ECS_TYPE>> systemTask = task.appendTask();
systemTask.init(0, archetypeChunkSize);
int i = 0;
for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) {
systemTask.get(i).init(system, dt, archetypeChunk, store, commandBuffer.fork());
}
} else {
for (int index = 0; index < archetypeChunkSize; index++) {
system.tick(dt, index, archetypeChunk, store, commandBuffer);
}
}
}
} catch (Exception e) {
if (lastError - System.currentTimeMillis() > 1000) {
e.printStackTrace();
lastError = System.currentTimeMillis();
}
}
}
public static class SystemTaskData<ECS_TYPE> implements IntConsumer {
@Nullable
private EntityTickingSystem<ECS_TYPE> system;
private float dt;
@Nullable
private ArchetypeChunk<ECS_TYPE> archetypeChunk;
@Nullable
private Store<ECS_TYPE> store;
@Nullable
private CommandBuffer<ECS_TYPE> commandBuffer;
public SystemTaskData() {
}
public void init(
EntityTickingSystem<ECS_TYPE> system, float dt, ArchetypeChunk<ECS_TYPE> archetypeChunk, Store<ECS_TYPE> store, CommandBuffer<ECS_TYPE> commandBuffer
) {
this.system = system;
this.dt = dt;
this.archetypeChunk = archetypeChunk;
this.store = store;
this.commandBuffer = commandBuffer;
}
@Override
public void accept(int index) {
assert this.commandBuffer.setThread();
this.system.tick(this.dt, index, this.archetypeChunk, this.store, this.commandBuffer);
}
public void clear() {
this.system = null;
this.archetypeChunk = null;
this.store = null;
this.commandBuffer = null;
}
public static <ECS_TYPE> void invokeParallelTask(
@Nonnull ParallelTask<EntityTickingSystem.SystemTaskData<ECS_TYPE>> parallelTask, @Nonnull CommandBuffer<ECS_TYPE> commandBuffer
) {
int parallelTaskSize = parallelTask.size();
if (parallelTaskSize > 0) {
parallelTask.doInvoke();
for (int x = 0; x < parallelTaskSize; x++) {
ParallelRangeTask<EntityTickingSystem.SystemTaskData<ECS_TYPE>> systemTask = parallelTask.get(x);
int i = 0;
for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) {
EntityTickingSystem.SystemTaskData<ECS_TYPE> taskData = systemTask.get(i);
taskData.commandBuffer.mergeParallel(commandBuffer);
taskData.clear();
}
}
}
}
}
}

View File

@@ -0,0 +1,517 @@
package com.hypixel.hytale.math.shape;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.function.predicate.TriIntObjPredicate;
import com.hypixel.hytale.function.predicate.TriIntPredicate;
import com.hypixel.hytale.math.Axis;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3i;
import jdk.internal.vm.annotation.ForceInline;
import javax.annotation.Nonnull;
public class Box implements Shape {
public static final Codec<Box> CODEC = BuilderCodec.builder(Box.class, Box::new)
.append(new KeyedCodec<>("Min", Vector3d.CODEC), (box, v) -> box.min.assign(v), box -> box.min)
.add()
.append(new KeyedCodec<>("Max", Vector3d.CODEC), (box, v) -> box.max.assign(v), box -> box.max)
.add()
.validator((box, results) -> {
if (box.width() <= 0.0) {
results.fail("Width is <= 0! Given: " + box.width());
}
if (box.height() <= 0.0) {
results.fail("Height is <= 0! Given: " + box.height());
}
if (box.depth() <= 0.0) {
results.fail("Depth is <= 0! Given: " + box.depth());
}
})
.build();
public static final Box UNIT = new Box(Vector3d.ZERO, Vector3d.ALL_ONES);
@Nonnull
public final Vector3d min = new Vector3d();
@Nonnull
public final Vector3d max = new Vector3d();
@Nonnull
public static Box horizontallyCentered(double width, double height, double depth) {
return new Box(-width / 2.0, 0.0, -depth / 2.0, width / 2.0, height, depth / 2.0);
}
public Box() {
}
public Box(@Nonnull Box box) {
this();
this.min.assign(box.min);
this.max.assign(box.max);
}
public Box(@Nonnull Vector3d min, @Nonnull Vector3d max) {
this();
this.min.assign(min);
this.max.assign(max);
}
public Box(double xMin, double yMin, double zMin, double xMax, double yMax, double zMax) {
this();
this.min.assign(xMin, yMin, zMin);
this.max.assign(xMax, yMax, zMax);
}
public static Box cube(@Nonnull Vector3d min, double side) {
return new Box(min.x, min.y, min.z, min.x + side, min.y + side, min.z + side);
}
public static Box centeredCube(@Nonnull Vector3d center, double inradius) {
return new Box(center.x - inradius, center.y - inradius, center.z - inradius, center.x + inradius, center.y + inradius, center.z + inradius);
}
@Nonnull
public Box setMinMax(@Nonnull Vector3d min, @Nonnull Vector3d max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box setMinMax(@Nonnull double[] min, @Nonnull double[] max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box setMinMax(@Nonnull float[] min, @Nonnull float[] max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box setEmpty() {
this.setMinMax(Double.MAX_VALUE, -Double.MAX_VALUE);
return this;
}
@Nonnull
public Box setMinMax(double min, double max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box union(@Nonnull Box bb) {
if (this.min.x > bb.min.x) {
this.min.x = bb.min.x;
}
if (this.min.y > bb.min.y) {
this.min.y = bb.min.y;
}
if (this.min.z > bb.min.z) {
this.min.z = bb.min.z;
}
if (this.max.x < bb.max.x) {
this.max.x = bb.max.x;
}
if (this.max.y < bb.max.y) {
this.max.y = bb.max.y;
}
if (this.max.z < bb.max.z) {
this.max.z = bb.max.z;
}
return this;
}
@Nonnull
public Box assign(@Nonnull Box other) {
this.min.assign(other.min);
this.max.assign(other.max);
return this;
}
@Nonnull
public Box assign(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
this.min.assign(minX, minY, minZ);
this.max.assign(maxX, maxY, maxZ);
return this;
}
@Nonnull
public Box minkowskiSum(@Nonnull Box bb) {
this.min.subtract(bb.max);
this.max.subtract(bb.min);
return this;
}
@Nonnull
public Box scale(float scale) {
this.min.scale(scale);
this.max.scale(scale);
return this;
}
@Nonnull
public Box normalize() {
if (this.min.x > this.max.x) {
double t = this.min.x;
this.min.x = this.max.x;
this.max.x = t;
}
if (this.min.y > this.max.y) {
double t = this.min.y;
this.min.y = this.max.y;
this.max.y = t;
}
if (this.min.z > this.max.z) {
double t = this.min.z;
this.min.z = this.max.z;
this.max.z = t;
}
return this;
}
@Nonnull
public Box rotateX(float angleInRadians) {
this.min.rotateX(angleInRadians);
this.max.rotateX(angleInRadians);
return this;
}
@Nonnull
public Box rotateY(float angleInRadians) {
this.min.rotateY(angleInRadians);
this.max.rotateY(angleInRadians);
return this;
}
@Nonnull
public Box rotateZ(float angleInRadians) {
this.min.rotateZ(angleInRadians);
this.max.rotateZ(angleInRadians);
return this;
}
@Nonnull
public Box offset(double x, double y, double z) {
this.min.add(x, y, z);
this.max.add(x, y, z);
return this;
}
@Nonnull
public Box offset(@Nonnull Vector3d pos) {
this.min.add(pos);
this.max.add(pos);
return this;
}
@Nonnull
public Box sweep(@Nonnull Vector3d v) {
if (v.x < 0.0) {
this.min.x = this.min.x + v.x;
} else if (v.x > 0.0) {
this.max.x = this.max.x + v.x;
}
if (v.y < 0.0) {
this.min.y = this.min.y + v.y;
} else if (v.y > 0.0) {
this.max.y = this.max.y + v.y;
}
if (v.z < 0.0) {
this.min.z = this.min.z + v.z;
} else if (v.z > 0.0) {
this.max.z = this.max.z + v.z;
}
return this;
}
@Nonnull
public Box extend(double extentX, double extentY, double extentZ) {
this.min.subtract(extentX, extentY, extentZ);
this.max.add(extentX, extentY, extentZ);
return this;
}
public double width() {
return this.max.x - this.min.x;
}
public double height() {
return this.max.y - this.min.y;
}
public double depth() {
return this.max.z - this.min.z;
}
public double dimension(@Nonnull Axis axis) {
return switch (axis) {
case X -> this.width();
case Y -> this.height();
case Z -> this.depth();
};
}
public double getThickness() {
return MathUtil.minValue(this.width(), this.height(), this.depth());
}
public double getMaximumThickness() {
return MathUtil.maxValue(this.width(), this.height(), this.depth());
}
public double getVolume() {
double w = this.width();
if (w <= 0.0) {
return 0.0;
} else {
double h = this.height();
if (h <= 0.0) {
return 0.0;
} else {
double d = this.depth();
return d <= 0.0 ? 0.0 : w * h * d;
}
}
}
public boolean hasVolume() {
return this.min.x <= this.max.x && this.min.y <= this.max.y && this.min.z <= this.max.z;
}
public boolean isIntersecting(@Nonnull Box other) {
return !(this.min.x > other.max.x)
&& !(other.min.x > this.max.x)
&& !(this.min.y > other.max.y)
&& !(other.min.y > this.max.y)
&& !(this.min.z > other.max.z)
&& !(other.min.z > this.max.z);
}
public boolean isUnitBox() {
return this.min.equals(Vector3d.ZERO) && this.max.equals(Vector3d.ALL_ONES);
}
public double middleX() {
return (this.min.x + this.max.x) / 2.0;
}
public double middleY() {
return (this.min.y + this.max.y) / 2.0;
}
public double middleZ() {
return (this.min.z + this.max.z) / 2.0;
}
@Nonnull
public Box clone() {
Box box = new Box();
box.assign(this);
return box;
}
@Nonnull
public Vector3d getMin() {
return this.min;
}
@Nonnull
public Vector3d getMax() {
return this.max;
}
@Nonnull
@Override
public Box getBox(double x, double y, double z) {
return new Box(this.min.getX() + x, this.min.getY() + y, this.min.getZ() + z, this.max.getX() + x, this.max.getY() + y, this.max.getZ() + z);
}
@Override
public boolean containsPosition(double x, double y, double z) {
return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY() && z >= this.min.getZ() && z <= this.max.getZ();
}
@Override
public void expand(double radius) {
this.extend(radius, radius, radius);
}
@ForceInline
public boolean containsBlock(int x, int y, int z) {
int minX = MathUtil.floor(this.min.getX());
int minY = MathUtil.floor(this.min.getY());
int minZ = MathUtil.floor(this.min.getZ());
int maxX = MathUtil.ceil(this.max.getX());
int maxY = MathUtil.ceil(this.max.getY());
int maxZ = MathUtil.ceil(this.max.getZ());
return x >= minX && x < maxX && y >= minY && y < maxY && z >= minZ && z < maxZ;
}
public boolean containsBlock(@Nonnull Vector3i origin, int x, int y, int z) {
return this.containsBlock(x - origin.getX(), y - origin.getY(), z - origin.getZ());
}
@Override
@ForceInline
public boolean forEachBlock(double x, double y, double z, double epsilon, @Nonnull TriIntPredicate consumer) {
int minX = MathUtil.floor(x + this.min.getX() - epsilon);
int minY = MathUtil.floor(y + this.min.getY() - epsilon);
int minZ = MathUtil.floor(z + this.min.getZ() - epsilon);
int maxX = MathUtil.floor(x + this.max.getX() + epsilon);
int maxY = MathUtil.floor(y + this.max.getY() + epsilon);
int maxZ = MathUtil.floor(z + this.max.getZ() + epsilon);
for (int _x = minX; _x <= maxX; _x++) {
for (int _y = minY; _y <= maxY; _y++) {
for (int _z = minZ; _z <= maxZ; _z++) {
if (!consumer.test(_x, _y, _z)) {
return false;
}
}
}
}
return true;
}
@Override
@ForceInline
public <T> boolean forEachBlock(double x, double y, double z, double epsilon, T t, @Nonnull TriIntObjPredicate<T> consumer) {
int minX = MathUtil.floor(x + this.min.getX() - epsilon);
int minY = MathUtil.floor(y + this.min.getY() - epsilon);
int minZ = MathUtil.floor(z + this.min.getZ() - epsilon);
int maxX = MathUtil.floor(x + this.max.getX() + epsilon);
int maxY = MathUtil.floor(y + this.max.getY() + epsilon);
int maxZ = MathUtil.floor(z + this.max.getZ() + epsilon);
for (int _x = minX; _x <= maxX; _x++) {
for (int _y = minY; _y <= maxY; _y++) {
for (int _z = minZ; _z <= maxZ; _z++) {
if (!consumer.test(_x, _y, _z, t)) {
return false;
}
}
}
}
return true;
}
public double getMaximumExtent() {
double maximumExtent = 0.0;
if (-this.min.x > maximumExtent) {
maximumExtent = -this.min.x;
}
if (-this.min.y > maximumExtent) {
maximumExtent = -this.min.y;
}
if (-this.min.z > maximumExtent) {
maximumExtent = -this.min.z;
}
if (this.max.x - 1.0 > maximumExtent) {
maximumExtent = this.max.x - 1.0;
}
if (this.max.y - 1.0 > maximumExtent) {
maximumExtent = this.max.y - 1.0;
}
if (this.max.z - 1.0 > maximumExtent) {
maximumExtent = this.max.z - 1.0;
}
return maximumExtent;
}
@ForceInline
public boolean intersectsLine(@Nonnull Vector3d start, @Nonnull Vector3d end) {
Vector3d direction = end.clone().subtract(start);
double tmin = 0.0;
double tmax = 1.0;
if (Math.abs(direction.x) < 1.0E-10) {
if (start.x < this.min.x || start.x > this.max.x) {
return false;
}
} else {
double t1 = (this.min.x - start.x) / direction.x;
double t2 = (this.max.x - start.x) / direction.x;
if (t1 > t2) {
double temp = t1;
t1 = t2;
t2 = temp;
}
tmin = Math.max(tmin, t1);
tmax = Math.min(tmax, t2);
if (tmin > tmax) {
return false;
}
}
if (Math.abs(direction.y) < 1.0E-10) {
if (start.y < this.min.y || start.y > this.max.y) {
return false;
}
} else {
double t1x = (this.min.y - start.y) / direction.y;
double t2x = (this.max.y - start.y) / direction.y;
if (t1x > t2x) {
double temp = t1x;
t1x = t2x;
t2x = temp;
}
tmin = Math.max(tmin, t1x);
tmax = Math.min(tmax, t2x);
if (tmin > tmax) {
return false;
}
}
if (!(Math.abs(direction.z) < 1.0E-10)) {
double t1xx = (this.min.z - start.z) / direction.z;
double t2xx = (this.max.z - start.z) / direction.z;
if (t1xx > t2xx) {
double temp = t1xx;
t1xx = t2xx;
t2xx = temp;
}
tmin = Math.max(tmin, t1xx);
tmax = Math.min(tmax, t2xx);
return !(tmin > tmax);
} else {
return !(start.z < this.min.z) && !(start.z > this.max.z);
}
}
@Nonnull
@Override
public String toString() {
return "Box{min=" + this.min + ", max=" + this.max + "}";
}
}

View File

@@ -0,0 +1,203 @@
package com.hypixel.hytale.math.shape;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.math.vector.Vector2d;
import javax.annotation.Nonnull;
public class Box2D implements Shape2D {
public static final BuilderCodec<Box2D> CODEC = BuilderCodec.builder(Box2D.class, Box2D::new)
.append(new KeyedCodec<>("Min", Vector2d.CODEC), (shape, min) -> shape.min.assign(min), shape -> shape.min)
.add()
.append(new KeyedCodec<>("Max", Vector2d.CODEC), (shape, max) -> shape.max.assign(max), shape -> shape.max)
.add()
.build();
@Nonnull
public final Vector2d min = new Vector2d();
@Nonnull
public final Vector2d max = new Vector2d();
public Box2D() {
}
public Box2D(@Nonnull Box2D box) {
this();
this.min.assign(box.min);
this.max.assign(box.max);
}
public Box2D(@Nonnull Vector2d min, @Nonnull Vector2d max) {
this();
this.min.assign(min);
this.max.assign(max);
}
public Box2D(double xMin, double yMin, double xMax, double yMax) {
this();
this.min.assign(xMin, yMin);
this.max.assign(xMax, yMax);
}
@Nonnull
public Box2D setMinMax(@Nonnull Vector2d min, @Nonnull Vector2d max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box2D setMinMax(@Nonnull double[] min, @Nonnull double[] max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box2D setMinMax(@Nonnull float[] min, @Nonnull float[] max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box2D setEmpty() {
this.setMinMax(Double.MAX_VALUE, -Double.MAX_VALUE);
return this;
}
@Nonnull
public Box2D setMinMax(double min, double max) {
this.min.assign(min);
this.max.assign(max);
return this;
}
@Nonnull
public Box2D union(@Nonnull Box2D bb) {
if (this.min.x > bb.min.x) {
this.min.x = bb.min.x;
}
if (this.min.y > bb.min.y) {
this.min.y = bb.min.y;
}
if (this.max.x < bb.max.x) {
this.max.x = bb.max.x;
}
if (this.max.y < bb.max.y) {
this.max.y = bb.max.y;
}
return this;
}
@Nonnull
public Box2D assign(@Nonnull Box2D other) {
this.min.assign(other.min);
this.max.assign(other.max);
return this;
}
@Nonnull
public Box2D minkowskiSum(@Nonnull Box2D bb) {
this.min.subtract(bb.max);
this.max.subtract(bb.min);
return this;
}
@Nonnull
public Box2D normalize() {
if (this.min.x > this.max.x) {
double t = this.min.x;
this.min.x = this.max.x;
this.max.x = t;
}
if (this.min.y > this.max.y) {
double t = this.min.y;
this.min.y = this.max.y;
this.max.y = t;
}
return this;
}
@Nonnull
public Box2D offset(@Nonnull Vector2d pos) {
this.min.add(pos);
this.max.add(pos);
return this;
}
@Nonnull
public Box2D sweep(@Nonnull Vector2d v) {
if (v.x < 0.0) {
this.min.x = this.min.x + v.x;
} else if (v.x > 0.0) {
this.max.x = this.max.x + v.x;
}
if (v.y < 0.0) {
this.min.y = this.min.y + v.y;
} else if (v.y > 0.0) {
this.max.y = this.max.y + v.y;
}
return this;
}
@Nonnull
public Box2D extendToInt() {
this.min.floor();
this.max.ceil();
return this;
}
@Nonnull
public Box2D extend(double extentX, double extentY) {
this.min.subtract(extentX, extentY);
this.max.add(extentX, extentY);
return this;
}
public double width() {
return this.max.x - this.min.x;
}
public double height() {
return this.max.y - this.min.y;
}
public boolean isIntersecting(@Nonnull Box2D other) {
return !(this.min.x > other.max.x) && !(other.min.x > this.max.x) && !(this.min.y > other.max.y) && !(other.min.y > this.max.y);
}
@Nonnull
@Override
public Box2D getBox(double x, double y) {
return new Box2D(this.min.getX() + x, this.min.getY() + y, this.max.getX() + x, this.max.getY() + y);
}
@Override
public boolean containsPosition(@Nonnull Vector2d origin, @Nonnull Vector2d position) {
double x = position.getX() - origin.getX();
double y = position.getY() - origin.getY();
return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY();
}
@Override
public boolean containsPosition(@Nonnull Vector2d origin, double xx, double yy) {
double x = xx - origin.getX();
double y = yy - origin.getY();
return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY();
}
@Nonnull
@Override
public String toString() {
return "Box2D{min=" + this.min + ", max=" + this.max + "}";
}
}

View File

@@ -0,0 +1,544 @@
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

@@ -0,0 +1,851 @@
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 + "}";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
package com.hypixel.hytale.server.core.entity;
import coldfusion.hytaleserver.InventoryOwnershipGuard;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.event.EventRegistration;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Transform;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.protocol.MovementStates;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction;
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction;
import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction;
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
import com.hypixel.hytale.server.core.modules.entity.BlockMigrationExtraInfo;
import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.World;
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.TargetUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
public abstract class LivingEntity extends Entity {
@Nonnull
public static final BuilderCodec<LivingEntity> CODEC = BuilderCodec.abstractBuilder(LivingEntity.class, Entity.CODEC)
.append(new KeyedCodec<>("Inventory", Inventory.CODEC), (livingEntity, inventory, extraInfo) -> {
livingEntity.setInventory(inventory);
if (extraInfo instanceof BlockMigrationExtraInfo) {
livingEntity.inventory.doMigration(((BlockMigrationExtraInfo) extraInfo).getBlockMigration());
}
}, (livingEntity, extraInfo) -> livingEntity.inventory)
.add()
.afterDecode(livingEntity -> {
if (livingEntity.inventory == null) {
livingEntity.setInventory(livingEntity.createDefaultInventory());
}
})
.build();
public static final int DEFAULT_ITEM_THROW_SPEED = 6;
@Nonnull
private final StatModifiersManager statModifiersManager = new StatModifiersManager();
private Inventory inventory;
protected double currentFallDistance;
private EventRegistration armorInventoryChangeEventRegistration;
private boolean isEquipmentNetworkOutdated;
public LivingEntity() {
this.setInventory(this.createDefaultInventory());
}
public LivingEntity(@Nonnull World world) {
super(world);
this.setInventory(this.createDefaultInventory());
}
protected abstract Inventory createDefaultInventory();
public boolean canBreathe(
@Nonnull Ref<EntityStore> ref, @Nonnull BlockMaterial breathingMaterial, int fluidId, @Nonnull ComponentAccessor<EntityStore> componentAccessor
) {
boolean invulnerable = componentAccessor.getArchetype(ref).contains(Invulnerable.getComponentType());
return invulnerable || breathingMaterial == BlockMaterial.Empty && fluidId == 0;
}
public static long getPackedMaterialAndFluidAtBreathingHeight(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
World world = componentAccessor.getExternalData().getWorld();
Transform lookVec = TargetUtil.getLook(ref, componentAccessor);
Vector3d position = lookVec.getPosition();
ChunkStore chunkStore = world.getChunkStore();
long chunkIndex = ChunkUtil.indexChunkFromBlock(position.x, position.z);
Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
return chunkRef != null && chunkRef.isValid()
? WorldUtil.getPackedMaterialAndFluidAtPosition(chunkRef, chunkStore.getStore(), position.x, position.y, position.z)
: MathUtil.packLong(BlockMaterial.Empty.ordinal(), 0);
}
public Inventory getInventory() {
return this.inventory;
}
@Nonnull
public Inventory setInventory(Inventory inventory) {
// HyFix #45: Validate inventory ownership before assignment
inventory = (Inventory) InventoryOwnershipGuard.validateAndClone(this, inventory);
return this.setInventory(inventory, false);
}
@Nonnull
public Inventory setInventory(Inventory inventory, boolean ensureCapacity) {
// HyFix #45: Validate inventory ownership before assignment
inventory = (Inventory) InventoryOwnershipGuard.validateAndClone(this, inventory);
List<ItemStack> remainder = ensureCapacity ? new ObjectArrayList<>() : null;
inventory = this.setInventory(inventory, ensureCapacity, remainder);
if (remainder != null && !remainder.isEmpty()) {
ListTransaction<ItemStackTransaction> transactionList = inventory.getCombinedHotbarFirst().addItemStacks(remainder);
for (ItemStackTransaction var6 : transactionList.getList()) {
;
}
}
return inventory;
}
@Nonnull
public Inventory setInventory(Inventory inventory, boolean ensureCapacity, List<ItemStack> remainder) {
// HyFix #45: Validate inventory ownership before assignment
inventory = (Inventory) InventoryOwnershipGuard.validateAndClone(this, inventory);
if (this.inventory != null) {
this.inventory.unregister();
}
if (this.armorInventoryChangeEventRegistration != null) {
this.armorInventoryChangeEventRegistration.unregister();
}
if (ensureCapacity) {
inventory = Inventory.ensureCapacity(inventory, remainder);
}
inventory.setEntity(this);
this.armorInventoryChangeEventRegistration = inventory.getArmor().registerChangeEvent(event -> this.statModifiersManager.setRecalculate(true));
this.inventory = inventory;
return inventory;
}
@Override
public void moveTo(@Nonnull Ref<EntityStore> ref, double locX, double locY, double locZ, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
assert transformComponent != null;
MovementStatesComponent movementStatesComponent = componentAccessor.getComponent(ref, MovementStatesComponent.getComponentType());
assert movementStatesComponent != null;
MovementStates movementStates = movementStatesComponent.getMovementStates();
boolean fallDamageActive = !movementStates.inFluid && !movementStates.climbing && !movementStates.flying && !movementStates.gliding;
if (fallDamageActive) {
Vector3d position = transformComponent.getPosition();
if (!movementStates.onGround) {
if (position.getY() > locY) {
this.currentFallDistance = this.currentFallDistance + (position.getY() - locY);
}
} else {
this.currentFallDistance = 0.0;
}
} else {
this.currentFallDistance = 0.0;
}
super.moveTo(ref, locX, locY, locZ, componentAccessor);
}
public boolean canDecreaseItemStackDurability(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
return false;
}
public boolean canApplyItemStackPenalties(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor) {
return true;
}
@Nullable
public ItemStackSlotTransaction decreaseItemStackDurability(
@Nonnull Ref<EntityStore> ref, @Nullable ItemStack itemStack, int inventoryId, int slotId, @Nonnull ComponentAccessor<EntityStore> componentAccessor
) {
if (!this.canDecreaseItemStackDurability(ref, componentAccessor)) {
return null;
} else if (itemStack == null || itemStack.isEmpty() || itemStack.getItem() == null) {
return null;
} else if (itemStack.isBroken()) {
return null;
} else {
Item item = itemStack.getItem();
ItemContainer section = this.inventory.getSectionById(inventoryId);
if (section == null) {
return null;
} else if (item.getArmor() != null) {
ItemStackSlotTransaction transaction = this.updateItemStackDurability(
ref, itemStack, section, slotId, -item.getDurabilityLossOnHit(), componentAccessor
);
if (transaction.getSlotAfter().isBroken()) {
this.statModifiersManager.setRecalculate(true);
}
return transaction;
} else {
return item.getWeapon() != null
? this.updateItemStackDurability(ref, itemStack, section, slotId, -item.getDurabilityLossOnHit(), componentAccessor)
: null;
}
}
}
@Nullable
public ItemStackSlotTransaction updateItemStackDurability(
@Nonnull Ref<EntityStore> ref,
@Nonnull ItemStack itemStack,
ItemContainer container,
int slotId,
double durabilityChange,
@Nonnull ComponentAccessor<EntityStore> componentAccessor
) {
ItemStack updatedItemStack = itemStack.withIncreasedDurability(durabilityChange);
return container.replaceItemStackInSlot((short) slotId, itemStack, updatedItemStack);
}
public void invalidateEquipmentNetwork() {
this.isEquipmentNetworkOutdated = true;
}
public boolean consumeEquipmentNetworkOutdated() {
boolean temp = this.isEquipmentNetworkOutdated;
this.isEquipmentNetworkOutdated = false;
return temp;
}
@Nonnull
public StatModifiersManager getStatModifiersManager() {
return this.statModifiersManager;
}
public double getCurrentFallDistance() {
return this.currentFallDistance;
}
public void setCurrentFallDistance(double currentFallDistance) {
this.currentFallDistance = currentFallDistance;
}
@Nonnull
@Override
public String toString() {
return "LivingEntity{, " + super.toString() + "}";
}
}

View File

@@ -0,0 +1,568 @@
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);
if (before == null) {
LOGIN_TIMING_LOGGER.at(level).log(message);
} else {
LOGIN_TIMING_LOGGER.at(level).log("%s took %s", 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

@@ -0,0 +1,768 @@
package com.hypixel.hytale.server.core.modules.entity.tracker;
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.ComponentType;
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.SystemGroup;
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.dependency.SystemGroupDependency;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.spatial.SpatialStructure;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
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.packets.entities.EntityUpdates;
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
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.system.NetworkSendableSpatialSystem;
import com.hypixel.hytale.server.core.receiver.IPacketReceiver;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;
public class EntityTrackerSystems {
public static final SystemGroup<EntityStore> FIND_VISIBLE_ENTITIES_GROUP = EntityStore.REGISTRY.registerSystemGroup();
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) {
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;
}
}
public static boolean clear(@Nonnull Ref<EntityStore> viewerRef, @Nonnull Store<EntityStore> store) {
EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType());
if (viewer == null) {
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);
}
}
viewer.sent.clear();
return true;
}
}
public static class AddToVisible extends EntityTickingSystem<EntityStore> {
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Collections.singleton(
new SystemDependency<>(Order.AFTER, EntityTrackerSystems.EnsureVisibleComponent.class)
);
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public AddToVisible(
ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType,
ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType
) {
this.entityViewerComponentType = entityViewerComponentType;
this.visibleComponentType = visibleComponentType;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public Query<EntityStore> getQuery() {
return this.entityViewerComponentType;
}
@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
) {
Ref<EntityStore> viewerRef = archetypeChunk.getReferenceTo(index);
EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.entityViewerComponentType);
for (Ref<EntityStore> ref : viewer.visible) {
commandBuffer.getComponent(ref, this.visibleComponentType).addViewerParallel(viewerRef, viewer);
}
}
}
public static class ClearEntityViewers extends EntityTickingSystem<EntityStore> {
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;
public ClearEntityViewers(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType) {
this.componentType = componentType;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
}
@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
) {
EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType);
viewer.visible.clear();
viewer.lodExcludedCount = 0;
viewer.hiddenCount = 0;
}
}
public static class ClearPreviouslyVisible extends EntityTickingSystem<EntityStore> {
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;
public ClearPreviouslyVisible(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
}
@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
) {
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();
}
}
public static class CollectVisible extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType;
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());
this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, NetworkSendableSpatialSystem.class));
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@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
) {
TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
Vector3d position = transform.getPosition();
EntityTrackerSystems.EntityViewer entityViewer = archetypeChunk.getComponent(index, this.componentType);
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);
}
}
public static class EffectControllerSystem extends EntityTickingSystem<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
@Nonnull
private final ComponentType<EntityStore, EffectControllerComponent> effectControllerComponentType;
@Nonnull
private final Query<EntityStore> query;
public EffectControllerSystem(
@Nonnull ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType,
@Nonnull ComponentType<EntityStore, EffectControllerComponent> effectControllerComponentType
) {
this.visibleComponentType = visibleComponentType;
this.effectControllerComponentType = effectControllerComponentType;
this.query = Query.and(visibleComponentType, effectControllerComponentType);
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.QUEUE_UPDATE_GROUP;
}
@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
) {
EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType);
assert visibleComponent != null;
Ref<EntityStore> entityRef = archetypeChunk.getReferenceTo(index);
EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, this.effectControllerComponentType);
assert effectControllerComponent != null;
if (!visibleComponent.newlyVisibleTo.isEmpty()) {
queueFullUpdate(entityRef, effectControllerComponent, visibleComponent.newlyVisibleTo);
}
if (effectControllerComponent.consumeNetworkOutdated()) {
queueUpdatesFor(entityRef, effectControllerComponent, visibleComponent.visibleTo, visibleComponent.newlyVisibleTo);
}
}
private static void queueFullUpdate(
@Nonnull Ref<EntityStore> ref,
@Nonnull EffectControllerComponent effectControllerComponent,
@Nonnull Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> visibleTo
) {
ComponentUpdate update = new ComponentUpdate();
update.type = ComponentUpdateType.EntityEffects;
update.entityEffectUpdates = effectControllerComponent.createInitUpdates();
for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) {
viewer.queueUpdate(ref, update);
}
}
private static void queueUpdatesFor(
@Nonnull Ref<EntityStore> ref,
@Nonnull EffectControllerComponent effectControllerComponent,
@Nonnull Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> visibleTo,
@Nonnull Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> exclude
) {
ComponentUpdate update = new ComponentUpdate();
update.type = ComponentUpdateType.EntityEffects;
update.entityEffectUpdates = effectControllerComponent.consumeChanges();
if (!exclude.isEmpty()) {
for (Entry<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) {
if (!exclude.containsKey(entry.getKey())) {
entry.getValue().queueUpdate(ref, update);
}
}
} else {
for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) {
viewer.queueUpdate(ref, update);
}
}
}
}
public static class EnsureVisibleComponent extends EntityTickingSystem<EntityStore> {
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Collections.singleton(
new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearPreviouslyVisible.class)
);
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
public EnsureVisibleComponent(
ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType,
ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType
) {
this.entityViewerComponentType = entityViewerComponentType;
this.visibleComponentType = visibleComponentType;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public Query<EntityStore> getQuery() {
return this.entityViewerComponentType;
}
@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
) {
for (Ref<EntityStore> ref : archetypeChunk.getComponent(index, this.entityViewerComponentType).visible) {
if (!commandBuffer.getArchetype(ref).contains(this.visibleComponentType)) {
commandBuffer.ensureComponent(ref, this.visibleComponentType);
}
}
}
}
public static class EntityUpdate {
@Nonnull
private final StampedLock removeLock = new StampedLock();
@Nonnull
private final EnumSet<ComponentUpdateType> removed;
@Nonnull
private final StampedLock updatesLock = new StampedLock();
@Nonnull
private final List<ComponentUpdate> updates;
public EntityUpdate() {
this.removed = EnumSet.noneOf(ComponentUpdateType.class);
this.updates = new ObjectArrayList<>();
}
public EntityUpdate(@Nonnull EntityTrackerSystems.EntityUpdate other) {
this.removed = EnumSet.copyOf(other.removed);
this.updates = new ObjectArrayList<>(other.updates);
}
@Nonnull
public EntityTrackerSystems.EntityUpdate clone() {
return new EntityTrackerSystems.EntityUpdate(this);
}
public void queueRemove(@Nonnull ComponentUpdateType type) {
long stamp = this.removeLock.writeLock();
try {
this.removed.add(type);
} finally {
this.removeLock.unlockWrite(stamp);
}
}
public void queueUpdate(@Nonnull ComponentUpdate update) {
long stamp = this.updatesLock.writeLock();
try {
this.updates.add(update);
} finally {
this.updatesLock.unlockWrite(stamp);
}
}
@Nullable
public ComponentUpdateType[] toRemovedArray() {
return this.removed.isEmpty() ? null : this.removed.toArray(ComponentUpdateType[]::new);
}
@Nullable
public ComponentUpdate[] toUpdatesArray() {
return this.updates.isEmpty() ? null : this.updates.toArray(ComponentUpdate[]::new);
}
}
public static class EntityViewer implements Component<EntityStore> {
private static final float VISIBILITY_UPDATE_INTERVAL = 0.2f;
public int viewRadiusBlocks;
public IPacketReceiver packetReceiver;
public Set<Ref<EntityStore>> visible;
public Map<Ref<EntityStore>, EntityTrackerSystems.EntityUpdate> updates;
public Object2IntMap<Ref<EntityStore>> sent;
public int lodExcludedCount;
public int hiddenCount;
public float visibilityTimeAccumulator;
public static ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> getComponentType() {
return EntityModule.get().getEntityViewerComponentType();
}
public EntityViewer(int viewRadiusBlocks, IPacketReceiver packetReceiver) {
this.viewRadiusBlocks = viewRadiusBlocks;
this.packetReceiver = packetReceiver;
this.visible = new ObjectOpenHashSet<>();
this.updates = new ConcurrentHashMap<>();
this.sent = new Object2IntOpenHashMap<>();
this.sent.defaultReturnValue(-1);
}
public EntityViewer(@Nonnull EntityTrackerSystems.EntityViewer other) {
this.viewRadiusBlocks = other.viewRadiusBlocks;
this.packetReceiver = other.packetReceiver;
this.visible = new HashSet<>(other.visible);
this.updates = new ConcurrentHashMap<>(other.updates.size());
for (Entry<Ref<EntityStore>, EntityTrackerSystems.EntityUpdate> entry : other.updates.entrySet()) {
this.updates.put(entry.getKey(), entry.getValue().clone());
}
this.sent = new Object2IntOpenHashMap<>(other.sent);
this.sent.defaultReturnValue(-1);
}
@Nonnull
@Override
public Component<EntityStore> clone() {
return new EntityTrackerSystems.EntityViewer(this);
}
public void queueRemove(Ref<EntityStore> ref, ComponentUpdateType type) {
if (!this.visible.contains(ref)) {
throw new IllegalArgumentException("Entity is not visible!");
} else {
this.updates.computeIfAbsent(ref, k -> new EntityTrackerSystems.EntityUpdate()).queueRemove(type);
}
}
public void queueUpdate(Ref<EntityStore> ref, ComponentUpdate update) {
if (!this.visible.contains(ref)) {
throw new IllegalArgumentException("Entity is not visible!");
} else {
this.updates.computeIfAbsent(ref, k -> new EntityTrackerSystems.EntityUpdate()).queueUpdate(update);
}
}
/**
* Accumulates dt and returns true if visibility should be processed this tick.
* Called by ClearEntityViewers; CollectVisible checks visibilityTimeAccumulator == 0.
*/
public boolean shouldProcessVisibility(float dt) {
this.visibilityTimeAccumulator += dt;
if (this.visibilityTimeAccumulator >= VISIBILITY_UPDATE_INTERVAL) {
this.visibilityTimeAccumulator = 0;
return true;
}
return false;
}
/**
* Resets all visibility state. Call on world switch to force immediate full refresh.
*/
public void resetVisibility() {
this.visible.clear();
this.sent.clear();
this.updates.clear();
this.visibilityTimeAccumulator = 0;
this.lodExcludedCount = 0;
this.hiddenCount = 0;
}
}
public static class RemoveEmptyVisibleComponent extends EntityTickingSystem<EntityStore> {
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;
public RemoveEmptyVisibleComponent(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
}
@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
) {
if (archetypeChunk.getComponent(index, this.componentType).visibleTo.isEmpty()) {
commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.componentType);
}
}
}
public static class RemoveVisibleComponent extends HolderSystem<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType;
public RemoveVisibleComponent(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
}
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
}
@Override
public void onEntityAdd(@Nonnull Holder<EntityStore> holder, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store) {
}
@Override
public void onEntityRemoved(@Nonnull Holder<EntityStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store) {
holder.removeComponent(this.componentType);
}
}
public static class SendPackets extends EntityTickingSystem<EntityStore> {
public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
public static final ThreadLocal<IntList> INT_LIST_THREAD_LOCAL = ThreadLocal.withInitial(IntArrayList::new);
public static final Set<Dependency<EntityStore>> DEPENDENCIES = Set.of(new SystemGroupDependency<>(Order.AFTER, EntityTrackerSystems.QUEUE_UPDATE_GROUP));
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType;
public SendPackets(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> componentType) {
this.componentType = componentType;
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityStore.SEND_PACKET_GROUP;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return DEPENDENCIES;
}
@Override
public Query<EntityStore> getQuery() {
return this.componentType;
}
@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
) {
EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType);
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());
}
ObjectIterator<it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<Ref<EntityStore>>> iterator = viewer.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)) {
removedEntities.add(entry.getIntValue());
iterator.remove();
if (viewer.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();
while (iteratorx.hasNext()) {
Ref<EntityStore> ref = iteratorx.next();
if (!ref.isValid() || ref.getStore() != store) {
iteratorx.remove();
} else if (!viewer.sent.containsKey(ref)) {
int networkId = commandBuffer.getComponent(ref, NetworkId.getComponentType()).getId();
if (networkId == -1) {
throw new IllegalArgumentException("Invalid entity network id: " + ref);
}
viewer.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()];
int i = 0;
for (Entry<Ref<EntityStore>, EntityTrackerSystems.EntityUpdate> entry : viewer.updates.entrySet()) {
com.hypixel.hytale.protocol.EntityUpdate entityUpdate = packet.updates[i++] = new com.hypixel.hytale.protocol.EntityUpdate();
entityUpdate.networkId = viewer.sent.getInt(entry.getKey());
EntityTrackerSystems.EntityUpdate update = entry.getValue();
entityUpdate.removed = update.toRemovedArray();
entityUpdate.updates = update.toUpdatesArray();
}
viewer.updates.clear();
viewer.packetReceiver.writeNoCache(packet);
}
}
}
public static class Visible implements Component<EntityStore> {
@Nonnull
private final StampedLock lock = new StampedLock();
@Nonnull
public Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> previousVisibleTo = new Object2ObjectOpenHashMap<>();
@Nonnull
public Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> visibleTo = new Object2ObjectOpenHashMap<>();
@Nonnull
public Map<Ref<EntityStore>, EntityTrackerSystems.EntityViewer> newlyVisibleTo = new Object2ObjectOpenHashMap<>();
public Visible() {
}
@Nonnull
public static ComponentType<EntityStore, EntityTrackerSystems.Visible> getComponentType() {
return EntityModule.get().getVisibleComponentType();
}
@Nonnull
@Override
public Component<EntityStore> clone() {
return new EntityTrackerSystems.Visible();
}
public void addViewerParallel(Ref<EntityStore> ref, EntityTrackerSystems.EntityViewer entityViewer) {
long stamp = this.lock.writeLock();
try {
this.visibleTo.put(ref, entityViewer);
if (!this.previousVisibleTo.containsKey(ref)) {
this.newlyVisibleTo.put(ref, entityViewer);
}
} finally {
this.lock.unlockWrite(stamp);
}
}
}
}

View File

@@ -0,0 +1,457 @@
package com.hypixel.hytale.server.core.modules.entity.tracker;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.SystemGroup;
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.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.ItemArmorSlot;
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;
import com.hypixel.hytale.server.core.entity.LivingEntity;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery;
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.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings;
import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent;
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.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class LegacyEntityTrackerSystems {
public LegacyEntityTrackerSystems() {
}
@Deprecated
public static boolean clear(@Nonnull Player player, @Nonnull Holder<EntityStore> holder) {
World world = player.getWorld();
if (world != null && world.isInThread()) {
Ref<EntityStore> ref = player.getReference();
return ref != null && ref.isValid() ? EntityTrackerSystems.clear(ref, world.getEntityStore().getStore()) : false;
} else {
EntityTrackerSystems.EntityViewer entityViewerComponent = holder.getComponent(EntityTrackerSystems.EntityViewer.getComponentType());
if (entityViewerComponent == null) {
return false;
} else {
entityViewerComponent.sent.clear();
return true;
}
}
}
public static class LegacyEntityModel extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType;
private final ComponentType<EntityStore, ModelComponent> modelComponentType;
@Nonnull
private final Query<EntityStore> query;
public LegacyEntityModel(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
this.modelComponentType = ModelComponent.getComponentType();
this.query = Query.and(componentType, this.modelComponentType);
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.QUEUE_UPDATE_GROUP;
}
@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
) {
EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.componentType);
assert visibleComponent != null;
ModelComponent modelComponent = archetypeChunk.getComponent(index, this.modelComponentType);
assert modelComponent != null;
float entityScale = 0.0F;
boolean scaleOutdated = false;
EntityScaleComponent entityScaleComponent = archetypeChunk.getComponent(index, EntityScaleComponent.getComponentType());
if (entityScaleComponent != null) {
entityScale = entityScaleComponent.getScale();
scaleOutdated = entityScaleComponent.consumeNetworkOutdated();
}
boolean modelOutdated = modelComponent.consumeNetworkOutdated();
if (modelOutdated || scaleOutdated) {
queueUpdatesFor(archetypeChunk.getReferenceTo(index), modelComponent, entityScale, visibleComponent.visibleTo);
} else if (!visibleComponent.newlyVisibleTo.isEmpty()) {
queueUpdatesFor(archetypeChunk.getReferenceTo(index), modelComponent, entityScale, visibleComponent.newlyVisibleTo);
}
}
private static void queueUpdatesFor(
Ref<EntityStore> ref, @Nullable ModelComponent model, float entityScale, @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;
for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) {
viewer.queueUpdate(ref, update);
}
}
}
public static class LegacyEntitySkin extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, PlayerSkinComponent> playerSkinComponentComponentType;
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType;
@Nonnull
private final Query<EntityStore> query;
public LegacyEntitySkin(
ComponentType<EntityStore, EntityTrackerSystems.Visible> visibleComponentType,
ComponentType<EntityStore, PlayerSkinComponent> playerSkinComponentComponentType
) {
this.visibleComponentType = visibleComponentType;
this.playerSkinComponentComponentType = playerSkinComponentComponentType;
this.query = Query.and(visibleComponentType, playerSkinComponentComponentType);
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.QUEUE_UPDATE_GROUP;
}
@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
) {
EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType);
assert visibleComponent != null;
PlayerSkinComponent playerSkinComponent = archetypeChunk.getComponent(index, this.playerSkinComponentComponentType);
assert playerSkinComponent != null;
if (playerSkinComponent.consumeNetworkOutdated()) {
queueUpdatesFor(archetypeChunk.getReferenceTo(index), playerSkinComponent, visibleComponent.visibleTo);
} else if (!visibleComponent.newlyVisibleTo.isEmpty()) {
queueUpdatesFor(archetypeChunk.getReferenceTo(index), playerSkinComponent, visibleComponent.newlyVisibleTo);
}
}
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;
update.skin = component.getPlayerSkin();
for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) {
viewer.queueUpdate(ref, update);
}
}
}
public static class LegacyEquipment extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType;
@Nonnull
private final Query<EntityStore> query;
public LegacyEquipment(ComponentType<EntityStore, EntityTrackerSystems.Visible> componentType) {
this.componentType = componentType;
this.query = Query.and(componentType, AllLegacyLivingEntityTypesQuery.INSTANCE);
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.QUEUE_UPDATE_GROUP;
}
@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
) {
EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.componentType);
assert visibleComponent != null;
LivingEntity entity = (LivingEntity) EntityUtils.getEntity(index, archetypeChunk);
assert entity != null;
if (entity.consumeEquipmentNetworkOutdated()) {
queueUpdatesFor(archetypeChunk.getReferenceTo(index), entity, visibleComponent.visibleTo);
} else if (!visibleComponent.newlyVisibleTo.isEmpty()) {
queueUpdatesFor(archetypeChunk.getReferenceTo(index), entity, visibleComponent.newlyVisibleTo);
}
}
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();
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);
Store<EntityStore> store = ref.getStore();
PlayerSettings playerSettings = store.getComponent(ref, PlayerSettings.getComponentType());
if (playerSettings != null) {
PlayerConfig.ArmorVisibilityOption armorVisibilityOption = store.getExternalData()
.getWorld()
.getGameplayConfig()
.getPlayerConfig()
.getArmorVisibilityOption();
if (armorVisibilityOption.canHideHelmet() && playerSettings.hideHelmet()) {
update.equipment.armorIds[ItemArmorSlot.Head.ordinal()] = "";
}
if (armorVisibilityOption.canHideCuirass() && playerSettings.hideCuirass()) {
update.equipment.armorIds[ItemArmorSlot.Chest.ordinal()] = "";
}
if (armorVisibilityOption.canHideGauntlets() && playerSettings.hideGauntlets()) {
update.equipment.armorIds[ItemArmorSlot.Hands.ordinal()] = "";
}
if (armorVisibilityOption.canHidePants() && playerSettings.hidePants()) {
update.equipment.armorIds[ItemArmorSlot.Legs.ordinal()] = "";
}
}
ItemStack itemInHand = inventory.getItemInHand();
update.equipment.rightHandItemId = itemInHand != null ? itemInHand.getItemId() : "Empty";
ItemStack utilityItem = inventory.getUtilityItem();
update.equipment.leftHandItemId = utilityItem != null ? utilityItem.getItemId() : "Empty";
for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) {
viewer.queueUpdate(ref, update);
}
}
}
public static class LegacyHideFromEntity extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
private final ComponentType<EntityStore, PlayerSettings> playerSettingsComponentType;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public LegacyHideFromEntity(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType) {
this.entityViewerComponentType = entityViewerComponentType;
this.playerSettingsComponentType = EntityModule.get().getPlayerSettingsComponentType();
this.query = Query.and(entityViewerComponentType, AllLegacyLivingEntityTypesQuery.INSTANCE);
this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, EntityTrackerSystems.CollectVisible.class));
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.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
) {
Ref<EntityStore> viewerRef = archetypeChunk.getReferenceTo(index);
PlayerSettings settings = archetypeChunk.getComponent(index, this.playerSettingsComponentType);
if (settings == null) {
settings = PlayerSettings.defaults();
}
EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType);
assert entityViewerComponent != null;
Iterator<Ref<EntityStore>> iterator = entityViewerComponent.visible.iterator();
while (iterator.hasNext()) {
Ref<EntityStore> ref = iterator.next();
Entity entity = EntityUtils.getEntity(ref, commandBuffer);
if (entity != null && entity.isHiddenFromLivingEntity(ref, viewerRef, commandBuffer) && canHideEntities(entity, settings)) {
entityViewerComponent.hiddenCount++;
iterator.remove();
}
}
}
private static boolean canHideEntities(Entity entity, @Nonnull PlayerSettings settings) {
return entity instanceof Player && !settings.showEntityMarkers();
}
}
public static class LegacyLODCull extends EntityTickingSystem<EntityStore> {
public static final double ENTITY_LOD_RATIO_DEFAULT = 3.5E-5;
public static double ENTITY_LOD_RATIO = 3.5E-5;
private final ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType;
private final ComponentType<EntityStore, BoundingBox> boundingBoxComponentType;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public LegacyLODCull(ComponentType<EntityStore, EntityTrackerSystems.EntityViewer> entityViewerComponentType) {
this.entityViewerComponentType = entityViewerComponentType;
this.boundingBoxComponentType = BoundingBox.getComponentType();
this.query = Query.and(entityViewerComponentType, TransformComponent.getComponentType());
this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, EntityTrackerSystems.CollectVisible.class));
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.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
) {
EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType);
assert entityViewerComponent != null;
TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
assert transformComponent != null;
Vector3d position = transformComponent.getPosition();
Iterator<Ref<EntityStore>> iterator = entityViewerComponent.visible.iterator();
while (iterator.hasNext()) {
Ref<EntityStore> targetRef = iterator.next();
BoundingBox targetBoundingBoxComponent = commandBuffer.getComponent(targetRef, this.boundingBoxComponentType);
if (targetBoundingBoxComponent != null) {
TransformComponent targetTransformComponent = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType());
if (targetTransformComponent != null) {
double distanceSq = targetTransformComponent.getPosition().distanceSquaredTo(position);
double maximumThickness = targetBoundingBoxComponent.getBoundingBox().getMaximumThickness();
if (maximumThickness < ENTITY_LOD_RATIO * distanceSq) {
entityViewerComponent.lodExcludedCount++;
iterator.remove();
}
}
}
}
}
}
}

View File

@@ -0,0 +1,124 @@
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

@@ -0,0 +1,962 @@
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()));
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()));
return world.addPlayer(playerRefComponent, null, false, false).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

@@ -0,0 +1,660 @@
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

@@ -0,0 +1,429 @@
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

@@ -0,0 +1,241 @@
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

@@ -0,0 +1,157 @@
package com.hypixel.hytale.server.core.universe.world.meta.state;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.common.util.ArrayUtil;
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.Store;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.system.RefSystem;
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.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData;
import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
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.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;
import java.util.logging.Level;
public class RespawnBlock implements Component<ChunkStore> {
public static final BuilderCodec<RespawnBlock> CODEC = BuilderCodec.builder(RespawnBlock.class, RespawnBlock::new)
.append(
new KeyedCodec<>("OwnerUUID", Codec.UUID_BINARY),
(respawnBlockState, uuid) -> respawnBlockState.ownerUUID = uuid,
respawnBlockState -> respawnBlockState.ownerUUID
)
.add()
.build();
private UUID ownerUUID;
public static ComponentType<ChunkStore, RespawnBlock> getComponentType() {
return BlockModule.get().getRespawnBlockComponentType();
}
public RespawnBlock() {
}
public RespawnBlock(UUID ownerUUID) {
this.ownerUUID = ownerUUID;
}
public UUID getOwnerUUID() {
return this.ownerUUID;
}
public void setOwnerUUID(UUID ownerUUID) {
this.ownerUUID = ownerUUID;
}
@Nullable
@Override
public Component<ChunkStore> clone() {
return new RespawnBlock(this.ownerUUID);
}
public static class OnRemove extends RefSystem<ChunkStore> {
@Nonnull
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
public static final ComponentType<ChunkStore, RespawnBlock> COMPONENT_TYPE_RESPAWN_BLOCK = RespawnBlock.getComponentType();
public static final ComponentType<ChunkStore, BlockModule.BlockStateInfo> COMPONENT_TYPE_BLOCK_STATE_INFO = BlockModule.BlockStateInfo.getComponentType();
@Nonnull
public static final Query<ChunkStore> QUERY = Query.and(COMPONENT_TYPE_RESPAWN_BLOCK, COMPONENT_TYPE_BLOCK_STATE_INFO);
public OnRemove() {
}
@Override
public void onEntityAdded(
@Nonnull Ref<ChunkStore> ref, @Nonnull AddReason reason, @Nonnull Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
}
@Override
public void onEntityRemove(
@Nonnull Ref<ChunkStore> ref, @Nonnull RemoveReason reason, @Nonnull Store<ChunkStore> store, @Nonnull CommandBuffer<ChunkStore> commandBuffer
) {
if (reason != RemoveReason.UNLOAD) {
RespawnBlock respawnState = commandBuffer.getComponent(ref, COMPONENT_TYPE_RESPAWN_BLOCK);
assert respawnState != null;
if (respawnState.ownerUUID != null) {
BlockModule.BlockStateInfo blockStateInfoComponent = commandBuffer.getComponent(ref, COMPONENT_TYPE_BLOCK_STATE_INFO);
assert blockStateInfoComponent != null;
PlayerRef playerRef = Universe.get().getPlayer(respawnState.ownerUUID);
if (playerRef == null) {
LOGGER.at(Level.WARNING).log("Failed to fetch player ref during removal of respawn block entity.");
} else {
Player playerComponent = playerRef.getComponent(Player.getComponentType());
if (playerComponent == null) {
LOGGER.at(Level.WARNING).log("Failed to fetch player component during removal of respawn block entity.");
} else {
Ref<ChunkStore> chunkRef = blockStateInfoComponent.getChunkRef();
if (chunkRef.isValid()) {
World world = commandBuffer.getExternalData().getWorld();
PlayerWorldData playerWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName());
PlayerRespawnPointData[] respawnPoints = playerWorldData.getRespawnPoints();
if (respawnPoints == null) {
LOGGER.at(Level.WARNING)
.log("Failed to find valid respawn points for player " + respawnState.ownerUUID + " during removal of respawn block entity.");
} else {
WorldChunk worldChunkComponent = commandBuffer.getComponent(chunkRef, WorldChunk.getComponentType());
assert worldChunkComponent != null;
Vector3i blockPosition = new Vector3i(
ChunkUtil.worldCoordFromLocalCoord(worldChunkComponent.getX(), ChunkUtil.xFromBlockInColumn(blockStateInfoComponent.getIndex())),
ChunkUtil.yFromBlockInColumn(blockStateInfoComponent.getIndex()),
ChunkUtil.worldCoordFromLocalCoord(worldChunkComponent.getZ(), ChunkUtil.zFromBlockInColumn(blockStateInfoComponent.getIndex()))
);
for (int i = 0; i < respawnPoints.length; i++) {
PlayerRespawnPointData respawnPoint = respawnPoints[i];
if (respawnPoint.getBlockPosition().equals(blockPosition)) {
LOGGER.at(Level.INFO)
.log(
"Removing respawn point for player "
+ respawnState.ownerUUID
+ " at position "
+ blockPosition
+ " due to respawn block removal."
);
playerWorldData.setRespawnPoints(ArrayUtil.remove(respawnPoints, i));
return;
}
}
}
}
}
}
}
}
}
@Nullable
@Override
public Query<ChunkStore> getQuery() {
return QUERY;
}
}
}

View File

@@ -0,0 +1,922 @@
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.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.io.PrintWriter;
import java.io.StringWriter;
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;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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, removing the corrupted chunk data,
* 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' - removing and regenerating. See logs/corrupted_chunks.log for details.",
x, z, this.world.getName()
);
// Remove the corrupted chunk data
CompletableFuture<Void> removeFuture;
if (this.saver != null) {
removeFuture = this.saver.removeHolder(x, z).exceptionally(removeError -> {
LOGGER.at(Level.WARNING).withCause(removeError).log(
"Failed to remove corrupted chunk data at (%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

@@ -0,0 +1,183 @@
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

@@ -0,0 +1,217 @@
package com.hypixel.hytale.server.core.util.thread;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.metrics.metric.HistoricMetric;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
public abstract class TickingThread implements Runnable {
public static final int NANOS_IN_ONE_MILLI = 1000000;
public static final int NANOS_IN_ONE_SECOND = 1000000000;
public static final int TPS = 30;
public static long SLEEP_OFFSET = 3000000L;
private final String threadName;
private final boolean daemon;
private final AtomicBoolean needsShutdown = new AtomicBoolean(true);
private int tps;
private int tickStepNanos;
private HistoricMetric bufferedTickLengthMetricSet;
@Nullable
private Thread thread;
@Nonnull
private CompletableFuture<Void> startedFuture = new CompletableFuture<>();
public TickingThread(String threadName) {
this(threadName, 30, false);
}
public TickingThread(String threadName, int tps, boolean daemon) {
this.threadName = threadName;
this.daemon = daemon;
this.tps = tps;
this.tickStepNanos = 1000000000 / tps;
this.bufferedTickLengthMetricSet = HistoricMetric.builder(this.tickStepNanos, TimeUnit.NANOSECONDS)
.addPeriod(10L, TimeUnit.SECONDS)
.addPeriod(1L, TimeUnit.MINUTES)
.addPeriod(5L, TimeUnit.MINUTES)
.build();
}
@Override
public void run() {
try {
this.onStart();
this.startedFuture.complete(null);
long beforeTick = System.nanoTime() - this.tickStepNanos;
while (this.thread != null && !this.thread.isInterrupted()) {
long delta;
if (!this.isIdle()) {
while ((delta = System.nanoTime() - beforeTick) < this.tickStepNanos) {
Thread.onSpinWait();
}
} else {
delta = System.nanoTime() - beforeTick;
}
beforeTick = System.nanoTime();
this.tick((float) delta / 1.0E9F);
long tickLength = System.nanoTime() - beforeTick;
this.bufferedTickLengthMetricSet.add(System.nanoTime(), tickLength);
long sleepLength = this.tickStepNanos - tickLength;
if (!this.isIdle()) {
sleepLength -= SLEEP_OFFSET;
}
if (sleepLength > 0L) {
Thread.sleep(sleepLength / 1000000L);
}
}
} catch (InterruptedException var9) {
Thread.currentThread().interrupt();
} catch (Throwable var10) {
HytaleLogger.getLogger().at(Level.SEVERE).withCause(var10).log("Exception in thread %s:", this.thread);
}
if (this.needsShutdown.getAndSet(false)) {
this.onShutdown();
}
}
protected boolean isIdle() {
return false;
}
protected abstract void tick(float var1);
protected void onStart() {
}
protected abstract void onShutdown();
@Nonnull
public CompletableFuture<Void> start() {
if (this.thread == null) {
this.thread = new Thread(this, this.threadName);
this.thread.setDaemon(this.daemon);
} else if (this.thread.isAlive()) {
throw new IllegalStateException("Thread '" + this.thread.getName() + "' is already started!");
}
this.thread.start();
return this.startedFuture;
}
public boolean interrupt() {
if (this.thread != null && this.thread.isAlive()) {
this.thread.interrupt();
return true;
} else {
return false;
}
}
@SuppressWarnings("deprecation")
public void stop() {
Thread thread = this.thread;
if (thread != null) {
try {
int i = 0;
while (thread.isAlive()) {
thread.interrupt();
thread.join(this.tickStepNanos / 1000000);
i += this.tickStepNanos / 1000000;
if (i > 30000) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement traceElement : thread.getStackTrace()) {
sb.append("\tat ").append(traceElement).append('\n');
}
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 var9 = null;
if (this.needsShutdown.getAndSet(false)) {
this.onShutdown();
}
return;
}
}
Thread var10 = null;
} catch (InterruptedException var8) {
Thread.currentThread().interrupt();
}
}
}
public void setTps(int tps) {
this.debugAssertInTickingThread();
if (tps > 0 && tps <= 2048) {
this.tps = tps;
this.tickStepNanos = 1000000000 / tps;
this.bufferedTickLengthMetricSet = HistoricMetric.builder(this.tickStepNanos, TimeUnit.NANOSECONDS)
.addPeriod(10L, TimeUnit.SECONDS)
.addPeriod(1L, TimeUnit.MINUTES)
.addPeriod(5L, TimeUnit.MINUTES)
.build();
} else {
throw new IllegalArgumentException("UpdatesPerSecond is out of bounds (<=0 or >2048): " + tps);
}
}
public int getTps() {
return this.tps;
}
public int getTickStepNanos() {
return this.tickStepNanos;
}
public HistoricMetric getBufferedTickLengthMetricSet() {
return this.bufferedTickLengthMetricSet;
}
public void clearMetrics() {
this.bufferedTickLengthMetricSet.clear();
}
public void debugAssertInTickingThread() {
if (!Thread.currentThread().equals(this.thread) && this.thread != null) {
throw new AssertionError("Assert not in ticking thread!");
}
}
public boolean isInThread() {
return Thread.currentThread().equals(this.thread);
}
public boolean isStarted() {
return this.thread != null && this.thread.isAlive() && this.needsShutdown.get();
}
@Deprecated
protected void setThread(Thread thread) {
this.thread = thread;
}
@Nullable
protected Thread getThread() {
return this.thread;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,390 @@
package com.hypixel.hytale.server.npc.systems;
import com.hypixel.hytale.common.collection.BucketItemPool;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.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.OrderPriority;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.server.core.entity.LivingEntity;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.flock.FlockMembership;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.instructions.Instruction;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.role.support.EntityList;
import com.hypixel.hytale.server.npc.role.support.PositionCache;
import com.hypixel.hytale.server.npc.statetransition.StateTransitionController;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
public class PositionCacheSystems {
public PositionCacheSystems() {
}
public static void initialisePositionCache(@Nonnull Role role, @Nullable StateEvaluator stateEvaluator, double flockInfluenceRange) {
PositionCache positionCache = role.getPositionCache();
positionCache.reset(true);
if (role.isAvoidingEntities()) {
double collisionProbeDistance = role.getCollisionProbeDistance();
positionCache.requireEntityDistanceAvoidance(collisionProbeDistance);
positionCache.requirePlayerDistanceAvoidance(collisionProbeDistance);
}
if (role.isApplySeparation()) {
double separationDistance = role.getSeparationDistance();
positionCache.requireEntityDistanceAvoidance(separationDistance);
positionCache.requirePlayerDistanceAvoidance(separationDistance);
}
if (flockInfluenceRange > 0.0) {
positionCache.requireEntityDistanceAvoidance(flockInfluenceRange);
positionCache.requirePlayerDistanceAvoidance(flockInfluenceRange);
}
Instruction instruction = role.getRootInstruction();
instruction.registerWithSupport(role);
Instruction interactionInstruction = role.getInteractionInstruction();
if (interactionInstruction != null) {
interactionInstruction.registerWithSupport(role);
positionCache.requirePlayerDistanceUnsorted(10.0);
}
Instruction deathInstruction = role.getDeathInstruction();
if (deathInstruction != null) {
deathInstruction.registerWithSupport(role);
}
StateTransitionController stateTransitions = role.getStateSupport().getStateTransitionController();
if (stateTransitions != null) {
stateTransitions.registerWithSupport(role);
}
if (stateEvaluator != null) {
stateEvaluator.setupNPC(role);
}
for (Consumer<Role> registration : positionCache.getExternalRegistrations()) {
registration.accept(role);
}
positionCache.finalizeConfiguration();
}
public static class OnFlockJoinSystem extends RefChangeSystem<EntityStore, FlockMembership> {
@Nonnull
private final ComponentType<EntityStore, FlockMembership> flockMembershipComponentType;
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType;
@Nonnull
private final Query<EntityStore> query;
public OnFlockJoinSystem(
@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull ComponentType<EntityStore, FlockMembership> flockMembershipComponentType
) {
this.flockMembershipComponentType = flockMembershipComponentType;
this.npcComponentType = npcComponentType;
this.stateEvaluatorComponentType = StateEvaluator.getComponentType();
this.query = Archetype.of(npcComponentType, flockMembershipComponentType);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.query;
}
@Nonnull
@Override
public ComponentType<EntityStore, FlockMembership> componentType() {
return this.flockMembershipComponentType;
}
public void onComponentAdded(
@Nonnull Ref<EntityStore> ref,
@Nonnull FlockMembership component,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
NPCEntity npcComponent = store.getComponent(ref, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
PositionCacheSystems.initialisePositionCache(role, store.getComponent(ref, this.stateEvaluatorComponentType), role.getFlockInfluenceRange());
}
public void onComponentSet(
@Nonnull Ref<EntityStore> ref,
FlockMembership oldComponent,
@Nonnull FlockMembership newComponent,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
NPCEntity npcComponent = store.getComponent(ref, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
PositionCacheSystems.initialisePositionCache(role, store.getComponent(ref, this.stateEvaluatorComponentType), role.getFlockInfluenceRange());
}
public void onComponentRemoved(
@Nonnull Ref<EntityStore> ref,
@Nonnull FlockMembership component,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
}
}
public static class RoleActivateSystem extends HolderSystem<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType;
public RoleActivateSystem(
@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType
) {
this.npcComponentType = npcComponentType;
this.stateEvaluatorComponentType = stateEvaluatorComponentType;
}
@Override
public void onEntityAdd(@Nonnull Holder<EntityStore> holder, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store) {
NPCEntity npcComponent = holder.getComponent(this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
double influenceRadius;
if (holder.getComponent(FlockMembership.getComponentType()) != null) {
influenceRadius = role.getFlockInfluenceRange();
} else {
influenceRadius = 0.0;
}
StateEvaluator stateEvaluator = holder.getComponent(this.stateEvaluatorComponentType);
if (stateEvaluator != null) {
stateEvaluator.setupNPC(holder);
}
PositionCacheSystems.initialisePositionCache(role, stateEvaluator, influenceRadius);
}
@Override
public void onEntityRemoved(@Nonnull Holder<EntityStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store) {
NPCEntity npcComponent = holder.getComponent(this.npcComponentType);
assert npcComponent != null;
npcComponent.getRole().getPositionCache().reset(false);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.npcComponentType;
}
}
public static class UpdateSystem extends SteppableTickingSystem {
@Nonnull
private static final ThreadLocal<BucketItemPool<Ref<EntityStore>>> BUCKET_POOL_THREAD_LOCAL = ThreadLocal.withInitial(BucketItemPool::new);
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, ModelComponent> modelComponentType;
@Nonnull
private final ComponentType<EntityStore, TransformComponent> transformComponentType;
@Nonnull
private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> playerSpatialResource;
@Nonnull
private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> npcSpatialResource;
@Nonnull
private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> itemSpatialResource;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies = Set.of(
new SystemDependency<>(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST),
new SystemDependency<>(Order.BEFORE, RoleSystems.PreBehaviourSupportTickSystem.class)
);
public UpdateSystem(
@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType,
@Nonnull ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> npcSpatialResource
) {
this.npcComponentType = npcComponentType;
this.modelComponentType = ModelComponent.getComponentType();
this.transformComponentType = TransformComponent.getComponentType();
this.playerSpatialResource = EntityModule.get().getPlayerSpatialResourceType();
this.npcSpatialResource = npcSpatialResource;
this.itemSpatialResource = EntityModule.get().getItemSpatialResourceType();
this.query = Query.and(npcComponentType, this.transformComponentType, this.modelComponentType);
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@Override
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return false;
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.query;
}
@Override
public void steppedTick(
float dt,
int index,
@Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
PositionCache positionCache = role.getPositionCache();
positionCache.setBenchmarking(NPCPlugin.get().isBenchmarkingSensorSupport());
if (positionCache.tickPositionCacheNextUpdate(dt)) {
positionCache.resetPositionCacheNextUpdate();
long packed = LivingEntity.getPackedMaterialAndFluidAtBreathingHeight(ref, commandBuffer);
BlockMaterial material = BlockMaterial.VALUES[MathUtil.unpackLeft(packed)];
int fluidId = MathUtil.unpackRight(packed);
positionCache.setCouldBreathe(role.canBreathe(material, fluidId));
TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType);
assert transformComponent != null;
Vector3d position = transformComponent.getPosition();
EntityList players = positionCache.getPlayers();
if (players.getSearchRadius() > 0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(this.playerSpatialResource);
players.setBucketItemPool(BUCKET_POOL_THREAD_LOCAL.get());
if (positionCache.isBenchmarking()) {
long startTime = System.nanoTime();
addEntities(ref, position, players, spatialResource, commandBuffer);
long getTime = System.nanoTime();
NPCPlugin.get()
.collectSensorSupportPlayerList(
role.getRoleIndex(),
getTime - startTime,
players.getMaxDistanceSorted(),
players.getMaxDistanceUnsorted(),
players.getMaxDistanceAvoidance(),
0
);
} else {
addEntities(ref, position, players, spatialResource, commandBuffer);
}
}
EntityList npcEntities = positionCache.getNpcs();
if (npcEntities.getSearchRadius() > 0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(this.npcSpatialResource);
npcEntities.setBucketItemPool(BUCKET_POOL_THREAD_LOCAL.get());
if (positionCache.isBenchmarking()) {
long startTime = System.nanoTime();
addEntities(ref, position, npcEntities, spatialResource, commandBuffer);
long getTime = System.nanoTime();
NPCPlugin.get()
.collectSensorSupportEntityList(
role.getRoleIndex(),
getTime - startTime,
npcEntities.getMaxDistanceSorted(),
npcEntities.getMaxDistanceUnsorted(),
npcEntities.getMaxDistanceAvoidance(),
0
);
} else {
addEntities(ref, position, npcEntities, spatialResource, commandBuffer);
}
}
double maxDroppedItemDistance = positionCache.getMaxDroppedItemDistance();
if (maxDroppedItemDistance > 0.0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(this.itemSpatialResource);
List<Ref<EntityStore>> list = positionCache.getDroppedItemList();
list.clear();
spatialResource.getSpatialStructure().ordered(position, (int) maxDroppedItemDistance + 1, list);
}
double maxSpawnMarkerDistance = positionCache.getMaxSpawnMarkerDistance();
if (maxSpawnMarkerDistance > 0.0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(SpawningPlugin.get().getSpawnMarkerSpatialResource());
List<Ref<EntityStore>> list = positionCache.getSpawnMarkerList();
list.clear();
spatialResource.getSpatialStructure().collect(position, (int) maxSpawnMarkerDistance + 1, list);
}
int maxSpawnBeaconDistance = positionCache.getMaxSpawnBeaconDistance();
if (maxSpawnBeaconDistance > 0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(SpawningPlugin.get().getManualSpawnBeaconSpatialResource());
List<Ref<EntityStore>> list = positionCache.getSpawnBeaconList();
list.clear();
spatialResource.getSpatialStructure().ordered(position, maxSpawnBeaconDistance + 1, list);
}
}
}
private static void addEntities(
@Nonnull Ref<EntityStore> self,
@Nonnull Vector3d position,
@Nonnull EntityList entityList,
@Nonnull SpatialResource<Ref<EntityStore>, EntityStore> spatialResource,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
List<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
spatialResource.getSpatialStructure().collect(position, entityList.getSearchRadius(), results);
for (Ref<EntityStore> result : results) {
if (result.isValid() && !result.equals(self)) {
entityList.add(result, position, commandBuffer);
}
}
}
}
}

View File

@@ -0,0 +1,406 @@
package com.hypixel.hytale.server.npc.systems;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.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.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.TickingSystem;
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.entity.component.BoundingBox;
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;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings;
import com.hypixel.hytale.server.core.modules.entity.system.ModelSystems;
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.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.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.Set;
import java.util.logging.Level;
public class RoleSystems {
private static final ThreadLocal<List<Ref<EntityStore>>> ENTITY_LIST = ThreadLocal.withInitial(ArrayList::new);
public RoleSystems() {
}
public static class BehaviourTickSystem extends TickingSystem<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, StepComponent> stepComponentType;
@Nonnull
private final ComponentType<EntityStore, Frozen> frozenComponentType;
@Nonnull
private final ComponentType<EntityStore, NewSpawnComponent> newSpawnComponentType;
@Nonnull
private final ComponentType<EntityStore, TransformComponent> transformComponentType;
public BehaviourTickSystem(
@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull ComponentType<EntityStore, StepComponent> stepComponentType
) {
this.npcComponentType = npcComponentType;
this.stepComponentType = stepComponentType;
this.frozenComponentType = Frozen.getComponentType();
this.newSpawnComponentType = NewSpawnComponent.getComponentType();
this.transformComponentType = TransformComponent.getComponentType();
}
@Override
public void tick(float dt, int systemIndex, @Nonnull Store<EntityStore> store) {
List<Ref<EntityStore>> entities = RoleSystems.ENTITY_LIST.get();
store.forEachChunk(this.npcComponentType, (archetypeChunk, commandBuffer) -> {
for (int index = 0; index < archetypeChunk.size(); index++) {
entities.add(archetypeChunk.getReferenceTo(index));
}
});
World world = store.getExternalData().getWorld();
boolean isAllNpcFrozen = world.getWorldConfig().isAllNPCFrozen();
for (Ref<EntityStore> entityReference : entities) {
if (entityReference.isValid() && store.getComponent(entityReference, this.newSpawnComponentType) == null) {
float tickLength;
if (store.getComponent(entityReference, this.frozenComponentType) == null && !isAllNpcFrozen) {
tickLength = dt;
} else {
StepComponent stepComponent = store.getComponent(entityReference, this.stepComponentType);
if (stepComponent == null) {
continue;
}
tickLength = stepComponent.getTickLength();
}
NPCEntity npcComponent = store.getComponent(entityReference, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
// Distance-based tick skipping optimization
TransformComponent transform = store.getComponent(entityReference, this.transformComponentType);
if (transform != null) {
TickSkipState tickSkip = role.getBehaviourTickSkip();
tickSkip.add(tickLength);
if (!tickSkip.shouldProcess(transform.getPosition(), store, tickLength)) {
continue;
}
tickLength = tickSkip.consume();
}
try {
boolean benchmarking = NPCPlugin.get().isBenchmarkingRole();
if (benchmarking) {
long start = System.nanoTime();
role.tick(entityReference, tickLength, store);
NPCPlugin.get().collectRoleTick(role.getRoleIndex(), System.nanoTime() - start);
} else {
role.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());
store.removeEntity(entityReference, RemoveReason.REMOVE);
}
}
}
entities.clear();
}
}
public static class PostBehaviourSupportTickSystem extends SteppableTickingSystem {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, TransformComponent> transformComponentType;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies = Set.of(
new SystemDependency<>(Order.AFTER, SteeringSystem.class), new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class)
);
public PostBehaviourSupportTickSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType) {
this.npcComponentType = npcComponentType;
this.transformComponentType = TransformComponent.getComponentType();
this.query = Query.and(npcComponentType, this.transformComponentType);
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@Override
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.query;
}
@Override
public void steppedTick(
float dt,
int index,
@Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcComponentType);
assert npcComponent != null;
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
Role role = npcComponent.getRole();
MotionController activeMotionController = role.getActiveMotionController();
activeMotionController.clearOverrides();
activeMotionController.constrainRotations(role, archetypeChunk.getComponent(index, this.transformComponentType));
role.getCombatSupport().tick(dt);
role.getWorldSupport().tick(dt);
EntitySupport entitySupport = role.getEntitySupport();
entitySupport.tick(dt);
entitySupport.handleNominatedDisplayName(ref, commandBuffer);
role.getStateSupport().update(commandBuffer);
npcComponent.clearDamageData();
role.getMarkedEntitySupport().setTargetSlotToIgnoreForAvoidance(Integer.MIN_VALUE);
role.setReachedTerminalAction(false);
role.getPositionCache().clear(dt);
}
}
public static class PreBehaviourSupportTickSystem extends SteppableTickingSystem {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, Player> playerComponentType;
@Nonnull
private final ComponentType<EntityStore, DeathComponent> deathComponentType;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public PreBehaviourSupportTickSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType) {
this.npcComponentType = npcComponentType;
this.playerComponentType = Player.getComponentType();
this.deathComponentType = DeathComponent.getComponentType();
this.dependencies = Set.of(new SystemDependency<>(Order.BEFORE, RoleSystems.BehaviourTickSystem.class));
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@Override
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.npcComponentType;
}
@Override
public void steppedTick(
float dt,
int index,
@Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport();
Ref<EntityStore>[] entityTargets = markedEntitySupport.getEntityTargets();
for (int i = 0; i < entityTargets.length; i++) {
Ref<EntityStore> targetReference = entityTargets[i];
if (targetReference != null) {
if (!targetReference.isValid()) {
entityTargets[i] = null;
} else {
Player playerComponent = commandBuffer.getComponent(targetReference, this.playerComponentType);
if (playerComponent != null && playerComponent.getGameMode() != GameMode.Adventure) {
if (playerComponent.getGameMode() != GameMode.Creative) {
entityTargets[i] = null;
continue;
}
PlayerSettings playerSettingsComponent = commandBuffer.getComponent(targetReference, PlayerSettings.getComponentType());
if (playerSettingsComponent == null || !playerSettingsComponent.creativeSettings().allowNPCDetection()) {
entityTargets[i] = null;
continue;
}
}
DeathComponent deathComponent = commandBuffer.getComponent(targetReference, this.deathComponentType);
if (deathComponent != null) {
entityTargets[i] = null;
}
}
}
}
role.clearOnceIfNeeded();
role.getBodySteering().clear();
role.getHeadSteering().clear();
role.getIgnoredEntitiesForAvoidance().clear();
npcComponent.invalidateCachedHorizontalSpeedMultiplier();
}
}
public static class RoleActivateSystem extends HolderSystem<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, ModelComponent> modelComponentType;
@Nonnull
private final ComponentType<EntityStore, BoundingBox> boundingBoxComponentType;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public RoleActivateSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType) {
this.npcComponentType = npcComponentType;
this.modelComponentType = ModelComponent.getComponentType();
this.boundingBoxComponentType = BoundingBox.getComponentType();
this.query = Query.and(npcComponentType, this.modelComponentType, this.boundingBoxComponentType);
this.dependencies = Set.of(
new SystemDependency<>(Order.AFTER, BalancingInitialisationSystem.class), new SystemDependency<>(Order.AFTER, ModelSystems.ModelSpawned.class)
);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.query;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@Override
public void onEntityAdd(@Nonnull Holder<EntityStore> holder, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store) {
NPCEntity npcComponent = holder.getComponent(this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
role.getStateSupport().activate();
role.getDebugSupport().activate();
ModelComponent modelComponent = holder.getComponent(this.modelComponentType);
assert modelComponent != null;
BoundingBox boundingBoxComponent = holder.getComponent(this.boundingBoxComponentType);
assert boundingBoxComponent != null;
role.updateMotionControllers(null, modelComponent.getModel(), boundingBoxComponent.getBoundingBox(), null);
role.clearOnce();
role.getActiveMotionController().activate();
holder.ensureComponent(InteractionModule.get().getChainingDataComponent());
}
@Override
public void onEntityRemoved(@Nonnull Holder<EntityStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store) {
NPCEntity npcComponent = holder.getComponent(this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
role.getActiveMotionController().deactivate();
role.getWorldSupport().resetAllBlockSensors();
}
}
public static class RoleDebugSystem extends SteppableTickingSystem {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public RoleDebugSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull Set<Dependency<EntityStore>> dependencies) {
this.npcComponentType = npcComponentType;
this.dependencies = dependencies;
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@Override
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.npcComponentType;
}
@Override
public void steppedTick(
float dt,
int index,
@Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
RoleDebugDisplay debugDisplay = role.getDebugSupport().getDebugDisplay();
if (debugDisplay != null) {
debugDisplay.display(role, index, archetypeChunk, commandBuffer);
}
}
}
}

View File

@@ -0,0 +1,430 @@
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

@@ -0,0 +1,128 @@
package com.hypixel.hytale.server.npc.systems;
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.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.math.vector.Vector3d;
import com.hypixel.hytale.server.core.entity.knockback.KnockbackSystems;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.system.TransformSystems;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.role.Role;
import javax.annotation.Nonnull;
import java.util.Set;
import java.util.logging.Level;
public class SteeringSystem extends SteppableTickingSystem {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcEntityComponent;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
@Nonnull
private final Query<EntityStore> query;
public SteeringSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcEntityComponent) {
this.npcEntityComponent = npcEntityComponent;
this.dependencies = Set.of(
new SystemDependency<>(Order.AFTER, AvoidanceSystem.class),
new SystemDependency<>(Order.AFTER, KnockbackSystems.ApplyKnockback.class),
new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class)
);
this.query = Query.and(npcEntityComponent, TransformComponent.getComponentType());
}
@Nonnull
@Override
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
@Override
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return false;
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return this.query;
}
@Override
public void steppedTick(
float dt,
int index,
@Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store,
@Nonnull CommandBuffer<EntityStore> commandBuffer
) {
NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcEntityComponent);
assert npcComponent != null;
TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
assert transformComponent != null;
Role role = npcComponent.getRole();
if (role != null) {
// Distance-based tick skipping optimization
TickSkipState tickSkip = role.getSteeringTickSkip();
tickSkip.add(dt);
if (!tickSkip.shouldProcess(transformComponent.getPosition(), store, dt)) {
return;
}
float effectiveDt = tickSkip.consume();
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
try {
if (role.getDebugSupport().isDebugMotionSteering()) {
Vector3d position = transformComponent.getPosition();
double x = position.getX();
double z = position.getZ();
float yaw = transformComponent.getRotation().getYaw();
role.getActiveMotionController().steer(ref, role, role.getBodySteering(), role.getHeadSteering(), effectiveDt, commandBuffer);
x = position.getX() - x;
z = position.getZ() - z;
double l = Math.sqrt(x * x + z * z);
double v = l / dt;
double vx = x / dt;
double vz = z / dt;
double vh = l > 0.0 ? PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)) : 0.0;
NPCPlugin.get()
.getLogger()
.at(Level.FINER)
.log(
"= Role = t =%.4f v =%.4f vx=%.4f vz=%.4f h =%.4f nh=%.4f vh=%.4f",
dt,
v,
vx,
vz,
(180.0F / (float) Math.PI) * yaw,
(180.0F / (float) Math.PI) * yaw,
180.0F / (float) Math.PI * vh
);
} else {
role.getActiveMotionController().steer(ref, role, role.getBodySteering(), role.getHeadSteering(), effectiveDt, commandBuffer);
}
} catch (IllegalStateException | IllegalArgumentException var26) {
NPCPlugin.get().getLogger().at(Level.SEVERE).withCause(var26).log();
commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
}
}
}
}

View File

@@ -0,0 +1,91 @@
package com.hypixel.hytale.server.npc.systems;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.math.vector.Vector3d;
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.universe.world.storage.EntityStore;
import javax.annotation.Nonnull;
/**
* Per-NPC state for distance-based tick skipping optimization.
* Caches player distance (100ms TTL) and accumulates skipped delta time.
*/
public final class TickSkipState {
private static final float DISTANCE_CACHE_TTL = 0.1f; // 100ms
private static final float MIN_DISTANCE = 32.0f;
private static final float MAX_DISTANCE = 96.0f;
private static final float DISTANCE_RANGE = MAX_DISTANCE - MIN_DISTANCE;
private static final int MAX_TICK_INTERVAL = 20;
private static final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> PLAYER_SPATIAL =
EntityModule.get().getPlayerSpatialResourceType();
private float accumulated;
private float cachedDistanceSq;
private float distanceCacheAge;
public void add(float dt) {
this.accumulated += dt;
this.distanceCacheAge += dt;
}
public float get() {
return this.accumulated;
}
public float consume() {
float value = this.accumulated;
this.accumulated = 0f;
return value;
}
/**
* Updates cached distance if stale, then checks if tick should be processed.
*/
public boolean shouldProcess(@Nonnull Vector3d position, @Nonnull Store<EntityStore> store, float dt) {
// Refresh distance cache if stale
if (this.distanceCacheAge >= DISTANCE_CACHE_TTL) {
this.cachedDistanceSq = getDistanceSquaredToNearestPlayer(position, store);
this.distanceCacheAge = 0f;
}
int interval = calculateTickInterval(this.cachedDistanceSq);
if (interval <= 1) {
return true;
}
return this.accumulated >= dt * interval;
}
private static float getDistanceSquaredToNearestPlayer(Vector3d position, Store<EntityStore> store) {
SpatialResource<Ref<EntityStore>, EntityStore> spatial = store.getResource(PLAYER_SPATIAL);
if (spatial == null) {
return 0f;
}
Ref<EntityStore> closest = spatial.getSpatialStructure().closest(position);
if (closest == null || !closest.isValid()) {
return Float.MAX_VALUE;
}
TransformComponent transform = store.getComponent(closest, TransformComponent.getComponentType());
if (transform == null) {
return Float.MAX_VALUE;
}
return (float) position.distanceSquaredTo(transform.getPosition());
}
private static int calculateTickInterval(float distanceSq) {
float distance = (float) Math.sqrt(distanceSq);
if (distance <= MIN_DISTANCE) {
return 1;
}
if (distance >= MAX_DISTANCE) {
return MAX_TICK_INTERVAL;
}
float t = (distance - MIN_DISTANCE) / DISTANCE_RANGE;
return 1 + (int) ((MAX_TICK_INTERVAL - 1) * t * t);
}
}

View File

@@ -0,0 +1,262 @@
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

@@ -0,0 +1,547 @@
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() {
}
}
}