HyCodeYourTale

Warp System

Warp System

Dokumentace warp systému pro pojmenované teleportační body.

---

Přehled

Warp systém v Hytale umožňuje vytvářet pojmenované teleportační body, které lze uložit a použít pro rychlou navigaci. Warpy jsou spravovány přes TeleportPlugin.

---

Warp Třída

Z Dekompilovaného Kódu

@Deprecated
public class Warp {
private String id;
private String world;
@Nullable
private Transform transform;
private String creator;
private Instant creationDate;

public Warp() {}

public Warp(
@Nonnull Transform transform,
@Nonnull String id,
@Nonnull World world,
@Nonnull String creator,
@Nonnull Instant creationDate
) {
this.id = id;
this.world = world.getName();
this.transform = transform;
this.creator = creator;
this.creationDate = creationDate;
}

// Gettery
public String getId() { return this.id; }
public String getWorld() { return this.world; }
@Nullable
public Transform getTransform() { return this.transform; }
public String getCreator() { return this.creator; }
public Instant getCreationDate() { return this.creationDate; }

// Konverze na Teleport komponentu
@Nullable
public Teleport toTeleport() {
World worldInstance = Universe.get().getWorld(this.world);
return worldInstance == null ? null : Teleport.createForPlayer(worldInstance, this.transform);
}

@Nonnull
@Override
public String toString() {
return "Warp{id='" + this.id + "', transform=" + this.transform +
", creator='" + this.creator + "', creationDate=" + this.creationDate + "}";
}
}

---

Warp Codec

Serializace/Deserializace

public static final Codec CODEC = new BsonFunctionCodec<>(
BuilderCodec.builder(Warp.class, Warp::new)
.addField(new KeyedCodec<>("Id", Codec.STRING), (warp, s) -> warp.id = s, warp -> warp.id)
.addField(new KeyedCodec<>("World", Codec.STRING), (warp, s) -> warp.world = s, warp -> warp.world)
.addField(new KeyedCodec<>("Creator", Codec.STRING), (warp, s) -> warp.creator = s, warp -> warp.creator)
// Migrace: starý "Date" klíč (epoch millis)
.append(new KeyedCodec<>("Date", Codec.LONG), (warp, s) -> warp.creationDate = Instant.ofEpochMilli(s), warp -> null)
.addValidator(Validators.deprecated())
.add()
// Nový "CreationDate" klíč (Instant)
.append(new KeyedCodec<>("CreationDate", Codec.INSTANT), (o, i) -> o.creationDate = i, o -> o.creationDate)
.add()
.build(),
// Dekódování transformu
(warp, bsonValue) -> {
warp.transform = Transform.CODEC.decode(bsonValue);
return warp;
},
// Enkódování transformu
(bsonValue, warp) -> {
bsonValue.asDocument().putAll(Transform.CODEC.encode(warp.transform).asDocument());
return bsonValue;
}
);

public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, Warp[]::new);

---

TeleportPlugin - Správa Warpů

Singleton a Inicializace

public class TeleportPlugin extends JavaPlugin {
private static TeleportPlugin instance;
public static final String WARP_MODEL_ID = "Warp";

@Nonnull
private final AtomicBoolean loaded = new AtomicBoolean();
@Nonnull
private final ReentrantLock saveLock = new ReentrantLock();
@Nonnull
private final AtomicBoolean postSaveRedo = new AtomicBoolean(false);
@Nonnull
private final Map warps = new ConcurrentHashMap<>();

private Model warpModel;

@Nonnull
public static TeleportPlugin get() {
return instance;
}

public boolean isWarpsLoaded() {
return this.loaded.get();
}

public Map getWarps() {
return this.warps;
}
}

Setup

