HyCodeYourTale

Troubleshooting

Troubleshooting

Průvodce řešením běžných problémů v Hytale pluginech.

---

Přehled Diagnostiky

Debugging Process

├── Check Logs
│ └── Server log, Console output

├── Identify Error Type
│ ├── Thread errors
│ ├── Null components
│ ├── Event issues
│ └── Plugin lifecycle

└── Debug Tools
├── Debug commands
├── Visual debug (DebugUtils)
└── Performance profiling

---

DebugPlugin a Debug Příkazy

DebugPlugin

Vestavěný plugin pro debugování:

public class DebugPlugin extends JavaPlugin {
@Nonnull
public static final PluginManifest MANIFEST = PluginManifest.corePlugin(DebugPlugin.class).build();
@Nullable
private static DebugPlugin instance;

@Nullable
public static DebugPlugin get() {
return instance;
}

@Override
protected void setup() {
this.getCommandRegistry().registerCommand(new DebugCommand());
}
}

Debug Shape Commands

/debug shape sphere   - Vytvoří debug kouli
/debug shape cube - Vytvoří debug krychli
/debug shape cylinder - Vytvoří debug válec
/debug shape cone - Vytvoří debug kužel
/debug shape arrow - Vytvoří debug šipku
/debug shape clear - Vyčistí všechny debug tvary
/debug shape showforce - Zobrazí síly

---

DebugUtils

Utility pro vizuální debugování:

public class DebugUtils {
public static boolean DISPLAY_FORCES = false;

// Základní tvary
public static void addSphere(World world, Vector3d pos, Vector3f color, double scale, float time);
public static void addCube(World world, Vector3d pos, Vector3f color, double scale, float time);
public static void addCylinder(World world, Vector3d pos, Vector3f color, double scale, float time);
public static void addCone(World world, Vector3d pos, Vector3f color, double scale, float time);
public static void addArrow(World world, Vector3d position, Vector3d direction, Vector3f color, float time, boolean fade);

// Frustum pro kameru
public static void addFrustum(World world, Matrix4d matrix, Matrix4d frustumProjection, Vector3f color, float time, boolean fade);

// Vyčištění
public static void clear(World world);

// Síly (knockback, physics)
public static void addForce(World world, Vector3d position, Vector3d force, VelocityConfig velocityConfig);
}

Použití DebugUtils

// Zobraz pozici hráče
Vector3d pos = transform.getPosition();
Vector3f color = new Vector3f(1.0f, 0.0f, 0.0f); // Červená
DebugUtils.addSphere(world, pos, color, 1.0, 5.0f); // 5 sekund

// Zobraz směr pohledu
Vector3d direction = getPlayerLookDirection(player);
Vector3f green = new Vector3f(0.0f, 1.0f, 0.0f);
DebugUtils.addArrow(world, pos, direction.multiply(3), green, 5.0f, true);

// Vyčisti všechny debug tvary
DebugUtils.clear(world);

// Povol zobrazení sil
DebugUtils.DISPLAY_FORCES = true;

Debug Shape Packets

// Vnitřně používá pakety
DisplayDebug packet = new DisplayDebug(
DebugShape.Sphere, // Tvar
matrix.asFloatData(), // Transformace
new Vector3f(r, g, b), // Barva
duration, // Doba zobrazení
fade, // Fade out
null // Extra data (pro Frustum)
);

// Poslat všem hráčům ve světě
for (PlayerRef playerRef : world.getPlayerRefs()) {
playerRef.getPacketHandler().write(packet);
}

---

Běžné Chyby

1. "Assert not in thread!"

Symptom:

