HyCodeYourTale

World Map Markers

World Map Markers

Detailní dokumentace k systému markerů na world mapě v Hytale.

---

Přehled Architektury

WorldMapManager (per-World manažer)

├── MarkerProvider[] (poskytovatelé markerů)
│ ├── SpawnMarkerProvider
│ ├── DeathMarkerProvider
│ ├── RespawnMarkerProvider
│ ├── POIMarkerProvider
│ ├── PlayerMarkersProvider
│ └── Custom providers...

├── MapMarker (packet struktura)

└── WorldMapTracker (per-Player tracker)

---

WorldMapManager

Hlavní manažer world mapy pro každý svět:

public class WorldMapManager extends TickingThread {
@Nonnull
private final World world;

// Registry marker providerů
private final Map markerProviders = new ConcurrentHashMap<>();

// Points of Interest (statické markery)
private final Map pointsOfInterest = new ConcurrentHashMap<>();

// Nastavení mapy
@Nonnull
private WorldMapSettings worldMapSettings = WorldMapSettings.DISABLED;

// Generátor map images
@Nullable
private IWorldMap generator;

public WorldMapManager(@Nonnull World world) {
super("WorldMap - " + world.getName(), 10, true);
this.world = world;

// Registrace vestavěných providerů
this.addMarkerProvider("spawn", SpawnMarkerProvider.INSTANCE);
this.addMarkerProvider("playerIcons", PlayerIconMarkerProvider.INSTANCE);
this.addMarkerProvider("death", DeathMarkerProvider.INSTANCE);
this.addMarkerProvider("respawn", RespawnMarkerProvider.INSTANCE);
this.addMarkerProvider("playerMarkers", PlayerMarkersProvider.INSTANCE);
this.addMarkerProvider("poi", POIMarkerProvider.INSTANCE);
}

// Přidání vlastního provideru
public void addMarkerProvider(@Nonnull String key, @Nonnull MarkerProvider provider) {
this.markerProviders.put(key, provider);
}

// Získání providerů
public Map getMarkerProviders() {
return this.markerProviders;
}

// Points of Interest
public Map getPointsOfInterest() {
return this.pointsOfInterest;
}

// Kontrola zda je mapa povolena
public boolean isWorldMapEnabled() {
return this.worldMapSettings.getSettingsPacket().enabled;
}

// Tick - aktualizuje tracker pro každého hráče
@Override
protected void tick(float dt) {
for (Player player : this.world.getPlayers()) {
player.getWorldMapTracker().tick(dt);
}
// Unload nepoužívaných images...
}
}

---

MarkerProvider Interface

Interface pro poskytovatele markerů:

public interface MarkerProvider {
/
* Aktualizuje markery pro hráče
*
* @param world Svět
* @param gameplayConfig Gameplay konfigurace
* @param tracker WorldMapTracker hráče
* @param chunkViewRadius Radius viditelnosti v chuncích
* @param playerChunkX X pozice hráče v chuncích
* @param playerChunkZ Z pozice hráče v chuncích
*/
void update(
World world,
GameplayConfig gameplayConfig,
WorldMapTracker tracker,
int chunkViewRadius,
int playerChunkX,
int playerChunkZ
);
}

---

MapMarker Packet

Struktura markeru posílaná na klienta:

public class MapMarker {
@Nullable
public String id; // Unikátní ID markeru

@Nullable
public String name; // Zobrazovaný název

@Nullable
public String markerImage; // Cesta k ikoně (např. "Spawn.png")

@Nullable
public Transform transform; // Pozice a rotace

@Nullable
public ContextMenuItem[] contextMenuItems; // Položky kontextového menu

public MapMarker() {}

public MapMarker(
@Nullable String id,
@Nullable String name,
@Nullable String markerImage,
@Nullable Transform transform,
@Nullable ContextMenuItem[] contextMenuItems
) {
this.id = id;
this.name = name;
this.markerImage = markerImage;
this.transform = transform;
this.contextMenuItems = contextMenuItems;
}
}

---

Vestavěné MarkerProviders

SpawnMarkerProvider

Zobrazuje spawn point světa:

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