@Override
protected void setup() {
instance = this;
CommandRegistry commandRegistry = this.getCommandRegistry();
EventRegistry eventRegistry = this.getEventRegistry();

// Registrace příkazů
commandRegistry.registerCommand(new TeleportCommand());
commandRegistry.registerCommand(new WarpCommand());
commandRegistry.registerCommand(new SpawnCommand());

// Eventy
eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelAssetChange);
eventRegistry.registerGlobal(ChunkPreLoadProcessEvent.class, this::onChunkPreLoadProcess);
eventRegistry.registerGlobal(AddWorldEvent.class, event ->
event.getWorld().getWorldMapManager().addMarkerProvider("warps", WarpMarkerProvider.INSTANCE)
);
eventRegistry.registerGlobal(AllWorldsLoadedEvent.class, event -> this.loadWarps());

// Registrace komponent
this.teleportHistoryComponentType = EntityStore.REGISTRY.registerComponent(
TeleportHistory.class, TeleportHistory::new
);
this.warpComponentType = EntityStore.REGISTRY.registerComponent(
WarpComponent.class,
() -> { throw new UnsupportedOperationException("WarpComponent must be created manually"); }
);
}

---

Načítání Warpů

Z Dekompilovaného Kódu

public void loadWarps() {
BsonDocument document = null;
Path universePath = Universe.get().getPath();
Path oldPath = universePath.resolve("warps.bson");
Path path = universePath.resolve("warps.json");

// Migrace starého formátu .bson → .json
if (Files.exists(oldPath) && !Files.exists(path)) {
try {
Files.move(oldPath, path);
} catch (IOException e) {
// Ignoruj chybu
}
}

// Načti dokument
if (Files.exists(path)) {
document = BsonUtil.readDocument(path).join();
}

if (document != null) {
// Podpora obou klíčů pro zpětnou kompatibilitu
BsonArray bsonWarps = document.containsKey("Warps")
? document.getArray("Warps")
: document.getArray("warps");

this.warps.clear();

// Dekódování pomocí ArrayCodec
for (Warp warp : Warp.ARRAY_CODEC.decode(bsonWarps)) {
this.warps.put(warp.getId().toLowerCase(), warp);
}

getLogger().at(Level.INFO).log("Loaded %d warps", bsonWarps.size());
} else {
getLogger().at(Level.INFO).log("Loaded 0 warps (No warps.json found)");
}

this.loaded.set(true);
}

---

Ukládání Warpů

Thread-Safe Save Pattern

private void saveWarps0() {
Warp[] array = this.warps.values().toArray(Warp[]::new);
BsonDocument document = new BsonDocument("Warps", Warp.ARRAY_CODEC.encode(array));
Path path = Universe.get().getPath().resolve("warps.json");
BsonUtil.writeDocument(path, document).join();
getLogger().at(Level.INFO).log("Saved %d warps to warps.json", array.length);
}

public void saveWarps() {
if (this.saveLock.tryLock()) {
try {
this.saveWarps0();
} catch (Throwable e) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(e)).log("Failed to save warps:");
} finally {
this.saveLock.unlock();
}

// Pokud někdo chtěl uložit během ukládání, ulož znovu
if (this.postSaveRedo.getAndSet(false)) {
this.saveWarps();
}
} else {
// Někdo jiný ukládá, označíme pro opakování
this.postSaveRedo.set(true);
}
}

---

Warp Entity

Vytvoření Warp Entity

@Nonnull
public Holder createWarp(@Nonnull Warp warp, @Nonnull Store store) {
Transform transform = warp.getTransform();
Holder holder = EntityStore.REGISTRY.newHolder();

// Pozice a rotace
holder.addComponent(
TransformComponent.getComponentType(),
new TransformComponent(transform.getPosition(), transform.getRotation())
);

// Network ID pro synchronizaci s klientem
holder.addComponent(
NetworkId.getComponentType(),
new NetworkId(store.getExternalData().takeNextNetworkId())
);

// Entity je nehmotná (nelze s ní kolidovat)
holder.ensureComponent(Intangible.getComponentType());

// Bounding box
holder.addComponent(
BoundingBox.getComponentType(),
new BoundingBox(this.warpModel.getBoundingBox())
);

// Model (vizuální reprezentace)
holder.addComponent(
ModelComponent.getComponentType(),
new ModelComponent(this.warpModel)
);

// Jmenovka nad warpem
holder.addComponent(
Nameplate.getComponentType(),
new Nameplate(warp.getId())
);

// Skrytý pro adventure hráče (jen pro buildery)
holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType());

