Files
Documentation/content/ui-systems/custom-pages.en.md
2026-01-20 20:33:59 +01:00

9.1 KiB

title, type, weight
title type weight
Custom Pages docs 2

Custom pages allow plugins to create interactive UI overlays that can receive user input and display dynamic content.

Package: com.hypixel.hytale.server.core.entity.entities.player.pages

Page Lifetimes

Control how pages can be closed:

Lifetime Description
CustomPageLifetime.CantClose Page cannot be dismissed by the player
CustomPageLifetime.CanDismiss Player can close with Escape key
CustomPageLifetime.CanDismissOrCloseThroughInteraction Can close via Escape or interaction

Creating a Custom Page

Extend CustomUIPage:

public class WelcomePage extends CustomUIPage {

    public WelcomePage(PlayerRef playerRef) {
        super(playerRef, CustomPageLifetime.CanDismiss);
    }

    @Override
    public void build(Ref<EntityStore> ref, UICommandBuilder builder,
                      UIEventBuilder events, Store<EntityStore> store) {
        // Load a UI template from assets (extension .ui is REQUIRED)
        builder.append("Pages/WelcomeScreen.ui");

        // Set dynamic values
        builder.set("#player_name", playerRef.getUsername());
        builder.set("#server_name", "My Server");
    }
}

UICommandBuilder

Build UI content dynamically:

Append Content

// Append a UI template (extension .ui is REQUIRED)
builder.append("Pages/TemplatePath.ui");

// Append to a specific selector
builder.append("#container", "Pages/ChildTemplate.ui");

// Append inline YAML/JSON
builder.appendInline("#list", "{ type: text, content: 'Hello' }");

Set Values

// Set text
builder.set("#title", "Welcome!");
builder.set("#description", Message.translation("my.key"));

// Set numbers
builder.set("#score", 1500);
builder.set("#progress", 0.75f);

// Set boolean
builder.set("#visible", true);

// Set to null
builder.setNull("#optional_field");

Set Objects

// Set ItemStack
ItemStack item = ...;
builder.setObject("#item_display", item);

// Set UI Area
Area area = new Area(0, 0, 100, 50);
builder.setObject("#bounds", area);

// Set arrays/lists
builder.set("#items", itemStackArray);
builder.set("#options", stringList);

Remove Content

// Clear children of an element
builder.clear("#container");

// Remove an element entirely
builder.remove("#element_id");

// Insert before an element
builder.insertBefore("#existing", "Pages/NewElement.ui");

UIEventBuilder

Bind events to UI elements using addEventBinding:

import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
import com.hypixel.hytale.server.core.ui.builder.EventData;

@Override
public void build(Ref<EntityStore> ref, UICommandBuilder builder,
                  UIEventBuilder events, Store<EntityStore> store) {
    builder.append("Pages/Menu.ui");

    // Bind click events using addEventBinding
    events.addEventBinding(CustomUIEventBindingType.Activating, "#button_start",
        EventData.of("Action", "start_game"));
    events.addEventBinding(CustomUIEventBindingType.Activating, "#button_settings",
        EventData.of("Action", "open_settings"));
}

Event Binding Types

Type Description
Activating Button click/activation
RightClicking Right-click action
DoubleClicking Double-click action
MouseEntered Mouse hover enter
MouseExited Mouse hover exit
ValueChanged Input value changed
FocusGained Element focused
FocusLost Element lost focus

Handling Events

Override handleDataEvent to process user interactions. The rawData parameter contains JSON with the event data:

public class MenuPage extends CustomUIPage {

    public MenuPage(PlayerRef ref) {
        super(ref, CustomPageLifetime.CanDismiss);
    }

    @Override
    public void build(Ref<EntityStore> ref, UICommandBuilder builder,
                      UIEventBuilder events, Store<EntityStore> store) {
        builder.append("Pages/MainMenu.ui");

        events.addEventBinding(CustomUIEventBindingType.Activating, "#play_button",
            EventData.of("Action", "play"));
        events.addEventBinding(CustomUIEventBindingType.Activating, "#quit_button",
            EventData.of("Action", "quit"));
    }

    @Override
    public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, String rawData) {
        // rawData contains JSON like: {"Action":"play"}
        if (rawData.contains("\"Action\":\"play\"")) {
            startGame();
        } else if (rawData.contains("\"Action\":\"quit\"")) {
            close();
        }
    }

    private void startGame() {
        // Game logic...
        close();  // Close the page
    }
}

