Flectone LogoFlectonePulse

Formatting

MessageContext, MessagePipeline and MessageFlag, adding your own MiniMessage tags and driving the formatting stages

MessageContext is one message on its way to one player. Inside sit the raw text, the sender, the receiver, a set of MiniMessage tags and the flags that switch formatting stages on and off

Package net.flectone.pulse.model.event.message.context
MethodReturnsWhat it gives
message()StringThe raw text, not rendered yet
sender()FEntityThe one who sent the message
receiver()FPlayerThe one who will read it
uuid()UUIDThe shared identifier of every copy of the message
tagResolver()TagResolverEvery tag available while rendering
flags()Map<MessageFlag, Boolean>The formatting flags
isFlag(MessageFlag)booleanThe flag state with its default value in mind
base()MessageContextThe base context, when the current one works as a wrapper

Creating and changing

import net.flectone.pulse.model.event.message.context.MessageContext;

MessageContext context = MessageContext.builder()
        .sender(fPlayer)
        .receiver(receiver)
        .message("<display_name> <white>hello")
        .build();

The context is immutable, so the with* and add* methods give a copy

MethodWhat it does
withMessage(String)Gives a copy with other text
withSender(FEntity)Gives a copy with another sender
withReceiver(FPlayer)Gives a copy with another receiver
withUuid(UUID)Gives a copy with another identifier
addFlag(MessageFlag, boolean)Gives a copy with a changed flag
addFlags(MessageFlag[], boolean[])Gives a copy with several changed flags
addTagResolver(TagResolver)Gives a copy with one more tag on top of the rest
addTagResolvers(TagResolver...)Gives a copy with several more tags
toBuilder()Opens a builder filled with the current values

Extended contexts

Some modules carry extra data along. Such contexts wrap the ordinary one and hand it the shared fields

ContextExtra field
ComponentMessageContextThe component() method gives a ready component to insert
StringMessageContextThe string() method gives an arbitrary string
ModerationMessageContextThe moderation() method gives a punishment
ExternalModerationMessageContextThe externalModeration() method gives a punishment from another plugin
VanishMessageContextThe vanished() method gives the hidden sender marker
// your own context is built on top of the base one
CoinMessageContext.builder()
        .base(MessageContext.builder()
                .sender(fPlayer)
                .receiver(receiver)
                .message(format)
                .build()
        )
        .percent(percent)
        .build();

Your own tag

The most common use of the API is your own MiniMessage tag, which you can then put into any message format in the localization files

Subscribe to MessageFormattingEvent

Create a TagResolver with your tag

Add it to the context through addTagResolver() and return the event

import net.flectone.pulse.annotation.Pulse;
import net.flectone.pulse.listener.PulseListener;
import net.flectone.pulse.model.event.Event;
import net.flectone.pulse.model.event.message.MessageFormattingEvent;
import net.flectone.pulse.model.event.message.context.MessageContext;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;

public class BalanceTagListener implements PulseListener {

    @Pulse(priority = Event.Priority.NORMAL)
    public Event onFormatting(MessageFormattingEvent event) {
        MessageContext context = event.context();

        TagResolver balanceTag = TagResolver.resolver("balance", (argumentQueue, ctx) -> {
            double balance = getBalance(context.sender().uuid());
            return Tag.selfClosingInserting(Component.text(String.format("%.2f", balance)));
        });

        return event.withContext(context.addTagResolver(balanceTag));
    }

}

Now you can write <balance> in any localization format

format: "<display_name> <gray>[<balance>$]<white>: <message>"

Ready resolvers

MessagePipeline can build resolvers for you

import net.flectone.pulse.pipeline.MessagePipeline;

MessagePipeline messagePipeline = flectonePulse.get(MessagePipeline.class);

// a tag with a ready component
TagResolver serverTag = messagePipeline.resolver("server", Component.text("survival"));

// a computed tag
TagResolver timeTag = messagePipeline.resolver("time", (queue, ctx) ->
        Tag.selfClosingInserting(Component.text(LocalTime.now().toString()))
);

// one tag under several names
TagResolver aliases = messagePipeline.resolver(Set.of("money", "balance"), Component.text("100"));

MessagePipeline

Package net.flectone.pulse.pipeline

It turns the raw text into what the client sees

