API Bridge
El bridge es el canal de comunicación binario que conecta el plugin del servidor con el mod del cliente. Esta página documenta el protocolo, los tipos de mensaje y cómo integrarse con EventUI desde un plugin o mod externo.
eventui-common como dependencia en tu plugin o mod para acceder a todas las interfaces, enums y contratos sin necesidad de reimplementarlos.
Arquitectura del bridge
La comunicación usa el canal eventui:bridge sobre la API de Plugin Messaging de Bukkit. La serialización es binaria con DataOutputStream / DataInputStream — compacta y sin dependencias extra.
Principios clave del protocolo:
| Principio | Descripción |
|---|---|
| Fuente de verdad en el servidor | El plugin gestiona todo el progreso y estado. El mod solo renderiza lo que recibe. |
| Comunicación asíncrona | Los requests usan messageId / replyToMessageId (UUID) para correlacionar respuestas. |
| Serialización binaria | No JSON. Formato fijo con DataOutputStream — más compacto y determinista. |
| Push del servidor | El plugin envía PROGRESS_UPDATE y EVENT_STATE_CHANGED sin que el cliente lo solicite. |
Formato de serialización
Cada mensaje se serializa en este orden exacto:
1. messageType → String (UTF-8) nombre del MessageType enum
2. playerId → long, long UUID como dos longs (MSB, LSB)
3. messageId → long, long UUID del mensaje
4. replyToId → boolean + long,long null flag + UUID si no es null
5. timestamp → long epoch millis
6. payloadSize → int número de entradas en el payload
7. payload entries → String, String clave y valor UTF-8 por cada entrada
eventui-common en lugar de serializar manualmente.
Tipos de mensaje
Todos los tipos están definidos en com.eventui.api.bridge.MessageType.
player_uuid: "..."
event_id: "..."
player_uuid: "..."
screen_id: "..."
objective_id: "..."
current: "5"
target: "10"
description: "..."
new_state: "COMPLETED"
value: "..."
Interfaces del common
El módulo eventui-common expone estas interfaces que implementan ambos lados:
| Interfaz / Clase | Paquete | Descripción |
|---|---|---|
EventBridge | api.bridge | Contrato bidireccional. Implementado por plugin y mod. |
BridgeMessage | api.bridge | Envelope de todo mensaje: tipo, payload, playerId, messageId. |
MessageType | api.bridge | Enum con todos los tipos de mensaje del protocolo. |
EventDefinition | api.event | Definición inmutable de un evento. |
EventProgress | api.event | Estado mutable de un evento para un jugador. |
EventState | api.event | Enum: AVAILABLE, IN_PROGRESS, COMPLETED, FAILED, LOCKED. |
ObjectiveDefinition | api.objective | Estructura de un objetivo: tipo, target, parámetros. |
ObjectiveType | api.objective | Enum con los 21 tipos de objetivo. |
UIConfig | api.ui | Configuración completa de una pantalla UI. |
UIElement | api.ui | Nodo del árbol de UI: tipo, posición, propiedades, hijos. |
Integración desde un plugin externo
Si tienes un plugin propio (Paper/Arclight) y quieres enviar actualizaciones de progreso a EventUI o escuchar acciones de la UI, necesitas registrarte en el canal eventui:bridge.
Dependencia en tu plugin
<dependency>
<groupId>com.eventui</groupId>
<artifactId>eventui-common</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
Enviar un PROGRESS_UPDATE al cliente
El caso más común: tu plugin avanza el progreso de un objetivo de EventUI desde lógica propia.
import com.eventui.api.bridge.MessageType;
import org.bukkit.entity.Player;
public void sendProgressUpdate(Player player, String eventId,
String objectiveId, int current, int target,
String description) {
// Construir el payload como bytes con DataOutputStream
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos)) {
// 1. Tipo de mensaje
out.writeUTF(MessageType.PROGRESS_UPDATE.name());
// 2. Player UUID
UUID uuid = player.getUniqueId();
out.writeLong(uuid.getMostSignificantBits());
out.writeLong(uuid.getLeastSignificantBits());
// 3. Message ID
UUID msgId = UUID.randomUUID();
out.writeLong(msgId.getMostSignificantBits());
out.writeLong(msgId.getLeastSignificantBits());
// 4. ReplyTo (null)
out.writeBoolean(false);
// 5. Timestamp
out.writeLong(System.currentTimeMillis());
// 6. Payload
Map<String, String> payload = Map.of(
"event_id", eventId,
"objective_id", objectiveId,
"current", String.valueOf(current),
"target", String.valueOf(target),
"description", description
);
out.writeInt(payload.size());
for (Map.Entry<String, String> entry : payload.entrySet()) {
out.writeUTF(entry.getKey());
out.writeUTF(entry.getValue());
}
// 7. Enviar al canal
player.sendPluginMessage(yourPlugin, "eventui:bridge", baos.toByteArray());
} catch (IOException e) {
getLogger().severe("Error sending EventUI progress: " + e.getMessage());
}
}
Recibir acciones de la UI desde el cliente
Para saber cuándo un jugador hace clic en un botón de EventUI desde tu plugin externo:
import org.bukkit.plugin.messaging.PluginMessageListener;
import com.eventui.api.bridge.MessageType;
public class MiPlugin extends JavaPlugin implements PluginMessageListener {
@Override
public void onEnable() {
// Registrar como listener del canal de EventUI
getServer().getMessenger().registerIncomingPluginChannel(
this, "eventui:bridge", this
);
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] data) {
if (!channel.equals("eventui:bridge")) return;
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(data))) {
String typeStr = in.readUTF();
MessageType type = MessageType.valueOf(typeStr);
// Leer y descartar playerId, messageId, replyTo, timestamp
in.readLong(); in.readLong(); // playerId
in.readLong(); in.readLong(); // messageId
if (in.readBoolean()) { in.readLong(); in.readLong(); } // replyTo
in.readLong(); // timestamp
// Leer payload
int size = in.readInt();
Map<String, String> payload = new HashMap<>();
for (int i = 0; i < size; i++) {
payload.put(in.readUTF(), in.readUTF());
}
if (type == MessageType.UI_BUTTON_CLICKED) {
String buttonId = payload.get("button_id");
String eventId = payload.get("event_id");
handleButtonClick(player, buttonId, eventId);
}
} catch (IOException e) {
getLogger().warning("Error reading EventUI message: " + e.getMessage());
}
}
private void handleButtonClick(Player player, String buttonId, String eventId) {
// Tu lógica aquí
getLogger().info(player.getName() + " clicked button: " + buttonId);
}
}
Integración desde un mod Fabric externo
Si quieres que tu mod reaccione a eventos de EventUI — por ejemplo mostrar un efecto de partículas cuando el jugador completa una misión — puedes escuchar el canal desde el cliente.
Dependencia en tu mod
dependencies {
modImplementation "com.eventui:eventui-common:1.0.0"
}
Escuchar mensajes del servidor
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.util.Identifier;
import com.eventui.api.bridge.MessageType;
public class MiModClient implements ClientModInitializer {
private static final Identifier EVENTUI_CHANNEL =
Identifier.of("eventui", "bridge");
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(
EVENTUI_CHANNEL,
(client, handler, buf, responseSender) -> {
// Leer el mensaje en el hilo de red
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
try (DataInputStream in =
new DataInputStream(new ByteArrayInputStream(data))) {
String typeStr = in.readUTF();
MessageType type = MessageType.valueOf(typeStr);
// Descartar campos de metadata
in.readLong(); in.readLong(); // playerId
in.readLong(); in.readLong(); // messageId
if (in.readBoolean()) { in.readLong(); in.readLong(); }
in.readLong(); // timestamp
// Leer payload
int size = in.readInt();
Map<String, String> payload = new HashMap<>();
for (int i = 0; i < size; i++) {
payload.put(in.readUTF(), in.readUTF());
}
if (type == MessageType.EVENT_STATE_CHANGED) {
String eventId = payload.get("event_id");
String newState = payload.get("new_state");
// Ejecutar en el hilo del juego (obligatorio para rendering)
client.execute(() -> {
if ("COMPLETED".equals(newState)) {
spawnCompletionParticles(client, eventId);
}
});
}
} catch (IOException e) {
// ignorar mensajes malformados
}
}
);
}
private void spawnCompletionParticles(MinecraftClient client, String eventId) {
// Tu lógica de partículas aquí
}
}
Enviar un mensaje al servidor desde el mod
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import com.eventui.api.bridge.MessageType;
public static void sendButtonClick(String buttonId, String eventId, UUID playerId) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos)) {
out.writeUTF(MessageType.UI_BUTTON_CLICKED.name());
out.writeLong(playerId.getMostSignificantBits());
out.writeLong(playerId.getLeastSignificantBits());
UUID msgId = UUID.randomUUID();
out.writeLong(msgId.getMostSignificantBits());
out.writeLong(msgId.getLeastSignificantBits());
out.writeBoolean(false); // sin replyTo
out.writeLong(System.currentTimeMillis());
out.writeInt(2);
out.writeUTF("button_id"); out.writeUTF(buttonId);
out.writeUTF("event_id"); out.writeUTF(eventId);
PacketByteBuf buf = PacketByteBufs.create();
buf.writeBytes(baos.toByteArray());
ClientPlayNetworking.send(
Identifier.of("eventui", "bridge"), buf
);
} catch (IOException e) {
// manejar error
}
}
Usar ClientEventBridge directamente
Si tu mod depende de EventUI como librería (no solo del common), puedes usar ClientEventBridge directamente en lugar de manejar el canal manualmente. Es más seguro y menos propenso a errores de serialización:
import com.eventui.fabric.client.bridge.ClientEventBridge;
import com.eventui.api.bridge.MessageType;
// En tu ClientModInitializer, después de que EventUI se haya inicializado:
ClientEventBridge bridge = ClientEventBridge.getInstance();
bridge.registerMessageHandler(MessageType.EVENT_STATE_CHANGED, message -> {
String eventId = message.getPayload().get("event_id");
String newState = message.getPayload().get("new_state");
MinecraftClient.getInstance().execute(() -> {
if ("COMPLETED".equals(newState)) {
// Tu lógica aquí
}
});
});
ClientEventBridge.getInstance() solo está disponible después de que el mod de EventUI se haya inicializado. Usa el evento ClientLifecycleEvents.CLIENT_STARTED de Fabric para garantizar el orden correcto.
Notas importantes
| Tema | Detalle |
|---|---|
| Hilo de rendering | Los mensajes del bridge llegan en el hilo de red. Cualquier operación de rendering o acceso al estado del juego en el cliente debe ejecutarse con client.execute(() -> ...). |
| Compatibilidad de servidores | El plugin de EventUI solo es compatible con Paper 1.21.1 y Arclight 1.21.1. Spigot no está soportado por incompatibilidades con la API de Adventure. |
| Canal bidireccional | Bukkit requiere que el plugin que registra el canal también sea el que lo usa para enviar. Si usas un plugin externo, registra el canal con tu plugin antes de enviar mensajes. |
| Versión del protocolo | El formato binario puede cambiar entre versiones de EventUI. Siempre usa la misma versión de eventui-common que el plugin y el mod instalados en el servidor/cliente. |