# HytaleJS
HytaleJS lets you write Hytale server plugins in TypeScript. Get full type safety, autocomplete, and a familiar development experience.
## Features
Write plugins with full type safety and IDE autocomplete
Listen to player joins, chat, block breaks, and 30+ events
Register custom commands with permissions
Run delayed and repeating tasks
## Quick Example
```typescript
import { EventListener, Colors } from "@hytalejs.com/core";
class MyPlugin {
@EventListener("PlayerConnectEvent")
onJoin(event: PlayerConnectEvent) {
const player = event.getPlayer();
player.sendMessage(
Message.raw("Welcome!").color(Colors.GREEN).bold(true)
);
}
}
commands.register("hello", "Say hello", (ctx) => {
ctx.sendMessage("Hello, " + ctx.getSenderName() + "!");
});
```
## Getting Started
Set up your development environment
Build a complete plugin step by step
Explore all available types and methods
# Your First Plugin
import { Step, Steps } from 'fumadocs-ui/components/steps';
Let's build a plugin that welcomes players, formats chat, and adds a `/players` command.
### Create src/main.ts
```typescript
import { type PlayerConnectEvent, type PlayerChatEvent, EventListener, Colors } from "@hytalejs.com/core";
```
### Add a welcome message on join
```typescript
class MyPlugin {
@EventListener("PlayerConnectEvent")
onJoin(event: PlayerConnectEvent) {
const player = event.getPlayer();
const name = event.getPlayerRef().getUsername();
player.sendMessage(
Message.raw("Welcome, " + name + "!").color(Colors.GREEN).bold(true)
);
Universe.get().sendMessage(
Message.raw(name + " joined the server!")
);
}
}
```
### Format chat messages
```typescript
@EventListener("PlayerChatEvent")
onChat(event: PlayerChatEvent) {
const sender = event.getSender();
const content = event.getContent();
const formatted = Message.empty()
.insert(Message.raw(sender.getUsername()).color(Colors.GOLD).bold(true))
.insert(Message.raw(": ").color(Colors.GRAY))
.insert(Message.raw(content).color(Colors.WHITE));
event.setCancelled(true);
const targets = event.getTargets();
for (let i = 0; i < targets.length; i++) {
targets[i].sendMessage(formatted);
}
}
```
### Register a command
```typescript
commands.register("players", "List online players", (ctx) => {
const universe = Universe.get();
ctx.sendMessage("Online: " + universe.getPlayerCount());
const players = universe.getPlayers();
for (let i = 0; i < players.length; i++) {
ctx.sendMessage("- " + players[i].getUsername());
}
});
```
### Build and deploy
```bash
npm run build
cp dist/plugin.js /path/to/hytale-server/mods/bmstefanski_HytaleJS/scripts/
```
Restart your server to load the plugin.
## Complete Code
```typescript
import { type PlayerConnectEvent, type PlayerChatEvent, EventListener, Colors } from "@hytalejs.com/core";
class MyPlugin {
@EventListener("PlayerConnectEvent")
onJoin(event: PlayerConnectEvent) {
const player = event.getPlayer();
const name = event.getPlayerRef().getUsername();
player.sendMessage(
Message.raw("Welcome, " + name + "!").color(Colors.GREEN).bold(true)
);
Universe.get().sendMessage(Message.raw(name + " joined the server!"));
}
@EventListener("PlayerChatEvent")
onChat(event: PlayerChatEvent) {
const sender = event.getSender();
const content = event.getContent();
const formatted = Message.empty()
.insert(Message.raw(sender.getUsername()).color(Colors.GOLD).bold(true))
.insert(Message.raw(": ").color(Colors.GRAY))
.insert(Message.raw(content).color(Colors.WHITE));
event.setCancelled(true);
const targets = event.getTargets();
for (let i = 0; i < targets.length; i++) {
targets[i].sendMessage(formatted);
}
}
}
commands.register("players", "List online players", (ctx) => {
const universe = Universe.get();
ctx.sendMessage("Online: " + universe.getPlayerCount());
const players = universe.getPlayers();
for (let i = 0; i < players.length; i++) {
ctx.sendMessage("- " + players[i].getUsername());
}
});
```
## Next Steps
Learn about all available events
Add permissions and parse arguments
# Installation
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
## Server Setup
### Download HytaleJS
Download the latest `HytaleJS.jar` from the [releases page](https://github.com/bmstefanski/HytaleJS/releases).
### Install the plugin
Copy `HytaleJS.jar` to your Hytale server's mods folder:
```
hytale-server/mods/HytaleJS.jar
```
### Start your server
The plugin will create a scripts folder at:
```
hytale-server/mods/bmstefanski_HytaleJS/scripts/
```
All `.js` files in the scripts folder are automatically loaded when the server starts.
## Development Setup
To write plugins in TypeScript with type checking and autocompletion:
### Prerequisites
* Node.js 18+
### Setup
### Create a new project
```bash
mkdir my-plugin && cd my-plugin
npm init -y
```
### Install dependencies
```bash
pnpm add @hytalejs.com/core
pnpm add -D typescript esbuild
```
```bash
npm install @hytalejs.com/core
npm install -D typescript esbuild
```
```bash
bun add @hytalejs.com/core
bun add -D typescript esbuild
```
### Create tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true,
"experimentalDecorators": true,
"types": ["@hytalejs.com/core"]
},
"include": ["src"]
}
```
### Add build script to package.json
```json
{
"scripts": {
"build": "esbuild src/main.ts --bundle --format=esm --target=es2022 --outfile=dist/plugin.js"
}
}
```
## Project Structure
```
my-plugin/
├── src/
│ └── main.ts # Your plugin code
├── dist/
│ └── plugin.js # Built output (deploy this)
├── package.json
└── tsconfig.json
```
## Deploying
Copy your built `dist/plugin.js` to the HytaleJS scripts folder:
```
hytale-server/mods/bmstefanski_HytaleJS/scripts/plugin.js
```
Restart your server to load the plugin.
## Next Steps
Build a complete plugin with events and commands
# API Reference
This page contains auto-generated documentation for all 695 HytaleJS types, sorted alphabetically.
## A
### AOECircleSelector
### AOECylinderSelector
### AbilityEffects
### AccessControlModule
### ActiveAnimationComponent
### ActiveEntityEffect
### AddItemInteraction
### AddPlayerToWorldEvent
### AliveCondition
### AllLegacyEntityTypesQuery
### AllLegacyLivingEntityTypesQuery
### Anchor
### AnimationUtils
### ApplicationEffects
### ApplyEffectInteraction
### ApplyForceInteraction
### ApplyRandomSkinPersistedComponent
### Area
### AssetStore
### AudioComponent
### AudioSystems
## B
### BanCommand
### BasicCollisionData
### BinaryPrefabBufferCodec
### BlockCollisionData
### BlockCollisionProvider
### BlockConditionInteraction
### BlockContactData
### BlockCounter
### BlockData
### BlockDataProvider
### BlockEntity
### BlockEntitySystems
### BlockFilter
### BlockHarvestUtils
### BlockHealth
### BlockHealthChunk
### BlockHealthModule
### BlockIdMatcher
### BlockInteractionUtils
### BlockMask
### BlockMaskConstants
### BlockMatcher
### BlockMigrationExtraInfo
### BlockModule
### BlockPattern
### BlockPlaceUtils
### BlockSetCommand
### BlockSetLookupTable
### BlockSetModule
### BlockState
### BlockTracker
### BlockType
### BodyType
### BootEvent
### BoundingBox
### Box
### BoxBlockIntersectionEvaluator
### BoxCollisionData
### BreakBlockEvent
### BreakBlockInteraction
### BsonPrefabBufferDeserializer
### BuilderToolInteraction
### ByteArrayCommonAsset
### ByteArrayCommonAssetStatic
## C
### CalculationResult
### CameraInteraction
### CameraManager
### CancelChainInteraction
### ChainFlagInteraction
### ChainingInteraction
### ChangeActiveSlotInteraction
### ChangeBlockInteraction
### ChangeGameModeEvent
### ChangeStatInteraction
### ChangeStatWithModifierInteraction
### ChangeStateInteraction
### CharacterCollisionData
### ChargingCondition
### ChargingInteraction
### CheckUniqueItemUsageInteraction
### ChunkLightingManager
### ChunkTracker
### ChunkUtil
### ClearEntityEffectInteraction
### ClientDelegatingProvider
### ClientReferral
### ClientSourcedSelector
### CollisionConfig
### CollisionDataArray
### CollisionMaterial
### CollisionMath
### CollisionModule
### CollisionModuleConfig
### CollisionResult
### CollisionResultComponent
### CollisionTracker
### Color
### ColorLight
### CombatTextUIComponent
### CombatTextUIComponentOpacityAnimationEvent
### CombatTextUIComponentPositionAnimationEvent
### CombatTextUIComponentScaleAnimationEvent
### CommandContext
### CommandSender
### CommonAsset
### CommonAssetModule
### CommonAssetModuleStatic
### ComponentAccessor
### ComponentType
### ConditionInteraction
### ContainerBlockWindow
### ContainerWindow
### CooldownConditionInteraction
### CooldownHandler
### CosmeticAssetValidator
### CosmeticRegistry
### CosmeticType
### CosmeticsModule
### CraftRecipeEvent
### CraftingRecipe
### CraftingRecipePacketGenerator
### CustomHud
### CustomPage
### CustomPageLifetime
### CustomPageLifetimeEnum
### CustomUICommand
### CustomUICommandType
### CustomUICommandTypeEnum
### CustomUIEventBinding
### CustomUIEventBindingType
### CustomUIEventBindingTypeEnum
### CustomUIHud
### CustomUIPage
### CycleBlockGroupInteraction
### Cylinder
## D
### DamageBlockEvent
### DamageCalculator
### DamageCalculatorSystems
### DamageCause
### DamageClass
### DamageDataComponent
### DamageDataSetupSystem
### DamageEffects
### DamageEntityInteraction
### DamageModule
### DamageSystems
### DeathComponent
### DeathItemLoss
### DeathSystems
### DebugCommand
### DebugPlugin
### DebugShapeArrowCommand
### DebugShapeClearCommand
### DebugShapeConeCommand
### DebugShapeCubeCommand
### DebugShapeCylinderCommand
### DebugShapeShowForceCommand
### DebugShapeSphereCommand
### DebugShapeSubCommand
### DebugUtils
### DefaultAssetMap
### DeferredCorpseRemoval
### DespawnComponent
### DespawnSystem
### DestroyBlockInteraction
### DestroyConditionInteraction
### DesyncDamageCommand
### Direction
### DirectionalKnockback
### DisconnectReason
### DiscoverZoneEvent
### DisplayNameComponent
### DoorInteraction
### DrainPlayerFromWorldEvent
### DropItemEvent
### DropdownEntryInfo
### DynamicLight
### DynamicLightSystems
## E
### EffectConditionInteraction
### EffectControllerComponent
### Emote
### EmoteCommand
### EnableTmpTagsCommand
### Entity
### EntityCleanCommand
### EntityCloneCommand
### EntityCollisionProvider
### EntityCommand
### EntityContactData
### EntityCountCommand
### EntityDumpCommand
### EntityEffect
### EntityEffectCommand
### EntityEffectPacketGenerator
### EntityEvent
### EntityGroup
### EntityHideFromAdventurePlayersCommand
### EntityIntangibleCommand
### EntityInteractableSystems
### EntityInvulnerableCommand
### EntityLodCommand
### EntityMakeInteractableCommand
### EntityModule
### EntityNameplateCommand
### EntityRefCollisionProvider
### EntityRegistration
### EntityRegistry
### EntityRemoveCommand
### EntityRemoveEvent
### EntityResendCommand
### EntityScaleComponent
### EntitySnapshot
### EntitySnapshotHistoryCommand
### EntitySnapshotLengthCommand
### EntitySnapshotSubCommand
### EntitySpatialSystem
### EntityStatMap
### EntityStatType
### EntityStatTypePacketGenerator
### EntityStatUIComponent
### EntityStatValue
### EntityStatsAddCommand
### EntityStatsDumpCommand
### EntityStatsGetCommand
### EntityStatsResetCommand
### EntityStatsSetCommand
### EntityStatsSetToMaxCommand
### EntityStatsSubCommand
### EntityStore
### EntitySystems
### EntityTrackerCommand
### EntityTrackerSystems
### EntityUIComponentPacketGenerator
### EntityUIModule
### EntityUtils
### EnvironmentCondition
### EquipItemInteraction
### EventData
### EventDataStatic
### EventHandler
### EventType
### ExplodeInteraction
### ExplosionConfig
### ExplosionUtils
## F
### FirstClickInteraction
### FloodLightCalculation
### FluidIterator
### FlyCameraModule
### ForceAccumulator
### ForceKnockback
### ForceProviderEntity
### ForceProviderStandardState
### Formatter
### FragileBlock
### FromPrefab
### FromWorldGen
### Frozen
### FullBrightLightCalculation
## G
### GameMode
### GenerateDefaultLanguageEvent
### GenerateI18nCommand
### GenericVelocityInstructionSystem
### GlidingCondition
### GroupPermissionChangeEvent
## H
### HeadRotation
### HexColor
### HiddenFromAdventurePlayers
### HiddenPlayersManager
### HideEntitySystems
### HitboxCollision
### HitboxCollisionConfig
### HitboxCollisionConfigPacketGenerator
### HitboxCollisionSystems
### HitboxCommand
### Holder
### HorizontalSelector
### HostAddress
### HotbarManager
### HudComponent
### HudComponentEnum
### HudManager
### HytaleBanProvider
### HytaleServerInstance
### HytaleServerStatic
### HytaleWhitelistProvider
## I
### I18nModule
### IncreaseBackpackCapacityInteraction
### IncrementCooldownInteraction
### IndexedAssetMap
### InfiniteBan
### InputUpdate
### Intangible
### IntangibleSystems
### Interactable
### InteractionCameraSettings
### InteractionChain
### InteractionClearCommand
### InteractionCommand
### InteractionConfiguration
### InteractionContext
### InteractionEffects
### InteractionEntry
### InteractionManager
### InteractionModule
### InteractionPacketGenerator
### InteractionPriorityCodec
### InteractionRules
### InteractionRunCommand
### InteractionRunSpecificCommand
### InteractionSetSnapshotSourceCommand
### InteractionSimulationHandler
### InteractionSnapshotSourceCommand
### InteractionSystems
### InteractionTarget
### InteractionType
### InteractionTypeUtils
### Interactions
### InteractivelyPickupItemEvent
### InternationalizationCommands
### InterruptInteraction
### InvalidatablePersistentRef
### Inventory
### Invulnerable
### InvulnerableSystems
### Item
### ItemClass
### ItemComponent
### ItemContainer
### ItemContainerStateSpatialSystem
### ItemGridSlot
### ItemMergeSystem
### ItemModule
### ItemPacketGenerator
### ItemPhysicsComponent
### ItemPhysicsSystem
### ItemPrePhysicsSystem
### ItemQualityPacketGenerator
### ItemRepairElement
### ItemRepairPage
### ItemRepairPageSupplier
### ItemReticleConfigPacketGenerator
### ItemSpatialSystem
### ItemStack
### ItemStackContainerWindow
### ItemStackSlotTransaction
### ItemStackTransaction
### ItemSystems
### ItemUtils
## J
### JavaByteArray
### JavaByteClass
### JavaClass
### JavaCompletableFuture
### JavaInterop
### JavaIterator
### JavaList
### JavaMap
### JavaSet
### JavetCallMetrics
### JumpOperation
## K
### KillFeedEvent
### KnockbackComponent
### KnockbackPredictionSystems
### KnockbackSimulation
### KnockbackSystems
## L
### Label
### LangFileParser
### LaunchPadInteraction
### LaunchProjectileInteraction
### LegacyEntityTrackerSystems
### LegacyModule
### LegacyProjectileSystems
### ListCollector
### LivingEntityEffectClearChangesSystem
### LivingEntityEffectSystem
### LivingEntityInventoryChangeEvent
### LivingEntityUseBlockEvent
### LocalizableString
### LogicCondition
## M
### MaterialExtraResourcesSection
### MaterialQuantity
### Message
### MessageStatic
### MessagesUpdated
### MigrationModule
### ModelComponent
### ModelOverride
### ModelSystems
### ModelTransform
### ModifyInventoryInteraction
### MouseButtonEvent
### MouseMotionEvent
### MovementAudioComponent
### MovementConditionInteraction
### MovementConfig
### MovementManager
### MovementStatesComponent
### MovementStatesSystems
### MovingBoxBoxCollisionEvaluator
### MultiBlockMask
## N
### NameMatching
### Nameplate
### NameplateSystems
### NetworkId
### NetworkSendableSpatialSystem
### NewSpawnComponent
### NoDamageTakenCondition
## O
### OpenContainerInteraction
### OpenItemStackContainerInteraction
### OperationsBuilder
### OutOfCombatCondition
### OverlapBehavior
## P
### PacketHandler
### Page
### PageEnum
### PageManager
### ParallelInteraction
### Particle
### ParticleAnimationFrame
### ParticleAttractor
### ParticleCollision
### ParticleCommand
### ParticleSpawnCommand
### ParticleSpawnPage
### ParticleSpawner
### ParticleSpawnerGroup
### ParticleSpawnerPacketGenerator
### ParticleSystemAsset
### ParticleSystemClass
### ParticleSystemPacketGenerator
### PatchStyle
### PendingTeleport
### PersistentDynamicLight
### PersistentModel
### PersistentRef
### PersistentRefCount
### PhysicsBodyState
### PhysicsBodyStateUpdater
### PhysicsBodyStateUpdaterMidpoint
### PhysicsBodyStateUpdaterRK4
### PhysicsBodyStateUpdaterSymplecticEuler
### PhysicsConstants
### PhysicsFlags
### PhysicsMath
### PhysicsValues
### PhysicsValuesAddSystem
### PickBlockInteraction
### PickupItemComponent
### PickupItemSystem
### PlaceBlockEvent
### PlaceBlockInteraction
### PlaceFluidInteraction
### PlacedByInteractionComponent
### PlacementCountConditionInteraction
### PlayCommand
### PlayFriendCommand
### PlayLanCommand
### PlayOnlineCommand
### PlaySoundEvent2D
### PlaySoundEvent3D
### PlaySoundEventEntity
### Player
### PlayerAuthentication
### PlayerCameraAddSystem
### PlayerChatEvent
### PlayerChunkTrackerSystems
### PlayerCollisionResultAddSystem
### PlayerCondition
### PlayerConfigData
### PlayerConnectEvent
### PlayerConnectionFlushSystem
### PlayerCraftEvent
### PlayerDeathPositionData
### PlayerDisconnectEvent
### PlayerEvent
### PlayerGroupEvent
### PlayerHudManagerSystems
### PlayerInteractEvent
### PlayerItemEntityPickupSystem
### PlayerMatcher
### PlayerMouseButtonEvent
### PlayerMouseMotionEvent
### PlayerMovementManagerSystems
### PlayerPermissionChangeEvent
### PlayerPingSystem
### PlayerProcessMovementSystem
### PlayerReadyEvent
### PlayerRef
### PlayerRefEvent
### PlayerRegenerateStatsSystem
### PlayerRespawnPointData
### PlayerSavingSystems
### PlayerSendInventorySystem
### PlayerSetupConnectEvent
### PlayerSetupDisconnectEvent
### PlayerSkin
### PlayerSkinComponent
### PlayerSkinGradient
### PlayerSkinGradientSet
### PlayerSkinPart
### PlayerSkinPartId
### PlayerSkinPartTexture
### PlayerSkinPartType
### PlayerSkinTintColor
### PlayerSpatialSystem
### PlayerSystems
### PlayerWorldData
### PointKnockback
### Position
### PositionDataComponent
### PredictedProjectile
### PredictedProjectileSystems
### PrefabBufferBlockEntry
### PrefabBufferCall
### PrefabBufferColumn
### PrefabBufferUtil
### PrefabCopyableComponent
### PrefabLoadException
### PrefabLoader
### PrefabPasteEvent
### PrefabPlaceEntityEvent
### PrefabRotation
### PrefabSaveException
### PrefabSpawnerCommand
### PrefabSpawnerGetCommand
### PrefabSpawnerModule
### PrefabSpawnerSetCommand
### PrefabSpawnerState
### PrefabSpawnerWeightCommand
### PrefabStore
### PrefabWeights
### PrepareUniverseEvent
### PreventItemMerging
### PreventPickup
### Projectile
### ProjectileComponent
### ProjectileConfig
### ProjectileConfigPacketGenerator
### ProjectileInteraction
### ProjectileModule
### PropComponent
## R
### RaycastSelector
### RecipePacketGenerator
### Ref
### RefillContainerInteraction
### RegenHealthCondition
### RegeneratingModifier
### RegeneratingValue
### RemovalBehavior
### RemoveEntityInteraction
### RepairItemInteraction
### RepeatInteraction
### ReplaceInteraction
### Repulsion
### RepulsionConfig
### RepulsionConfigPacketGenerator
### RepulsionSystems
### ResetCooldownInteraction
### RespawnPage
### RespawnSystems
### RespondToHit
### RespondToHitSystems
### RestingSupport
### RootInteraction
### RootInteractionPacketGenerator
### RotateObjectComponent
### RotateObjectSystem
### RotationTuple
### RunRootInteraction
## S
### ScriptCommandRegistry
### ScriptCustomUIHud
### ScriptCustomUIHudBuildCallback
### ScriptCustomUIHudStatic
### ScriptCustomUIPage
### ScriptCustomUIPageBuildCallback
### ScriptCustomUIPageDismissCallback
### ScriptCustomUIPageEventCallback
### ScriptCustomUIPageStatic
### ScriptLogger
### ScriptScheduler
### ScriptTask
### SelectInteraction
### SelectionManager
### SelectionPrefabSerializer
### SendMessageInteraction
### SerialInteraction
### ServerPlayerListModule
### ServerSetBlocks
### SetBlockCmd
### ShutdownEvent
### SimpleInteraction
### SimplePhysicsProvider
### SingleCollector
### SingleplayerModule
### SingleplayerRequestAccessEvent
### SnapshotBuffer
### SnapshotSystems
### SoundCategory
### SoundCategoryEnum
### SoundEvent
### SoundEventClass
### SpawnItemCommand
### SpawnParticleSystem
### SpawnPrefabInteraction
### SplitVelocity
### SprintStaminaRegenDelay
### SprintingCondition
### StabSelector
### StaminaGameplayConfig
### StaminaModule
### StaminaSystems
### StandardPhysicsConfig
### StandardPhysicsProvider
### StandardPhysicsTickSystem
### StatCondition
### StatModifiersManager
### StatModifyingSystem
### StaticModifier
### StatsConditionInteraction
### StatsConditionWithModifierInteraction
### Store
### StringTag
### SuffocatingCondition
### SwitchActiveSlotEvent
## T
### TangiableEntitySpatialSystem
### TargetEntityEffect
### Teleport
### TeleportSystems
### TimeCommand
### TimeModule
### TimePacketSystem
### TimeResource
### TimeSystem
### TimedBan
### ToggleGliderInteraction
### TrackedPlacement
### Transaction
### Transform
### TransformComponent
### TransformSystems
### TranslationMap
### TreeCollector
### TreeCollectorNode
### TriggerCooldownInteraction
## U
### UICommandBuilder
### UICommandBuilderStatic
### UIComponentList
### UIComponentSystems
### UIEventBuilder
### UIEventBuilderStatic
### UUIDComponent
### UnarmedInteractions
### UnarmedInteractionsPacketGenerator
### UnbanCommand
### UniqueItemUsagesComponent
### Universe
### UniverseStatic
### UpdateBinaryPrefabException
### UpdateEntitySeedSystem
### UpdateLocationSystems
### UseBlockEvent
### UseBlockInteraction
### UseEntityInteraction
## V
### Value
### ValueStatic
### Vector2d
### Vector2f
### Vector2i
### Vector3d
### Vector3f
### Vector3i
### Velocity
### VelocityConfig
### VelocityInstruction
### VelocitySystems
### VulnerableMatcher
## W
### WhitelistAddCommand
### WhitelistClearCommand
### WhitelistCommand
### WhitelistDisableCommand
### WhitelistEnableCommand
### WhitelistListCommand
### WhitelistRemoveCommand
### WhitelistStatusCommand
### WieldingCondition
### WieldingInteraction
### WindowManager
### World
### WorldConfigProvider
### WorldGenId
### WorldParticle
### WorldTimeResource
### WorldTimeSystems
### WorldUtil
## Z
### ZoneDiscoveryInfo
# Block Manipulation
Modify the game world by placing, breaking, and reading blocks.
## Reading Blocks
Get the block type at any position:
```typescript
const world = Universe.get().getDefaultWorld();
const blockType = world.getBlockType(100, 64, 200);
if (blockType) {
ctx.sendMessage("Block: " + blockType.getId());
}
```
You can also use a `Vector3i`:
```typescript
const pos = new Vector3i(100, 64, 200);
const blockType = world.getBlockType(pos);
```
## Placing Blocks
Place a single block at coordinates:
```typescript
const world = Universe.get().getDefaultWorld();
world.setBlock(100, 64, 200, "Cloth_Block_Wool_black");
```
With rotation (0-3):
```typescript
world.setBlock(100, 64, 200, "Cloth_Block_Wool_black", 2);
```
## Breaking Blocks
Break a block with a harvest level:
```typescript
const world = Universe.get().getDefaultWorld();
const success = world.breakBlock(100, 64, 200, 1);
if (success) {
ctx.sendMessage("Block broken!");
}
```
## Building Shapes
Create a platform at the player's position:
```typescript
const pos = playerRef.getTransform().getPosition();
const baseX = Math.floor(pos.getX());
const baseY = Math.floor(pos.getY()) - 1;
const baseZ = Math.floor(pos.getZ());
const world = Universe.get().getDefaultWorld();
const size = 5;
for (let x = 0; x < size; x++) {
for (let z = 0; z < size; z++) {
world.setBlock(baseX + x, baseY, baseZ + z, "Cloth_Block_Wool_black");
}
}
```
## Batched Block Updates
For large-scale building, individual `setBlock` calls are slow because each one sends a network packet. Use `ServerSetBlocks` to batch multiple block changes into a single packet.
### How It Works
Hytale divides the world into 32x32x32 block sections. The `ServerSetBlocks` packet sends multiple block changes within a section at once. Use `ChunkUtil` for section calculations:
1. Get section coordinates: `ChunkUtil.chunkCoordinate(blockPos)`
2. Get block index within section: `ChunkUtil.indexBlock(x, y, z)`
3. Check if blocks are in the same section: `ChunkUtil.isSameChunkSection(...)`
4. Send packet to all players
### Getting Block IDs
Convert a block name to its numeric ID:
```typescript
const blockId = BlockType.getBlockIdOrUnknown(
"Cloth_Block_Wool_black",
"Unknown block: %s",
"Cloth_Block_Wool_black"
);
```
### Creating Block Commands
Each block in the batch needs a `SetBlockCmd`:
```typescript
const index = ChunkUtil.indexBlock(worldX, worldY, worldZ);
const cmd = new SetBlockCmd(index, blockId, 0, 0);
```
Parameters: `(index, blockId, filler, rotation)`
### Sending the Packet
```typescript
const sectionX = ChunkUtil.chunkCoordinate(baseX);
const sectionY = ChunkUtil.chunkCoordinate(baseY);
const sectionZ = ChunkUtil.chunkCoordinate(baseZ);
const packet = new ServerSetBlocks(sectionX, sectionY, sectionZ, cmds);
const players = Universe.get().getPlayers();
for (let i = 0; i < players.length; i++) {
players[i].getPacketHandler().write(packet);
}
```
### Complete Example
Build a platform using batched updates:
```typescript
const pos = playerRef.getTransform().getPosition();
const baseX = Math.floor(pos.getX());
const baseY = Math.floor(pos.getY()) - 1;
const baseZ = Math.floor(pos.getZ());
const blockId = BlockType.getBlockIdOrUnknown("Cloth_Block_Wool_black", "Unknown block");
const sectionX = ChunkUtil.chunkCoordinate(baseX);
const sectionY = ChunkUtil.chunkCoordinate(baseY);
const sectionZ = ChunkUtil.chunkCoordinate(baseZ);
const cmds = [];
const size = 10;
for (let dx = 0; dx < size; dx++) {
for (let dz = 0; dz < size; dz++) {
const worldX = baseX + dx;
const worldZ = baseZ + dz;
if (!ChunkUtil.isSameChunkSection(baseX, baseY, baseZ, worldX, baseY, worldZ)) {
continue;
}
const index = ChunkUtil.indexBlock(worldX, baseY, worldZ);
cmds.push(new SetBlockCmd(index, blockId, 0, 0));
}
}
const packet = new ServerSetBlocks(sectionX, sectionY, sectionZ, cmds);
const players = Universe.get().getPlayers();
for (let i = 0; i < players.length; i++) {
players[i].getPacketHandler().write(packet);
}
```
### Limitations
* Maximum 32,768 blocks per packet (one full 32x32x32 section)
* Blocks must be in the same section
* For builds spanning multiple sections, send one packet per section
## When to Use Each Approach
| Approach | Use Case |
| ------------------ | --------------------------------------------- |
| `world.setBlock()` | Small changes, single blocks |
| `ServerSetBlocks` | Large builds, performance-critical operations |
For most commands placing a few blocks, `world.setBlock()` is simpler and works fine. Use batched updates when building large structures where performance matters.
# Commands
Create custom commands that players can run in-game.
## Basic Command
```typescript
commands.register("hello", "Say hello", (ctx) => {
ctx.sendMessage("Hello, " + ctx.getSenderName() + "!");
});
```
## With Permission
```typescript
commands.register("heal", "Heal yourself", "admin.heal", (ctx) => {
ctx.sendMessage("You have been healed!");
});
```
Players need the `admin.heal` permission to use this command.
## World-Thread Commands
Some operations (like large block edits) are significantly faster when they run on the **world thread**. Use `commands.registerWorld(...)` to execute a command on the world executor.
```typescript
commands.registerWorld("sphere", "Build a sphere on the world thread", (ctx) => {
// Heavy block edits here
});
```
### Why This Exists
Hytale's world state is updated on its own thread. When commands run off-thread, each `setBlock` can incur extra scheduling and synchronization overhead. `registerWorld` runs the JS callback on the world executor, which avoids per-block re-queueing and matches the fastest native command path.
### Caveats
* **Blocks the world thread:** long loops will pause world ticks while the command runs.
* **Use sparingly:** prefer batching (`ServerSetBlocks`) for very large edits and keep world-thread commands short where possible.
## Parsing Arguments
Access the full command input and parse arguments:
```typescript
commands.register("give", "Give items", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
if (parts.length < 2) {
ctx.sendMessage("Usage: /give [quantity]");
return;
}
const itemId = parts[1];
const quantity = parts.length >= 3 ? parseInt(parts[2], 10) : 1;
if (isNaN(quantity) || quantity < 1) {
ctx.sendMessage("Invalid quantity");
return;
}
ctx.sendMessage("Giving " + quantity + "x " + itemId);
});
```
## Finding the Player
Get the player entity from the command sender:
```typescript
commands.register("pos", "Show your position", (ctx) => {
const senderName = ctx.getSenderName();
const world = Universe.get().getDefaultWorld();
const players = world.getPlayers();
let player = null;
for (let i = 0; i < players.length; i++) {
if (players[i].getDisplayName() === senderName) {
player = players[i];
break;
}
}
if (!player) {
ctx.sendMessage("Could not find player");
return;
}
const ref = player.getPlayerRef();
const transform = ref.getTransform();
const pos = transform.getPosition();
ctx.sendMessage("Position: " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ());
});
```
## Full Example
```typescript
commands.register("serverinfo", "Show server info", (ctx) => {
ctx.sendMessage("Server: " + HytaleServer.get().getServerName());
ctx.sendMessage("Players: " + Universe.get().getPlayerCount());
ctx.sendMessage("World: " + Universe.get().getDefaultWorld().getName());
});
commands.register("items", "List items", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
const filter = parts.length >= 2 ? parts[1].toLowerCase() : "";
const map = Item.getAssetStore().getAssetMap().getAssetMap();
const keys = map.keySet();
const iterator = keys.iterator();
let count = 0;
while (iterator.hasNext() && count < 20) {
const key = iterator.next() as string;
if (!filter || key.toLowerCase().includes(filter)) {
ctx.sendMessage(key);
count++;
}
}
});
```
# Events
HytaleJS provides 30+ events for player actions, world changes, and server lifecycle.
## Basic Usage
Use the `@EventListener` decorator to handle events:
```typescript
import { type PlayerConnectEvent, EventListener } from "@hytalejs.com/core";
class MyPlugin {
@EventListener("PlayerConnectEvent")
onJoin(event: PlayerConnectEvent) {
const player = event.getPlayer();
player.sendMessage(Message.raw("Welcome!"));
}
}
```
## Cancelling Events
Some events can be cancelled to prevent the default action:
```typescript
@EventListener("PlayerChatEvent")
onChat(event: PlayerChatEvent) {
if (event.getContent().includes("badword")) {
event.setCancelled(true);
event.getSender().sendMessage(Message.raw("Watch your language!"));
}
}
```
## Common Events
### Player Events
| Event | Description |
| ----------------------- | ------------------------------- |
| `PlayerConnectEvent` | Player joins the server |
| `PlayerDisconnectEvent` | Player leaves the server |
| `PlayerChatEvent` | Player sends a chat message |
| `PlayerReadyEvent` | Player finished loading |
| `PlayerInteractEvent` | Player interacts with something |
### Block Events
| Event | Description |
| ------------------ | ----------------------- |
| `BreakBlockEvent` | Block is broken |
| `PlaceBlockEvent` | Block is placed |
| `DamageBlockEvent` | Block takes damage |
| `UseBlockEvent` | Block is used/activated |
### Entity Events
| Event | Description |
| ------------------- | ----------------- |
| `EntityEvent` | Entity spawns |
| `EntityRemoveEvent` | Entity is removed |
### Server Events
| Event | Description |
| ---------------------- | -------------------------- |
| `BootEvent` | Server starts |
| `ShutdownEvent` | Server stops |
| `PrepareUniverseEvent` | Universe is being prepared |
## Full Example
```typescript
import {
type PlayerConnectEvent,
type PlayerDisconnectEvent,
type BreakBlockEvent,
EventListener,
Colors
} from "@hytalejs.com/core";
class EventsPlugin {
@EventListener("PlayerConnectEvent")
onJoin(event: PlayerConnectEvent) {
logger.info("Player joined: " + event.getPlayerRef().getUsername());
}
@EventListener("PlayerDisconnectEvent")
onLeave(event: PlayerDisconnectEvent) {
logger.info("Player left: " + event.getPlayerRef().getUsername());
}
@EventListener("BreakBlockEvent")
onBreak(event: BreakBlockEvent) {
const block = event.getTargetBlock();
logger.info("Block broken at: " + block.getX() + ", " + block.getY() + ", " + block.getZ());
}
}
```
See the [Events API Reference](/docs/api/events) for all event types and their properties.
# Messages
Create rich, formatted messages with colors and styling.
## Basic Message
```typescript
const msg = Message.raw("Hello, world!");
player.sendMessage(msg);
```
## Colors
Use the `Colors` constant or hex codes:
```typescript
import { Colors } from "@hytalejs.com/core";
Message.raw("Success!").color(Colors.GREEN)
Message.raw("Warning!").color(Colors.YELLOW)
Message.raw("Error!").color(Colors.RED)
Message.raw("Custom").color("#FF5733")
```
### Available Colors
| Color | Hex |
| ---------------- | ------- |
| `Colors.RED` | #FF0000 |
| `Colors.GREEN` | #00FF00 |
| `Colors.BLUE` | #0000FF |
| `Colors.YELLOW` | #FFFF00 |
| `Colors.CYAN` | #00FFFF |
| `Colors.MAGENTA` | #FF00FF |
| `Colors.WHITE` | #FFFFFF |
| `Colors.BLACK` | #000000 |
| `Colors.ORANGE` | #FFA500 |
| `Colors.PINK` | #FFC0CB |
| `Colors.PURPLE` | #800080 |
| `Colors.GOLD` | #FFD700 |
| `Colors.GRAY` | #808080 |
## Styling
```typescript
Message.raw("Bold text").bold(true)
Message.raw("Italic text").italic(true)
Message.raw("Bold and italic").bold(true).italic(true)
```
## Chaining
Build complex messages by chaining methods:
```typescript
const welcome = Message.raw("Welcome!")
.color(Colors.GREEN)
.bold(true);
```
## Combining Messages
Use `Message.empty()` and `insert()` to combine multiple styled parts:
```typescript
const formatted = Message.empty()
.insert(Message.raw("Player").color(Colors.GOLD).bold(true))
.insert(Message.raw(": ").color(Colors.GRAY))
.insert(Message.raw("Hello everyone!").color(Colors.WHITE));
```
## Sending Messages
### To a specific player
```typescript
player.sendMessage(Message.raw("Private message"));
```
### To all players in a world
```typescript
const world = Universe.get().getDefaultWorld();
world.sendMessage(Message.raw("World announcement"));
```
### To all players on the server
```typescript
Universe.get().sendMessage(Message.raw("Server announcement"));
```
## Full Example
```typescript
@EventListener("PlayerChatEvent")
onChat(event: PlayerChatEvent) {
const sender = event.getSender();
const content = event.getContent();
const playerName = Message.raw(sender.getUsername())
.color(Colors.GOLD)
.bold(true);
const separator = Message.raw(": ").color(Colors.GRAY);
const text = Message.raw(content).color(Colors.WHITE);
const formatted = Message.empty()
.insert(playerName)
.insert(separator)
.insert(text);
event.setCancelled(true);
const targets = event.getTargets();
for (let i = 0; i < targets.length; i++) {
targets[i].sendMessage(formatted);
}
}
```
# Particles
Spawn particle systems in the world with customizable properties like position, rotation, scale, and color.
## Spawning a Particle System
```typescript
const position = new Position(100, 64, -50);
const rotation = new Direction(0, 0, 0);
const color = new Color(255, 0, 0); // Red (requires byte conversion)
const packet = new SpawnParticleSystem(
"Fire_ChargeRed",
position,
rotation,
1.0, // Scale
color
);
playerRef.getPacketHandler().write(packet);
```
## Color Conversion
The `Color` class uses Java signed bytes (-128 to 127) internally, but you provide RGB values (0-255). Values above 127 must be converted:
```typescript
// Helper to convert 0-255 RGB to signed bytes
let r = 255, g = 100, b = 50;
// Convert values > 127 to negative bytes
if (r > 127) r = r - 256; // 255 → -1
if (g > 127) g = g - 256; // 100 stays 100
if (b > 127) b = b - 256; // 50 stays 50
const color = new Color(r, g, b);
```
**Common conversions:**
* `255` (bright) → `-1`
* `200` → `-56`
* `128` → `-128`
* `127` and below → no conversion needed
## Listing Available Particles
```typescript
commands.register("particlelist", "List particles", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
const filter = parts.length >= 2 ? parts[1].toLowerCase() : "";
const assetMap = ParticleSystem.getAssetMap();
const map = assetMap.getAssetMap();
const keys = map.keySet();
const iterator = keys.iterator();
let count = 0;
const maxResults = 20;
while (iterator.hasNext() && count < maxResults) {
const key = iterator.next() as string;
if (!filter || key.toLowerCase().includes(filter)) {
ctx.sendMessage(key);
count++;
}
}
const total = assetMap.getAssetCount();
ctx.sendMessage(`Showing ${count} of ${total} particles`);
});
```
## Full Example: Particle Command
```typescript
commands.register("particle", "Spawn a particle system", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
if (parts.length < 2) {
ctx.sendMessage("Usage: /particle [scale] [r] [g] [b]");
return;
}
const particleId = parts[1];
const scale = parts.length >= 3 ? parseFloat(parts[2]) : 1.0;
let r = parts.length >= 4 ? parseInt(parts[3], 10) : 255;
let g = parts.length >= 5 ? parseInt(parts[4], 10) : 255;
let b = parts.length >= 6 ? parseInt(parts[5], 10) : 255;
// Validate inputs
if (isNaN(scale) || scale <= 0) {
ctx.sendMessage("Invalid scale");
return;
}
if (isNaN(r) || r < 0 || r > 255 || isNaN(g) || g < 0 || g > 255 || isNaN(b) || b < 0 || b > 255) {
ctx.sendMessage("Invalid RGB values (0-255)");
return;
}
// Convert RGB to signed bytes
if (r > 127) r = r - 256;
if (g > 127) g = g - 256;
if (b > 127) b = b - 256;
// Find the player
const senderName = ctx.getSenderName();
const players = Universe.get().getPlayers();
let playerRef = null;
for (let i = 0; i < players.length; i++) {
if (players[i].getUsername() === senderName) {
playerRef = players[i];
break;
}
}
if (!playerRef) {
ctx.sendMessage("Could not find player");
return;
}
// Get player position and spawn particles above them
const transform = playerRef.getTransform();
const pos = transform.getPosition();
const rot = transform.getRotation();
const position = new Position(pos.getX(), pos.getY() + 2.0, pos.getZ());
const direction = new Direction(rot.getX(), rot.getY(), rot.getZ());
const color = new Color(r, g, b);
const packet = new SpawnParticleSystem(particleId, position, direction, scale, color);
playerRef.getPacketHandler().write(packet);
ctx.sendMessage(`Spawned particle: ${particleId}`);
});
```
## Example Usage
```bash
# Spawn default white fire charge
/particle Fire_ChargeRed
# Large red particles (scale 2.0, RGB 255,0,0)
/particle Fire_ChargeRed 2.0 255 0 0
# Blue fire charge (scale 1.5, RGB 0,100,255)
/particle Fire_ChargeRed 1.5 0 100 255
# Purple fire charge (RGB 200,0,200)
/particle Fire_ChargeRed 1.0 200 0 200
```
## Particle Properties
### Position
* **X**: East (+) / West (-)
* **Y**: Up (+) / Down (-)
* **Z**: South (+) / North (-)
### Scale
* Multiplier for particle size
* Typical range: `0.5` to `5.0`
* `1.0` = default size
### Color Tinting
* RGB values: `0` to `255`
* Automatically converted to signed bytes for Java
* Tints the particle texture
## Broadcasting to All Players
To show particles to everyone:
```typescript
const players = Universe.get().getPlayers();
for (let i = 0; i < players.length; i++) {
players[i].getPacketHandler().write(packet);
}
```
# Scheduler
Execute code after a delay or on a repeating schedule.
## Run After Delay
Execute code once after a delay:
```typescript
scheduler.runLater(() => {
logger.info("This runs after 5 seconds");
}, 5000);
```
## Repeating Task
Run code repeatedly at a fixed interval:
```typescript
scheduler.runRepeating(() => {
logger.info("This runs every 10 seconds");
}, 10000, 10000);
```
Parameters: `callback`, `initialDelay`, `period` (all in milliseconds)
## Repeating with Initial Delay
```typescript
scheduler.runRepeatingWithDelay(() => {
logger.info("Starts after 5s, then every 10s");
}, 5000, 10000);
```
## Cancelling Tasks
Store the task reference to cancel it later:
```typescript
const task = scheduler.runRepeating(() => {
logger.info("Running...");
}, 1000, 1000);
scheduler.runLater(() => {
task.cancel();
logger.info("Task cancelled");
}, 10000);
```
## Task Methods
| Method | Description |
| --------------- | ------------------ |
| `cancel()` | Stop the task |
| `isCancelled()` | Check if cancelled |
| `isDone()` | Check if completed |
## Full Example: Auto Broadcast
```typescript
const messages = [
"Remember to stay hydrated!",
"Join our Discord for updates!",
"Try the /items command!",
"Found a bug? Report it!",
];
scheduler.runRepeating(() => {
if (Universe.get().getPlayerCount() > 0) {
const index = Math.floor(Math.random() * messages.length);
const msg = Message.raw("[Auto] " + messages[index])
.color(Colors.CYAN)
.italic(true);
Universe.get().sendMessage(msg);
}
}, 15000, 15000);
```
## Full Example: Countdown
```typescript
commands.register("countdown", "Start a countdown", (ctx) => {
let count = 5;
const task = scheduler.runRepeating(() => {
if (count > 0) {
Universe.get().sendMessage(
Message.raw(count + "...").color(Colors.YELLOW)
);
count--;
} else {
Universe.get().sendMessage(
Message.raw("Go!").color(Colors.GREEN).bold(true)
);
task.cancel();
}
}, 0, 1000);
});
```
# Sounds
Play sound effects to players using the sound system. HytaleJS supports both 2D (non-positional) and 3D (positional) sound playback.
## Playing a 2D Sound
2D sounds are played without spatial audio - all players hear them at the same volume regardless of position.
```typescript
const assetMap = SoundEvent.getAssetMap();
const soundIndex = assetMap.getIndex("SFX_Global_Weather_Thunder");
const packet = new PlaySoundEvent2D(
soundIndex,
SoundCategory.SFX,
1.0, // Volume
1.0 // Pitch
);
playerRef.getPacketHandler().write(packet);
```
## Playing a 3D Sound
3D sounds are played at a specific world position with spatial audio. Players hear the sound with volume and panning based on their distance and direction from the source.
```typescript
const assetMap = SoundEvent.getAssetMap();
const soundIndex = assetMap.getIndex("SFX_Global_Weather_Thunder");
// Create a position in world space
const position = new Position(100, 64, -50);
const packet = new PlaySoundEvent3D(
soundIndex,
SoundCategory.SFX,
position,
1.0, // Volume modifier
1.0 // Pitch modifier
);
playerRef.getPacketHandler().write(packet);
```
## Playing Sound Attached to Entity
Play a sound that follows an entity as it moves:
```typescript
const soundIndex = SoundEvent.getAssetMap().getIndex("SFX_Global_Weather_Thunder");
const packet = new PlaySoundEventEntity(
soundIndex,
entityNetworkId, // Entity's network ID
1.0, // Volume
1.0 // Pitch
);
playerRef.getPacketHandler().write(packet);
```
## Sound Categories
| Category | Use |
| ----------------------- | -------------------- |
| `SoundCategory.Music` | Background music |
| `SoundCategory.Ambient` | Environmental sounds |
| `SoundCategory.SFX` | Sound effects |
| `SoundCategory.UI` | Interface sounds |
## Volume and Pitch
* **Volume**: `0.0` to `2.0` (1.0 = normal)
* **Pitch**: `0.5` to `2.0` (1.0 = normal)
```typescript
new PlaySoundEvent2D(soundIndex, SoundCategory.SFX, 0.5, 1.5)
```
## Listing Available Sounds
```typescript
commands.register("soundlist", "List sounds", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
const filter = parts.length >= 2 ? parts[1].toLowerCase() : "";
const assetMap = SoundEvent.getAssetMap();
const map = assetMap.getAssetMap();
const keys = map.keySet();
const iterator = keys.iterator();
let count = 0;
while (iterator.hasNext() && count < 20) {
const key = iterator.next() as string;
if (!filter || key.toLowerCase().includes(filter)) {
ctx.sendMessage(key);
count++;
}
}
ctx.sendMessage("Showing " + count + " of " + assetMap.getAssetCount() + " sounds");
});
```
## Full Example: Playsound Command
```typescript
commands.register("playsound", "Play a sound", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
if (parts.length < 2) {
ctx.sendMessage("Usage: /playsound [volume] [pitch]");
return;
}
const soundId = parts[1];
const volume = parts.length >= 3 ? parseFloat(parts[2]) : 1.0;
const pitch = parts.length >= 4 ? parseFloat(parts[3]) : 1.0;
if (isNaN(volume) || volume < 0 || volume > 2) {
ctx.sendMessage("Invalid volume (0-2)");
return;
}
if (isNaN(pitch) || pitch < 0.5 || pitch > 2) {
ctx.sendMessage("Invalid pitch (0.5-2)");
return;
}
const assetMap = SoundEvent.getAssetMap();
const soundIndex = assetMap.getIndex(soundId);
if (soundIndex < 0) {
ctx.sendMessage("Sound not found: " + soundId);
return;
}
const senderName = ctx.getSenderName();
const players = Universe.get().getPlayers();
let playerRef = null;
for (let i = 0; i < players.length; i++) {
if (players[i].getUsername() === senderName) {
playerRef = players[i];
break;
}
}
if (!playerRef) {
ctx.sendMessage("Could not find player");
return;
}
const packet = new PlaySoundEvent2D(soundIndex, SoundCategory.SFX, volume, pitch);
playerRef.getPacketHandler().write(packet);
ctx.sendMessage("Playing: " + soundId);
});
```
## Full Example: 3D Playsound Command
```typescript
commands.register("playsound3d", "Play a sound at your position", (ctx) => {
const input = ctx.getInput();
const parts = input.split(" ");
if (parts.length < 2) {
ctx.sendMessage("Usage: /playsound3d [volume] [pitch]");
return;
}
const soundId = parts[1];
const volume = parts.length >= 3 ? parseFloat(parts[2]) : 1.0;
const pitch = parts.length >= 4 ? parseFloat(parts[3]) : 1.0;
const assetMap = SoundEvent.getAssetMap();
const soundIndex = assetMap.getIndex(soundId);
if (soundIndex < 0) {
ctx.sendMessage("Sound not found: " + soundId);
return;
}
const senderName = ctx.getSenderName();
const players = Universe.get().getPlayers();
let playerRef = null;
for (let i = 0; i < players.length; i++) {
if (players[i].getUsername() === senderName) {
playerRef = players[i];
break;
}
}
if (!playerRef) {
ctx.sendMessage("Could not find player");
return;
}
// Get player's position for 3D sound
const transform = playerRef.getTransform();
const pos = transform.getPosition();
const position = new Position(pos.getX(), pos.getY(), pos.getZ());
const packet = new PlaySoundEvent3D(soundIndex, SoundCategory.SFX, position, volume, pitch);
playerRef.getPacketHandler().write(packet);
ctx.sendMessage("Playing 3D sound: " + soundId);
});
```
# UIBuilder
The `UIBuilder` provides a fluent TypeScript API for generating Hytale UI DSL files programmatically. Instead of writing raw DSL strings, you can use type-safe builder methods with full IDE autocompletion.
## Quick Start
```typescript
import { UIBuilder, group, label, slot } from "@hytalejs.com/core";
const ui = new UIBuilder()
.import("C", "../Common.ui")
.root(
group({ id: "MyPanel" })
.anchor({ width: 300, height: 200 })
.background({ color: "#1a2530", opacity: 0.9 })
.padding({ full: 16 })
.children(
label({ id: "Title", text: "Hello World" })
.style({ fontSize: 24, textColor: "#FFFFFF", renderBold: true })
)
)
.build();
```
This generates:
```
$C = "../Common.ui";
Group #MyPanel {
Anchor: (Width: 300, Height: 200);
Background: (Color: #1a2530(0.9));
Padding: (Full: 16);
Label #Title {
Text: "Hello World";
Style: (FontSize: 24, TextColor: #FFFFFF, RenderBold: true);
}
}
```
## UIBuilder Class
The main class for constructing UI documents.
### Methods
| Method | Description |
| --------------------- | -------------------------- |
| `import(alias, path)` | Add an import statement |
| `root(element)` | Set single root element |
| `roots(...elements)` | Set multiple root elements |
| `build()` | Generate the DSL string |
```typescript
const ui = new UIBuilder()
.import("C", "../Common.ui")
.import("Sounds", "../Sounds.ui")
.roots(
group().template("$C.@PageOverlay").children(...),
group().template("$C.@BackButton")
)
.build();
```
## Element Functions
Create UI elements using factory functions:
### Basic Elements
| Function | Description |
| --------------------- | ------------------ |
| `group(props?)` | Container element |
| `label(props?)` | Text display |
| `textButton(props?)` | Button with text |
| `button(props?)` | Icon button |
| `textField(props?)` | Text input |
| `sprite(props?)` | Image display |
| `checkbox(props?)` | Boolean toggle |
| `slider(props?)` | Numeric slider |
| `progressBar(props?)` | Progress indicator |
### Special Elements
| Function | Description |
| --------------------- | ------------------------------- |
| `slot(id)` | Template slot reference (`#id`) |
| `backButton()` | Back button element |
| `timerLabel(props?)` | Timer display |
| `dropdownBox(props?)` | Dropdown selector |
| `itemSlot(props?)` | Inventory slot |
| `itemGrid(props?)` | Inventory grid |
### Element Props
All element functions accept optional props:
```typescript
interface ElementProps {
id?: string;
text?: string;
placeholder?: string;
texturePath?: string;
value?: boolean | number;
min?: number;
max?: number;
}
```
Examples:
```typescript
group({ id: "Container" })
label({ id: "Title", text: "Welcome" })
slider({ id: "Volume", min: 0, max: 100, value: 50 })
checkbox({ id: "Enabled", value: true })
textField({ id: "Name", placeholder: "Enter name..." })
```
## Element Methods
All elements support these chainable methods:
### Layout & Positioning
```typescript
element
.anchor({ width: 200, height: 100, top: 10, right: 20 })
.layoutMode("Top")
.padding({ full: 16 })
.flexWeight(1)
.visible(true)
```
#### Anchor Props
| Property | Description |
| -------------------------------- | --------------------------------------- |
| `width`, `height` | Fixed dimensions |
| `top`, `bottom`, `left`, `right` | Position offsets |
| `full` | Shorthand for all sides |
| `horizontal`, `vertical` | Shorthand for left/right and top/bottom |
| `minWidth`, `maxWidth` | Size constraints |
#### Layout Modes
| Mode | Description |
| ---------------- | ---------------------------- |
| `"Top"` | Stack children from top |
| `"Left"` | Stack children from left |
| `"Center"` | Center children horizontally |
| `"Middle"` | Center children vertically |
| `"CenterMiddle"` | Center both axes |
| `"Full"` | Children fill container |
### Styling
```typescript
element
.background({ color: "#1a2530", opacity: 0.85 })
.style({
fontSize: 16,
textColor: "#FFFFFF",
renderBold: true,
horizontalAlignment: "Center"
})
```
#### Background Props
| Property | Description |
| ------------- | ----------------------------- |
| `color` | Hex color (e.g., `"#1a2530"`) |
| `opacity` | 0-1 transparency |
| `texturePath` | Image path |
| `border` | 9-patch border size |
#### Style Props
| Property | Description |
| --------------------- | ------------------------------- |
| `fontSize` | Font size in pixels |
| `textColor` | Text color |
| `renderBold` | Bold text |
| `renderUppercase` | Uppercase transform |
| `horizontalAlignment` | `"Start"`, `"Center"`, `"End"` |
| `verticalAlignment` | `"Top"`, `"Center"`, `"Bottom"` |
| `wrap` | Text wrapping |
### Content
```typescript
label({ id: "Title" })
.text("Hello World")
textField({ id: "Input" })
.raw("PlaceholderText", '"Enter text..."')
```
### Templates
```typescript
group()
.template("$C.@PageOverlay")
.param("Text", '"My Title"')
.param("Sounds", "$Sounds.@ButtonsCancel")
```
### Children
```typescript
group({ id: "Container" })
.children(
label({ text: "First" }),
label({ text: "Second" }),
group({ id: "Nested" }).children(...)
)
```
## Template Slots
When using templates like `@DecoratedContainer`, use `slot()` for slot references:
```typescript
group()
.template("$C.@DecoratedContainer")
.anchor({ width: 400 })
.children(
slot("Title").children(
group().template("$C.@Title").param("Text", '"My Page"')
),
slot("Content")
.padding({ vertical: 32, horizontal: 45 })
.children(
label({ id: "message", text: "Content here" })
)
)
```
This generates:
```
$C.@DecoratedContainer {
Anchor: (Width: 400);
#Title {
$C.@Title {
@Text = "My Page";
}
}
#Content {
Padding: (Vertical: 32, Horizontal: 45);
Label #message {
Text: "Content here";
}
}
}
```
Note: Use `slot("Title")` instead of `group({ id: "Title" })` for template slots. Using `group` would generate `Group #Title` instead of `#Title`, which breaks the template slot binding.
## Complete Example: Custom Page
```typescript
import { UIBuilder, group, label, slot } from "@hytalejs.com/core";
const EXAMPLE_PAGE = new UIBuilder()
.import("C", "../Common.ui")
.import("Sounds", "../Sounds.ui")
.roots(
group()
.template("$C.@PageOverlay")
.layoutMode("Middle")
.children(
group()
.template("$C.@DecoratedContainer")
.anchor({ width: 400 })
.children(
slot("Title").children(
group().template("$C.@Title").param("Text", '"Example Page"')
),
slot("Content")
.padding({ vertical: 32, horizontal: 45 })
.children(
label({ id: "title" })
.style({ renderBold: true, textColor: "#FFFFFF" })
.text("Welcome!"),
label({ id: "message" })
.anchor({ top: 20 })
.style({ textColor: "#94a7bb" })
.text("This is an example page."),
group()
.layoutMode("Center")
.anchor({ top: 30 })
.children(
group({ id: "CloseButton" })
.template("$C.@TextButton")
.param("Sounds", "$Sounds.@ButtonsCancel")
.text("Close")
.flexWeight(1)
)
)
)
),
group().template("$C.@BackButton")
)
.build();
const asset = new ByteArrayCommonAsset("UI/Custom/Pages/Example.ui", EXAMPLE_PAGE);
CommonAssetModule.get().addCommonAsset("myplugin", asset);
```
## Complete Example: HUD Scoreboard
```typescript
import { UIBuilder, group, label } from "@hytalejs.com/core";
const SCOREBOARD_HUD = new UIBuilder()
.import("C", "../Common.ui")
.root(
group({ id: "Scoreboard" })
.anchor({ right: 20, top: 100, width: 200, height: 170 })
.layoutMode("Top")
.background({ color: "#0a0e14", opacity: 0.85 })
.padding({ full: 12 })
.children(
label({ id: "Title" })
.style({
fontSize: 24,
textColor: "#f5c542",
renderBold: true,
renderUppercase: true,
horizontalAlignment: "Center"
})
.anchor({ height: 30 })
.text("My Server"),
group({ id: "Separator" })
.anchor({ height: 1, top: 8 })
.background({ color: "#2b3542" }),
group({ id: "PlayerRow" })
.layoutMode("Left")
.anchor({ height: 22, top: 10 })
.children(
label({ id: "PlayerLabel" })
.style({ fontSize: 13, textColor: "#8a9aab", verticalAlignment: "Center" })
.anchor({ width: 80 })
.text("Player:"),
label({ id: "PlayerValue" })
.style({ fontSize: 13, textColor: "#ffffff", verticalAlignment: "Center", renderBold: true })
.flexWeight(1)
.text("Unknown")
),
group({ id: "OnlineRow" })
.layoutMode("Left")
.anchor({ height: 22, top: 4 })
.children(
label({ id: "OnlineLabel" })
.style({ fontSize: 13, textColor: "#8a9aab", verticalAlignment: "Center" })
.anchor({ width: 80 })
.text("Online:"),
label({ id: "OnlineValue" })
.style({ fontSize: 13, textColor: "#5cb85c", verticalAlignment: "Center", renderBold: true })
.flexWeight(1)
.text("0")
)
)
)
.build();
```
# UI Elements
This document lists all UI element types available in Hytale's UI DSL, derived from analysis of the game's asset files and decompiled code.
## Layout Elements
### Group
The fundamental container element. Groups can contain other elements and control their layout.
```
Group #MyContainer {
Anchor: (Width: 400, Height: 300);
LayoutMode: Top;
Padding: (Horizontal: 16, Vertical: 8);
Background: (Color: #1a2530);
Visible: true;
// Child elements
}
```
**Common Properties:**
| Property | Type | Description |
| ------------ | ---------------- | ------------------------- |
| `Anchor` | Anchor | Positioning and sizing |
| `LayoutMode` | Enum | How children are arranged |
| `Padding` | Padding | Internal spacing |
| `Background` | PatchStyle/Color | Background appearance |
| `Visible` | Boolean | Visibility state |
| `FlexWeight` | Number | Flex layout weight |
## Text Elements
### Label
Displays text content.
```
Label #Title {
Text: "Hello World";
Style: (
FontSize: 24,
TextColor: #FFFFFF,
RenderBold: true,
HorizontalAlignment: Center
);
Anchor: (Height: 40);
}
```
**Properties:**
| Property | Type | Description |
| ----------------- | ---------- | ---------------------------------------- |
| `Text` | String | Text content (can be localized with `%`) |
| `Style` | LabelStyle | Text styling |
| `Anchor` | Anchor | Positioning |
| `MaskTexturePath` | String | Texture mask for effects |
### TimerLabel
Specialized label for displaying time values.
```
TimerLabel #Countdown {
Style: (FontSize: 20, TextColor: #FFFFFF);
Anchor: (Height: 30);
}
```
## Button Elements
### Button
A basic clickable button without text.
```
Button #IconButton {
Anchor: (Width: 44, Height: 44);
Style: (
Default: (Background: "icon.png"),
Hovered: (Background: "icon_hover.png"),
Pressed: (Background: "icon_pressed.png"),
Disabled: (Background: "icon_disabled.png"),
Sounds: $Sounds.@ButtonsLight
);
}
```
**Properties:**
| Property | Type | Description |
| --------- | ----------- | ------------------------ |
| `Style` | ButtonStyle | Button appearance states |
| `Anchor` | Anchor | Positioning and sizing |
| `Visible` | Boolean | Visibility |
| `Enabled` | Boolean | Interactive state |
### TextButton
A button with text label.
```
TextButton #SubmitButton {
Anchor: (Width: 200, Height: 44);
Text: "Submit";
Style: (
Default: (Background: ..., LabelStyle: ...),
Hovered: (Background: ..., LabelStyle: ...),
Pressed: (Background: ..., LabelStyle: ...),
Disabled: (Background: ..., LabelStyle: ...),
Sounds: $Sounds.@ButtonsLight
);
Padding: (Horizontal: 24);
}
```
**Properties:**
| Property | Type | Description |
| --------- | --------------- | ----------------- |
| `Text` | String | Button label |
| `Style` | TextButtonStyle | Appearance states |
| `Padding` | Padding | Internal spacing |
### BackButton
Standard back/close navigation button. Usually placed at bottom-left of pages.
```
BackButton {}
```
### ItemSlotButton
A button styled as an item slot, useful for inventory-like interfaces.
```
ItemSlotButton #TradeButton {
Anchor: (Full: 0);
Style: (
Default: (Background: #252f3a),
Hovered: (Background: #c9a050),
Pressed: (Background: #a08040),
Disabled: (Background: #1a1e24),
Sounds: $C.@ButtonSounds
);
// Can contain child elements
}
```
## Input Elements
### TextField
Single-line text input.
```
TextField #NameInput {
Anchor: (Height: 35, Width: 300);
Style: (FontSize: 16);
PlaceholderText: "Enter name...";
PlaceholderStyle: (TextColor: #666666);
MaxLength: 100;
Padding: (Horizontal: 12);
}
```
**Properties:**
| Property | Type | Description |
| ------------------ | --------------- | ------------------- |
| `Style` | InputFieldStyle | Text styling |
| `PlaceholderText` | String | Placeholder text |
| `PlaceholderStyle` | LabelStyle | Placeholder styling |
| `MaxLength` | Integer | Maximum characters |
| `Background` | PatchStyle | Input background |
| `Value` | String | Current value |
### MultilineTextField
Multi-line text input area.
```
MultilineTextField #Description {
Anchor: (Height: 100);
Style: $C.@DefaultInputFieldStyle;
PlaceholderStyle: $C.@DefaultInputFieldPlaceholderStyle;
Background: $C.@InputBoxBackground;
AutoGrow: false;
Padding: (Horizontal: 10, Vertical: 8);
}
```
### CompactTextField
A text field that expands when focused.
```
CompactTextField #SearchInput {
Anchor: (Height: 30);
CollapsedWidth: 34;
ExpandedWidth: 200;
PlaceholderText: %server.customUI.searchPlaceholder;
Style: (FontSize: 16);
PlaceholderStyle: (TextColor: #3d5a85, RenderUppercase: true, FontSize: 14);
Padding: (Horizontal: 12, Left: 34);
Decoration: (
Default: (
Icon: (Texture: "Common/SearchIcon.png", Width: 16, Height: 16, Offset: 9),
ClearButtonStyle: @ClearButtonStyle
)
);
}
```
### NumberField
Numeric input with optional constraints.
```
NumberField #Amount {
Anchor: (Width: 60);
Format: (
MaxDecimalPlaces: 0,
Step: 1,
Min: 0,
Max: 100
);
}
```
### CheckBox
Boolean toggle input.
```
CheckBox #EnableOption {
Anchor: (Width: 26, Height: 26);
Value: true;
Style: $C.@DefaultCheckBoxStyle;
}
```
### Slider
Range input slider.
```
Slider #VolumeSlider {
Anchor: (Width: 200, Height: 20);
Style: $C.@DefaultSliderStyle;
Value: 0.5;
Min: 0;
Max: 1;
}
```
### FloatSlider
Slider specifically for floating-point values.
```
FloatSlider #OpacitySlider {
Anchor: (Width: 200);
Min: 0.0;
Max: 1.0;
Step: 0.01;
}
```
### DropdownBox
Selection dropdown.
```
DropdownBox #CategorySelect {
Anchor: (Width: 330, Height: 32);
Style: $C.@DefaultDropdownBoxStyle;
}
```
Entries are typically populated from server code using `UICommandBuilder`.
### ColorPicker
Color selection input.
```
ColorPicker #TintColor {
Anchor: (Width: 200, Height: 32);
Style: $C.@DefaultColorPickerStyle;
}
```
### ColorPickerDropdownBox
Dropdown with color picker.
```
ColorPickerDropdownBox #ColorSelect {
Style: $C.@DefaultColorPickerDropdownBoxStyle;
}
```
## Display Elements
### Sprite
Displays an image, optionally animated.
```
Sprite #LoadingSpinner {
Anchor: (Width: 32, Height: 32);
TexturePath: "Common/Spinner.png";
Frame: (Width: 32, Height: 32, PerRow: 8, Count: 72);
FramesPerSecond: 30;
}
```
**Properties:**
| Property | Type | Description |
| ----------------- | ------ | -------------------- |
| `TexturePath` | String | Image path |
| `Frame` | Frame | Animation frame info |
| `FramesPerSecond` | Number | Animation speed |
### AssetImage
Displays an asset-based image.
```
AssetImage #Preview {
Anchor: (Width: 128, Height: 128);
AssetPath: "textures/items/sword.png";
}
```
### ItemSlot
Displays an item with optional quality background.
```
ItemSlot #OutputSlot {
Anchor: (Full: 0);
ShowQualityBackground: true;
}
```
### ItemIcon
Displays just an item's icon.
```
ItemIcon #WeaponIcon {
Anchor: (Width: 48, Height: 48);
}
```
### ItemGrid
Grid of item slots.
```
ItemGrid #InventoryGrid {
Anchor: (Width: 400, Height: 200);
Columns: 8;
Rows: 4;
SlotSize: 48;
}
```
### ProgressBar
Progress indicator.
```
ProgressBar #LoadingBar {
Anchor: (Width: 300, Height: 20);
Style: (
Background: "ProgressBar.png",
Fill: "ProgressBarFill.png"
);
Value: 0.5;
}
```
## Common Properties
These properties are available on most elements:
| Property | Type | Description |
| -------------- | ----------------------- | --------------------- |
| `Anchor` | Anchor | Position and size |
| `Visible` | Boolean | Show/hide element |
| `Padding` | Padding | Internal spacing |
| `Background` | PatchStyle/String/Color | Background appearance |
| `LayoutMode` | Enum | Child element layout |
| `FlexWeight` | Number | Flex layout weight |
| `Tooltip` | String | Hover tooltip text |
| `TooltipStyle` | TextTooltipStyle | Tooltip appearance |
## Using Common.ui Templates
The `Common.ui` file provides pre-styled templates for common UI patterns:
```
$C = "../Common.ui";
// Pre-styled containers
$C.@PageOverlay { }
$C.@Container { }
$C.@DecoratedContainer { }
// Pre-styled buttons
$C.@TextButton #Button { @Text = "Click"; }
$C.@SecondaryTextButton #Alt { @Text = "Cancel"; }
$C.@CancelTextButton #Delete { @Text = "Delete"; }
// Pre-styled inputs
$C.@TextField #Input { }
$C.@NumberField #Amount { }
$C.@CheckBox #Toggle { }
$C.@DropdownBox #Select { }
// Utility elements
$C.@DefaultSpinner {}
$C.@BackButton {}
$C.@ContentSeparator {}
```
# UI Events
The Custom UI system supports 24 different event types that allow you to respond to user interactions. Events are bound using the `UIEventBuilder` and handled in your event callback.
## Event Binding
Events are registered in your build callback using `UIEventBuilder`:
```typescript
const page = new ScriptCustomUIPage(
playerRef,
CustomPageLifetime.CanDismiss,
(ref, cmd, events, store) => {
cmd.append("Pages/MyPage.ui");
events.addEventBinding(
CustomUIEventBindingType.Activating,
"#MyButton",
EventData.of("action", "clicked"),
false
);
},
(ref, store, rawData) => {
const data = JSON.parse(rawData);
if (data.action === "clicked") {
console.log("Button was clicked!");
}
}
);
```
### addEventBinding Parameters
| Parameter | Type | Description |
| ----------------- | ------------------------ | ---------------------------------------- |
| `type` | CustomUIEventBindingType | The event type to listen for |
| `selector` | String | CSS-like selector for the target element |
| `data` | EventData | Data to include in the event payload |
| `stopPropagation` | Boolean | Whether to stop event bubbling |
## Event Types
### Click Events
#### Activating
Triggered when an element is clicked/activated (left mouse button).
```typescript
events.addEventBinding(
CustomUIEventBindingType.Activating,
"#SubmitButton",
EventData.of("action", "submit"),
false
);
```
**Used with:** Button, TextButton, ItemSlotButton, CheckBox, and other interactive elements.
#### RightClicking
Triggered on right mouse button click.
```typescript
events.addEventBinding(
CustomUIEventBindingType.RightClicking,
"#ItemSlot",
EventData.of("action", "context-menu"),
false
);
```
**Used with:** Elements that need context menu or alternate action support.
#### DoubleClicking
Triggered on double-click.
```typescript
events.addEventBinding(
CustomUIEventBindingType.DoubleClicking,
"#ListItem",
EventData.of("action", "open"),
false
);
```
### Mouse Events
#### MouseEntered
Triggered when the mouse cursor enters an element's bounds.
```typescript
events.addEventBinding(
CustomUIEventBindingType.MouseEntered,
"#HoverZone",
EventData.of("action", "hover-start"),
false
);
```
#### MouseExited
Triggered when the mouse cursor leaves an element's bounds.
```typescript
events.addEventBinding(
CustomUIEventBindingType.MouseExited,
"#HoverZone",
EventData.of("action", "hover-end"),
false
);
```
#### MouseButtonReleased
Triggered when a mouse button is released over an element.
```typescript
events.addEventBinding(
CustomUIEventBindingType.MouseButtonReleased,
"#DragTarget",
EventData.of("action", "drop"),
false
);
```
### Value Events
#### ValueChanged
Triggered when an input element's value changes.
```typescript
events.addEventBinding(
CustomUIEventBindingType.ValueChanged,
"#NameInput",
EventData.of("field", "name"),
false
);
```
**Used with:** TextField, NumberField, CheckBox, Slider, DropdownBox, ColorPicker.
The event payload includes the new value:
```typescript
(ref, store, rawData) => {
const data = JSON.parse(rawData);
console.log("Field:", data.field, "New value:", data.value);
}
```
#### Validating
Triggered to validate input before acceptance.
```typescript
events.addEventBinding(
CustomUIEventBindingType.Validating,
"#EmailInput",
EventData.of("validate", "email"),
false
);
```
### Focus Events
#### FocusGained
Triggered when an element receives focus.
```typescript
events.addEventBinding(
CustomUIEventBindingType.FocusGained,
"#SearchInput",
EventData.of("action", "focus"),
false
);
```
#### FocusLost
Triggered when an element loses focus.
```typescript
events.addEventBinding(
CustomUIEventBindingType.FocusLost,
"#SearchInput",
EventData.of("action", "blur"),
false
);
```
### Keyboard Events
#### KeyDown
Triggered when a key is pressed while an element has focus.
```typescript
events.addEventBinding(
CustomUIEventBindingType.KeyDown,
"#ChatInput",
EventData.of("action", "keypress"),
false
);
```
### Navigation Events
#### Dismissing
Triggered when the UI page is being closed/dismissed.
```typescript
events.addEventBinding(
CustomUIEventBindingType.Dismissing,
"#Page",
EventData.of("action", "close"),
false
);
```
#### SelectedTabChanged
Triggered when a tab navigation selection changes.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SelectedTabChanged,
"#TabNav",
EventData.of("action", "tab-change"),
false
);
```
### Reordering Events
#### ElementReordered
Triggered when an element is reordered (in sortable lists).
```typescript
events.addEventBinding(
CustomUIEventBindingType.ElementReordered,
"#SortableList",
EventData.of("action", "reorder"),
false
);
```
### Slot Events
These events are specific to item slot interactions in inventory-like interfaces.
#### SlotClicking
Triggered when clicking on an item slot.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotClicking,
"#InventorySlot",
EventData.of("slot", "inventory", "index", "0"),
false
);
```
#### SlotDoubleClicking
Triggered on double-clicking an item slot.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotDoubleClicking,
"#InventorySlot",
EventData.of("action", "use-item"),
false
);
```
#### SlotMouseEntered
Triggered when hovering over an item slot.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotMouseEntered,
"#InventorySlot",
EventData.of("action", "show-tooltip"),
false
);
```
#### SlotMouseExited
Triggered when leaving an item slot hover.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotMouseExited,
"#InventorySlot",
EventData.of("action", "hide-tooltip"),
false
);
```
### Drag Events
#### DragCancelled
Triggered when a drag operation is cancelled.
```typescript
events.addEventBinding(
CustomUIEventBindingType.DragCancelled,
"#DraggableItem",
EventData.of("action", "drag-cancel"),
false
);
```
#### Dropped
Triggered when a dragged item is dropped.
```typescript
events.addEventBinding(
CustomUIEventBindingType.Dropped,
"#DropZone",
EventData.of("action", "item-dropped"),
false
);
```
#### SlotMouseDragCompleted
Triggered when a slot drag operation completes.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotMouseDragCompleted,
"#InventorySlot",
EventData.of("action", "drag-complete"),
false
);
```
#### SlotMouseDragExited
Triggered when dragging exits a slot area.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotMouseDragExited,
"#InventorySlot",
EventData.of("action", "drag-exit"),
false
);
```
#### SlotClickReleaseWhileDragging
Triggered when releasing click while dragging over a slot.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotClickReleaseWhileDragging,
"#InventorySlot",
EventData.of("action", "drag-drop"),
false
);
```
#### SlotClickPressWhileDragging
Triggered when clicking while dragging over a slot.
```typescript
events.addEventBinding(
CustomUIEventBindingType.SlotClickPressWhileDragging,
"#InventorySlot",
EventData.of("action", "drag-swap"),
false
);
```
## EventData
`EventData` is used to attach custom data to events. The data is serialized to JSON and passed to your event handler.
### Creating EventData
```typescript
EventData.of("key1", "value1", "key2", "value2");
EventData.of(
"action", "submit",
"formId", "registration",
"timestamp", Date.now().toString()
);
```
### Accessing Event Data
In your event handler, parse the raw data string:
```typescript
(ref, store, rawData) => {
const data = JSON.parse(rawData);
console.log(data.action);
console.log(data.formId);
if (data.value !== undefined) {
console.log("Input value:", data.value);
}
}
```
## Complete Example
```typescript
const page = new ScriptCustomUIPage(
playerRef,
CustomPageLifetime.CanDismiss,
(ref, cmd, events, store) => {
cmd.append("Pages/SettingsPage.ui");
events.addEventBinding(
CustomUIEventBindingType.Activating,
"#SaveButton",
EventData.of("action", "save"),
false
);
events.addEventBinding(
CustomUIEventBindingType.Activating,
"#CancelButton",
EventData.of("action", "cancel"),
false
);
events.addEventBinding(
CustomUIEventBindingType.ValueChanged,
"#VolumeSlider",
EventData.of("setting", "volume"),
false
);
events.addEventBinding(
CustomUIEventBindingType.ValueChanged,
"#MuteCheckbox",
EventData.of("setting", "mute"),
false
);
events.addEventBinding(
CustomUIEventBindingType.SelectedTabChanged,
"#CategoryTabs",
EventData.of("action", "tab-change"),
false
);
},
(ref, store, rawData) => {
const data = JSON.parse(rawData);
switch (data.action) {
case "save":
saveSettings();
page.triggerClose();
break;
case "cancel":
page.triggerClose();
break;
case "tab-change":
loadTabContent(data.tabIndex);
break;
}
if (data.setting) {
updateSetting(data.setting, data.value);
}
}
);
```
## Event Summary Table
| Event Type | Description | Common Elements |
| ------------------------------- | ---------------------- | ---------------------------- |
| `Activating` | Left click/activate | Button, TextButton, CheckBox |
| `RightClicking` | Right click | Any interactive element |
| `DoubleClicking` | Double click | List items, slots |
| `MouseEntered` | Hover start | Any element |
| `MouseExited` | Hover end | Any element |
| `MouseButtonReleased` | Mouse up | Drag targets |
| `ValueChanged` | Input value change | TextField, Slider, CheckBox |
| `Validating` | Input validation | TextField, NumberField |
| `FocusGained` | Element focused | Input elements |
| `FocusLost` | Element unfocused | Input elements |
| `KeyDown` | Key pressed | Input elements |
| `Dismissing` | Page closing | Page root |
| `SelectedTabChanged` | Tab selection | Tab navigation |
| `ElementReordered` | Element reordered | Sortable lists |
| `SlotClicking` | Slot clicked | ItemSlot, ItemGrid |
| `SlotDoubleClicking` | Slot double-clicked | ItemSlot, ItemGrid |
| `SlotMouseEntered` | Slot hover start | ItemSlot, ItemGrid |
| `SlotMouseExited` | Slot hover end | ItemSlot, ItemGrid |
| `DragCancelled` | Drag cancelled | Draggable elements |
| `Dropped` | Item dropped | Drop zones |
| `SlotMouseDragCompleted` | Slot drag complete | ItemSlot |
| `SlotMouseDragExited` | Drag exits slot | ItemSlot |
| `SlotClickReleaseWhileDragging` | Release while dragging | ItemSlot |
| `SlotClickPressWhileDragging` | Click while dragging | ItemSlot |
# Custom HUD
While pages are modal UI screens that block gameplay, HUDs are always-visible overlays that display information during normal gameplay. Common uses include scoreboards, minimaps, status indicators, and notifications.
## ScriptCustomUIHud
HytaleJS provides `ScriptCustomUIHud` for creating custom HUD elements from TypeScript.
```typescript
const hud = new ScriptCustomUIHud(
playerRef,
(cmd: UICommandBuilder) => {
cmd.append("Hud/MyHud.ui");
cmd.set("#PlayerName.Text", playerRef.getUsername());
}
);
const hudManager = player.getHudManager();
hudManager.setCustomHud(playerRef, hud);
hud.triggerShow();
```
### Constructor
```typescript
new ScriptCustomUIHud(
playerRef: PlayerRef,
buildCallback: (cmd: UICommandBuilder) => void
): ScriptCustomUIHud
```
| Parameter | Type | Description |
| --------------- | --------- | --------------------------------------- |
| `playerRef` | PlayerRef | The player to show the HUD to |
| `buildCallback` | Function | Called to build the initial HUD content |
### Methods
| Method | Description |
| --------------------------- | --------------------------------- |
| `triggerShow()` | Displays the HUD to the player |
| `triggerUpdate(cmd)` | Updates the HUD with new commands |
| `triggerUpdate(cmd, clear)` | Updates with optional clear flag |
| `getPlayerRef()` | Returns the associated PlayerRef |
## Creating a HUD
### 1. Define the UI Template
Create a `.ui` file for your HUD layout:
```
$C = "../Common.ui";
Group #MyHud {
Anchor: (Right: 20, Top: 100, Width: 200, Height: 150);
LayoutMode: Top;
Background: (Color: #0a0e14(0.85));
Padding: (Full: 12);
Label #Title {
Style: (FontSize: 20, TextColor: #f5c542, RenderBold: true, HorizontalAlignment: Center);
Anchor: (Height: 25);
Text: "My Server";
}
Label #Info {
Style: (FontSize: 14, TextColor: #ffffff, HorizontalAlignment: Center);
Anchor: (Height: 20, Top: 10);
Text: "Welcome!";
}
}
```
### 2. Register the Asset
```typescript
let assetsRegistered = false;
function registerHudAssets(): void {
if (assetsRegistered) return;
assetsRegistered = true;
const asset = new ByteArrayCommonAsset("UI/Custom/Hud/MyHud.ui", HUD_UI_CONTENT);
CommonAssetModule.get().addCommonAsset("myplugin", asset);
}
```
### 3. Show the HUD
```typescript
function showHud(player: Player): ScriptCustomUIHud {
const playerRef = player.getPlayerRef();
const hud = new ScriptCustomUIHud(playerRef, (cmd) => {
cmd.append("Hud/MyHud.ui");
cmd.set("#Info.Text", "Hello, " + playerRef.getUsername() + "!");
});
const hudManager = player.getHudManager();
hudManager.setCustomHud(playerRef, hud);
hud.triggerShow();
return hud;
}
```
### 4. Update the HUD
```typescript
function updateHud(hud: ScriptCustomUIHud, newInfo: string): void {
const cmd = new UICommandBuilder();
cmd.set("#Info.Text", newInfo);
hud.triggerUpdate(cmd);
}
```
### 5. Hide the HUD
```typescript
function hideHud(player: Player): void {
const playerRef = player.getPlayerRef();
const hudManager = player.getHudManager();
hudManager.setCustomHud(playerRef, null);
}
```
## Complete Example: Scoreboard
```typescript
import type { Player, PlayerRef, UICommandBuilder, ScriptCustomUIHud } from "@hytalejs.com/core";
const SCOREBOARD_UI = `$C = "../Common.ui";
Group #Scoreboard {
Anchor: (Right: 20, Top: 100, Width: 200, Height: 180);
LayoutMode: Top;
Background: (Color: #0a0e14(0.85));
Padding: (Full: 12);
Label #Title {
Style: (FontSize: 24, TextColor: #f5c542, RenderBold: true, HorizontalAlignment: Center);
Anchor: (Height: 30);
Text: "Server Name";
}
Group #Separator {
Anchor: (Height: 1, Top: 8);
Background: (Color: #2b3542);
}
Group #PlayerRow {
LayoutMode: Left;
Anchor: (Height: 22, Top: 10);
Label { Style: (FontSize: 13, TextColor: #8a9aab); Anchor: (Width: 80); Text: "Player:"; }
Label #PlayerValue { Style: (FontSize: 13, TextColor: #ffffff, RenderBold: true); FlexWeight: 1; Text: ""; }
}
Group #OnlineRow {
LayoutMode: Left;
Anchor: (Height: 22, Top: 4);
Label { Style: (FontSize: 13, TextColor: #8a9aab); Anchor: (Width: 80); Text: "Online:"; }
Label #OnlineValue { Style: (FontSize: 13, TextColor: #5cb85c, RenderBold: true); FlexWeight: 1; Text: "0"; }
}
Group #KillsRow {
LayoutMode: Left;
Anchor: (Height: 22, Top: 4);
Label { Style: (FontSize: 13, TextColor: #8a9aab); Anchor: (Width: 80); Text: "Kills:"; }
Label #KillsValue { Style: (FontSize: 13, TextColor: #5cb85c, RenderBold: true); FlexWeight: 1; Text: "0"; }
}
}
`;
const playerHuds = new Map();
export function registerScoreboardAssets(): void {
const asset = new ByteArrayCommonAsset("UI/Custom/Hud/Scoreboard.ui", SCOREBOARD_UI);
CommonAssetModule.get().addCommonAsset("myplugin", asset);
}
export function showScoreboard(player: Player, kills: number): ScriptCustomUIHud {
const playerRef = player.getPlayerRef();
const uuid = player.getUuid().toString();
const hud = new ScriptCustomUIHud(playerRef, (cmd) => {
cmd.append("Hud/Scoreboard.ui");
cmd.set("#PlayerValue.Text", playerRef.getUsername());
cmd.set("#OnlineValue.Text", Universe.get().getPlayerCount().toString());
cmd.set("#KillsValue.Text", kills.toString());
});
player.getHudManager().setCustomHud(playerRef, hud);
hud.triggerShow();
playerHuds.set(uuid, hud);
return hud;
}
export function updateScoreboard(player: Player, kills: number): void {
const uuid = player.getUuid().toString();
const hud = playerHuds.get(uuid);
if (!hud) return;
const cmd = new UICommandBuilder();
cmd.set("#OnlineValue.Text", Universe.get().getPlayerCount().toString());
cmd.set("#KillsValue.Text", kills.toString());
hud.triggerUpdate(cmd);
}
export function hideScoreboard(player: Player): void {
const playerRef = player.getPlayerRef();
const uuid = player.getUuid().toString();
player.getHudManager().setCustomHud(playerRef, null);
playerHuds.delete(uuid);
}
```
## Showing HUD on Player Join
```typescript
import { EventListener, type PlayerConnectEvent } from "@hytalejs.com/core";
import { showScoreboard } from "./scoreboard";
export class PlayerConnectHandler {
@EventListener("PlayerConnectEvent")
onPlayerJoin(event: PlayerConnectEvent): void {
const player = event.getPlayer();
showScoreboard(player, 0);
}
}
```
## HUD vs Page
| Feature | HUD | Page |
| --------------- | -------------- | -------------- |
| Blocks gameplay | No | Yes |
| Mouse cursor | Hidden | Visible |
| Player can move | Yes | No |
| Event callbacks | No | Yes |
| Use case | Status display | Menus, dialogs |
## HUD Positioning Tips
Position HUDs to avoid overlapping with native UI elements:
```
Group #TopRight {
Anchor: (Right: 20, Top: 100, Width: 200, Height: 150);
}
Group #TopLeft {
Anchor: (Left: 20, Top: 100, Width: 200, Height: 150);
}
Group #BottomRight {
Anchor: (Right: 20, Bottom: 100, Width: 200, Height: 150);
}
Group #CenterTop {
Anchor: (Top: 50, Width: 300, Height: 50);
LayoutMode: Center;
}
```
Avoid:
* Top center (game messages)
* Bottom center (hotbar, health)
* Far corners (minimap, other native HUD)
# Custom UI System
The Custom UI system allows you to create rich, interactive GUI pages that display to players. UI definitions are written in Hytale's proprietary DSL (Domain Specific Language) and can be registered programmatically or loaded from asset files.
## Quick Start
### 1. Define Your UI
Create a UI definition string using the Hytale UI DSL:
```
$C = "../Common.ui";
$C.@PageOverlay {
LayoutMode: Middle;
$C.@DecoratedContainer {
Anchor: (Width: 400);
#Title {
$C.@Title {
@Text = "My Custom Page";
}
}
#Content {
Padding: (Vertical: 32, Horizontal: 45);
Label #title {
Style: (RenderBold: true, TextColor: #FFFFFF);
Text: "Hello World";
}
Group {
LayoutMode: Center;
Anchor: (Top: 30);
$C.@TextButton #CloseButton {
Text: "Close";
}
}
}
}
}
$C.@BackButton {}
```
### 2. Register the Asset
Register your UI file programmatically so it's available to clients:
```typescript
const UI_CONTENT = `...`; // Your UI DSL string
const asset = new ByteArrayCommonAsset("UI/Custom/Pages/MyPage.ui", UI_CONTENT);
CommonAssetModule.get().addCommonAsset("myplugin", asset);
```
### 3. Display the Page
Use `ScriptCustomUIPage` to show your UI to a player:
```typescript
import type { PlayerRef, Ref, Store, EntityStore, UICommandBuilder, UIEventBuilder, ScriptCustomUIPage } from "@hytalejs.com/core";
const playerRef: PlayerRef = player.getPlayerRef();
const page = new ScriptCustomUIPage(
playerRef,
CustomPageLifetime.CanDismiss,
(ref: Ref, cmd: UICommandBuilder, events: UIEventBuilder, store: Store) => {
cmd.append("Pages/MyPage.ui");
cmd.set("#title.Text", "Welcome, " + player.getDisplayName() + "!");
events.addEventBinding(CustomUIEventBindingType.Activating, "#CloseButton", EventData.of("action", "close"), false);
},
(ref: Ref, store: Store, rawData: string) => {
const data = JSON.parse(rawData);
if (data.action === "close") {
page.triggerClose();
}
}
);
const pageManager = player.getPageManager();
const ref = playerRef.getReference();
if (ref) {
pageManager.openCustomPage(ref, ref.getStore(), page);
}
```
## Key Concepts
### UI Files Location
UI files must be placed in `Common/UI/Custom/` within an asset pack. The path you use in `cmd.append()` is relative to this directory.
### The Build Callback
The build callback is called when the UI needs to be constructed or rebuilt. Use it to:
* Append UI templates with `cmd.append("path/to/file.ui")`
* Set property values with `cmd.set("#selector.Property", value)`
* Register event bindings with `events.addEventBinding(...)`
### Selectors
Selectors target elements in your UI by their ID (prefixed with `#`):
* `#title` - Element with id "title"
* `#title.Text` - The "Text" property of element "title"
* `#Container #Button` - Nested element selection
### Dynamic Updates
After the page is open, you can update it dynamically:
```typescript
const updateCmd = new UICommandBuilder();
updateCmd.set("#counter.Text", "Count: " + value);
page.triggerSendUpdate(updateCmd);
```
## Documentation Sections
* [Syntax Reference](/docs/guides/custom-ui/syntax) - Variables, imports, comments, and spread operator
* [Elements](/docs/guides/custom-ui/elements) - All available UI element types
* [Styles](/docs/guides/custom-ui/styles) - Style types and properties
* [Layout](/docs/guides/custom-ui/layout) - Layout modes and anchoring system
* [Events](/docs/guides/custom-ui/events) - Event binding and handling
* [Custom HUD](/docs/guides/custom-ui/hud) - Creating always-visible HUD overlays
* [UIBuilder](/docs/guides/custom-ui/builder) - Programmatically generate .ui files with TypeScript
# Layout System
Hytale's UI system uses a combination of anchoring and layout modes to position and size elements. Understanding these systems is essential for building complex UIs.
## Layout Modes
The `LayoutMode` property on container elements (primarily `Group`) determines how child elements are arranged.
### Top
Children stack vertically from top to bottom. Most common layout mode.
```
Group {
LayoutMode: Top;
Label { Text: "First"; }
Label { Text: "Second"; }
Label { Text: "Third"; }
}
```
### TopScrolling
Like `Top`, but enables vertical scrolling when content exceeds container height.
```
Group {
LayoutMode: TopScrolling;
ScrollbarStyle: $C.@DefaultScrollbarStyle;
Anchor: (Height: 200);
Label { Text: "Item 1"; Anchor: (Height: 30); }
Label { Text: "Item 2"; Anchor: (Height: 30); }
Label { Text: "Item 3"; Anchor: (Height: 30); }
}
```
### Left
Children stack horizontally from left to right.
```
Group {
LayoutMode: Left;
Anchor: (Height: 44);
Button #Icon1 { Anchor: (Width: 44, Height: 44); }
Button #Icon2 { Anchor: (Width: 44, Height: 44); }
Button #Icon3 { Anchor: (Width: 44, Height: 44); }
}
```
### Right
Children stack horizontally from right to left.
```
Group {
LayoutMode: Right;
TextButton #Cancel { Text: "Cancel"; }
TextButton #Save { Text: "Save"; }
}
```
### Center
Children are centered horizontally within the container.
```
Group {
LayoutMode: Center;
TextButton #Action { Text: "Click Me"; }
}
```
### Middle
Children are centered vertically within the container. Often used as the root layout.
```
Group {
LayoutMode: Middle;
Group #DialogBox {
Anchor: (Width: 400, Height: 300);
}
}
```
### CenterMiddle
Centers children both horizontally and vertically.
```
Group {
LayoutMode: CenterMiddle;
$C.@DefaultSpinner {}
}
```
### Full
Children fill the entire container space.
```
Group {
LayoutMode: Full;
Sprite #Background {
TexturePath: "Common/Background.png";
}
}
```
### LeftCenterWrap
Children flow left to right and wrap to next line when full.
```
Group {
LayoutMode: LeftCenterWrap;
Anchor: (Width: 300);
Button { Anchor: (Width: 64, Height: 64); }
Button { Anchor: (Width: 64, Height: 64); }
Button { Anchor: (Width: 64, Height: 64); }
Button { Anchor: (Width: 64, Height: 64); }
Button { Anchor: (Width: 64, Height: 64); }
}
```
## Anchor System
The `Anchor` property controls element positioning and sizing. It uses a constraint-based system where you specify distances from container edges and/or explicit dimensions.
### Basic Positioning
```
Anchor: (
Left: 10,
Right: 10,
Top: 10,
Bottom: 10
);
```
| Property | Description |
| -------- | ------------------------------------- |
| `Left` | Distance from container's left edge |
| `Right` | Distance from container's right edge |
| `Top` | Distance from container's top edge |
| `Bottom` | Distance from container's bottom edge |
### Explicit Dimensions
```
Anchor: (Width: 200, Height: 50);
```
| Property | Description |
| ---------- | ------------------------- |
| `Width` | Explicit width in pixels |
| `Height` | Explicit height in pixels |
| `MinWidth` | Minimum width constraint |
| `MaxWidth` | Maximum width constraint |
### Shorthand Properties
```
Anchor: (Full: 10);
Anchor: (Horizontal: 20, Vertical: 10);
```
| Property | Equivalent To |
| ------------ | ---------------------------------------------- |
| `Full` | Left, Right, Top, Bottom all set to same value |
| `Horizontal` | Left and Right set to same value |
| `Vertical` | Top and Bottom set to same value |
### Common Anchor Patterns
**Fill container with padding:**
```
Anchor: (Full: 10);
```
**Fixed size, centered (when parent has Center/Middle layout):**
```
Anchor: (Width: 300, Height: 200);
```
**Full width, fixed height, at top:**
```
Anchor: (Top: 0, Height: 50);
```
**Full width, fixed height, at bottom:**
```
Anchor: (Bottom: 0, Height: 50);
```
**Fixed width, full height, on left:**
```
Anchor: (Left: 0, Width: 200);
```
**Offset from edges:**
```
Anchor: (Top: 10, Right: 10, Width: 100, Height: 100);
```
**Negative values for overflow:**
```
Anchor: (Top: -16, Right: -16, Width: 32, Height: 32);
```
## Padding System
The `Padding` property adds internal spacing within containers.
```
Padding: (
Left: 10,
Right: 10,
Top: 20,
Bottom: 20
);
```
### Padding Properties
| Property | Description |
| ------------ | -------------- |
| `Left` | Left padding |
| `Right` | Right padding |
| `Top` | Top padding |
| `Bottom` | Bottom padding |
| `Full` | All sides |
| `Horizontal` | Left and right |
| `Vertical` | Top and bottom |
### Example
```
Group {
LayoutMode: Top;
Padding: (Full: 16);
Label { Text: "Content with 16px padding all around"; }
}
```
## FlexWeight
For flex layouts, `FlexWeight` determines how elements share available space.
```
Group {
LayoutMode: Left;
Anchor: (Height: 50);
Label #Left {
FlexWeight: 1;
Text: "Left (1/3)";
}
Group #Spacer {
FlexWeight: 2;
}
Label #Right {
FlexWeight: 1;
Text: "Right (1/3)";
}
}
```
Elements with higher FlexWeight values receive proportionally more space.
## Combining Anchor with Variables
Use variables for reusable anchor configurations:
```
@ButtonAnchor = Anchor(Height: 44);
@FullWidthAnchor = Anchor(Horizontal: 0);
TextButton #SaveButton {
Anchor: (...@ButtonAnchor, ...@FullWidthAnchor);
Text: "Save";
}
```
## Complex Layout Example
```
$C = "../Common.ui";
Group #MainLayout {
LayoutMode: Top;
Anchor: (Full: 0);
Group #Header {
LayoutMode: Left;
Anchor: (Height: 60);
Padding: (Horizontal: 20);
Background: (Color: #1a2530);
Label #Title {
FlexWeight: 1;
Text: "Dashboard";
Style: (FontSize: 24, VerticalAlignment: Center);
}
Group #Actions {
LayoutMode: Right;
Anchor: (Width: 200);
$C.@SecondaryButton #Settings {
Anchor: (Width: 44, Height: 44);
}
}
}
Group #Content {
LayoutMode: Left;
FlexWeight: 1;
Group #Sidebar {
Anchor: (Width: 250);
LayoutMode: TopScrolling;
Background: (Color: #0f1620);
}
Group #Main {
FlexWeight: 1;
LayoutMode: Top;
Padding: (Full: 20);
}
}
Group #Footer {
LayoutMode: Center;
Anchor: (Height: 50);
Background: (Color: #1a2530);
Label {
Text: "Footer Content";
Style: (VerticalAlignment: Center);
}
}
}
```
## Responsive Patterns
### Centered Dialog
```
$C.@PageOverlay {
LayoutMode: Middle;
Group #Dialog {
Anchor: (Width: 500);
LayoutMode: Top;
Background: $C.@Panel;
}
}
```
### Side Panel
```
Group {
LayoutMode: Left;
Group #Panel {
Anchor: (Width: 300);
LayoutMode: TopScrolling;
}
Group #Content {
FlexWeight: 1;
LayoutMode: Top;
}
}
```
### Bottom Action Bar
```
Group {
LayoutMode: Top;
Group #Content {
FlexWeight: 1;
}
Group #ActionBar {
LayoutMode: Right;
Anchor: (Height: 60);
Padding: (Full: 10);
$C.@TextButton #Cancel { @Text = "Cancel"; }
Group { Anchor: (Width: 10); }
$C.@TextButton #Save { @Text = "Save"; }
}
}
```
# UI Styles
Styles in Hytale's UI DSL define the visual appearance of elements. They support state-based variations (Default, Hovered, Pressed, Disabled) and can be combined using the spread operator.
## PatchStyle
The most fundamental style type. Used for 9-patch textures that scale without distorting borders.
```
@MyBackground = PatchStyle(
TexturePath: "Common/ContainerPatch.png",
Border: 20,
Color: #FFFFFF
);
@WideBorder = PatchStyle(
TexturePath: "Common/ButtonWide.png",
HorizontalBorder: 80,
VerticalBorder: 12
);
```
**Properties:**
| Property | Type | Description |
| ------------------ | ------- | -------------------------------------- |
| `TexturePath` | String | Path to texture file |
| `Border` | Integer | Uniform border size for 9-patch |
| `HorizontalBorder` | Integer | Left/right border size |
| `VerticalBorder` | Integer | Top/bottom border size |
| `Color` | Color | Tint color with optional opacity |
| `Area` | Area | Source rectangle (X, Y, Width, Height) |
You can also use inline syntax for simple backgrounds:
```
Background: (TexturePath: "Common/Panel.png", Border: 16);
Background: (Color: #1a2530);
Background: "Common/Icon.png";
```
## LabelStyle
Controls text rendering appearance.
```
@TitleStyle = LabelStyle(
FontSize: 24,
TextColor: #FFFFFF,
RenderBold: true,
RenderUppercase: true,
HorizontalAlignment: Center,
VerticalAlignment: Center,
LetterSpacing: 2
);
@BodyStyle = LabelStyle(
FontSize: 16,
TextColor: #96a9be,
Wrap: true,
FontName: "Secondary"
);
```
**Properties:**
| Property | Type | Description |
| --------------------- | ------- | ------------------------ |
| `FontSize` | Integer | Font size in pixels |
| `TextColor` | Color | Text color |
| `RenderBold` | Boolean | Bold rendering |
| `RenderUppercase` | Boolean | Force uppercase |
| `HorizontalAlignment` | Enum | Start, Center, End |
| `VerticalAlignment` | Enum | Top, Center, Bottom |
| `LetterSpacing` | Integer | Space between characters |
| `Wrap` | Boolean | Enable text wrapping |
| `FontName` | String | "Default" or "Secondary" |
## ButtonStyle
Defines button appearance across interaction states. Used for icon buttons.
```
@IconButtonStyle = ButtonStyle(
Default: (Background: "Common/Buttons/Primary_Square.png"),
Hovered: (Background: "Common/Buttons/Primary_Square_Hovered.png"),
Pressed: (Background: "Common/Buttons/Primary_Square_Pressed.png"),
Disabled: (Background: "Common/Buttons/Disabled.png"),
Sounds: $Sounds.@ButtonsLight
);
```
**State Properties:**
| State | Description |
| ---------- | ----------------------- |
| `Default` | Normal appearance |
| `Hovered` | Mouse over appearance |
| `Pressed` | Click/active appearance |
| `Disabled` | Inactive appearance |
**Each state contains:**
| Property | Type | Description |
| ------------ | ----------------- | --------------------- |
| `Background` | PatchStyle/String | Background appearance |
**Additional Properties:**
| Property | Type | Description |
| -------- | ---------- | ------------------ |
| `Sounds` | SoundStyle | Interaction sounds |
## TextButtonStyle
Extended button style with text label styling per state.
```
@PrimaryButtonStyle = TextButtonStyle(
Default: (
Background: PatchStyle(TexturePath: "Common/Buttons/Primary.png", Border: 12),
LabelStyle: (FontSize: 17, TextColor: #bfcdd5, RenderBold: true, RenderUppercase: true)
),
Hovered: (
Background: PatchStyle(TexturePath: "Common/Buttons/Primary_Hovered.png", Border: 12),
LabelStyle: (FontSize: 17, TextColor: #bfcdd5, RenderBold: true, RenderUppercase: true)
),
Pressed: (
Background: PatchStyle(TexturePath: "Common/Buttons/Primary_Pressed.png", Border: 12),
LabelStyle: (FontSize: 17, TextColor: #bfcdd5, RenderBold: true, RenderUppercase: true)
),
Disabled: (
Background: PatchStyle(TexturePath: "Common/Buttons/Disabled.png", Border: 12),
LabelStyle: (FontSize: 17, TextColor: #797b7c, RenderBold: true, RenderUppercase: true)
),
Sounds: $Sounds.@ButtonsLight
);
```
**Each state contains:**
| Property | Type | Description |
| ------------ | ---------- | ----------------- |
| `Background` | PatchStyle | Button background |
| `LabelStyle` | LabelStyle | Text appearance |
## CheckBoxStyle
Defines checkbox appearance for checked and unchecked states.
```
@CustomCheckBoxStyle = CheckBoxStyle(
Unchecked: (
DefaultBackground: (Color: #00000000),
HoveredBackground: (Color: #00000000),
PressedBackground: (Color: #00000000),
DisabledBackground: (Color: #424242),
ChangedSound: (SoundPath: "sounds/untick.ogg", Volume: 6)
),
Checked: (
DefaultBackground: (TexturePath: "Common/Checkmark.png"),
HoveredBackground: (TexturePath: "Common/Checkmark.png"),
PressedBackground: (TexturePath: "Common/Checkmark.png"),
ChangedSound: (SoundPath: "sounds/tick.ogg", Volume: 6)
)
);
```
## SliderStyle
Defines slider track and handle appearance.
```
@CustomSliderStyle = SliderStyle(
Background: (TexturePath: "Common/SliderBackground.png", Border: 2),
Handle: "Common/SliderHandle.png",
HandleWidth: 16,
HandleHeight: 16,
Sounds: (
MouseHover: (SoundPath: "sounds/hover.ogg", Volume: 6)
)
);
```
**Properties:**
| Property | Type | Description |
| -------------- | ---------- | ----------------------- |
| `Background` | PatchStyle | Track background |
| `Handle` | String | Handle texture path |
| `HandleWidth` | Integer | Handle width in pixels |
| `HandleHeight` | Integer | Handle height in pixels |
| `Sounds` | SoundStyle | Interaction sounds |
## ScrollbarStyle
Defines scrollbar appearance.
```
@CustomScrollbarStyle = ScrollbarStyle(
Spacing: 6,
Size: 6,
Background: (TexturePath: "Common/Scrollbar.png", Border: 3),
Handle: (TexturePath: "Common/ScrollbarHandle.png", Border: 3),
HoveredHandle: (TexturePath: "Common/ScrollbarHandleHovered.png", Border: 3),
DraggedHandle: (TexturePath: "Common/ScrollbarHandleDragged.png", Border: 3),
OnlyVisibleWhenHovered: false
);
```
**Properties:**
| Property | Type | Description |
| ------------------------ | ---------- | ----------------------- |
| `Spacing` | Integer | Space from content edge |
| `Size` | Integer | Scrollbar width/height |
| `Background` | PatchStyle | Track background |
| `Handle` | PatchStyle | Default handle |
| `HoveredHandle` | PatchStyle | Hovered handle |
| `DraggedHandle` | PatchStyle | Dragging handle |
| `OnlyVisibleWhenHovered` | Boolean | Auto-hide scrollbar |
## DropdownBoxStyle
Comprehensive style for dropdown selections.
```
@CustomDropdownStyle = DropdownBoxStyle(
DefaultBackground: (TexturePath: "Common/Dropdown.png", Border: 16),
HoveredBackground: (TexturePath: "Common/DropdownHovered.png", Border: 16),
PressedBackground: (TexturePath: "Common/DropdownPressed.png", Border: 16),
DefaultArrowTexturePath: "Common/DropdownCaret.png",
HoveredArrowTexturePath: "Common/DropdownCaret.png",
PressedArrowTexturePath: "Common/DropdownPressedCaret.png",
ArrowWidth: 13,
ArrowHeight: 18,
LabelStyle: (TextColor: #96a9be, RenderUppercase: true, FontSize: 13),
EntryLabelStyle: (TextColor: #b7cedd),
SelectedEntryLabelStyle: (TextColor: #b7cedd, RenderBold: true),
NoItemsLabelStyle: (TextColor: #b7cedd(0.5)),
HorizontalPadding: 8,
PanelScrollbarStyle: @DefaultScrollbarStyle,
PanelBackground: (TexturePath: "Common/DropdownBox.png", Border: 16),
PanelPadding: 6,
PanelAlign: Right,
PanelOffset: 7,
EntryHeight: 31,
EntriesInViewport: 10,
HorizontalEntryPadding: 7,
HoveredEntryBackground: (Color: #0a0f17),
PressedEntryBackground: (Color: #0f1621),
Sounds: $Sounds.@DropdownBox,
EntrySounds: $Sounds.@ButtonsLight,
FocusOutlineSize: 1,
FocusOutlineColor: #ffffff(0.4)
);
```
## InputFieldStyle
Style for text input fields.
```
@CustomInputStyle = InputFieldStyle(
TextColor: #FFFFFF,
FontSize: 16
);
@PlaceholderStyle = InputFieldStyle(
TextColor: #6e7da1
);
```
## TabNavigationStyle
Defines tabbed navigation appearance.
```
@TabStyle = TabStateStyle(
Background: "Common/Tab.png",
Overlay: "Common/TabOverlay.png",
IconAnchor: (Width: 44, Height: 44),
Anchor: (Width: 82, Height: 62, Right: 5, Bottom: -14),
IconOpacity: 1.0
);
@NavigationStyle = TabNavigationStyle(
TabStyle: (
Default: @TabStyle,
Hovered: (...@TabStyle, Anchor: (...@TabAnchor, Bottom: -5)),
Pressed: (...@TabStyle, Anchor: (...@TabAnchor, Bottom: -8))
),
SelectedTabStyle: (
Default: (...@TabStyle, Overlay: "Common/TabSelectedOverlay.png")
),
SeparatorAnchor: (Width: 5, Height: 34),
SeparatorBackground: "Common/HeaderTabSeparator.png"
);
```
## ColorPickerStyle
Style for color picker elements.
```
@CustomColorPickerStyle = ColorPickerStyle(
OpacitySelectorBackground: "Common/ColorPickerOpacitySelectorBackground.png",
ButtonBackground: "Common/ColorPickerButton.png",
ButtonFill: "Common/ColorPickerFill.png",
TextFieldDecoration: (
Default: (
Background: #000000(0.5)
)
),
TextFieldPadding: (Left: 7),
TextFieldHeight: 32
);
```
## TextTooltipStyle
Style for hover tooltips.
```
@CustomTooltipStyle = TextTooltipStyle(
Background: (TexturePath: "Common/TooltipDefaultBackground.png", Border: 24),
MaxWidth: 400,
LabelStyle: (Wrap: true, FontSize: 16),
Padding: 24
);
```
## PopupMenuLayerStyle
Style for context menus and popups.
```
@CustomPopupStyle = PopupMenuLayerStyle(
Background: (TexturePath: "Common/Popup.png", Border: 16),
Padding: 2,
BaseHeight: 5,
MaxWidth: 200,
TitleStyle: (RenderBold: true, RenderUppercase: true, FontSize: 13, TextColor: #ccb588),
TitleBackground: (TexturePath: "Common/PopupTitle.png"),
RowHeight: 25,
ItemLabelStyle: (RenderBold: true, RenderUppercase: true, FontSize: 11, TextColor: #96a9be(0.8)),
ItemPadding: (Vertical: 5, Horizontal: 8),
ItemBackground: (TexturePath: "Common/PopupItem.png"),
ItemIconSize: 16,
HoveredItemBackground: (TexturePath: "Common/HoveredPopupItem.png"),
PressedItemBackground: (TexturePath: "Common/PressedPopupItem.png"),
ItemSounds: $Sounds.@ButtonsLight
);
```
## Using Common.ui Styles
Common.ui provides pre-defined styles ready to use:
```
$C = "../Common.ui";
TextButton #MyButton {
Style: $C.@DefaultTextButtonStyle;
}
TextField #MyInput {
Style: $C.@DefaultInputFieldStyle;
PlaceholderStyle: $C.@DefaultInputFieldPlaceholderStyle;
}
CheckBox #MyCheckbox {
Style: $C.@DefaultCheckBoxStyle;
}
Slider #MySlider {
Style: $C.@DefaultSliderStyle;
}
DropdownBox #MyDropdown {
Style: $C.@DefaultDropdownBoxStyle;
}
```
## Extending Styles with Spread
Use the spread operator to extend existing styles:
```
@CustomButtonLabel = LabelStyle(
...$C.@DefaultButtonLabelStyle,
TextColor: #FF0000,
FontSize: 20
);
@CustomScrollbar = ScrollbarStyle(
...$C.@DefaultScrollbarStyle,
Spacing: 12,
Size: 10
);
```
## Sound Styles
Sounds can be attached to interactive elements:
```
@ButtonSounds = (
Activating: (SoundPath: "sounds/click.ogg", Volume: 6),
MouseHover: (SoundPath: "sounds/hover.ogg", Volume: 4)
);
@SliderSounds = (
MouseHover: (SoundPath: "sounds/tick.ogg", Volume: 6)
);
```
# UI DSL Syntax
This document covers the complete syntax of Hytale's UI Domain Specific Language (DSL).
## Comments
Single-line comments use `//`:
```
// This is a comment
Group {
// Comments can appear inside elements
Anchor: (Width: 100);
}
```
## Imports
Import other UI files using the `$` prefix:
```
$C = "../Common.ui";
$Sounds = "../Sounds.ui";
$Browser = "PrefabBrowser.ui";
```
Paths are relative to the current file's location. Common imports include:
* `Common.ui` - Standard UI components, styles, and templates
* `Sounds.ui` - Sound definitions for UI interactions
### Using Imports
Reference imported values with dot notation:
```
$C.@PageOverlay {}
$C.@TextButton #MyButton {
Text: "Click Me";
}
$Sounds.@ButtonsLight
```
## Variables and Templates
### Local Variables
Define local variables with `@`:
```
@ButtonHeight = 44;
@PrimaryColor = #4a90d9;
@DefaultPadding = 16;
```
Variables can hold:
* Numbers: `@Height = 100;`
* Strings: `@Title = "Hello";`
* Colors: `@Color = #FF5500;`
* Complex values: `@Style = (FontSize: 16, TextColor: #FFFFFF);`
### Templates (Macros)
Templates define reusable element structures:
```
@InputLabel = Label {
Anchor: (Left: 6, Right: 16, Width: 250);
Style: (...$C.@DefaultLabelStyle, VerticalAlignment: Center, Wrap: true);
};
```
Use templates by referencing them:
```
@InputLabel {
Text: "Username:";
}
```
### Parameterized Templates
Templates can have parameters with default values:
```
@TextButton = TextButton {
@Anchor = Anchor();
@Sounds = ();
Style: (
...@DefaultTextButtonStyle,
Sounds: (...$Sounds.@ButtonsLight, ...@Sounds)
);
Anchor: (...@Anchor, Height: @DefaultButtonHeight);
Text: @Text;
};
```
Override parameters when using the template:
```
@TextButton #MyButton {
@Anchor = (Width: 200);
@Text = "Submit";
}
```
## Elements
Elements are the building blocks of UI:
```
ElementType #ElementId {
Property1: value;
Property2: value;
ChildElement {
// nested content
}
}
```
### Element IDs
IDs are optional and prefixed with `#`:
```
Group #MainContainer {
Label #Title {
Text: "Hello";
}
}
```
IDs are used for:
* Targeting elements with selectors
* Dynamic updates from server code
## Property Values
### Primitive Values
```
Text: "Hello World"; // String
Visible: true; // Boolean
FontSize: 16; // Integer
Opacity: 0.5; // Float
FlexWeight: 1; // Number
```
### Colors
Colors use hex notation with optional opacity:
```
TextColor: #FFFFFF; // White, full opacity
Background: #000000(0.5); // Black, 50% opacity
OutlineColor: #FF5500(0.8); // Orange, 80% opacity
```
### Structured Values
Use parentheses for structured values:
```
Anchor: (Width: 100, Height: 50, Top: 10);
Style: (FontSize: 16, TextColor: #FFFFFF, RenderBold: true);
Padding: (Horizontal: 10, Vertical: 5);
```
### Localization
Reference translated strings with `%`:
```
Text: %server.customUI.respawnPage.title;
PlaceholderText: %server.customUI.searchPlaceholder;
```
### References
Reference values from other documents:
```
Style: Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle");
```
In the DSL, this is written as:
```
Style: $Other.@SelectedLabelStyle;
```
## Spread Operator
The `...` operator merges properties from another value:
```
@BaseStyle = (FontSize: 16, TextColor: #FFFFFF);
@BoldStyle = (...@BaseStyle, RenderBold: true);
// Result: (FontSize: 16, TextColor: #FFFFFF, RenderBold: true)
```
Spread can be used in:
* Style definitions
* Anchor definitions
* Any structured value
```
Style: (
...$C.@DefaultLabelStyle,
TextColor: #FF0000, // Override specific property
RenderBold: true // Add new property
);
```
## Type Constructors
Some values require explicit type constructors:
```
Background: PatchStyle(TexturePath: "Common/Button.png", Border: 12);
Style: LabelStyle(FontSize: 16, TextColor: #FFFFFF);
Anchor: Anchor(Width: 100, Height: 50);
Padding: Padding(Horizontal: 10, Vertical: 5);
```
### Available Constructors
| Constructor | Purpose |
| ------------------------- | -------------------------- |
| `PatchStyle(...)` | 9-patch texture background |
| `LabelStyle(...)` | Text styling |
| `ButtonStyle(...)` | Button state styles |
| `TextButtonStyle(...)` | Text button state styles |
| `ScrollbarStyle(...)` | Scrollbar appearance |
| `SliderStyle(...)` | Slider appearance |
| `DropdownBoxStyle(...)` | Dropdown styling |
| `CheckBoxStyle(...)` | Checkbox appearance |
| `TabNavigationStyle(...)` | Tab navigation |
| `TabStateStyle(...)` | Tab state appearance |
| `TextTooltipStyle(...)` | Tooltip styling |
## File Structure
A typical UI file structure:
```
// Imports
$C = "../Common.ui";
$Sounds = "../Sounds.ui";
// Local variables and templates
@MyStyle = (FontSize: 18, TextColor: #FFFFFF);
@MyButton = TextButton {
@Text = "";
Style: @MyStyle;
Text: @Text;
};
// Root element(s)
$C.@PageOverlay {
// Page content
Group #Content {
@MyButton #SubmitButton {
@Text = "Submit";
}
}
}
```
## Sound Definitions
Sound definitions in `Sounds.ui`:
```
@ButtonsLightActivate = "Sounds/ButtonsLightActivate.ogg";
@ButtonsLightHover = "Sounds/ButtonsLightHover.ogg";
@ButtonsLight = (
Activate: (
SoundPath: @ButtonsLightActivate,
MinPitch: -0.4,
MaxPitch: 0.4,
Volume: 4
),
MouseHover: (
SoundPath: @ButtonsLightHover,
Volume: 6
)
);
```
Reference sounds in button styles:
```
Style: (
...$C.@DefaultButtonStyle,
Sounds: $Sounds.@ButtonsLight
);
```