@Override
public void update(
@Nonnull World world,
@Nonnull GameplayConfig gameplayConfig,
@Nonnull WorldMapTracker tracker,
int chunkViewRadius,
int playerChunkX,
int playerChunkZ
) {
WorldMapConfig worldMapConfig = gameplayConfig.getWorldMapConfig();

// Kontrola zda zobrazovat spawn
if (!worldMapConfig.isDisplaySpawn()) {
return;
}

Player player = tracker.getPlayer();
Transform spawnPoint = world.getWorldConfig().getSpawnProvider().getSpawnPoint(player);

if (spawnPoint != null) {
Vector3d spawnPosition = spawnPoint.getPosition();

tracker.trySendMarker(
chunkViewRadius,
playerChunkX,
playerChunkZ,
spawnPosition,
spawnPoint.getRotation().getYaw(),
"Spawn", // ID
"Spawn", // Název
spawnPosition, // Data pro factory
(id, name, pos) -> new MapMarker(
id,
name,
"Spawn.png", // Ikona
PositionUtil.toTransformPacket(new Transform(pos)),
null // Bez context menu
)
);
}
}
}

DeathMarkerProvider

Zobrazuje místa smrti hráče:

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

@Override
public void update(
@Nonnull World world,
@Nonnull GameplayConfig gameplayConfig,
@Nonnull WorldMapTracker tracker,
int chunkViewRadius,
int playerChunkX,
int playerChunkZ
) {
WorldMapConfig worldMapConfig = gameplayConfig.getWorldMapConfig();

if (!worldMapConfig.isDisplayDeathMarker()) {
return;
}

Player player = tracker.getPlayer();
PlayerWorldData perWorldData = player.getPlayerConfigData().getPerWorldData(world.getName());

// Projdi všechny pozice smrti
for (PlayerDeathPositionData deathPosition : perWorldData.getDeathPositions()) {
addDeathMarker(tracker, playerChunkX, playerChunkZ, deathPosition);
}
}

private static void addDeathMarker(
@Nonnull WorldMapTracker tracker,
int playerChunkX,
int playerChunkZ,
@Nonnull PlayerDeathPositionData deathPosition
) {
String markerId = deathPosition.getMarkerId();
Transform transform = deathPosition.getTransform();
int deathDay = deathPosition.getDay();

tracker.trySendMarker(
-1, // Bez view radius omezení
playerChunkX,
playerChunkZ,
transform.getPosition(),
transform.getRotation().getYaw(),
markerId,
"Death (Day " + deathDay + ")", // Dynamický název s dnem
transform,
(id, name, t) -> new MapMarker(
id,
name,
"Death.png",
PositionUtil.toTransformPacket(t),
null
)
);
}
}

---

Vytvoření Vlastního MarkerProvider

Základní Implementace

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

private final WarpManager warpManager;

public WarpMarkerProvider(WarpManager warpManager) {
this.warpManager = warpManager;
}

@Override
public void update(
@Nonnull World world,
@Nonnull GameplayConfig gameplayConfig,
@Nonnull WorldMapTracker tracker,
int chunkViewRadius,
int playerChunkX,
int playerChunkZ
) {
// Kontrola konfigurace
WorldMapConfig config = gameplayConfig.getWorldMapConfig();
if (!config.isEnabled()) {
return;
}

// Projdi všechny warpy
for (Warp warp : warpManager.getWarps()) {
// Pouze warpy v tomto světě
if (!warp.getWorld().equals(world.getName())) {
continue;
}

// Kontrola oprávnění
Player player = tracker.getPlayer();
if (!player.hasPermission("myplugin.warp." + warp.getId())) {
continue;
}

Transform transform = warp.getTransform();
Vector3d position = transform.getPosition();

tracker.trySendMarker(
chunkViewRadius,
playerChunkX,
playerChunkZ,
position,
transform.getRotation().getYaw(),
"warp-" + warp.getId(), // Unikátní ID
warp.getDisplayName(), // Zobrazovaný název
warp, // Data pro factory
(id, name, w) -> createWarpMarker(id, name, w)
);
}
}

private MapMarker createWarpMarker(String id, String name, Warp warp) {
// Context menu položky
ContextMenuItem[] contextMenu = new ContextMenuItem[] {
new ContextMenuItem("teleport", "Teleport", "teleport_icon.png"),
new ContextMenuItem("info", "Info", "info_icon.png")
};

return new MapMarker(
id,
name,
"WarpMarker.png",
PositionUtil.toTransformPacket(warp.getTransform()),
contextMenu
);
}
}