MethodReturnsWhat it does
build(MessageContext)ComponentRenders a component
buildStandard(MessageContext)StringRenders back into MiniMessage text
buildPlain(MessageContext)StringRenders text with formatting stripped
buildLegacy(MessageContext)StringRenders text with legacy color codes
buildLegacy(FPlayer, String)Optional<String>Renders a loose string for a player
buildJson(MessageContext)StringRenders the JSON form used by the protocol
messageComponent(FEntity, FPlayer, String)ComponentRenders a nested message
Component component = messagePipeline.build(MessageContext.builder()
        .sender(fPlayer)
        .receiver(receiver)
        .message("<rainbow>Hello, <display_name></rainbow>")
        .build()
);

messageSender.sendMessage(receiver, component, false);

Tags of the built in modules

The MessagePipeline.ReplacementTag enum holds every tag the built in modules insert. The tag name in a message is the constant name in lower case

GroupTags
Namesdisplay_name, player, nickname, prefix, suffix, constant
Statusesafk, mute, stream, server, world
Moderationswear, delete
Objectsplayer_head, sprite, texture and their variants with the _or suffix
The restanimation, condition, mention, online, toponline, padding, question, replacement, translation, fading, fcolor
// the tag name in a message
String tagName = MessagePipeline.ReplacementTag.DISPLAY_NAME.getTagName(); // display_name

// a resolver that erases the tag when your module is off
TagResolver empty = MessagePipeline.ReplacementTag.MENTION.emptyResolver();

Flags

Package net.flectone.pulse.constant

MessageFlag switches single processing stages on and off for one message

import net.flectone.pulse.constant.MessageFlag;

// turn off the swear check and the cache for this message
MessageContext context = messageContext
        .addFlag(MessageFlag.SWEAR_MODULE, false)
        .addFlag(MessageFlag.USE_CACHE, false);

Main flags

FlagDefaultWhat it does
PLAYER_MESSAGEfalseThe value true says a player wrote the message and turns on the full input processing
USE_CACHEtrueCaches rendered messages
PLAYER_NAMEtrueProcesses the player name tag
REMOVE_DISABLED_TAGStrueErases the tags of disabled modules
URL_PROCESSINGtrueLooks for links
ITEM_DETECTIONtrueLooks for items in the message
LEGACY_COLOR_CONVERSIONtrueSupports legacy color codes
COLOR_CONTEXT_SENDERtrueTakes colors from the sender, otherwise from the receiver
PLACEHOLDER_CONTEXT_SENDERtrueResolves placeholders from the sender, otherwise from the receiver
INVISIBLE_NAME_DETECTIONtrueCheck player invisible name
INTERACTIVE_CHAT_COMPATtrueGives compatibility with InteractiveChat
VIOLATION_PROCESSINGtrueCounts violations in the moderation system

Module flags

A flag turned off skips its formatting module

CAPS_MODULE, DELETE_MODULE, FIXATION_MODULE, FLOOD_MODULE, ICU_MODULE, MENTION_MODULE, NICKNAME_MODULE, PADDING_MODULE, QUESTIONANSWER_MODULE, REPLACEMENT_MODULE, SWEAR_MODULE, TRANSLATE_MODULE

Object flags

OBJECT_DEFAULT_VALUE, OBJECT_PLAYER_HEAD_PROCESSING, OBJECT_SPRITE_PROCESSING, OBJECT_TEXTURE_PROCESSING, OBJECT_RECEIVER_VALIDATION

Warning

When the PLAYER_MESSAGE flag stands at true, the ICU_MODULE flag counts as on as well, whatever its own value happens to be


Editing the text

@Pulse(priority = Event.Priority.HIGH)
public Event onFormatting(MessageFormattingEvent event) {
    MessageContext context = event.context();

    // touch only the messages players wrote
    if (!context.isFlag(MessageFlag.PLAYER_MESSAGE)) return event;

    String message = context.message();
    if (!message.contains("advertisement")) return event;

    // cancel the whole message
    return event.withCancelled(true);
}

Decorating components

ComponentDecorator helps you add a tooltip or a decoration for component and child components without wiping the existing ones

Package net.flectone.pulse.decorator
MethodWhat it does
hover(Component, HoverEvent)Adds a hover tooltip
hoverIfAbsent(Component, HoverEvent)Adds a tooltip only when there is none yet
decorate(Component, TextDecoration, State)Applies a decoration, bold or italic for example
decorateIfAbsent(Component, TextDecoration, State)Applies a decoration only when it is not set
Note

Formatting runs for each receiver separately, keep that in mind with heavy work inside a resolver. The cache is on by default, so turn the USE_CACHE flag off for fast changing data. The syntax of the tags themselves is described on the message formatting page

Last update August 11, 2026
Edit on Github

On this page