{{< callout type="info" >}} Tip: For more complex event handling, consider using a JSON parser library or extending InteractiveCustomUIPage<T> with a typed data class. {{< /callout >}}

Opening Pages

Use PageManager to open pages:

Player player = ...;
PageManager pageManager = player.getPageManager();

// Open custom page
pageManager.openCustomPage(
    player.getReference(),
    player.getReference().getStore(),
    new MenuPage(player.getPlayerRef())
);

Closing Pages

// From within the page
protected void close() {
    // Inherited method - closes this page
}

// From outside
pageManager.setPage(ref, store, Page.None);

Updating Pages

Update content without rebuilding:

// Send update with new commands
UICommandBuilder update = new UICommandBuilder();
update.set("#score", newScore);
sendUpdate(update);

// Full rebuild
rebuild();

// Clear and update
sendUpdate(update, true);  // clear=true

Page with Windows

Open pages alongside inventory windows:

// Open page with windows
pageManager.openCustomPageWithWindows(
    ref, store,
    new CraftingPage(playerRef),
    craftingWindow,
    playerInventoryWindow
);

Interactive Page Example

public class ShopPage extends CustomUIPage {

    private int selectedItem = -1;
    private final List<ShopItem> items;

    public ShopPage(PlayerRef ref, List<ShopItem> items) {
        super(ref, CustomPageLifetime.CanDismiss);
        this.items = items;
    }

    @Override
    public void build(Ref<EntityStore> ref, UICommandBuilder builder,
                      UIEventBuilder events, Store<EntityStore> store) {
        builder.append("Pages/Shop.ui");

        // Populate items
        for (int i = 0; i < items.size(); i++) {
            ShopItem item = items.get(i);
            builder.set("#item_" + i + "_name", item.getName());
            builder.set("#item_" + i + "_price", item.getPrice());
            events.addEventBinding(CustomUIEventBindingType.Activating, "#item_" + i,
                EventData.of("Action", "select").append("Index", String.valueOf(i)));
        }

        events.addEventBinding(CustomUIEventBindingType.Activating, "#buy_button",
            EventData.of("Action", "buy"));
        events.addEventBinding(CustomUIEventBindingType.Activating, "#close_button",
            EventData.of("Action", "close"));
    }

    @Override
    public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, String rawData) {
        // Parse JSON event data
        if (rawData.contains("\"Action\":\"select\"")) {
            // Extract index from JSON
            int indexStart = rawData.indexOf("\"Index\":\"") + 9;
            int indexEnd = rawData.indexOf("\"", indexStart);
            selectedItem = Integer.parseInt(rawData.substring(indexStart, indexEnd));
            updateSelection();
        } else if (rawData.contains("\"Action\":\"buy\"") && selectedItem >= 0) {
            purchaseItem(selectedItem);
        } else if (rawData.contains("\"Action\":\"close\"")) {
            close();
        }
    }

    private void updateSelection() {
        UICommandBuilder update = new UICommandBuilder();
        update.set("#selected_index", selectedItem);
        update.set("#selected_name", items.get(selectedItem).getName());
        sendUpdate(update);
    }

    private void purchaseItem(int index) {
        // Purchase logic...
        rebuild();  // Refresh UI
    }
}

Built-in Pages

Available page types:

Page Description
Page.None No page open (close current page)
Page.Bench Crafting/workbench interface
Page.Inventory Player inventory
Page.ToolsSettings Tools settings
Page.Map World map
Page.MachinimaEditor Machinima editor
Page.ContentCreation Content creation tools
Page.Custom Custom UI page
// Open built-in page
pageManager.setPage(ref, store, Page.Inventory);

Best Practices

{{< callout type="info" >}} Custom Page Guidelines:

  • Use CanDismiss unless the page must be modal
  • Handle the onDismiss callback for cleanup
  • Use sendUpdate() for small changes, rebuild() for major changes
  • Store page state in instance fields
  • Close pages properly using close() or setPage(Page.None) {{< /callout >}}

{{< callout type="warning" >}} Important: Always check if playerRef.getReference() is null before accessing the store, as the player may have disconnected. {{< /callout >}}