Skip to content

Creating Worlds

Package: com.hypixel.hytale.server.core.universe

The Universe singleton manages all loaded worlds. Each world runs on its own thread, has its own config, and can be created, loaded, or removed at runtime.

Universe universe = Universe.get();
// create a world with default config
CompletableFuture<World> future = universe.addWorld("arena");
future.thenAccept(world -> {
// world is running and ready
});

addWorld() throws IllegalArgumentException if the name is already taken (in memory or on disk). The world is saved to {universe}/worlds/{name}/.

A deprecated overload accepts generator and storage type names:

// create with a specific generator
universe.addWorld("flatworld", "Flat", null);

For complete control over the world, build a WorldConfig and call makeWorld():

WorldConfig config = new WorldConfig();
config.setDisplayName("PvP Arena");
config.setPvpEnabled(true);
config.setGameMode(GameMode.ADVENTURE);
config.setDeleteOnRemove(true); // clean up files when removed
Path savePath = universe.getPath().resolve("worlds").resolve("pvp-arena");
CompletableFuture<World> future = universe.makeWorld("pvp-arena", savePath, config);

makeWorld() dispatches AddWorldEvent before registering the world — if a listener cancels it, the future completes exceptionally with WorldLoadCancelledException.

A start parameter (defaults to true) controls whether the world thread starts immediately:

// create without starting — useful for setup before ticking begins
universe.makeWorld("staging", savePath, config, false);

When a world is created:

  1. WorldConfig validated (required mods checked)
  2. World object constructed (stores, thread created)
  3. AddWorldEvent dispatched (cancellable)
  4. World registered in the universe maps
  5. world.init() — world generator loaded
  6. world.start() — ticking thread starts, ECS stores initialized
  7. StartWorldEvent dispatched — safe to access stores and entities

Load a world that exists on disk but isn’t currently in memory:

// check if a world can be loaded
if (universe.isWorldLoadable("saved-world")) {
CompletableFuture<World> future = universe.loadWorld("saved-world");
}

isWorldLoadable() returns true if the save directory contains config.bson or config.json. loadWorld() reads the config from disk and delegates to makeWorld().

The built-in hub system uses this pattern to handle worlds that may or may not exist:

World world = universe.getWorld(worldName);
if (world != null) {
// already loaded — use directly
teleportPlayer(playerRef, world);
} else if (universe.isWorldLoadable(worldName)) {
// exists on disk — load it
universe.loadWorld(worldName).thenAccept(w -> teleportPlayer(playerRef, w));
} else {
// doesn't exist — create it
universe.addWorld(worldName).thenAccept(w -> teleportPlayer(playerRef, w));
}
// remove a world (returns false if cancelled or not found)
boolean removed = universe.removeWorld("arena");

Removal dispatches RemoveWorldEvent (cancellable unless the reason is EXCEPTIONAL). Before the world thread stops, all players are drained to the default world.

When a non-default world is removed, its players are moved automatically. You can also drain manually:

// move all players from one world to another
world.drainPlayersTo(targetWorld);

Each player drain dispatches DrainPlayerFromWorldEvent (keyed by the source world name), which allows listeners to redirect the target world or modify the spawn transform:

eventRegistry.registerGlobal(DrainPlayerFromWorldEvent.class, event -> {
// redirect to a specific world instead of the default
World lobby = Universe.get().getWorld("lobby");
if (lobby != null) {
event.setWorld(lobby);
}
});

Two WorldConfig flags control what happens to world files:

FlagDefaultEffect
DeleteOnRemovefalseDelete world files when the world is removed at runtime
DeleteOnUniverseStartfalseDelete world files on server startup

These are useful for ephemeral worlds like game arenas or instances.

World generators are registered as codec types. Set them on WorldConfig before creating the world, or pass the type name to addWorld().

TypeClassDescription
"Flat"FlatWorldGenProviderFlat world with configurable layers, block types, and environment
"Void"VoidWorldGenProviderEmpty world — no blocks, configurable tint and environment
"Dummy"DummyWorldGenProviderSingle layer of blocks at Y=0
"Hytale"(WorldGenPlugin)Full terrain generation with biomes, caves, structures

"Void" is the default when no generator is specified. The "Hytale" generator is loaded by the built-in WorldGenPlugin.

// create a flat world
universe.addWorld("flatworld", "Flat", null);
// or configure via WorldConfig
WorldConfig config = new WorldConfig();
config.setWorldGenProvider(new FlatWorldGenProvider());
universe.makeWorld("flatworld", savePath, config);

Spawn providers control where new players appear in the world. Set them on WorldConfig via setSpawnProvider().

TypeBehavior
GlobalSpawnProviderSingle static spawn point for all players
IndividualSpawnProviderSelects from a list of spawn points per player (hash-based on UUID)
FitToHeightMapSpawnProviderWraps another provider, adjusts Y to the heightmap if below 0

If no spawn provider is set, the world generator’s default is used.

TypeDescription
"Hytale" (default)Standard chunk persistence (delegates to IndexedStorageChunkStorageProvider internally)
"Empty"No persistence — chunks are never saved
"Migration"Migration from older formats
"IndexedStorage"Indexed storage format (same implementation as "Hytale")

Set via WorldConfig.setChunkStorageProvider() or the chunkStorageType parameter on addWorld().

The built-in instance system (InstancesPlugin) demonstrates runtime world creation for ephemeral gameplay areas. Instances are template-based worlds that auto-clean up.

  1. Template files stored in Server/Instances/{name}/ (same format as world save)
  2. spawnInstance() copies the template to a temporary world directory
  3. Loads config from instance.bson (same schema as WorldConfig)
  4. Creates the world with universe.makeWorld() using a unique key: instance-{safeName}-{uuid}
  5. Auto-removal conditions handle cleanup when the instance is no longer needed

Instances register removal conditions that automatically call removeWorld():

ConditionBehavior
WorldEmptyConditionRemove when no players remain
IdleTimeoutConditionRemove after idle with no players for N seconds
TimeoutConditionRemove after N seconds regardless of players

When a player enters an instance for the first time, DiscoverInstanceEvent.Display fires as an ECS event (cancellable). This triggers the event title display. See Event Titles for how to listen to and suppress instance discovery titles.

The WorldConfigProvider interface controls how world configs are loaded and saved. By default, it reads from JSON/BSON files in the world’s save directory.

Replace it during PrepareUniverseEvent by calling setWorldConfigProvider() on the event:

eventRegistry.registerGlobal(PrepareUniverseEvent.class, event -> {
event.setWorldConfigProvider(new MyCustomConfigProvider());
});

World Overview | Configuration | World Events | Threading