# 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 ); ```