Skip to content

Custom Interactions

This document covers creating custom interactions and UI page suppliers.

  • Operations: com.hypixel.hytale.server.core.modules.interaction.interaction.operation
  • Configs: com.hypixel.hytale.server.core.modules.interaction.interaction.config
  • Suppliers: com.hypixel.hytale.server.core.modules.interaction.suppliers

Custom interactions can be created by:

  1. Implementing the Operation interface
  2. Registering with the interaction codec
  3. Defining in RootInteraction assets

Core interface for executable interaction logic:

package com.hypixel.hytale.server.core.modules.interaction.interaction.operation;
public interface Operation {
// Server-side tick execution
void tick(
Ref<EntityStore> ref,
LivingEntity entity,
boolean firstRun,
float time,
InteractionType type,
InteractionContext context,
CooldownHandler cooldownHandler
);
// Client-side predictive simulation
void simulateTick(
Ref<EntityStore> ref,
LivingEntity entity,
boolean firstRun,
float time,
InteractionType type,
InteractionContext context,
CooldownHandler cooldownHandler
);
// Post-tick handling
default void handle(
Ref<EntityStore> ref,
boolean firstRun,
float time,
InteractionType type,
InteractionContext context
) {}
// Sync point (Server, Client, or None)
WaitForDataFrom getWaitForDataFrom();
// Optional interrupt/blocking rules
@Nullable
default InteractionRules getRules() { return null; }
// Optional tags for categorization
default Int2ObjectMap<IntSet> getTags() {
return Int2ObjectMaps.emptyMap();
}
}
ParameterDescription
refEntity reference
entityLiving entity instance
firstRunTrue on first tick of operation
timeElapsed time in seconds
typeInteraction type (Held, HeldOffhand, etc.)
contextExecution context with metadata
cooldownHandlerCooldown management

Sync point for server-client coordination:

ValueDescription
NoneNo synchronization needed
ServerWait for server data
ClientWait for client data
package com.example.myplugin.interaction;
import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import com.hypixel.hytale.codec.BuilderCodec;
import com.hypixel.hytale.protocol.WaitForDataFrom;
public class CustomInteraction extends Interaction implements Operation {
// Configuration fields
private float damage;
private String effectId;
// Codec for serialization
public static final BuilderCodec<CustomInteraction> CODEC = BuilderCodec.builder(
CustomInteraction.class,
CustomInteraction::new
)
.extend(Interaction.CODEC)
.withFloat("Damage", c -> c.damage, (c, v) -> c.damage = v)
.withString("Effect", c -> c.effectId, (c, v) -> c.effectId = v)
.build();
@Override
public void tick(
Ref<EntityStore> ref,
LivingEntity entity,
boolean firstRun,
float time,
InteractionType type,
InteractionContext context,
CooldownHandler cooldownHandler
) {
if (firstRun) {
// One-time initialization
applyDamage(context, damage);
applyEffect(context, effectId);
}
// Check if complete
if (time >= runTime) {
context.getChain().updateServerState(InteractionState.Finished);
}
}
@Override
public void simulateTick(
Ref<EntityStore> ref,
LivingEntity entity,
boolean firstRun,
float time,
InteractionType type,
InteractionContext context,
CooldownHandler cooldownHandler
) {
// Client-side prediction (optional)
}
@Override
public WaitForDataFrom getWaitForDataFrom() {
return WaitForDataFrom.None;
}
private void applyDamage(InteractionContext context, float damage) {
// Implementation
}
private void applyEffect(InteractionContext context, String effectId) {
// Implementation
}
}
@Override
protected void setup() {
// Register with interaction codec
Interaction.CODEC.register(
"CustomInteraction",
CustomInteraction.class,
CustomInteraction.CODEC
);
}

Create a RootInteraction asset that uses your custom interaction:

{
"Id": "custom_ability",
"InteractionIds": ["CustomInteraction"],
"Cooldown": {
"CooldownId": "custom_ability",
"Cooldown": 1.0
}
}

Create custom UI pages triggered by interactions.