java.lang.IllegalStateException: Assert not in thread!
Thread[#108,WorldThread - default,5,...]
but was in Thread[#75,Scheduler,5,main]

Příčina: Přístup ke komponentám z jiného vlákna než World Thread.

Řešení:

// ŠPATNĚ - přímo v async kontextu
CompletableFuture.runAsync(() -> {
Store store = ref.getStore();
store.getComponent(ref, Component.getComponentType()); // CRASH!
});

// SPRÁVNĚ - přepni na world thread
CompletableFuture.runAsync(() -> {
// Async práce...
}).thenAccept(result -> {
world.execute(() -> {
Store store = ref.getStore();
store.getComponent(ref, Component.getComponentType()); // OK
});
});

Pro příkazy:

// ŠPATNĚ - CommandBase běží mimo world thread
public class MyCommand extends CommandBase { ... } // Problém!

// SPRÁVNĚ - AbstractPlayerCommand běží na world thread
public class MyCommand extends AbstractPlayerCommand {
@Override
protected void execute(CommandContext context, Store store,
Ref ref, PlayerRef playerRef, World world) {
// Bezpečný přístup ke komponentám
}
}

2. NullPointerException u Komponent

Symptom:

java.lang.NullPointerException
at MyPlugin.onPlayerJoin(MyPlugin.java:45)

Příčina: Komponenta neexistuje na entitě.

Řešení:

// ŠPATNĚ
Player player = store.getComponent(ref, Player.getComponentType());
player.sendMessage(message); // NPE pokud player == null

// SPRÁVNĚ - null check
Player player = store.getComponent(ref, Player.getComponentType());
if (player != null) {
player.sendMessage(message);
} else {
getLogger().atWarning().log("Player component not found!");
}

// SPRÁVNĚ - assert s logem
Player player = store.getComponent(ref, Player.getComponentType());
if (player == null) {
getLogger().at(Level.SEVERE).log("Expected Player component on entity!");
return;
}
player.sendMessage(message);

3. Event se Nevyvolá

Symptom: Event listener se nikdy nezavolá.

Diagnostika:

@Override
protected void setup() {
getLogger().atInfo().log("Registering events...");

// Test registrace
getEventRegistry().register(PlayerJoinEvent.class, event -> {
getLogger().atInfo().log("PlayerJoinEvent received!"); // Vypisuje se?
});

getLogger().atInfo().log("Events registered");
}

Řešení podle typu eventu:

// 1. Async eventy (síťové)
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, event -> { ... });
getEventRegistry().registerAsync(PlayerLoginEvent.class, event -> { ... });

// 2. ECS eventy (bloky, entity) - potřebují EntityEventSystem
public class MyBlockSystem implements EntityEventSystem {
@Override
public void processEvent(BreakBlockEvent event, Context context) { ... }
}
getEntityStoreRegistry().registerSystem(new MyBlockSystem());

// 3. Globální eventy
getEventRegistry().registerGlobal(ServerTickEvent.class, event -> { ... });

4. Plugin se Nenačte

Symptom: Plugin není vidět v seznamu pluginů.

Checklist:

| Kontrola | Jak ověřit |
|----------|------------|
| manifest.json existuje | Otevři JAR, najdi manifest.json |
| Správný EntryPoint | Zkontroluj cestu k třídě |
| Dependencies dostupné | Zkontroluj závislosti |
| Konstruktor správný | Musí přijímat JavaPluginInit |
| setup() bez chyb | Zkontroluj logy |

Správný manifest.json:

{
"Id": "MyPlugin",
"Version": "1.0.0",
"Name": "My Plugin",
"EntryPoint": "com.example.myplugin.MyPlugin"
}

Správný konstruktor:

public class MyPlugin extends JavaPlugin {
public MyPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
}

5. "Dependency not found"

Symptom:

Failed to load plugin MyPlugin: Dependency not found: Hytale:DamageModule

Řešení:

{
"Dependencies": {
"Hytale:DamageModule": "*"
}
}

6. Circular Dependency

Symptom:

Circular dependency detected: PluginA -> PluginB -> PluginA

Řešení: Reorganizuj závislosti nebo použij soft dependencies.

// Plugin A
{
"Id": "PluginA",
"SoftDependencies": {
"PluginB": "*"
}
}

// Plugin B
{
"Id": "PluginB",
"Dependencies": {
"PluginA": "*"
}
}

---

Debug Příkazy pro Plugin

Základní Debug Command

public class DebugCommand extends AbstractPlayerCommand {

public DebugCommand() {
super("mydebug", "myplugin.commands.debug.desc");
requirePermission(HytalePermissions.of("myplugin.admin"));
}

@Override
protected void execute(CommandContext context, Store store,
Ref ref, PlayerRef playerRef, World world) {

Player player = store.getComponent(ref, Player.getComponentType());
if (player == null) return;

// Thread info
player.sendMessage(Message.raw("=== Debug Info ==="));
player.sendMessage(Message.raw("Thread: " + Thread.currentThread().getName()));
player.sendMessage(Message.raw("In world thread: " + world.isInThread()));

// World info
player.sendMessage(Message.raw("World: " + world.getName()));

// Position
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (transform != null) {
Vector3d pos = transform.getPosition();
player.sendMessage(Message.raw(String.format(
"Position: %.2f, %.2f, %.2f",
pos.getX(), pos.getY(), pos.getZ()
)));
}

// Entity ref
player.sendMessage(Message.raw("Entity ID: " + ref.getId()));
player.sendMessage(Message.raw("Ref valid: " + ref.isValid()));
}
}

Component Inspector

public class InspectCommand extends AbstractPlayerCommand {

public InspectCommand() {
super("inspect", "myplugin.commands.inspect.desc");
}

@Override
protected void execute(CommandContext context, Store store,
Ref ref, PlayerRef playerRef, World world) {

Player player = store.getComponent(ref, Player.getComponentType());
if (player == null) return;

player.sendMessage(Message.raw("=== Component Inspector ==="));

// Základní komponenty
inspectComponent(player, store, ref, "Player", Player.getComponentType());
inspectComponent(player, store, ref, "Transform", TransformComponent.getComponentType());
inspectComponent(player, store, ref, "HeadRotation", HeadRotation.getComponentType());
inspectComponent(player, store, ref, "NetworkId", NetworkId.getComponentType());

// Custom komponenty
inspectComponent(player, store, ref, "MyComponent",
MyPlugin.get().getMyComponentType());
}

private void inspectComponent(Player player, Store store,
Ref ref, String name,
ComponentType type) {
T comp = store.getComponent(ref, type);
String status = comp != null ? "PRESENT" : "NULL";
String color = comp != null ? "green" : "red";
player.sendMessage(Message.raw(" " + name + ": " + status));
}
}

Thread Debug Utility

public class ThreadDebugger {

private final JavaPlugin plugin;

public ThreadDebugger(JavaPlugin plugin) {
this.plugin = plugin;
}

public void logCurrentThread(String context) {
Thread thread = Thread.currentThread();
plugin.getLogger().atInfo().log("[%s] Thread: %s (ID: %d)",
context, thread.getName(), thread.getId());
}

public void assertWorldThread(World world, String operation) {
if (!world.isInThread()) {
plugin.getLogger().at(Level.SEVERE).log(
"THREAD VIOLATION: %s called from %s (expected WorldThread)",
operation, Thread.currentThread().getName()
);

// Log stack trace pro nalezení příčiny
new Exception("Stack trace for thread violation").printStackTrace();
}
}

public void safeWorldOperation(World world, Runnable operation, String description) {
if (world.isInThread()) {
operation.run();
} else {
plugin.getLogger().at(Level.FINE).log(
"Scheduling '%s' to world thread (was on %s)",
description, Thread.currentThread().getName()
);
world.execute(operation);
}
}
}

---

Performance Debugging

Měření Času Operací

public class PerformanceProfiler {

private final JavaPlugin plugin;
private final Map> measurements = new ConcurrentHashMap<>();

public PerformanceProfiler(JavaPlugin plugin) {
this.plugin = plugin;
}

public void measure(String name, Runnable operation) {
long start = System.nanoTime();
try {
operation.run();
} finally {
long elapsed = System.nanoTime() - start;
measurements.computeIfAbsent(name, k -> new ArrayList<>()).add(elapsed);

plugin.getLogger().at(Level.FINE).log(
"%s: %.2f ms", name, elapsed / 1_000_000.0
);
}
}

public T measureReturn(String name, Supplier operation) {
long start = System.nanoTime();
try {
return operation.get();
} finally {
long elapsed = System.nanoTime() - start;
measurements.computeIfAbsent(name, k -> new ArrayList<>()).add(elapsed);
}
}

public void printStats(String name) {
List data = measurements.get(name);
if (data == null || data.isEmpty()) {
plugin.getLogger().atInfo().log("No measurements for: %s", name);
return;
}

double avg = data.stream().mapToLong(l -> l).average().orElse(0);
long min = data.stream().mapToLong(l -> l).min().orElse(0);
long max = data.stream().mapToLong(l -> l).max().orElse(0);

plugin.getLogger().atInfo().log(
"%s stats: avg=%.2fms, min=%.2fms, max=%.2fms, samples=%d",
name, avg / 1_000_000.0, min / 1_000_000.0, max / 1_000_000.0, data.size()
);
}
}

Memory Debugging

public class MemoryDebugger {

public static void logMemoryUsage(HytaleLogger logger) {
Runtime runtime = Runtime.getRuntime();
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long used = total - free;
long max = runtime.maxMemory();

logger.atInfo().log(
"Memory: %d MB used / %d MB total / %d MB max (%.1f%% used)",
used / (1024 * 1024),
total / (1024 * 1024),
max / (1024 * 1024),
(used * 100.0) / max
);
}

public static void suggestGC(HytaleLogger logger) {
Runtime runtime = Runtime.getRuntime();
long usedBefore = runtime.totalMemory() - runtime.freeMemory();

System.gc();

long usedAfter = runtime.totalMemory() - runtime.freeMemory();
long freed = usedBefore - usedAfter;

logger.atInfo().log("GC freed %d MB", freed / (1024 * 1024));
}
}

---

Diagnostické Checklists

Plugin Nenačten

  • [ ] manifest.json je v root JAR souboru?

  • [ ] Id je unikátní?

  • [ ] EntryPoint odpovídá skutečné třídě?

  • [ ] Konstruktor přijímá JavaPluginInit?

  • [ ] setup() nevyhazuje výjimku?

  • [ ] Všechny Dependencies jsou dostupné?
  • Event Nefunguje

  • [ ] Je listener registrován v setup()?

  • [ ] Správný typ registrace?

  • - register() pro synchronní eventy
    - registerAsync() pro async eventy
    - EntityEventSystem pro ECS eventy
  • [ ] Pro ECS: je systém registrován v EntityStoreRegistry?

  • [ ] Není event zrušen předchozím listenerem?
  • Thread Error

  • [ ] Přistupuješ ke komponentám z async kontextu?

  • [ ] Používáš CommandBase místo AbstractPlayerCommand?

  • [ ] Chybí world.execute() pro přístup ke komponentám?

  • [ ] Blokuješ world thread (Thread.sleep, blocking I/O)?
  • Null Component

  • [ ] Je ComponentType správně inicializován?

  • [ ] Je komponenta registrována v pluginu?

  • [ ] Je komponenta přidána k entitě?

  • [ ] Kontroluješ null před použitím?

  • [ ] Je ref stále validní (ref.isValid())?
  • Performance Issues

  • [ ] Není blokující operace na world thread?

  • [ ] Jsou těžké operace na async thread?

  • [ ] Jsou query optimalizované (cache, batch)?

  • [ ] Nejsou memory leaky (neodregistrované listenery)?

---

Shrnutí

| Problém | První Krok |
|---------|------------|
| Plugin nenačten | Zkontroluj manifest.json a logy |
| Thread error | Přidej world.execute() |
| Null component | Přidej null check |
| Event nefunguje | Ověř typ registrace |
| Performance | Profiluj s measureOperation() |

| Debug Nástroj | Použití |
|---------------|---------|
| DebugUtils | Vizuální debug tvary |
| HytaleLogger | Logování s úrovněmi |
| /debug shape | In-game debug příkazy |
| ThreadDebugger | Kontrola thread safety |
| PerformanceProfiler | Měření výkonu |

Last updated: 20. ledna 2026