// Nesérializovaná komponenta (neukládá se)
holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType());

// Reference na Warp objekt
holder.addComponent(this.warpComponentType, new WarpComponent(warp));

return holder;
}

Spawn Warp Entity při Načítání Chunku

private void onChunkPreLoadProcess(@Nonnull ChunkPreLoadProcessEvent event) {
WorldChunk chunk = event.getChunk();
BlockChunk blockChunk = chunk.getBlockChunk();
if (blockChunk == null) return;

int chunkX = blockChunk.getX();
int chunkZ = blockChunk.getZ();
World world = chunk.getWorld();
String worldName = world.getName();

for (Entry warpEntry : this.warps.entrySet()) {
Warp warp = warpEntry.getValue();
Transform transform = warp.getTransform();

if (transform == null) continue;

Vector3d position = transform.getPosition();

// Kontrola zda warp patří do tohoto chunku
if (ChunkUtil.isInsideChunk(chunkX, chunkZ, MathUtil.floor(position.x), MathUtil.floor(position.z))
&& warp.getWorld().equals(worldName)) {

world.execute(() -> {
Store store = world.getEntityStore().getStore();
store.addEntity(this.createWarp(warp, store), AddReason.LOAD);
});
}
}
}

---

Warp Příkazy

WarpCommand (Hlavní)

public class WarpCommand extends AbstractCommandCollection {
@Nonnull
private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED =
Message.translation("server.commands.teleport.warp.notLoaded");
@Nonnull
private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_UNKNOWN_WARP =
Message.translation("server.commands.teleport.warp.unknownWarp");
@Nonnull
private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_WORLD_NAME_FOR_WARP_NOT_FOUND =
Message.translation("server.commands.teleport.warp.worldNameForWarpNotFound");
@Nonnull
private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_WARPED_TO =
Message.translation("server.commands.teleport.warp.warpedTo");

public WarpCommand() {
super("warp", "server.commands.warp.desc");
this.addUsageVariant(new WarpGoVariantCommand());
this.addSubCommand(new WarpGoCommand());
this.addSubCommand(new WarpSetCommand());
this.addSubCommand(new WarpListCommand());
this.addSubCommand(new WarpRemoveCommand());
this.addSubCommand(new WarpReloadCommand());
}

// Teleport na warp
static void tryGo(
@Nonnull CommandContext context,
@Nonnull String warp,
@Nonnull Ref ref,
@Nonnull Store store
) {
if (!TeleportPlugin.get().isWarpsLoaded()) {
context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED);
return;
}

Warp targetWarp = TeleportPlugin.get().getWarps().get(warp.toLowerCase());
if (targetWarp == null) {
context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_UNKNOWN_WARP.param("name", warp));
return;
}

String worldName = targetWarp.getWorld();
World world = Universe.get().getWorld(worldName);
Teleport teleportComponent = targetWarp.toTeleport();

if (world == null || teleportComponent == null) {
context.sendMessage(
MESSAGE_COMMANDS_TELEPORT_WARP_WORLD_NAME_FOR_WARP_NOT_FOUND
.param("worldName", worldName)
.param("warp", warp)
);
return;
}

// Uložení historie
TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType());
HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType());

Vector3d playerPosition = transformComponent.getPosition();
Vector3f playerHeadRotation = headRotationComponent.getRotation();

store.ensureAndGetComponent(ref, TeleportHistory.getComponentType())
.append(world, playerPosition.clone(), playerHeadRotation.clone(), "Warp '" + warp + "'");

// Teleportace
store.addComponent(ref, Teleport.getComponentType(), teleportComponent);
context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_WARPED_TO.param("name", warp));
}
}

