A teleport request plugin for Hytale servers allowing players to request teleports to each other.
The Hytale server API is in Server/HytaleServer.jar. Since there's no official documentation, use these commands to discover available classes and methods:
cd "/Users/golemgrid/Library/Application Support/Hytale/install/release/package/game/latest/Server"
# Find all event-related classes
jar -tf HytaleServer.jar | grep -i "event"
# Find player-related classes
jar -tf HytaleServer.jar | grep -i "player"
# Find all classes in a specific package
jar -tf HytaleServer.jar | grep "com/hypixel/hytale/server/core/event/events/"# Basic class inspection
javap -classpath HytaleServer.jar com.hypixel.hytale.server.core.event.events.player.PlayerInteractEvent
# With more detail (private members too)
javap -p -classpath HytaleServer.jar com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEventcom.hypixel.hytale.server.core.plugin # JavaPlugin, PluginBase
com.hypixel.hytale.server.core.event.events # All events
com.hypixel.hytale.server.core.command # Command system
com.hypixel.hytale.server.core.entity # Entity/Player classes
com.hypixel.hytale.component # ECS system (Store, Ref, Query)
com.hypixel.hytale.component.system # EntityEventSystem, EcsEvent
com.hypixel.hytale.event # EventRegistry
com.hypixel.hytale.math.vector # Vector3d, Vector3f, Vector3i
com.hypixel.hytale.protocol # InteractionType, etc.
plugins/PluginName/
├── src/main/java/com/yourplugin/
│ ├── YourPlugin.java # Extends JavaPlugin
│ ├── commands/ # Extend AbstractPlayerCommand
│ ├── listeners/ # Event handlers
│ ├── systems/ # ECS EntityEventSystems
│ └── data/ # Data classes
├── src/main/resources/
│ └── manifest.json # MUST use PascalCase fields
├── pom.xml
└── target/PluginName-1.0.0.jar # Goes in Server/mods/
{
"Group": "cryptobench",
"Name": "EasyHome",
"Version": "1.0.0",
"Main": "com.easyhome.EasyHome",
"Description": "Description here",
"Authors": [],
"Dependencies": {}
}import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
public class YourPlugin extends JavaPlugin {
public YourPlugin(JavaPluginInit init) { super(init); }
@Override
public void setup() {
// Register commands, events, systems
getCommandRegistry().registerCommand(new YourCommand());
getEventRegistry().registerGlobal(EventType.class, this::handler);
getEntityStoreRegistry().registerSystem(new YourSystem());
}
@Override
public void start() { }
@Override
public void shutdown() { }
}getEventRegistry() // For IBaseEvent events (PlayerInteractEvent, etc.)
getEntityStoreRegistry() // For ECS systems and components on entities
getChunkStoreRegistry() // For ECS systems and components on chunks
getCommandRegistry() // For commands
getBlockStateRegistry() // For block states
getEntityRegistry() // For entity types
getTaskRegistry() // For scheduled tasks
getDataDirectory() // Plugin data folder: mods/Group_PluginName/Hytale uses a multi-threaded server model. Understanding this is MANDATORY before writing any plugin code.
| Component | Description |
|---|---|
| HytaleServer | Singleton root; owns SCHEDULED_EXECUTOR for background tasks |
| Universe | Singleton container for all worlds; thread-safe player lookups via ConcurrentHashMap |
| World | Each world runs on its own dedicated thread |
Key Benefit: Lag in "World A" does NOT cause lag in "World B" - worlds run in parallel.
The EntityStore and ALL ECS operations (getComponent, addComponent, removeComponent) are THREAD-BOUND.
They can ONLY be accessed from their specific world's thread. Hytale uses assertThread() internally - accessing from the wrong thread throws IllegalStateException immediately to prevent silent data corruption.
// WRONG - will crash if called from wrong thread
store.getComponent(playerRef, Player.getComponentType());
// CORRECT - ensures execution on world thread
world.execute(() -> {
store.getComponent(playerRef, Player.getComponentType());
});To run code on a specific world's thread from an external thread (background task, different world, etc.), use world.execute():
// From a background task or different thread
world.execute(() -> {
// This code runs safely on the world's thread
Store<EntityStore> store = world.getEntityStore().getStore();
// Now safe to access ECS components
});| Always Safe (Any Thread) | Unsafe (Requires world.execute()) |
|---|---|
Universe.get().getPlayer(uuid) |
store.getComponent(ref, type) |
playerRef.sendMessage(message) |
store.addComponent(...) |
HytaleServer.SCHEDULED_EXECUTOR.schedule(...) |
store.removeComponent(...) |
world.execute(runnable) |
Modifying entity position/health/inventory |
When sharing data across multiple worlds (global state), use Java's thread-safe types:
// Counters - use AtomicInteger
private final AtomicInteger globalKills = new AtomicInteger(0);
globalKills.incrementAndGet();
// Collections/Maps - use ConcurrentHashMap
private final ConcurrentHashMap<UUID, Integer> playerKills = new ConcurrentHashMap<>();
playerKills.merge(playerId, 1, Integer::sum);
// One-time initialization - use AtomicBoolean
private final AtomicBoolean initialized = new AtomicBoolean(false);
if (initialized.compareAndSet(false, true)) {
// Initialize only once
}
// Simple flags - use volatile
private volatile boolean enabled = true;SCHEDULED_EXECUTOR runs on its own background thread, NOT a world thread:
// WRONG - crashes when touching entity
HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> {
store.getComponent(ref, type); // IllegalStateException!
}, 1, TimeUnit.SECONDS);
// CORRECT - bridge back to world thread
HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> {
world.execute(() -> {
store.getComponent(ref, type); // Safe!
});
}, 1, TimeUnit.SECONDS);Never call .join() or .get() on a CompletableFuture inside a world thread - it blocks the entire world tick:
// WRONG - blocks world tick
CompletableFuture<Data> future = fetchDataAsync();
Data data = future.get(); // DON'T DO THIS
// CORRECT - use callbacks
fetchDataAsync().thenAccept(data -> {
world.execute(() -> {
// Process data on world thread
});
});Remember that counter++ is secretly three operations (read, increment, write):
// WRONG - race condition
private int counter = 0;
counter++; // Lost updates!
// CORRECT - atomic operation
private final AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();| Spec | Value | Notes |
|---|---|---|
| Tick Rate | 30 TPS | 33.3ms per tick (vs Minecraft's 20 TPS) |
| Tick Budget | 33ms | Heavy logic (>33ms) lags the entire world |
| Scaling | Per-core | More CPU cores = more parallel worlds |
- Offload Heavy Work: Move expensive operations (pathfinding, database I/O, HTTP requests) to
SCHEDULED_EXECUTORorCompletableFuture.runAsync() - Avoid Object Creation in Ticks: Reuse objects where possible to reduce GC pressure
- Use
world.execute()Sparingly: Queue minimal work back to world threads
| Event Type | Thread Context | Example |
|---|---|---|
| Local Events | Fires on the World Thread | PlayerInteractEvent, BreakBlockEvent - safe to touch ECS directly |
| Global Events | May fire on different thread | Server-wide events - must use world.execute() before touching entities |
"Always assume you are on the wrong thread unless you are inside a standard World System or event handler. If you touch
store, verify you are thread-bound or wrapped inworld.execute()."
If you see:
IllegalStateException: Assert not in thread!→ You're accessing ECS from wrong threadIllegalStateException: Store is currently processing!→ You're modifying during iteration- Random crashes or data corruption → Race condition, use atomic types
First debug step: "Is this code touching a Store/Component while running on an Executor thread?"
Events implementing IBaseEvent<KeyType>. Registered via EventRegistry.
// Registration
eventRegistry.registerGlobal(PlayerInteractEvent.class, this::onPlayerInteract);
eventRegistry.register(PlayerConnectEvent.class, this::onConnect); // Instance-specific
// Handler
private void onPlayerInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
Vector3i block = event.getTargetBlock();
InteractionType action = event.getActionType();
event.setCancelled(true); // Cancel the event
}Available Player Events:
PlayerInteractEvent- player interactions (has getPlayer(), getTargetBlock(), getActionType())PlayerConnectEvent- player joinsPlayerDisconnectEvent- player leavesPlayerChatEvent- chat messages
Events extending EcsEvent. Cannot use EventRegistry - need EntityEventSystem.
public class MyProtectionSystem extends EntityEventSystem<EntityStore, BreakBlockEvent> {
public MyProtectionSystem() {
super(BreakBlockEvent.class);
}
@Override
public Query<EntityStore> getQuery() {
return Query.any(); // Required - match all entities
}
@Override
public void handle(int entityIndex, ArchetypeChunk<EntityStore> chunk,
Store<EntityStore> store, CommandBuffer<EntityStore> commandBuffer,
BreakBlockEvent event) {
Vector3i block = event.getTargetBlock();
event.setCancelled(true); // Cancel the event
}
}
// Registration in plugin setup()
getEntityStoreRegistry().registerSystem(new MyProtectionSystem());Available ECS Block Events:
BreakBlockEvent- block destroyed (getTargetBlock(), setCancelled())DamageBlockEvent- block taking damage (getTargetBlock(), getDamage(), setDamage(), setCancelled())PlaceBlockEvent- block placed (getTargetBlock(), setCancelled())UseBlockEvent.Pre- block used/chest opened (getTargetBlock(), getContext(), setCancelled())
Key Difference: ECS events don't have player info directly! Must track via PlayerInteractEvent.
Primary // Left click - break/attack
Secondary // Right click - place/use
Use // Use action
Pick // Pick block (middle click)
Pickup // Pick up item
Ability1/2/3 // Abilities
// ... and morepublic class MyCommand extends AbstractPlayerCommand {
private final OptionalArg<String> arg;
public MyCommand() {
super("commandname", "Description");
this.arg = withOptionalArg("argname", "desc", ArgTypes.STRING);
// Or: withRequiredArg(...)
requirePermission("myplugin.use");
}
@Override
protected void execute(CommandContext ctx, Store<EntityStore> store,
Ref<EntityStore> playerRef, PlayerRef playerData, World world) {
String value = arg.get(ctx);
Player player = store.getComponent(playerRef, Player.getComponentType());
playerData.sendMessage(Message.raw("Hello").color(new Color(85, 255, 85)));
}
}Use requirePermission() in command constructors to require a permission:
public class MyCommand extends AbstractPlayerCommand {
public MyCommand() {
super("mycommand", "Description");
requirePermission("myplugin.use"); // Players need this permission to use the command
}
}// On Player object (in command execute methods)
Player player = store.getComponent(playerRef, Player.getComponentType());
if (player.hasPermission("myplugin.admin")) {
// Do admin stuff
}
// Via PermissionsModule (when you only have UUID)
import com.hypixel.hytale.server.core.permissions.PermissionsModule;
if (PermissionsModule.get().hasPermission(playerData.getUuid(), "myplugin.admin")) {
// Do admin stuff
}# Add permission to a group
perm group add <GroupName> <permission>
perm group add Adventure tpa.use
perm group add Adventure tpa.bypass.warmup
# Remove permission from a group
perm group remove <GroupName> <permission>
# List group permissions
perm group list <GroupName>
# Add user to a group
perm user group add <username> <GroupName>
# Add permission directly to user
perm user add <username> <permission>
# Test if you have a permission
perm test <permission>myplugin.use # Exact permission
myplugin.* # Wildcard - grants all myplugin.* permissions
* # All permissions (admin/OP)
-myplugin.use # Negates/denies a specific permission
-* # Denies all permissions
Located at Server/permissions.json:
{
"users": {
"uuid-here": {
"groups": ["Adventure", "Builder"]
}
},
"groups": {
"Default": [],
"Adventure": ["tpa.use"],
"OP": ["*"]
}
}Important: Group names are case-sensitive!
| Permission | Description |
|---|---|
tpa.use |
Basic access to all TPA commands |
tpa.bypass.warmup |
Skip teleport warmup delay |
tpa.admin |
Admin commands (if any) |
import com.hypixel.hytale.server.core.Message;
import java.awt.Color;
// Correct
playerData.sendMessage(Message.raw("Success!").color(new Color(85, 255, 85)));
// WRONG - shows literal "§a"
playerData.sendMessage(Message.raw("§aSuccess!"));
// Common colors
Color GREEN = new Color(85, 255, 85);
Color RED = new Color(255, 85, 85);
Color YELLOW = new Color(255, 255, 85);
Color GOLD = new Color(255, 170, 0);
Color GRAY = new Color(170, 170, 170);// Get position
TransformComponent transform = store.getComponent(playerRef, TransformComponent.getComponentType());
Vector3d position = transform.getPosition();
Vector3f rotation = transform.getRotation();
// Teleport (MUST run on world thread)
world.execute(() -> {
Vector3d pos = new Vector3d(x, y, z);
Vector3f rot = new Vector3f(yaw, pitch, 0);
Teleport teleport = new Teleport(world, pos, rot);
store.addComponent(playerRef, Teleport.getComponentType(), teleport);
});Path dataDir = getDataDirectory(); // mods/Group_PluginName/
Gson gson = new GsonBuilder().setPrettyPrinting().create(); // Gson provided by Hytalemvn clean package
# Copy target/PluginName-1.0.0.jar to Server/mods/// Plugin
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
// Events
import com.hypixel.hytale.event.EventRegistry;
import com.hypixel.hytale.server.core.event.events.player.*;
import com.hypixel.hytale.server.core.event.events.ecs.*;
// ECS Systems
import com.hypixel.hytale.component.system.EntityEventSystem;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.*;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
// Commands
import com.hypixel.hytale.server.core.command.system.*;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
// Entity/Player
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.World;
// Math
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.math.vector.Vector3i;
// Messages
import com.hypixel.hytale.server.core.Message;
// Permissions
import com.hypixel.hytale.server.core.permissions.PermissionsModule;