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
CanDismissunless the page must be modal - Handle the
onDismisscallback for cleanup - Use
sendUpdate()for small changes,rebuild()for major changes - Store page state in instance fields
- Close pages properly using
close()orsetPage(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 >}}