WarpSetCommand

public class WarpSetCommand extends AbstractPlayerCommand {
@Nonnull
private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED =
Message.translation("server.commands.teleport.warp.notLoaded");
@Nonnull
private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_RESERVED_KEYWORD =
Message.translation("server.commands.teleport.warp.reservedKeyword");

@Nonnull
private final RequiredArg nameArg = this.withRequiredArg(
"name", "server.commands.warp.set.name.desc", ArgTypes.STRING
);

public WarpSetCommand() {
super("set", "server.commands.warp.set.desc");
this.requirePermission(HytalePermissions.fromCommand("warp.set"));
}

@Override
protected void execute(
@Nonnull CommandContext context,
@Nonnull Store store,
@Nonnull Ref ref,
@Nonnull PlayerRef playerRef,
@Nonnull World world
) {
if (!TeleportPlugin.get().isWarpsLoaded()) {
context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED);
return;
}

Map warps = TeleportPlugin.get().getWarps();
String newId = this.nameArg.get(context).toLowerCase();

// Kontrola rezervovaných klíčových slov
if ("reload".equals(newId) || "remove".equals(newId) ||
"set".equals(newId) || "list".equals(newId) || "go".equals(newId)) {
context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_RESERVED_KEYWORD);
return;
}

// Získej pozici a rotaci hráče
TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType());
HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType());

Vector3d position = transformComponent.getPosition();
Vector3f headRotation = headRotationComponent.getRotation();

// Vytvoř nový warp
Transform transform = new Transform(position.clone(), headRotation.clone());
Warp newWarp = new Warp(transform, newId, world, playerRef.getUsername(), Instant.now());

// Přidej do mapy
warps.put(newWarp.getId().toLowerCase(), newWarp);

// Ulož a vytvoř entity
TeleportPlugin plugin = TeleportPlugin.get();
plugin.saveWarps();
store.addEntity(plugin.createWarp(newWarp, store), AddReason.LOAD);

context.sendMessage(
Message.translation("server.commands.teleport.warp.setWarp")
.param("name", newWarp.getId())
);
}
}

WarpGoCommand

public class WarpGoCommand extends AbstractPlayerCommand {
@Nonnull
private final RequiredArg warpNameArg = this.withRequiredArg(
"warpName", "server.commands.warp.warpName.desc", ArgTypes.STRING
);

public WarpGoCommand() {
super("go", "server.commands.warp.go.desc");
this.requirePermission(HytalePermissions.fromCommand("warp.go"));
}

@Override
protected void execute(
@Nonnull CommandContext context,
@Nonnull Store store,
@Nonnull Ref ref,
@Nonnull PlayerRef playerRef,
@Nonnull World world
) {
String warpName = this.warpNameArg.get(context);
WarpCommand.tryGo(context, warpName, ref, store);
}
}

---

World Map Marker

WarpMarkerProvider

Warpy se zobrazují na mapě světa.

public static class WarpMarkerProvider implements WorldMapManager.MarkerProvider {
public static final WarpMarkerProvider INSTANCE = new WarpMarkerProvider();

@Override
public void update(
@Nonnull World world,
@Nonnull GameplayConfig gameplayConfig,
@Nonnull WorldMapTracker tracker,
int chunkViewRadius,
int playerChunkX,
int playerChunkZ
) {
Map warps = TeleportPlugin.get().getWarps();
if (warps.isEmpty()) return;

// Kontrola nastavení - zda zobrazovat warpy
if (!gameplayConfig.getWorldMapConfig().isDisplayWarps()) return;

for (Warp warp : warps.values()) {
if (!warp.getWorld().equals(world.getName())) continue;

tracker.trySendMarker(
chunkViewRadius,
playerChunkX,
playerChunkZ,
warp.getTransform().getPosition(),
warp.getTransform().getRotation().getYaw(),
"Warp-" + warp.getId(), // Unikátní ID
"Warp: " + warp.getId(), // Zobrazované jméno
warp,
(id, name, w) -> new MapMarker(
id,
name,
"Warp.png", // Ikona markeru
PositionUtil.toTransformPacket(w.getTransform()),
null
)
);
}
}
}

