Uso Avanzado del Meta System en Mods¶
El Meta System de Hytale es un sistema potente para almacenar datos personalizados en entidades. Esta guía completa cubre cómo usar el sistema para almacenar stats de jugador, inventarios custom, quest systems, y más.
¿Qué es el Meta System?¶
El Meta System permite adjuntar datos arbitrarios a entidades (jugadores, NPCs, mobs, etc.) de forma:
- Tipada: Type-safe con genéricos de Java
- Eficiente: Almacenamiento optimizado
- Persistente: Los datos pueden guardarse en disco
- Flexible: Cualquier tipo de dato con Codecs
Conceptos Fundamentales¶
MetaRegistry¶
El registro que gestiona todas las MetaKeys de un tipo de entidad.
// Registro para jugadores
MetaRegistry<Player> playerRegistry = new MetaRegistry<>();
// Registro para contextos de interacción
MetaRegistry<InteractionContext> contextRegistry = new MetaRegistry<>();
MetaKey¶
Una clave tipada que identifica un tipo de dato específico.
// Key temporal (solo en memoria)
MetaKey<Integer> killCount = registry.registerMetaObject(
player -> 0 // Valor inicial
);
// Key persistente (se guarda en disco)
MetaKey<PlayerStats> stats = registry.registerMetaObject(
player -> new PlayerStats(),
true, // Persistente
"mimod:player_stats", // Nombre único
PlayerStats.CODEC // Codec para serialización
);
MetaStore¶
Almacén de datos adjunto a cada entidad.
// Obtener meta store de un jugador
var metaStore = player.getMetaStore();
// Guardar dato
metaStore.putMetaObject(statsKey, new PlayerStats());
// Obtener dato
PlayerStats stats = metaStore.getMetaObject(statsKey);
Datos Persistentes vs Temporales¶
Datos Temporales¶
Solo existen mientras la entidad está cargada en memoria.
Casos de uso: - Cooldowns de habilidades - Estados temporales - Cache de cálculos - Flags de sesión
public class TemporaryDataExample {
private static final MetaRegistry<Player> REGISTRY = new MetaRegistry<>();
// Key temporal - no persistente
private static final MetaKey<Long> LAST_TELEPORT = REGISTRY.registerMetaObject(
player -> 0L // Valor inicial: timestamp 0
);
public static void teleportPlayer(Player player, Vector3d destination) {
var metaStore = player.getMetaStore();
// Verificar cooldown
long lastTeleport = metaStore.getMetaObject(LAST_TELEPORT);
long currentTime = System.currentTimeMillis();
if (currentTime - lastTeleport < 5000) {
player.sendMessage("§c¡Espera 5 segundos entre teletransportes!");
return;
}
// Teletransportar
player.setPosition(destination);
// Actualizar timestamp
metaStore.putMetaObject(LAST_TELEPORT, currentTime);
player.sendMessage("§a¡Teletransportado!");
}
}
Datos Persistentes¶
Se guardan en disco y persisten entre sesiones.
Casos de uso: - Estadísticas de jugador - Progreso de quests - Inventarios personalizados - Configuraciones de jugador
package com.ejemplo.mimod.systems;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.meta.MetaKey;
import com.hypixel.hytale.server.core.meta.MetaRegistry;
import com.hypixel.hytale.server.core.meta.PersistentMetaKey;
public class PlayerStatsSystem {
private static final MetaRegistry<Player> PLAYER_REGISTRY = new MetaRegistry<>();
// Key persistente con Codec
private static final PersistentMetaKey<PlayerStats> STATS_KEY =
(PersistentMetaKey<PlayerStats>) PLAYER_REGISTRY.registerMetaObject(
player -> new PlayerStats(), // Factory: crear stats iniciales
true, // Persistente
"mimod:player_stats", // ID único
PlayerStats.CODEC // Codec para serialización
);
/**
* Obtiene las stats de un jugador.
* Si no existen, crea stats nuevas.
*/
public static PlayerStats getStats(Player player) {
var metaStore = player.getMetaStore();
return metaStore.getMetaObject(STATS_KEY);
}
/**
* Actualiza las stats de un jugador.
*/
public static void updateStats(Player player, PlayerStats stats) {
var metaStore = player.getMetaStore();
metaStore.putMetaObject(STATS_KEY, stats);
}
/**
* Stats de jugador con serialización.
*/
public static class PlayerStats {
public int kills;
public int deaths;
public int playTime; // Minutos
public int blocksPlaced;
public int blocksBroken;
public PlayerStats() {
this(0, 0, 0, 0, 0);
}
public PlayerStats(int kills, int deaths, int playTime,
int blocksPlaced, int blocksBroken) {
this.kills = kills;
this.deaths = deaths;
this.playTime = playTime;
this.blocksPlaced = blocksPlaced;
this.blocksBroken = blocksBroken;
}
/**
* Codec para serialización/deserialización.
*
* Define cómo convertir PlayerStats a/desde formato persistente.
*/
public static final Codec<PlayerStats> CODEC =
Codec.INT.flatMap(kills ->
Codec.INT.flatMap(deaths ->
Codec.INT.flatMap(playTime ->
Codec.INT.flatMap(blocksPlaced ->
Codec.INT.map(blocksBroken ->
new PlayerStats(kills, deaths, playTime,
blocksPlaced, blocksBroken)
)
)
)
)
);
public double getKDRatio() {
return deaths == 0 ? kills : (double) kills / deaths;
}
@Override
public String toString() {
return String.format(
"PlayerStats{kills=%d, deaths=%d, KD=%.2f, playtime=%dm}",
kills, deaths, getKDRatio(), playTime
);
}
}
}
Sincronización Cliente-Servidor¶
Datos Solo Servidor¶
Datos que solo existen en el servidor.
// NO sincronizar con cliente
private static final MetaKey<SecretData> SECRET_KEY =
REGISTRY.registerMetaObject(
player -> new SecretData(),
true,
"mimod:secret_data",
SecretData.CODEC
);
// Estos datos nunca se envían al cliente
Datos Sincronizados¶
Para sincronizar datos con el cliente, necesitas manejar la sincronización manualmente.
public class SyncedDataExample {
private static final MetaKey<ManaData> MANA_KEY =
REGISTRY.registerMetaObject(
player -> new ManaData(100),
true,
"mimod:mana",
ManaData.CODEC
);
/**
* Actualiza mana y sincroniza con el cliente.
*/
public static void setMana(Player player, int current, int max) {
// Actualizar servidor
ManaData data = new ManaData(current, max);
player.getMetaStore().putMetaObject(MANA_KEY, data);
// Sincronizar con cliente
syncManaToClient(player, data);
}
/**
* Envía datos de mana al cliente.
*/
private static void syncManaToClient(Player player, ManaData data) {
// Crear packet custom
// ManaUpdatePacket packet = new ManaUpdatePacket(data.current, data.max);
// Enviar al cliente
// player.getPacketHandler().write(packet);
// Simplificado: usar comando o mensaje
player.sendMessage("§bMana: " + data.current + "/" + data.max);
}
public static class ManaData {
public int current;
public int max;
public ManaData(int max) {
this(max, max);
}
public ManaData(int current, int max) {
this.current = current;
this.max = max;
}
public static final Codec<ManaData> CODEC =
Codec.INT.flatMap(current ->
Codec.INT.map(max ->
new ManaData(current, max)
)
);
}
}
Serialización con Codecs¶
Los Codecs definen cómo convertir objetos a/desde formato persistente.
Codec Simple¶
public class SimpleCodecExample {
// Codec para un solo valor
public static final Codec<Integer> INT_CODEC = Codec.INT;
// Codec para String
public static final Codec<String> STRING_CODEC = Codec.STRING;
// Codec para Boolean
public static final Codec<Boolean> BOOL_CODEC = Codec.BOOLEAN;
}
Codec para Clase Custom¶
public class CustomClassCodec {
public static class QuestProgress {
public String questId;
public int stage;
public boolean completed;
public QuestProgress(String questId, int stage, boolean completed) {
this.questId = questId;
this.stage = stage;
this.completed = completed;
}
/**
* Codec que serializa QuestProgress como:
* questId -> stage -> completed
*/
public static final Codec<QuestProgress> CODEC =
Codec.STRING.flatMap(questId -> // Serializar questId
Codec.INT.flatMap(stage -> // Serializar stage
Codec.BOOLEAN.map(completed -> // Serializar completed
new QuestProgress(questId, stage, completed)
)
)
);
}
}
Codec para Listas¶
import com.hypixel.hytale.codec.codecs.array.ListCodec;
import java.util.List;
public class ListCodecExample {
public static class QuestLog {
public List<String> completedQuests;
public List<String> activeQuests;
public QuestLog(List<String> completedQuests, List<String> activeQuests) {
this.completedQuests = completedQuests;
this.activeQuests = activeQuests;
}
/**
* Codec para listas de Strings.
*/
public static final Codec<QuestLog> CODEC =
new ListCodec<>(Codec.STRING).flatMap(completedQuests ->
new ListCodec<>(Codec.STRING).map(activeQuests ->
new QuestLog(completedQuests, activeQuests)
)
);
}
}
Codec para Maps¶
import com.hypixel.hytale.codec.codecs.map.MapCodec;
import java.util.Map;
public class MapCodecExample {
public static class SkillLevels {
public Map<String, Integer> skills; // skillName -> level
public SkillLevels(Map<String, Integer> skills) {
this.skills = skills;
}
/**
* Codec para Map<String, Integer>.
*/
public static final Codec<SkillLevels> CODEC =
new MapCodec<>(Codec.STRING, Codec.INT).map(
skills -> new SkillLevels(skills)
);
}
}
Ejemplos Prácticos Completos¶
Ejemplo 1: Sistema de Stats de Jugador¶
package com.ejemplo.mimod.systems;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.meta.MetaKey;
import com.hypixel.hytale.server.core.meta.MetaRegistry;
public class PlayerStatsTracker {
private static final MetaRegistry<Player> REGISTRY = new MetaRegistry<>();
private static final MetaKey<DetailedStats> STATS_KEY =
REGISTRY.registerMetaObject(
player -> new DetailedStats(),
true, // Persistente
"mimod:detailed_stats",
DetailedStats.CODEC
);
/**
* Registra un kill.
*/
public static void registerKill(Player player, String victimType) {
DetailedStats stats = getStats(player);
stats.totalKills++;
if (victimType.equals("Player")) {
stats.playerKills++;
} else {
stats.mobKills++;
}
updateStats(player, stats);
// Verificar logros
checkAchievements(player, stats);
}
/**
* Registra una muerte.
*/
public static void registerDeath(Player player, String killer) {
DetailedStats stats = getStats(player);
stats.totalDeaths++;
if (killer.equals("Player")) {
stats.deathsByPlayer++;
} else {
stats.deathsByMob++;
}
updateStats(player, stats);
}
/**
* Registra tiempo de juego.
*/
public static void addPlayTime(Player player, int minutes) {
DetailedStats stats = getStats(player);
stats.playTimeMinutes += minutes;
updateStats(player, stats);
}
/**
* Registra bloque colocado.
*/
public static void registerBlockPlaced(Player player, String blockType) {
DetailedStats stats = getStats(player);
stats.blocksPlaced++;
stats.blockTypesCounts.put(
blockType,
stats.blockTypesCounts.getOrDefault(blockType, 0) + 1
);
updateStats(player, stats);
}
/**
* Muestra estadísticas al jugador.
*/
public static void showStats(Player player) {
DetailedStats stats = getStats(player);
player.sendMessage("§6=== Tus Estadísticas ===");
player.sendMessage("§eKills: §f" + stats.totalKills +
" §7(Players: " + stats.playerKills +
", Mobs: " + stats.mobKills + ")");
player.sendMessage("§eDeaths: §f" + stats.totalDeaths +
" §7(K/D: " + String.format("%.2f", stats.getKDRatio()) + ")");
player.sendMessage("§ePlay Time: §f" + formatPlayTime(stats.playTimeMinutes));
player.sendMessage("§eBloques Colocados: §f" + stats.blocksPlaced);
player.sendMessage("§eBloques Rotos: §f" + stats.blocksBroken);
}
private static void checkAchievements(Player player, DetailedStats stats) {
if (stats.totalKills == 100 && !stats.achievements.contains("centurion")) {
stats.achievements.add("centurion");
player.sendMessage("§6✦ ¡Logro Desbloqueado: Centurión! §7(100 kills)");
}
if (stats.playTimeMinutes >= 1000 && !stats.achievements.contains("veteran")) {
stats.achievements.add("veteran");
player.sendMessage("§6✦ ¡Logro Desbloqueado: Veterano! §7(1000 min jugados)");
}
}
private static String formatPlayTime(int minutes) {
int hours = minutes / 60;
int mins = minutes % 60;
return hours + "h " + mins + "m";
}
private static DetailedStats getStats(Player player) {
return player.getMetaStore().getMetaObject(STATS_KEY);
}
private static void updateStats(Player player, DetailedStats stats) {
player.getMetaStore().putMetaObject(STATS_KEY, stats);
}
/**
* Stats detalladas con múltiples campos.
*/
public static class DetailedStats {
public int totalKills = 0;
public int playerKills = 0;
public int mobKills = 0;
public int totalDeaths = 0;
public int deathsByPlayer = 0;
public int deathsByMob = 0;
public int playTimeMinutes = 0;
public int blocksPlaced = 0;
public int blocksBroken = 0;
public Map<String, Integer> blockTypesCounts = new HashMap<>();
public List<String> achievements = new ArrayList<>();
public double getKDRatio() {
return totalDeaths == 0 ? totalKills : (double) totalKills / totalDeaths;
}
/**
* Codec complejo para serializar todos los campos.
*/
public static final Codec<DetailedStats> CODEC =
Codec.INT.flatMap(totalKills ->
Codec.INT.flatMap(playerKills ->
Codec.INT.flatMap(mobKills ->
Codec.INT.flatMap(totalDeaths ->
Codec.INT.flatMap(deathsByPlayer ->
Codec.INT.flatMap(deathsByMob ->
Codec.INT.flatMap(playTimeMinutes ->
Codec.INT.flatMap(blocksPlaced ->
Codec.INT.flatMap(blocksBroken ->
new MapCodec<>(Codec.STRING, Codec.INT).flatMap(blockTypes ->
new ListCodec<>(Codec.STRING).map(achievements -> {
DetailedStats stats = new DetailedStats();
stats.totalKills = totalKills;
stats.playerKills = playerKills;
stats.mobKills = mobKills;
stats.totalDeaths = totalDeaths;
stats.deathsByPlayer = deathsByPlayer;
stats.deathsByMob = deathsByMob;
stats.playTimeMinutes = playTimeMinutes;
stats.blocksPlaced = blocksPlaced;
stats.blocksBroken = blocksBroken;
stats.blockTypesCounts = new HashMap<>(blockTypes);
stats.achievements = new ArrayList<>(achievements);
return stats;
})
)
)
)
)
)
)
)
)
)
);
}
}
Ejemplo 2: Inventario Custom¶
package com.ejemplo.mimod.systems;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.codecs.array.ListCodec;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.meta.MetaKey;
import com.hypixel.hytale.server.core.meta.MetaRegistry;
import java.util.ArrayList;
import java.util.List;
public class CustomInventorySystem {
private static final MetaRegistry<Player> REGISTRY = new MetaRegistry<>();
private static final int MAX_SLOTS = 27;
private static final MetaKey<CustomInventory> INVENTORY_KEY =
REGISTRY.registerMetaObject(
player -> new CustomInventory(),
true,
"mimod:custom_inventory",
CustomInventory.CODEC
);
/**
* Abre el inventario custom del jugador.
*/
public static void openInventory(Player player) {
CustomInventory inventory = getInventory(player);
// Mostrar GUI
// (Simplificado - requiere integración con sistema de UI)
player.sendMessage("§a=== Inventario Mágico ===");
for (int i = 0; i < inventory.items.size(); i++) {
CustomItem item = inventory.items.get(i);
if (item != null) {
player.sendMessage("§e[" + i + "] §f" +
item.itemId + " x" + item.quantity);
}
}
}
/**
* Agrega un item al inventario.
*/
public static boolean addItem(Player player, String itemId, int quantity) {
CustomInventory inventory = getInventory(player);
// Buscar slot con el mismo item
for (CustomItem item : inventory.items) {
if (item != null && item.itemId.equals(itemId)) {
item.quantity += quantity;
updateInventory(player, inventory);
return true;
}
}
// Buscar slot vacío
for (int i = 0; i < MAX_SLOTS; i++) {
if (i >= inventory.items.size() || inventory.items.get(i) == null) {
CustomItem newItem = new CustomItem(itemId, quantity);
if (i >= inventory.items.size()) {
inventory.items.add(newItem);
} else {
inventory.items.set(i, newItem);
}
updateInventory(player, inventory);
return true;
}
}
// Inventario lleno
player.sendMessage("§c¡Inventario lleno!");
return false;
}
/**
* Remueve un item del inventario.
*/
public static boolean removeItem(Player player, String itemId, int quantity) {
CustomInventory inventory = getInventory(player);
for (CustomItem item : inventory.items) {
if (item != null && item.itemId.equals(itemId)) {
if (item.quantity >= quantity) {
item.quantity -= quantity;
if (item.quantity == 0) {
inventory.items.remove(item);
}
updateInventory(player, inventory);
return true;
} else {
return false; // Cantidad insuficiente
}
}
}
return false; // Item no encontrado
}
/**
* Verifica si el jugador tiene un item.
*/
public static boolean hasItem(Player player, String itemId, int quantity) {
CustomInventory inventory = getInventory(player);
int total = 0;
for (CustomItem item : inventory.items) {
if (item != null && item.itemId.equals(itemId)) {
total += item.quantity;
}
}
return total >= quantity;
}
private static CustomInventory getInventory(Player player) {
return player.getMetaStore().getMetaObject(INVENTORY_KEY);
}
private static void updateInventory(Player player, CustomInventory inventory) {
player.getMetaStore().putMetaObject(INVENTORY_KEY, inventory);
}
/**
* Inventario custom con lista de items.
*/
public static class CustomInventory {
public List<CustomItem> items;
public CustomInventory() {
this.items = new ArrayList<>();
}
public CustomInventory(List<CustomItem> items) {
this.items = items;
}
public static final Codec<CustomInventory> CODEC =
new ListCodec<>(CustomItem.CODEC).map(
items -> new CustomInventory(items)
);
}
/**
* Item custom en el inventario.
*/
public static class CustomItem {
public String itemId;
public int quantity;
public CustomItem(String itemId, int quantity) {
this.itemId = itemId;
this.quantity = quantity;
}
public static final Codec<CustomItem> CODEC =
Codec.STRING.flatMap(itemId ->
Codec.INT.map(quantity ->
new CustomItem(itemId, quantity)
)
);
}
}
Ejemplo 3: Sistema de Quests¶
package com.ejemplo.mimod.systems;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.codecs.array.ListCodec;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.meta.MetaKey;
import com.hypixel.hytale.server.core.meta.MetaRegistry;
import java.util.ArrayList;
import java.util.List;
public class QuestSystem {
private static final MetaRegistry<Player> REGISTRY = new MetaRegistry<>();
private static final MetaKey<QuestData> QUEST_KEY =
REGISTRY.registerMetaObject(
player -> new QuestData(),
true,
"mimod:quests",
QuestData.CODEC
);
/**
* Inicia una quest para el jugador.
*/
public static void startQuest(Player player, String questId) {
QuestData data = getQuestData(player);
// Verificar si ya está completada
if (data.completedQuests.contains(questId)) {
player.sendMessage("§c¡Ya completaste esta quest!");
return;
}
// Verificar si ya está activa
for (ActiveQuest quest : data.activeQuests) {
if (quest.questId.equals(questId)) {
player.sendMessage("§c¡Esta quest ya está activa!");
return;
}
}
// Iniciar quest
ActiveQuest newQuest = new ActiveQuest(questId, 0, false);
data.activeQuests.add(newQuest);
updateQuestData(player, data);
player.sendMessage("§a✦ Quest iniciada: §f" + getQuestName(questId));
}
/**
* Avanza el progreso de una quest.
*/
public static void progressQuest(Player player, String questId, int progress) {
QuestData data = getQuestData(player);
for (ActiveQuest quest : data.activeQuests) {
if (quest.questId.equals(questId)) {
quest.progress += progress;
// Verificar si se completó
int requiredProgress = getQuestRequiredProgress(questId);
if (quest.progress >= requiredProgress) {
completeQuest(player, questId);
} else {
player.sendMessage("§eProgreso de quest: §f" +
quest.progress + "/" + requiredProgress);
}
updateQuestData(player, data);
return;
}
}
}
/**
* Completa una quest.
*/
public static void completeQuest(Player player, String questId) {
QuestData data = getQuestData(player);
// Remover de activas
data.activeQuests.removeIf(q -> q.questId.equals(questId));
// Agregar a completadas
if (!data.completedQuests.contains(questId)) {
data.completedQuests.add(questId);
}
updateQuestData(player, data);
// Dar recompensa
giveQuestReward(player, questId);
player.sendMessage("§a✦✦✦ Quest Completada: §f" + getQuestName(questId));
}
/**
* Muestra quests activas del jugador.
*/
public static void showActiveQuests(Player player) {
QuestData data = getQuestData(player);
if (data.activeQuests.isEmpty()) {
player.sendMessage("§7No tienes quests activas");
return;
}
player.sendMessage("§6=== Quests Activas ===");
for (ActiveQuest quest : data.activeQuests) {
int required = getQuestRequiredProgress(quest.questId);
player.sendMessage("§e" + getQuestName(quest.questId) +
" §7[" + quest.progress + "/" + required + "]");
}
}
private static void giveQuestReward(Player player, String questId) {
// Dar recompensas según la quest
switch (questId) {
case "kill_10_zombies":
// Dar 100 monedas, 50 XP
player.sendMessage("§a+100 monedas, +50 XP");
break;
case "mine_50_ores":
// Dar pico especial
player.sendMessage("§a+1 Pico Mágico");
break;
}
}
private static String getQuestName(String questId) {
// Mapear IDs a nombres
return switch (questId) {
case "kill_10_zombies" -> "Exterminar Zombies";
case "mine_50_ores" -> "Minero Experto";
case "explore_dungeon" -> "Explorador de Mazmorras";
default -> questId;
};
}
private static int getQuestRequiredProgress(String questId) {
return switch (questId) {
case "kill_10_zombies" -> 10;
case "mine_50_ores" -> 50;
case "explore_dungeon" -> 1;
default -> 100;
};
}
private static QuestData getQuestData(Player player) {
return player.getMetaStore().getMetaObject(QUEST_KEY);
}
private static void updateQuestData(Player player, QuestData data) {
player.getMetaStore().putMetaObject(QUEST_KEY, data);
}
/**
* Datos de quests del jugador.
*/
public static class QuestData {
public List<ActiveQuest> activeQuests;
public List<String> completedQuests;
public QuestData() {
this.activeQuests = new ArrayList<>();
this.completedQuests = new ArrayList<>();
}
public QuestData(List<ActiveQuest> activeQuests, List<String> completedQuests) {
this.activeQuests = activeQuests;
this.completedQuests = completedQuests;
}
public static final Codec<QuestData> CODEC =
new ListCodec<>(ActiveQuest.CODEC).flatMap(activeQuests ->
new ListCodec<>(Codec.STRING).map(completedQuests ->
new QuestData(activeQuests, completedQuests)
)
);
}
/**
* Quest activa con progreso.
*/
public static class ActiveQuest {
public String questId;
public int progress;
public boolean tracked;
public ActiveQuest(String questId, int progress, boolean tracked) {
this.questId = questId;
this.progress = progress;
this.tracked = tracked;
}
public static final Codec<ActiveQuest> CODEC =
Codec.STRING.flatMap(questId ->
Codec.INT.flatMap(progress ->
Codec.BOOLEAN.map(tracked ->
new ActiveQuest(questId, progress, tracked)
)
)
);
}
}
Mejores Prácticas¶
1. Siempre Proporcionar Factory¶
// ✅ BIEN - Factory que crea valor inicial
MetaKey<Integer> key = registry.registerMetaObject(
player -> 0
);
// ❌ MAL - null puede causar NPE
MetaKey<Integer> key = registry.registerMetaObject(
player -> null
);
2. Usar Codecs Apropiados¶
// ✅ BIEN - Codec completo para serialización
public static final Codec<MyData> CODEC =
Codec.INT.flatMap(field1 ->
Codec.STRING.map(field2 ->
new MyData(field1, field2)
)
);
// ❌ MAL - Falta codec para datos persistentes
// (Causará error al intentar guardar)
3. Cleanup de Datos¶
public class CleanupExample {
private static final Map<Player, CustomData> CACHE = new ConcurrentHashMap<>();
/**
* Limpia datos cuando un jugador se desconecta.
*/
public static void onPlayerDisconnect(Player player) {
// Guardar datos finales
savePlayerData(player);
// Limpiar cache
CACHE.remove(player);
System.out.println("[Cleanup] Cleaned data for player");
}
}
4. Validar Datos Cargados¶
public static PlayerData getPlayerData(Player player) {
var metaStore = player.getMetaStore();
PlayerData data = metaStore.getMetaObject(DATA_KEY);
// Validar datos cargados
if (data == null || !data.isValid()) {
// Crear datos nuevos si están corruptos
data = new PlayerData();
metaStore.putMetaObject(DATA_KEY, data);
}
return data;
}
Debugging¶
Logging de Metadata¶
public static void debugMetadata(Player player) {
var metaStore = player.getMetaStore();
System.out.println("=== Metadata for player ===");
// Iterar sobre todas las keys
metaStore.forEachMetaObject((id, value) -> {
System.out.println(" ID " + id + ": " + value);
});
}
Verificar Persistencia¶
// Guardar
player.getMetaStore().putMetaObject(KEY, data);
// Forzar guardado a disco (si aplica)
// player.save();
// Recargar jugador y verificar
// Player reloaded = loadPlayer(player.getUUID());
// Data reloadedData = reloaded.getMetaStore().getMetaObject(KEY);
// assert reloadedData.equals(data);
Recursos Adicionales¶
- Creating a Mod: Tutorial completo de mods
- Interaction System: Sistema de interacciones
- API Reference: Documentación completa
¿Problemas? Consulta Troubleshooting