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() |