---

Formát warps.json

Příklad Souboru

{
"Warps": [
{
"Id": "spawn",
"World": "overworld",
"Creator": "Admin",
"CreationDate": "2024-01-15T10:30:00Z",
"Position": {
"X": 100.5,
"Y": 64.0,
"Z": 200.5
},
"Rotation": {
"Pitch": 0.0,
"Yaw": 90.0,
"Roll": 0.0
}
},
{
"Id": "arena",
"World": "pvp_world",
"Creator": "Builder",
"CreationDate": "2024-01-16T14:45:00Z",
"Position": {
"X": 0.0,
"Y": 100.0,
"Z": 0.0
},
"Rotation": {
"Pitch": -10.0,
"Yaw": 180.0,
"Roll": 0.0
}
}
]
}

---

Vlastní Warp Manager

Rozšíření pro Vlastní Plugin

public class CustomWarpManager {
private final Map warps = new ConcurrentHashMap<>();
private final Path warpsPath;
private final ReentrantLock saveLock = new ReentrantLock();
private final AtomicBoolean pendingSave = new AtomicBoolean(false);

public CustomWarpManager(Path dataFolder) {
this.warpsPath = dataFolder.resolve("warps.json");
}

public void createWarp(String name, World world, Transform transform, String creator) {
Warp warp = new Warp(transform, name, world, creator, Instant.now());
warps.put(name.toLowerCase(), warp);
save();
}

public Warp getWarp(String name) {
return warps.get(name.toLowerCase());
}

public boolean deleteWarp(String name) {
Warp removed = warps.remove(name.toLowerCase());
if (removed != null) {
save();
return true;
}
return false;
}

public Collection getAllWarps() {
return Collections.unmodifiableCollection(warps.values());
}

public Collection getWarpsInWorld(String worldName) {
return warps.values().stream()
.filter(w -> w.getWorld().equals(worldName))
.collect(Collectors.toList());
}

public void load() {
if (!Files.exists(warpsPath)) {
getLogger().atInfo().log("No warps file found");
return;
}

BsonDocument doc = BsonUtil.readDocument(warpsPath).join();
if (doc == null) return;

BsonArray array = doc.getArray("Warps");
warps.clear();

for (Warp warp : Warp.ARRAY_CODEC.decode(array)) {
warps.put(warp.getId().toLowerCase(), warp);
}

getLogger().atInfo().log("Loaded %d warps", warps.size());
}

public void save() {
if (saveLock.tryLock()) {
try {
Warp[] array = warps.values().toArray(Warp[]::new);
BsonDocument doc = new BsonDocument("Warps", Warp.ARRAY_CODEC.encode(array));
BsonUtil.writeDocument(warpsPath, doc).join();
} finally {
saveLock.unlock();
}

if (pendingSave.getAndSet(false)) {
save();
}
} else {
pendingSave.set(true);
}
}
}

---

Shrnutí

| Operace | Metoda |
|---------|--------|
| Získat všechny warpy | TeleportPlugin.get().getWarps() |
| Získat warp | warps.get(name.toLowerCase()) |
| Vytvořit warp | new Warp(transform, id, world, creator, Instant.now()) |
| Teleport na warp | warp.toTeleport()store.addComponent() |
| Načíst warpy | TeleportPlugin.get().loadWarps() |
| Uložit warpy | TeleportPlugin.get().saveWarps() |
| Warp entity | TeleportPlugin.get().createWarp(warp, store) |

| Příkaz | Popis |
|--------|-------|
| /warp | Teleport na warp |
| /warp go | Teleport na warp |
| /warp set | Vytvořit warp na aktuální pozici |
| /warp list | Seznam všech warpů |
| /warp remove | Smazat warp |
| /warp reload | Znovu načíst warpy |

Last updated: 20. ledna 2026