Skip to main content

Networking

Embers Text API uses server-to-client packets for all message delivery. This page covers the NetworkHelper API, packet types, and client-side message management.

Package: net.tysontheember.emberstextapi.network


Quick Start

For the most common case — send a new message to a player:

import net.tysontheember.emberstextapi.EmbersTextAPI;

EmbersTextAPI.sendMessage(serverPlayer, immersiveMessage);

NetworkHelper API

For full control over the message lifecycle, use NetworkHelper:

NetworkHelper net = NetworkHelper.getInstance();

Individual Message Methods

// Send a new message (generates UUID automatically)
net.sendMessage(ServerPlayer player, ImmersiveMessage message)

// Send with explicit UUID tracking
net.sendOpenMessage(ServerPlayer player, ImmersiveMessage message)

// Update an existing message before it expires
net.sendUpdateMessage(ServerPlayer player, String messageId, ImmersiveMessage message)

// Close a specific message immediately
net.sendCloseMessage(ServerPlayer player, String messageId)

// Close all active messages across all channels
net.sendCloseAllMessages(ServerPlayer player)

Queue Methods

// Send an ordered sequence of steps on a named channel
// steps: outer list = sequential steps, inner list = simultaneous messages per step
net.sendQueue(ServerPlayer player, String channel, List<List<ImmersiveMessage>> steps)

// Clear pending (not-yet-started) steps from a channel
net.sendClearQueue(ServerPlayer player, String channel)

// Clear all channels and close all messages immediately
net.sendClearAllQueues(ServerPlayer player)

Packet Types

All communication is server-to-client (S2C). There are 6 packet types:

S2C_OpenMessagePacket

Creates a new on-screen message.

public record S2C_OpenMessagePacket(UUID id, CompoundTag nbt)
FieldDescription
idUnique identifier for this message — used for updates and removal
nbtSerialized ImmersiveMessage (all spans, effects, positioning, etc.)

Handler: ClientMessageManager.open(id, message)


S2C_UpdateMessagePacket

Replaces an existing message's content without removing and re-creating it.

public record S2C_UpdateMessagePacket(UUID id, CompoundTag nbt)

Use this to refresh a message before it expires, or to change content/effects mid-display.

Handler: ClientMessageManager.update(id, message)


S2C_CloseMessagePacket

Removes a specific message.

public record S2C_CloseMessagePacket(UUID id)

Handler: ClientMessageManager.close(id)


S2C_CloseAllMessagesPacket

Removes all active messages and clears all queues.

public record S2C_CloseAllMessagesPacket()

Handler: ClientMessageManager.closeAll()


S2C_OpenQueuePacket

Sends a full ordered sequence of steps on a named channel.

FieldTypeDescription
channelStringNamed channel identifier
idsList<List<UUID>>Per-step, per-message UUIDs
stepDataList<List<CompoundTag>>Per-step, per-message serialized messages

If the channel already has an active queue, the new steps are appended.

Handler: ClientMessageManager.enqueueSteps(channel, steps)


S2C_ClearQueuePacket

Clears pending steps from a channel, or all channels.

public record S2C_ClearQueuePacket(String channel)
  • Named channel: Removes pending steps; current step plays to completion.
  • Empty string: Clears all channels and closes all active messages immediately.

Handler: ClientMessageManager.clearQueue(channel) or ClientMessageManager.clearAllQueues()


Network Channel Registration

The network channel is registered automatically during mod initialization. The registration method differs by loader:

LoaderMechanism
Forge 1.20.1Forge SimpleChannel during FMLCommonSetupEvent
NeoForge 1.21.1NeoForge StreamCodec system at mod init
FabricFabric Networking API with custom packet codecs

The packet payload structure and client handling are identical across all loaders.

note

You don't need to register any network channels yourself — ETA handles this automatically during its own mod initialization.


ClientMessageManager

The client-side manager that stores and renders all active messages.

Package: net.tysontheember.emberstextapi.client

Message Lifecycle

ClientMessageManager.open(UUID id, ImmersiveMessage message)    // Add to active set
ClientMessageManager.update(UUID id, ImmersiveMessage message) // Replace existing
ClientMessageManager.close(UUID id) // Remove specific
ClientMessageManager.closeAll() // Remove all

Queue Operations

// Enqueue steps; starts immediately if channel is idle
ClientMessageManager.enqueueSteps(String channel, List<QueueStep> steps)

// Cancel pending steps (current step plays to completion)
ClientMessageManager.clearQueue(String channel)

// Cancel all queues and close all messages
ClientMessageManager.clearAllQueues()

Queue Data Types

// One message within a queue step
public record QueuedMessage(UUID id, ImmersiveMessage message)

// One sequential step — all messages in this list display simultaneously
public record QueueStep(List<QueuedMessage> messages)

Queue Advancement Logic

On each tick, after processing message expiry:

  1. If all UUIDs tracked for a channel have expired, the next QueueStep is dequeued.
  2. The step's messages are opened via open(), and their UUIDs become the channel's active set.
  3. When the queue is empty, the channel entry is removed.

Individual messages and queue messages coexist freely — channels only advance based on their own tracked UUIDs.

Messages are stored in a ConcurrentHashMap<UUID, ActiveMessage>.


Effect Serialization

Effects within spans are serialized as strings:

effectName key1=value1 key2=value2

On deserialization, EffectRegistry.parseTag() recreates the effect instance. If an effect name is unrecognized on the receiving client (e.g., a custom effect from a mod the client doesn't have), it is logged and skipped — the message still renders without that effect.


Serialization Limits

These limits are enforced during TextSpan serialization to prevent malicious data:

PropertyLimit
Content length65,536 characters
Item/Entity ID length256 characters
Effect tag string length512 characters
Array sizes256 entries
Scale values100.0
Offset values10,000.0

Values exceeding limits are clamped. No exception is thrown.