package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server;
public interface CustomPageSupplier {
CustomUIPage tryCreate(
Ref<EntityStore> ref,
ComponentAccessor<EntityStore> componentAccessor,
PlayerRef playerRef,
InteractionContext context
);
}
package com.example.myplugin.ui;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction;
import com.hypixel.hytale.server.ui.CustomUIPage;
import com.hypixel.hytale.codec.BuilderCodec;
public class MyCustomPageSupplier
implements OpenCustomUIInteraction.CustomPageSupplier {
// Configuration
private String title;
private int maxItems;
// Codec
public static final BuilderCodec<MyCustomPageSupplier> CODEC =
BuilderCodec.builder(MyCustomPageSupplier.class, MyCustomPageSupplier::new)
.withString("Title", s -> s.title, (s, v) -> s.title = v)
.withInt("MaxItems", s -> s.maxItems, (s, v) -> s.maxItems = v)
.build();
@Override
public CustomUIPage tryCreate(
Ref<EntityStore> ref,
ComponentAccessor<EntityStore> componentAccessor,
PlayerRef playerRef,
InteractionContext context
) {
// Get player
Player player = componentAccessor.getComponent(
ref,
Player.getComponentType()
);
if (player == null) {
return null;
}
// Create custom page
return new MyCustomPage(
playerRef,
player.getInventory(),
title,
maxItems
);
}
}
@Override
protected void setup() {
OpenCustomUIInteraction.registerCustomPageSupplier(
this,
MyCustomPageSupplier.class,
"MyCustomUI",
new MyCustomPageSupplier()
);
// Or register codec for asset-defined suppliers
OpenCustomUIInteraction.PAGE_CODEC.register(
"MyCustomUI",
MyCustomPageSupplier.class,
MyCustomPageSupplier.CODEC
);
}

For UIs triggered by block interaction:

public class BlockCustomPageSupplier<T extends Component<ChunkStore>>
implements OpenCustomUIInteraction.CustomPageSupplier {
private final ComponentType<ChunkStore, T> componentType;
@Override
public CustomUIPage tryCreate(
Ref<EntityStore> ref,
ComponentAccessor<EntityStore> componentAccessor,
PlayerRef playerRef,
InteractionContext context
) {
// Get target block from context
Vector3i targetBlock = context.getMetaStore().get(Interaction.TARGET_BLOCK);
if (targetBlock == null) {
return null;
}
// Get block component
T blockComponent = getBlockComponent(targetBlock, componentType);
if (blockComponent == null) {
return null;
}
// Create page using block data
return createPage(playerRef, blockComponent);
}
}
SupplierDescription
ItemRepairPageSupplierItem repair/anvil UI
BlockEntityCustomPageSupplierGeneric block entity UI
BlockCustomPageSupplierGeneric block component UI
public class ItemRepairPageSupplier
implements OpenCustomUIInteraction.CustomPageSupplier {
protected double repairPenalty;
@Override
public CustomUIPage tryCreate(
Ref<EntityStore> ref,
ComponentAccessor<EntityStore> componentAccessor,
PlayerRef playerRef,
InteractionContext context
) {
Player player = componentAccessor.getComponent(
ref,
Player.getComponentType()
);
ItemContext itemContext = context.createHeldItemContext();
if (itemContext == null) {
return null;
}
return new ItemRepairPage(
playerRef,
player.getInventory().getCombinedArmorHotbarUtilityStorage(),
repairPenalty,
itemContext
);
}
}

Control how interactions interact with each other:

public class InteractionRules {
// What this interaction can interrupt
Set<String> canInterrupt;
// What can interrupt this interaction
Set<String> interruptedBy;
// What this interaction blocks
Set<String> blocks;
// What blocks this interaction
Set<String> blockedBy;
}
// Ability can interrupt basic attacks
InteractionRules rules = new InteractionRules();
rules.canInterrupt = Set.of("basic_attack");
rules.interruptedBy = Set.of("stun", "knockback");
rules.blocks = Set.of();
rules.blockedBy = Set.of("silence");