Skip to content

Server Transfer

Hytale implements a native server transfer system that allows players to seamlessly move between servers without proxy software like BungeeCord. This is accomplished through a “referral” mechanism with support for a 4KB custom data payload.

The transfer is entirely client-driven - Server A tells the client where to go and what data to carry, then the client connects to Server B with that data.

Sent from server to client to initiate a transfer.

FieldTypeMax SizeDescription
hostToHostAddress~1KBTarget server address
databyte[]4096 bytesCustom payload for the target server

The HostAddress structure contains:

public class HostAddress {
public String host; // max 256 characters
public short port; // 1-65535
}

The initial connection packet, which includes referral data when the player was transferred.

FieldTypeDescription
referralDatabyte[]The 4KB payload from the source server
referralSourceHostAddressWhich server sent this player
// transfer player to another server
playerRef.referToServer("minigames.example.com", 25565);

Pass custom data (up to 4KB) to the destination server:

// serialize player state, session info, etc.
byte[] sessionData = new byte[4096];
// ... populate with your data
playerRef.referToServer("minigames.example.com", 25565, sessionData);

The PlayerRef class provides the transfer API:

public class PlayerRef {
// simple transfer
public void referToServer(String host, int port);
// transfer with custom data (max 4096 bytes)
public void referToServer(String host, int port, byte[] data);
}

Listen to this event to handle incoming transferred players:

eventRegistry.register(PlayerSetupConnectEvent.class, event -> {
// check if this is a transferred player
if (event.isReferralConnection()) {
byte[] data = event.getReferralData();
HostAddress source = event.getReferralSource();
// log the transfer
System.out.println("Player " + event.getUsername() +
" transferred from " + source.host + ":" + source.port +
" with " + data.length + " bytes of data");
// validate and process the data
if (!validateReferralData(data)) {
event.setCancelled(true);
event.setReason("Invalid transfer data");
return;
}
// restore player state from the data
restorePlayerState(event.getUuid(), data);
}
});

You can also redirect incoming players to a different server:

eventRegistry.register(PlayerSetupConnectEvent.class, event -> {
// route to a different server based on some logic
if (shouldRedirect(event.getUuid())) {
event.referToServer("lobby.example.com", 25565, event.getReferralData());
// player will be redirected, won't join this server
}
});
public class PlayerSetupConnectEvent {
// player information
UUID getUuid();
String getUsername();
PlayerAuthentication getAuth();
// referral information
boolean isReferralConnection();
byte[] getReferralData(); // the 4KB payload (or null)
HostAddress getReferralSource(); // source server (or null)
// actions
void referToServer(String host, int port);
void referToServer(String host, int port, byte[] data);
// cancel connection
void setCancelled(boolean cancelled);
void setReason(String reason);
}

Hytale includes a built-in command for testing transfers:

/refer <player> <host> <port>
/transfer <player> <host> <port>

Permissions:

  • command.refer.self - Transfer yourself
  • command.refer.other - Transfer other players
// on source server - sign the data
public byte[] createSignedTransferData(UUID playerId, long timestamp) {
ByteBuffer buffer = ByteBuffer.allocate(4096);
// write payload
buffer.putLong(playerId.getMostSignificantBits());
buffer.putLong(playerId.getLeastSignificantBits());
buffer.putLong(timestamp);
// ... more data
// sign with shared secret
byte[] signature = hmacSign(buffer.array(), SHARED_SECRET);
buffer.put(signature);
return buffer.array();
}
// on destination server - verify signature
public boolean validateTransferData(byte[] data, UUID expectedPlayer) {
ByteBuffer buffer = ByteBuffer.wrap(data);
UUID playerId = new UUID(buffer.getLong(), buffer.getLong());
long timestamp = buffer.getLong();
// check timestamp (prevent replay)
if (System.currentTimeMillis() - timestamp > 30000) {
return false; // expired
}
// verify signature
// ... extract and verify HMAC
return playerId.equals(expectedPlayer);
}