Registrace Provideru

@Override
protected void setup() {
// Registrace při přidání světa
getEventRegistry().registerGlobal(AddWorldEvent.class, event -> {
World world = event.getWorld();
world.getWorldMapManager().addMarkerProvider(
"warps",
new WarpMarkerProvider(this.warpManager)
);
});
}

---

WorldMapTracker

Per-player tracker pro world mapu:

// Získání trackeru
Player player = ...;
WorldMapTracker tracker = player.getWorldMapTracker();

// Odeslání markeru
tracker.trySendMarker(
chunkViewRadius, // Radius viditelnosti
playerChunkX, // Pozice hráče X
playerChunkZ, // Pozice hráče Z
markerPosition, // Pozice markeru
yaw, // Rotace
markerId, // Unikátní ID
markerName, // Název
data, // Libovolná data
markerFactory // Factory funkce (id, name, data) -> MapMarker
);

// Vymazání trackeru
tracker.clear();

---

PlayerMarkerReference

Pro perzistentní hráčské markery:

public static class PlayerMarkerReference implements MarkerReference {
public static final BuilderCodec CODEC;

private UUID player; // UUID hráče
private String world; // Název světa
private String markerId; // ID markeru

public PlayerMarkerReference(@Nonnull UUID player, @Nonnull String world, @Nonnull String markerId) {
this.player = player;
this.world = world;
this.markerId = markerId;
}

@Override
public String getMarkerId() {
return this.markerId;
}

@Override
public void remove() {
PlayerRef playerRef = Universe.get().getPlayer(this.player);

if (playerRef != null) {
// Online hráč
Player playerComponent = playerRef.getComponent(Player.getComponentType());
removeMarkerFromOnlinePlayer(playerComponent);
} else {
// Offline hráč
removeMarkerFromOfflinePlayer();
}
}

private void removeMarkerFromOfflinePlayer() {
Universe.get().getPlayerStorage().load(this.player)
.thenApply(holder -> {
Player player = holder.getComponent(Player.getComponentType());
PlayerConfigData data = player.getPlayerConfigData();
String world = this.world != null ? this.world : data.getWorld();

removeMarkerFromData(data, world, this.markerId);
return holder;
})
.thenCompose(holder ->
Universe.get().getPlayerStorage().save(this.player, holder)
);
}

@Nullable
private static MapMarker removeMarkerFromData(
@Nonnull PlayerConfigData data,
@Nonnull String worldName,
@Nonnull String markerId
) {
PlayerWorldData perWorldData = data.getPerWorldData(worldName);
MapMarker[] worldMapMarkers = perWorldData.getWorldMapMarkers();

if (worldMapMarkers == null) {
return null;
}

// Najdi index markeru
int index = -1;
for (int i = 0; i < worldMapMarkers.length; i++) {
if (worldMapMarkers[i].id.equals(markerId)) {
index = i;
break;
}
}

if (index == -1) {
return null;
}

// Vytvoř nové pole bez markeru
MapMarker[] newWorldMapMarkers = new MapMarker[worldMapMarkers.length - 1];
System.arraycopy(worldMapMarkers, 0, newWorldMapMarkers, 0, index);
System.arraycopy(worldMapMarkers, index + 1, newWorldMapMarkers, index,
newWorldMapMarkers.length - index);

perWorldData.setWorldMapMarkers(newWorldMapMarkers);
return worldMapMarkers[index];
}
}

Vytvoření Hráčského Markeru

// Vytvoření markeru uloženého v datech hráče
@Nonnull
public static PlayerMarkerReference createPlayerMarker(
@Nonnull Ref playerRef,
@Nonnull MapMarker marker,
@Nonnull ComponentAccessor componentAccessor
) {
World world = componentAccessor.getExternalData().getWorld();
Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType());
UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType());

PlayerWorldData perWorldData = playerComponent.getPlayerConfigData()
.getPerWorldData(world.getName());

// Přidej marker do pole
MapMarker[] worldMapMarkers = perWorldData.getWorldMapMarkers();
perWorldData.setWorldMapMarkers(ArrayUtil.append(worldMapMarkers, marker));

