Implementación y eventos
Implementación y eventos describe la superficie de hooks que DragonMineZ ofrece a los addons: sus eventos, cómo leer el estado del jugador, los registros de extensión y dónde conectar tu propia red y tus propios registros. Léela cuando tu addon ya compile contra DMZ (consulta Primeros pasos con addons).
Escuchar un evento de DMZ
Todos los eventos de servidor y comunes son clases estáticas anidadas de com.dragonminez.common.events.DMZEvent, publicadas en el bus de eventos estándar de Forge (MinecraftForge.EVENT_BUS). Cada clase anidada extiende directamente el Event de Forge (DMZEvent en sí solo es el contenedor), así que suscríbete a la clase anidada concreta:
@Mod.EventBusSubscriber(modid = "your_addon_modid", bus = Mod.EventBusSubscriber.Bus.FORGE)
public class DmzHooks {
@SubscribeEvent
public static void onTpGain(DMZEvent.TPGainEvent event) {
int boosted = Math.round(event.getTpGain() * 1.10f);
event.setTpGain(boosted);
}
@SubscribeEvent
public static void onFormChange(DMZEvent.FormChangeEvent event) {
if (event.isTransform()) {
// player transformed into event.getNewGroup() / event.getNewForm()
}
}
}
Los eventos marcados abajo como cancelables llevan la anotación @Cancelable de Forge: event.setCanceled(true) bloquea la acción subyacente. Los campos marcados como modificables tienen un setter (setTpGain, setAmount, ...), y DMZ usa el valor resultante después de que se ejecuten todos los listeners. Todos los getters siguen los nombres de los campos (getPlayer(), getOldValue(), ...); los campos booleanos usan is... (isParry(), isBlocked()).
Lista de eventos
Origen: src/main/java/com/dragonminez/common/events/DMZEvent.java.
Estadísticas, recursos y progreso
| Evento | Cancelable | Se dispara cuando | Campos clave |
|---|---|---|---|
StatChangeEvent |
Sí | Una estadística (STR, SKP, RES, VIT, PWR o ENE) cambia mediante una llamada a Stats.set*(): comandos, objetos, menús, y también cuando se cargan o copian las estadísticas (tanto en el servidor como en el cliente). |
player, stat (STRENGTH, STRIKE_POWER, RESISTANCE, VITALITY, KI_POWER o ENERGY), oldValue, newValue
|
TPGainEvent |
Sí | Antes de que se añada una cantidad positiva de puntos de entrenamiento (Resources.addTrainingPoints). |
player, oldValue, tpGain (modificable), shareWithParty (modificable); auxiliar getNewTpsValue()
|
KiChargeEvent |
Sí | Una vez por segundo mientras el jugador está cargando ki activamente. Cancélalo para detener la ganancia de ki de ese segundo. | player, currentEnergy, maxEnergy; auxiliar isEnergyFull()
|
HealthRegenEvent / EnergyRegenEvent / StaminaRegenEvent |
Sí | Antes de aplicar la regeneración por segundo de vida, ki o stamina. Comparten la base ResourceRegenEvent. |
player, statsData, amount (modificable)
|
FormChangeEvent / StackFormChangeEvent |
No | Después de que cambie la forma de raza o la forma acumulable del jugador (lado del servidor). | player, oldGroup, oldForm, newGroup, newForm; auxiliares isTransform() y isUntransform()
|
PlayerDataSaveEvent |
No | Los datos del jugador están a punto de escribirse en el backend de almacenamiento JSON o DATABASE. Puedes añadir tus propias claves a data. |
player, data
|
PlayerDataLoadEvent |
No | Los datos del jugador se leyeron del backend JSON o DATABASE, justo antes de aplicarse al jugador. |
player, data
|
StatChangeEvent también se dispara mientras DMZ carga los datos guardados y aplica las sincronizaciones del cliente. Si lo cancelas, comprueba antes event.getPlayer().level().isClientSide() y tus propias condiciones, o podrías impedir que se carguen las estadísticas guardadas de un jugador.PlayerDataSaveEvent y PlayerDataLoadEvent no se disparan con el backend NBT por defecto, en el que vanilla guarda los datos de DMZ junto con el archivo del jugador. Consulta Configuración del servidor para ver los ajustes de storage.
Combate
| Evento | Cancelable | Se dispara cuando | Campos clave |
|---|---|---|---|
DamageModifyEvent |
Sí | Cálculo del daño antes de la mitigación, para un golpe de un jugador (golpes cuerpo a cuerpo y Super Golpes). Cancélalo para no hacer daño de DMZ. | attacker, victim, amount (modificable), defensePenetration (modificable), sourceType (MELEE o STRIKE)
|
DamageDealtEvent |
No | Después de resolver el bloqueo de un golpe cuerpo a cuerpo de un jugador (cantidad antes de la mitigación). | attacker, victim, amount, blocked, parried, sourceType
|
PlayerDamageMitigatedEvent |
No | Después de que DMZ termine de mitigar un golpe recibido por un jugador. Solo lectura: no cambies el daño aquí. | victim, source (DamageSource), rawDamage, defenseMitigated, blockMitigated, finalDamage
|
PlayerBlockEvent |
Sí | El jugador bloquea o hace parry a un ataque con éxito. Cancélalo y el jugador recibe el golpe completo. | victim, attacker, originalDamage, finalDamage (modificable), isParry (modificable), poiseDamage (modificable)
|
BarrierAbsorbEvent |
No | Una barrera de ki (la técnica de barrera o la barrera de los Androides) absorbe un golpe antes de que llegue a la entidad protegida. | owner (quien creó la barrera), protectedEntity, attacker, amount
|
PlayerDashEvent |
Sí | Antes de que se ejecute un dash. | player, dashType (NORMAL o DOUBLE), distance (modificable), kiCost (modificable)
|
PlayerEvasionEvent |
Sí | Antes de que se active una evasión. | player, attacker, originalDamage, kiCost (modificable)
|
CritChanceEvent |
No | Mientras se calcula la probabilidad crítica de un jugador. | player, chance (modificable, de 0 a 1)
|
KiAttackCastEvent |
No | Un jugador empieza a cargar (lanza) un ataque de ki. | player, statsData, kiAttack
|
KiAttackFireEvent |
No | Se suelta un ataque de ki cargado. | player, statsData, kiAttack, chargeMultiplier, cooldownTicks (modificable, mínimo 1)
|
StrikeAttackCastEvent |
No | Un jugador inicia un Super Golpe. | player, statsData, strike
|
StrikeAttackFireEvent |
No | Un Super Golpe conecta y empieza sobre un objetivo. | player, statsData, strike, target
|
DamageSourceType también contiene un valor KI; en la v2.2, los ataques de ki no publican DamageModifyEvent ni DamageDealtEvent.
Fusiones y dragones
| Evento | Cancelable | Se dispara cuando | Campos clave |
|---|---|---|---|
FusionEvent |
Sí | Antes de un intento de fusión. DMZ lo publica para las fusiones METAMORU, POTHALA y BEETLE (Escarabajo Medi-Bug); el enum también contiene ABSORPTION y ASSIMILATION. |
initiator, target, type
|
DragonSummonedEvent |
No | Después de invocar un dragón con un conjunto de esferas completo. | player, level, position, dragonDefinition, ballSetDefinition, consumedPositions; auxiliares getDragonId() y getBallSetId()
|
Consulta Fusiones y Dragones y deseos para ver la parte de jugabilidad.
Misiones
Todos los eventos de misiones extienden QuestLifecycleEvent y comparten sus campos: player, questKey (sagaId:questId para las misiones de saga, el id de texto para las misiones secundarias), saga (null para las misiones secundarias), quest y partyMembers.
| Evento | Cancelable | Se dispara cuando | Campos extra |
|---|---|---|---|
QuestStartEvent |
Sí | Antes de aceptar una misión. | difficulty (modificable)
|
QuestObjectiveProgressEvent |
Sí | Antes de guardar el progreso de un objetivo. | objectiveIndex, oldProgress, newProgress (modificable), objectiveRequired
|
QuestFailEvent |
Sí | Antes de marcar una misión como fallida. | reason (PLAYER_DEATH, FORCED_RESET, SCRIPT, TIME_EXPIRED, ESCORT_FAILED)
|
QuestTurnInEvent |
Sí | Antes de aplicar la entrega de una misión. | npcId
|
QuestRewardClaimEvent |
Sí | Antes de reclamar una recompensa individual. | rewardIndex
|
QuestCompletedEvent |
No | Cuando se completa una misión. | Solo los campos compartidos |
Eventos de cliente
Los eventos solo de cliente están en com.dragonminez.client.events.DMZClientEvent y se publican en el bus de Forge del cliente físico. Registra estos listeners con value = Dist.CLIENT. Ninguno es cancelable y son solo informativos: el servidor sigue decidiendo lo que realmente ocurre.
| Evento | Se dispara cuando | Campos clave |
|---|---|---|
PlayerAttackStart |
El jugador local empieza la preparación del ataque. | player, attackHand
|
PlayerAttackHit |
El ataque del jugador local resuelve sus objetivos (cero o más). | player, attackHand, targets, cursorTarget (puede ser null)
|
KiAttackCast |
El jugador local empieza a cargar el ataque de ki de una ranura. | player, slot
|
KiAttackRelease |
El jugador local suelta un ataque de ki cargado. | player
|
StrikeAttack |
El jugador local inicia un Super Golpe. | player, targetId
|
Leer el estado del jugador
Obtén la capability de cualquier Player (un jugador del servidor, o el jugador local en el cliente; DMZ mantiene sincronizada la copia del cliente):
StatsProvider.get(StatsCapability.INSTANCE, player).ifPresent(data -> {
String race = data.getCharacter().getRaceName();
String activeForm = data.getCharacter().getActiveForm();
float currentKi = data.getResources().getCurrentEnergy();
float maxKi = data.getMaxEnergy();
int level = data.getLevel();
});
StatsProvider.get(cap, entity) es un atajo de player.getCapability(StatsCapability.INSTANCE); ambos devuelven un LazyOptional<StatsData>. Lombok genera los getters de DMZ en tiempo de compilación, así que no necesitas Lombok en tu addon para llamarlos.
Lecturas útiles de StatsData:
| Getter | Devuelve |
|---|---|
getCharacter().getRaceName() / getCharacterClass() / getGender() |
Raza, clase y género |
getCharacter().getActiveForm() / getActiveFormGroup() |
Forma de raza actual (un texto vacío en la forma base) |
getCharacter().getActiveStackForm() / getActiveStackFormGroup() |
Forma acumulable actual (por ejemplo, el nivel de Kaioken) |
getStats().getStrength() / getStrikePower() / getResistance() / getVitality() / getKiPower() / getEnergy() |
Valores base de las estadísticas |
getResources().getCurrentEnergy() / getCurrentStamina() / getCurrentPoise() / getAlignment() |
Reservas de recursos actuales y alineamiento |
getMaxHealth() / getMaxEnergy() / getMaxStamina() / getMaxPoise() |
Topes de recursos calculados |
getBattlePower() / getMeleeDamage() / getStrikeDamage() / getKiDamage() / getDefense() |
Estadísticas de combate calculadas |
getLevel() |
Nivel de jugador calculado |
getPlayerQuestData() |
Progreso de misiones (getQuestStatus(questKey), isQuestAccepted(questKey), isQuestCompleted(questKey), isSagaLocked(sagaId), getTrackedQuestId(), getDifficulty())
|
getSkills() |
Niveles de habilidades (hasSkill(name), getSkillLevel(name))
|
getStatus(), getEffects(), getTechniques(), getBonusStats() |
Indicadores de estado, efectos activos, técnicas de ki y de golpe, modificadores de estadísticas extra |
Los cálculos de raza, forma y estadísticas leen la configuración en vivo cada vez, así que los valores siempre reflejan la configuración actual del servidor (también después de /dmzreload).
Cambiar el estado de forma segura
Los setters de estadísticas propios de DMZ (por ejemplo Stats.setStrength()) publican primero un evento cancelable y solo aplican el cambio si no se canceló:
DMZEvent.StatChangeEvent event = new DMZEvent.StatChangeEvent(player, StatType.STRENGTH, oldValue, newValue);
if (!MinecraftForge.EVENT_BUS.post(event)) {
// apply the change
}
En tu addon, es preferible reaccionar a los eventos (cambiando sus campos modificables o cancelándolos) que llamar directamente a los setters de DMZ. No todos los setters validan o publican un evento (la mayoría de los setters de Resources, por ejemplo, solo limitan el valor y lo aplican sin evento), así que pasar por los eventos es el único punto de integración garantizado que se mantendrá estable.
Si cambias datos de DMZ desde el servidor, hazlo en el hilo del servidor y después envía al cliente una copia actualizada, igual que hace DMZ:
NetworkHandler.sendToTrackingEntityAndSelf(new StatsSyncS2C(serverPlayer), serverPlayer);
StatsSyncS2C envía todos los datos del jugador; DMZ también usa ProgressionSyncS2C (estadísticas, estadísticas extra, habilidades, técnicas, datos de misiones) y ResourceSyncS2C (recursos y estado) para actualizaciones más pequeñas.
Objetivos y recompensas de misión personalizados
Los addons pueden añadir nuevos tipos de objetivos y recompensas de misiones que luego pueden usar los archivos JSON de misiones, sin tocar el parser de DMZ. Regístralos durante la configuración del mod (por ejemplo, en FMLCommonSetupEvent) en ambos lados: el cliente reconstruye las misiones a partir del registro sincronizado con las mismas fábricas.
QuestObjectiveRegistry.register("MEDITATE",
json -> new MeditateObjective(json.get("seconds").getAsInt()), // parse quest JSON
(objective, out) -> out.addProperty("seconds", ((MeditateObjective) objective).getSeconds()), // client sync fields
objective -> Component.translatable("myaddon.quest.meditate.obj")); // HUD/journal text
QuestRewardRegistry.register("MY_CURRENCY",
json -> new MyCurrencyReward(json.get("amount").getAsInt()),
(reward, out) -> out.addProperty("amount", ((MyCurrencyReward) reward).getAmount()));
El contrato:
- Las clases de objetivos extienden
QuestObjectivey llaman al constructorQuestObjective(String customType, int required); las clases de recompensas extiendenQuestReward, llaman aQuestReward(String customType)e implementangiveReward(ServerPlayer)ygetDescription(). Los objetivos también deben implementarcheckProgress(Object... params). - En el JSON de las misiones, usa tu clave como
"type"(por ejemplo{"type": "MEDITATE", "seconds": 60}). Las claves de tipo no distinguen mayúsculas de minúsculas (se guardan en mayúsculas). Trátalas como nombres de registro: no las renombres una vez que las misiones las usen. - El escritor de sincronización debe escribir todos los campos que lee tu fábrica, porque el cliente vuelve a analizar el JSON sincronizado con tu fábrica.
- Los tipos desconocidos se informan en el reporte de carga de JSON (consulta Encontrar errores de JSON) y el objetivo o la recompensa se omite.
- El progreso lo controla el servidor y se basa en eventos: escucha tus propios eventos de juego en el servidor y guarda el progreso con
PlayerQuestData.setObjectiveProgress(questKey, objectiveIndex, progress), y luego sincroniza conProgressionSyncS2C. UsaDMZEvent.QuestObjectiveProgressEventyQuestCompletedEventpara reaccionar a las misiones incluidas.
Consulta Misiones personalizadas para ver el formato JSON de las misiones.
Placeholders de texto personalizados
Los textos de misiones, diálogos y NPCs admiten %placeholders% como %player%, %race%, %class%, %level%, %alignment%, %deaths%, %form% y %dimension%. Añade los tuyos durante la configuración del mod:
DMZTextPlaceholders.register("zeni", (player, data) -> String.valueOf(MyAddon.getZeni(player)));
// "%zeni%" in quest/dialogue text now resolves to your value
Las claves no distinguen mayúsculas de minúsculas. El resolvedor se ejecuta en el lado que renderiza el texto (normalmente el cliente) y debe manejar un StatsData null.
Botones del menú de utilidades
El menú de utilidades (radial) del cliente tiene dos ranuras libres, a la izquierda y a la derecha de la rueda, reservadas para addons. Añade un botón desde la configuración del cliente:
UtilityMenuScreen.addMenuSlot(new IUtilityMenuSlot() {
@Override
public ButtonInfo render(StatsData statsData) {
return new ButtonInfo(Component.literal("My Addon"), Component.literal("Open"));
}
@Override
public void handle(StatsData statsData, boolean rightClick) {
// client side: open your screen or send your own packet to the server
}
});
Las ranuras se llenan en orden de registro; los registros que superen las dos ranuras libres no se muestran. Sobrescribe hasRightClickAction para que devuelva true si tu botón también reacciona al clic derecho. IUtilityMenuSlot y ButtonInfo están en com.dragonminez.client.gui.utilitymenu.
Armaduras y capas de renderizado
- Armaduras de tu addon: la clase de objeto de armadura de DMZ,
DbzArmorItem(common/init/armor), tiene un constructorDbzArmorItem(material, type, properties, modId, itemId). Pasa tu propiomodIdy las texturas se leen de tu propio namespace:assets/<modId>/textures/armor/<itemId>_layer1.png(_layer2para las grebas), más las opcionales<itemId>_damaged_layer1.pngy_damaged_layer2.png, que se usan cuando el objeto ha perdido más de la mitad de su durabilidad. - Otras armaduras: los
ArmorItemnormales de otros mods se dibujan sobre el modelo de jugador de DMZ con su textura de armadura habitual. - Capas de renderizado de terceros: las capas de renderizado que otros mods añaden al renderizador de jugador vanilla se trasladan al modelo de jugador de DMZ. Se omiten las capas vanilla y las capas de armaduras cosméticas, y no se traslada nada mientras el jugador es un Oozaru.
Red
El canal de DMZ es NetworkHandler.INSTANCE (un SimpleChannel de Forge en dragonminez:network). No registres tus propios paquetes en él: DMZ asigna los ids de paquete por orden de registro, así que cualquier cosa que añadas desplazaría los ids de DMZ o chocaría con ellos. En su lugar, registra tu propio SimpleChannel en tu addon y usa el de DMZ como referencia del patrón (messageBuilder, codificador, decodificador y manejador, con una dirección explícita).
Puedes usar los helpers de envío de DMZ (sendToPlayer, sendToTrackingEntityAndSelf, ...) para enviar los paquetes de sincronización propios de DMZ, como se muestra arriba.
Tampoco confíes nunca en el cliente en tus propios paquetes: valida en el servidor todo lo que afecte a estadísticas, desbloqueos, misiones, deseos o progreso.
No existe un evento de "datos sincronizados" en el lado del cliente. En el cliente, lee la capability cuando la necesites (por ejemplo, al renderizar) en lugar de intentar interceptar los paquetes de DMZ.
Usar los registros de DMZ
Importa directamente los contenedores de registro Main* en lugar de escribir a mano las resource locations:
import com.dragonminez.common.init.MainItems;
import com.dragonminez.common.init.MainAttributes;
Item redCapsule = MainItems.RED_CAPSULE.get();
Attribute strength = MainAttributes.STRENGTH.get();
Disponibles en common/init/: MainItems, MainBlocks, MainBlockEntities, MainEntities, MainAttributes, MainEffects, MainEnchants, MainPotions, MainSounds, MainParticles, MainFluids, MainMenus, MainRecipes, MainTabs, MainTags, MainVillagers, MainDamageTypes, MainGameRules, MainLootModifiers.
Patrón de dependencia opcional
Si tu addon debe funcionar con o sin DMZ instalado, sigue el patrón que usa DMZ para sus propias integraciones opcionales (common/compat/WeaponLevelingCompat.java):
public final class DmzCompat {
private static Boolean loaded;
public static boolean isLoaded() {
if (loaded == null) loaded = ModList.get().isLoaded("dragonminez");
return loaded;
}
public static int getLevel(Player player) {
if (!isLoaded()) return 0; // safe fallback without DMZ
return Impl.getLevel(player);
}
// Only this nested class imports DMZ classes, so it is never loaded without DMZ.
private static final class Impl {
static int getLevel(Player player) {
return StatsProvider.get(StatsCapability.INSTANCE, player).map(StatsData::getLevel).orElse(0);
}
}
}
- Comprueba
ModList.get().isLoaded("dragonminez")una sola vez y guarda el resultado en caché. - Mantén todos los imports de DMZ dentro de clases que solo se tocan después de esa comprobación (incluidos los listeners de eventos de DMZ: regístralos a mano y solo cuando DMZ esté cargado).
- Protege con ese indicador toda llamada que toque DMZ, con un comportamiento alternativo seguro o un valor por defecto cuando sea
false. - Registra en el log un mensaje claro de información o de advertencia en ambos casos, para que los dueños de servidores vean de un vistazo si la integración está activa.
Siguiente paso
Consulta Crear un addon de DMZ para la configuración completa del proyecto de Gradle y la declaración de la dependencia en mods.toml.