Skip to content

Message

The message module provides a Kotlin DSL for building Adventure components in a declarative, type-safe manner. The Paper integration adds extension functions for sending messages directly to players and command senders.

Modules

Module Description
kraftlin-message-core DSL for building Adventure components + Audience.message extensions
kraftlin-message-paper (Deprecated) Legacy CommandSender.message — use core's Audience.message instead
kraftlin-message-bungee BungeeCord CommandSender.message extensions via BungeeComponentSerializer
kraftlin-message-velocity Convenience dependency — re-exports core (Velocity natively supports Adventure)

Getting Started

Build a simple message:

val msg = message {
    text("Hello ")
    text("World!") {
        color(NamedTextColor.GOLD)
        bold()
    }
}

With Paper integration, send messages directly:

player.message {
    text("Click here") {
        color(NamedTextColor.GREEN)
        runCommand("/help")
        hoverMessage("Get help")
    }
}

Core Concepts

Component Building

Messages are built using nested DSL blocks:

message {
    text("Part 1")
    text("Part 2") {
        color(NamedTextColor.BLUE)
    }
    newLine()
    text("Part 3")
}

Formatting Inheritance

Base formatting applies to all components unless overridden:

message {
    color(NamedTextColor.YELLOW)  // Base color
    bold()                         // Base decoration

    text("Inherits yellow + bold")
    text("Custom color") {
        color(NamedTextColor.GRAY)  // Overrides base
        // Still bold
    }
    text("Back to yellow + bold")
}

Interactive Components

Add click and hover actions:

message {
    text("Execute command") {
        runCommand("/say hello")
        hoverMessage("Click to say hello")
    }
    text("Open link") {
        openUrl("https://example.com")
        underlined()
    }
}

Text Components

Basic Text

text("Simple text")

text("Formatted text") {
    color(NamedTextColor.RED)
    bold()
    italic()
}

Color Shorthand

text("Colored", NamedTextColor.BLUE)

Convenience Methods

message {
    text("Line 1")
    newLine()  // Add line break
    text("Line 2")
    space()    // Add space
    text("after space")
}

Styling

Colors

text("Text") {
    color(NamedTextColor.GOLD)
    // or
    color(TextColor.color(0xFF5733))
}

Decorations

All standard text decorations are supported:

text("Formatted") {
    bold()
    italic()
    underlined()
    strikeThrough()
    obfuscated()
}

Click Events

Only one click action per component (mutually exclusive):

// Execute command as the clicking player
text("Execute") { runCommand("/gamemode creative") }

// Insert command into chat bar (doesn't execute)
text("Suggest") { suggestCommand("/msg ") }

// Open URL dialog
text("Link") { openUrl("https://example.com") }

// Copy text to clipboard
text("Copy") { copyToClipboard("Copied text") }

Hover Events

Only one hover event per component:

Simple Text Hover

text("Hover me") {
    hoverMessage("Simple tooltip")
}

Formatted Hover

text("Hover me") {
    hoverMessage("Tooltip text") {
        color(NamedTextColor.GOLD)
        bold()
    }
}

Multi-Component Hover

text("Hover me") {
    hoverMessage {
        text("Line 1") { color(NamedTextColor.GOLD) }
        newLine()
        text("Line 2") { color(NamedTextColor.GRAY) }
    }
}

Custom Hover Events

text("Item") {
    hoverEvent(itemStack.asHoverEvent())
}

Insertion

Text inserted into chat on shift+click (can be combined with other actions):

text("Insert text") {
    insert("This text is inserted")
    runCommand("/help")  // Can combine with click action
}

Legacy Support

Converting Legacy Text

Convert §-style formatted text to components:

message {
    legacyText("§aGreen §bBlue §lbold")
}

Converting to Legacy

val component: Component = message { /* ... */ }
val legacy: String = component.toLegacyMessage()

Existing Components

Wrap existing Adventure components:

val existingComponent: Component = Component.text("Existing")

message {
    text(existingComponent) {
        bold()  // Add additional formatting
    }
}

Sending Messages

Audience Extensions (all platforms)

The core module provides Audience.message extensions that work on any platform whose sender implements Adventure's Audience interface (Paper, Velocity, and any other):

audience.message {
    text("Hello ") { color(NamedTextColor.GREEN) }
    text("World!")
}

audience.message("Simple text")
audience.message("Colored text", NamedTextColor.BLUE)

BungeeCord

BungeeCord does not implement Audience, so kraftlin-message-bungee provides CommandSender.message extensions that serialize via BungeeComponentSerializer:

sender.message {
    text("Hello from BungeeCord!") { color(NamedTextColor.GOLD) }
}

Paper (Legacy)

The Paper module's CommandSender.message extensions are deprecated. Use Audience.message from the core module instead — Paper's CommandSender implements Audience.

Extension functions on CommandSender:

// Build and send complex message
player.message {
    text("Hello ") { color(NamedTextColor.GREEN) }
    text("World!")
}

// Send simple text
player.message("Simple message")

// Send colored text
player.message("Colored", NamedTextColor.BLUE)

// Send single component with formatting
player.message("Click me") {
    runCommand("/help")
    hoverMessage("Get help")
}

// Send existing component
player.message(existingComponent) {
    bold()  // Optional additional formatting
}

Advanced Examples

Command Feedback

player.message {
    text("Teleported to ") { color(NamedTextColor.GRAY) }
    text("${target.name}") {
        color(NamedTextColor.GOLD)
        hoverMessage {
            text("Location: ")
            text("${target.location.blockX}, ${target.location.blockY}, ${target.location.blockZ}")
        }
    }
}

Interactive Menu

player.message {
    text("[Accept]") {
        color(NamedTextColor.GREEN)
        runCommand("/quest accept ${questId}")
        hoverMessage("Click to accept quest")
    }
    space()
    text("[Decline]") {
        color(NamedTextColor.RED)
        runCommand("/quest decline ${questId}")
        hoverMessage("Click to decline quest")
    }
}

Formatted List

message {
    color(NamedTextColor.GRAY)
    text("Players online:")
    newLine()

    players.forEach { player ->
        text("• ${player.name}") {
            color(NamedTextColor.WHITE)
            hoverMessage {
                text("Click to teleport")
            }
            runCommand("/tp ${player.name}")
        }
        newLine()
    }
}

Best Practices

  1. Use formatting inheritance for consistent styling:

    message {
        color(NamedTextColor.GRAY)  // Set base color once
        text("Part 1")
        text("Part 2")
    }
    

  2. Use Audience.message for sending messages:

    player.message { /* ... */ }  // ✓ Clean and direct
    player.sendMessage(message { /* ... */ })  // ✗ Verbose
    

  3. Use hover messages for additional context:

    text("Hover for details") {
        hoverMessage("Additional information here")
    }
    

  4. Validate actions - The DSL will throw exceptions if you try to add multiple conflicting actions

Why Kraftlin Messages?

Benefits over raw Adventure builders:

Without Kraftlin:

Component.text()
    .content("Click me")
    .color(NamedTextColor.GREEN)
    .clickEvent(ClickEvent.runCommand("/help"))
    .hoverEvent(HoverEvent.showText(Component.text("Help")))
    .build()

With Kraftlin:

message {
    text("Click me") {
        color(NamedTextColor.GREEN)
        runCommand("/help")
        hoverMessage("Help")
    }
}

Benefits: - Declarative DSL matches structure of message - Cleaner, more readable syntax - Formatting inheritance reduces repetition - Type-safe at compile time - Easy composition of multi-component messages - Validation prevents conflicting actions