// Vrať referenci pro pozdější odebrání
return new PlayerMarkerReference(
uuidComponent.getUuid(),
world.getName(),
marker.id
);
}

---

Points of Interest

Statické markery generované při načtení světa:

// Z WorldMapManager.setGenerator():
this.logger.at(Level.INFO).log("Generating Points of Interest...");

CompletableFutureUtil._catch(
generator.generatePointsOfInterest(this.world)
.thenAcceptAsync(pointsOfInterest -> {
this.pointsOfInterest.putAll(pointsOfInterest);
this.logger.at(Level.INFO).log("Finished Generating Points of Interest!");
})
);

Přidání POI

// Přímé přidání POI
WorldMapManager manager = world.getWorldMapManager();
Map pois = manager.getPointsOfInterest();

MapMarker poi = new MapMarker(
"dungeon-1",
"Ancient Dungeon",
"DungeonIcon.png",
PositionUtil.toTransformPacket(new Transform(new Vector3d(100, 64, 200))),
null
);

pois.put("dungeon-1", poi);

---

Příklad: Kompletní Plugin s Markery

public class QuestMarkerPlugin extends JavaPlugin {
private ComponentType questMarkerType;

@Override
protected void setup() {
// Registrace komponenty
this.questMarkerType = getEntityStoreRegistry().registerComponent(
QuestMarkerComponent.class,
QuestMarkerComponent::new
);

// Registrace marker provideru pro všechny světy
getEventRegistry().registerGlobal(AddWorldEvent.class, event -> {
event.getWorld().getWorldMapManager().addMarkerProvider(
"quests",
new QuestMarkerProvider()
);
});

// Quest commands...
}

private class QuestMarkerProvider implements WorldMapManager.MarkerProvider {
@Override
public void update(
World world,
GameplayConfig gameplayConfig,
WorldMapTracker tracker,
int chunkViewRadius,
int playerChunkX,
int playerChunkZ
) {
Player player = tracker.getPlayer();
UUID uuid = player.getUuid();

// Získej aktivní questy hráče
List activeQuests = QuestManager.getActiveQuests(uuid);

for (Quest quest : activeQuests) {
// Pouze questy v tomto světě
if (!quest.getWorld().equals(world.getName())) {
continue;
}

Vector3d position = quest.getTargetPosition();

tracker.trySendMarker(
chunkViewRadius,
playerChunkX,
playerChunkZ,
position,
0f,
"quest-" + quest.getId(),
quest.getName(),
quest,
(id, name, q) -> new MapMarker(
id,
name,
q.isMainQuest() ? "MainQuest.png" : "SideQuest.png",
PositionUtil.toTransformPacket(new Transform(q.getTargetPosition())),
createQuestContextMenu(q)
)
);
}
}

private ContextMenuItem[] createQuestContextMenu(Quest quest) {
return new ContextMenuItem[] {
new ContextMenuItem("track", "Track Quest", "track.png"),
new ContextMenuItem("abandon", "Abandon Quest", "abandon.png")
};
}
}
}

---

Shrnutí

| Třída | Účel |
|-------|------|
| WorldMapManager | Per-World manažer mapy |
| MarkerProvider | Interface pro poskytovatele markerů |
| MapMarker | Packet struktura markeru |
| WorldMapTracker | Per-Player tracker |
| PlayerMarkerReference | Perzistentní hráčské markery |

| Vestavěný Provider | Zobrazuje |
|--------------------|-----------|
| SpawnMarkerProvider | Spawn point světa |
| DeathMarkerProvider | Místa smrti hráče |
| RespawnMarkerProvider | Respawn body |
| PlayerMarkersProvider | Vlastní markery hráče |
| POIMarkerProvider | Points of Interest |
| PlayerIconMarkerProvider | Ikony ostatních hráčů |

| Operace | Metoda |
|---------|--------|
| Přidání provideru | world.getWorldMapManager().addMarkerProvider(key, provider) |
| Odeslání markeru | tracker.trySendMarker(...) |
| Vytvoření player markeru | WorldMapManager.createPlayerMarker(...) |
| Získání POI | worldMapManager.getPointsOfInterest() |
| Kontrola povolení | worldMapManager.isWorldMapEnabled() |

Last updated: 20. ledna 2026