# BetterHud BetterHud is a multiplatform server-side HUD implementation for Minecraft servers that supports auto-generating resource packs, animations, image sequences, and asynchronous processing. Unlike traditional HUD plugins, BetterHud renders custom UI elements through boss bars and resource packs, allowing for highly customizable heads-up displays including health bars, skill indicators, buff icons, compass markers, and more. The plugin supports Bukkit/Paper (1.20.4-1.21.11), Velocity (3.3-3.4), and Fabric server (1.21.11) platforms. The core architecture revolves around a hierarchical structure of HUDs, Popups, Layouts, and Images. HUDs bundle multiple layouts positioned on the player's screen using percentage-based coordinates, while Layouts combine text, images, and player heads with precise pixel positioning. Popups extend this with trigger-based displays, queuing systems, and animations. BetterHud integrates with PlaceholderAPI for dynamic content and supports plugins like MMOCore, MythicMobs, WorldGuard, Vault, and Skript. ## API Installation Configure your build system to include BetterHud's API dependencies from Maven Central. ```kotlin // build.gradle.kts - Gradle Kotlin DSL repositories { mavenCentral() } dependencies { // Standard API (required) compileOnly("io.github.toxicity188:BetterHud-standard-api:VERSION") // Platform-specific API (choose one) compileOnly("io.github.toxicity188:BetterHud-bukkit-api:VERSION") // For Bukkit/Paper // OR modCompileOnly("io.github.toxicity188:BetterHud-fabric-api:VERSION") // For Fabric // BetterCommand library (for command handling) compileOnly("io.github.toxicity188:BetterCommand:VERSION") } ``` ## Getting the BetterHud Instance Access the main BetterHud instance to interact with all managers and functionality. ```java import kr.toxicity.hud.api.BetterHud; import kr.toxicity.hud.api.BetterHudAPI; public class MyPlugin extends JavaPlugin { @Override public void onEnable() { // Get the BetterHud instance BetterHud betterHud = BetterHudAPI.inst(); // Or using legacy method BetterHud betterHudLegacy = BetterHud.getInstance(); // Access various managers HudManager hudManager = betterHud.getHudManager(); PopupManager popupManager = betterHud.getPopupManager(); PlayerManager playerManager = betterHud.getPlayerManager(); PlaceholderManager placeholderManager = betterHud.getPlaceholderManager(); CompassManager compassManager = betterHud.getCompassManager(); TriggerManager triggerManager = betterHud.getTriggerManager(); // Trigger a reload ReloadState state = betterHud.reload(); getLogger().info("Reload result: " + state.name()); } } ``` ## Managing HUD Players Get and manipulate player-specific HUD data including enabling/disabling HUDs and accessing player state. ```java import kr.toxicity.hud.api.BetterHudAPI; import kr.toxicity.hud.api.player.HudPlayer; import kr.toxicity.hud.api.manager.PlayerManager; public void managePlayer(Player bukkitPlayer) { PlayerManager playerManager = BetterHudAPI.inst().getPlayerManager(); // Get HudPlayer by UUID HudPlayer hudPlayer = playerManager.getHudPlayer(bukkitPlayer.getUniqueId()); if (hudPlayer != null) { // Toggle HUD display hudPlayer.setHudEnabled(!hudPlayer.isHudEnabled()); // Get player's current tick count long tick = hudPlayer.getTick(); // Access player variables (mutable map) Map variables = hudPlayer.getVariableMap(); variables.put("custom_var", "custom_value"); // Get currently active HUDs and Popups Set activeHuds = hudPlayer.getHuds(); Set activePopups = hudPlayer.getPopups(); Set activeCompasses = hudPlayer.getCompasses(); // Force update the player's HUD hudPlayer.update(); // Save player data to database hudPlayer.save(); // Reload player's HUD configuration hudPlayer.reload(); // Set bossbar color hudPlayer.setBarColor(BossBar.Color.RED); } // Get all online HUD players Collection allPlayers = playerManager.getAllHudPlayer(); } ``` ## Managing HUDs and Popups Add, remove, and query HUDs and Popups for players programmatically. ```java import kr.toxicity.hud.api.BetterHudAPI; import kr.toxicity.hud.api.hud.Hud; import kr.toxicity.hud.api.popup.Popup; import kr.toxicity.hud.api.popup.PopupUpdater; import kr.toxicity.hud.api.update.UpdateEvent; public void manageHudsAndPopups(HudPlayer hudPlayer) { BetterHud api = BetterHudAPI.inst(); // Get HUD Manager HudManager hudManager = api.getHudManager(); // Get a specific HUD by name Hud healthHud = hudManager.getHud("health_hud"); // List all available HUDs Set hudNames = hudManager.getAllNames(); Set allHuds = hudManager.getAllHuds(); // Get Popup Manager PopupManager popupManager = api.getPopupManager(); // Get a specific popup by name Popup damagePopup = popupManager.getPopup("damage_indicator"); if (damagePopup != null) { // Show popup to player with custom update event UpdateEvent event = UpdateEvent.empty(); // or create custom event PopupUpdater updater = damagePopup.show(event, hudPlayer); if (updater != null) { // Popup was successfully shown // updater can be used to track/manage this popup instance } // Hide popup from player boolean hidden = damagePopup.hide(hudPlayer); // Get popup properties String groupName = damagePopup.getGroupName(); int maxStack = damagePopup.getMaxStack(); } // List all popups Set popupNames = popupManager.getAllNames(); Set allPopups = popupManager.getAllPopups(); } ``` ## Registering Custom Placeholders Create and register custom placeholders that can be used in HUD configurations. ```java import kr.toxicity.hud.api.BetterHudAPI; import kr.toxicity.hud.api.placeholder.HudPlaceholder; import kr.toxicity.hud.api.placeholder.PlaceholderContainer; import kr.toxicity.hud.api.player.HudPlayer; public void registerPlaceholders() { PlaceholderManager manager = BetterHudAPI.inst().getPlaceholderManager(); // Register a number placeholder PlaceholderContainer numberContainer = manager.getNumberContainer(); HudPlaceholder.builder() .requiredArgsLength(0) .function((args, event) -> (player) -> { // Return player's kill count from your plugin return getKillCount(player.uuid()); }) .add("my_kills", numberContainer); // Register a string placeholder PlaceholderContainer stringContainer = manager.getStringContainer(); HudPlaceholder.builder() .requiredArgsLength(1) // requires one argument .function((args, event) -> (player) -> { String arg = args.get(0); return "Hello " + player.name() + " - " + arg; }) .add("my_greeting", stringContainer); // Register a boolean placeholder PlaceholderContainer boolContainer = manager.getBooleanContainer(); HudPlaceholder.builder() .requiredArgsLength(0) .function((args, event) -> (player) -> { return isPlayerInCombat(player.uuid()); }) .add("my_in_combat", boolContainer); // Simple placeholder using factory method numberContainer.addPlaceholder("simple_health", HudPlaceholder.of((args, event) -> player -> player.location().y())); } ``` ## Creating Custom Triggers Register custom triggers that can activate popups based on plugin events. ```java import kr.toxicity.hud.api.BetterHudAPI; import kr.toxicity.hud.api.trigger.HudTrigger; import kr.toxicity.hud.api.manager.TriggerManager; import kr.toxicity.hud.api.update.UpdateEvent; public void registerCustomTrigger() { TriggerManager triggerManager = BetterHudAPI.inst().getTriggerManager(); // Register a custom trigger triggerManager.addTrigger("my_custom_event", yamlConfig -> { // Read configuration from YAML if needed String configOption = yamlConfig.getAsString("option", "default"); return new HudTrigger() { @Override public @NotNull Object getKey(MyCustomEvent event) { // Return unique key for this event (for key-mapping popups) return event.getTargetId(); } @Override public void registerEvent(@NotNull BiConsumer eventConsumer) { // Register your event listener Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler public void onCustomEvent(MyCustomEvent event) { UUID playerUuid = event.getPlayer().getUniqueId(); UpdateEvent updateEvent = new UpdateEvent() { // Implement update event with custom data }; eventConsumer.accept(playerUuid, updateEvent); } }, myPlugin); } }; }); } ``` ## Firing Custom Popup Events (Bukkit) Trigger popups programmatically using the CustomPopupEvent on Bukkit servers. ```java import kr.toxicity.hud.api.bukkit.event.CustomPopupEvent; import org.bukkit.Bukkit; import org.bukkit.entity.Player; public void showCustomPopup(Player player, String popupName) { // Create custom popup event CustomPopupEvent event = new CustomPopupEvent(player, popupName); // Add custom variables that can be used in the popup event.getVariables().put("damage", "150"); event.getVariables().put("attacker", "Zombie"); event.getVariables().put("critical", "true"); // Fire the event - BetterHud will handle showing the popup Bukkit.getPluginManager().callEvent(event); } // In popup configuration (popups/custom_damage.yml): // custom_damage: // triggers: // 1: // class: custom // name: damage_popup // layouts: // 1: // name: damage_layout ``` ## Reload Event Handling Listen for BetterHud reload events to synchronize your plugin's state. ```java import kr.toxicity.hud.api.BetterHudAPI; import kr.toxicity.hud.api.plugin.ReloadState; public void setupReloadHandlers() { BetterHud api = BetterHudAPI.inst(); // Add task to run when reload starts api.addReloadStartTask(() -> { getLogger().info("BetterHud reload starting - clearing caches..."); clearMyCaches(); }); // Add task to run when reload completes api.addReloadEndTask((ReloadState state) -> { switch (state) { case SUCCESS: getLogger().info("BetterHud reloaded successfully!"); reRegisterMyPlaceholders(); break; case FAIL: getLogger().warning("BetterHud reload failed!"); break; case STILL_ON_RELOAD: getLogger().info("BetterHud is still reloading..."); break; } }); // Check if currently reloading if (api.isOnReload()) { getLogger().info("BetterHud is currently reloading"); } } ``` ## Image Configuration Define images in YAML for use in layouts. Images can be single static images, animated sequences, or listener-based dynamic bars. ```yaml # images/my_images.yml # Static single image coin_icon: type: single file: "icons/coin.png" # Animated image sequence (plays at 1 tick per frame) loading_spinner: type: sequence files: - spinner/frame_00.png - spinner/frame_01.png - spinner/frame_02.png:3 # This frame lasts 3 ticks - spinner/frame_03.png setting: animation-type: loop # or play_once # Dynamic health bar with listener health_bar: type: listener file: "bars/health_bar.png" split: 20 # Split into 20 segments for smooth animation split-type: left # Types: up, down, left, right, circle, reverse_circle setting: listener: class: health # Built-in listener # Custom listener using PlaceholderAPI mana_bar: type: listener file: "bars/mana_bar.png" split: 10 split-type: up setting: listener: class: placeholder value: "(number)papi:mmocore_mana" max: "(number)papi:mmocore_max_mana" ``` ## Layout Configuration Layouts combine images, text, and heads with pixel-precise positioning and animation support. ```yaml # layouts/player_status.yml player_status_layout: # Layout-level coordinates (pixels) x: 0 y: 0 # Images section images: 1: name: health_bar_empty x: -50 y: -20 scale: 1.0 layer: 0 # Lower layers render behind conditions: 1: first: health_percentage second: 0 operation: ">" 2: name: health_bar x: -50 y: -20 layer: 1 # Text section texts: 1: name: my_custom_font pattern: "[health]/[max_health]" x: 0 y: -35 scale: 0.8 align: center outline: true number-format: "#,###" 2: name: my_custom_font pattern: "$[vault_money@t]" # Using operations x: 0 y: -50 use-legacy-format: false placeholder-option: evaluate: t * 1 # Mathematical operations on placeholder # Player heads section heads: 1: name: player_head_config x: -70 y: -25 scale: 1.0 fancy: true # 3D head rendering # Animations using exp4j math expressions animations: type: loop # or play_once duration: 40 # ticks x-equation: 0 y-equation: 2 * sin(t / 20 * pi) # Bobbing animation ``` ## HUD Configuration HUDs bundle layouts and position them on the player's screen using percentage-based coordinates. ```yaml # huds/main_hud.yml main_hud: layouts: 1: name: player_status_layout x: 50 # 50% = center horizontally y: 100 # 100% = bottom of screen 2: name: minimap_layout x: 100 # Right edge y: 0 # Top edge 3: name: quest_tracker x: 0 # Left edge y: 50 # Middle vertically # config.yml - Register default HUDs # default-hud: # - main_hud # - party_hud ``` ## Popup Configuration Popups are triggered HUD elements with duration, animations, and queuing systems. ```yaml # popups/damage_indicator.yml damage_indicator: # Trigger configuration triggers: 1: class: entity_attack # Built-in: entity_attack, entity_damage, entity_kill, entity_dead # Popup behavior group: damage_group # Group related popups unique: false # Allow duplicates queue: true # Queue overflow popups always-check-condition: false # Don't tick queued popups sort: last # Queue order: last (FIFO) or first (LIFO) duration: 40 # Display time in ticks (0 = infinite) key-mapping: true # Unique per entity # Screen position (percentage) x: 50 y: 35 # Movement for stacking multiple popups move: duration: 5 # Max visible at once pixel: x-equation: 0 y-equation: 25 * t # Stack vertically, 25px apart layouts: 1: name: damage_number_layout # Buff indicator popup (condition-based, no trigger) buff_strength: group: buffs unique: true layouts: 1: name: strength_buff_icon x: 90 y: 10 conditions: 1: first: potion_effect_duration:strength second: 0 operation: ">" move: duration: 16 pixel: x-equation: -floor((t - 1) / 4) * 30 y-equation: ((t - 1) % 4) * 30 ``` ## Conditions Configuration Apply conditions to layouts, images, or popups to show/hide elements dynamically. ```yaml # In any layout or popup configuration layout_with_conditions: images: 1: name: low_health_warning x: 0 y: 0 conditions: 1: first: health_percentage second: 0.25 operation: "<=" gate: and # and, or 2: first: dead second: false operation: "==" # Condition operators: >, >=, <, <=, ==, != # For strings, only == and != are available # Using PAPI in conditions (no brackets) complex_conditions: conditions: 1: first: "(number)papi:player_level" second: 10 operation: ">=" 2: first: "papi:player_world" second: "'world_nether'" # String comparison needs quotes operation: "==" ``` ## Font Configuration Configure custom fonts with TrueType files and embedded image glyphs. ```yaml # texts/custom_fonts.yml # Basic TTF font damage_font: file: damage_numbers.ttf scale: 24 height: 8 ascent: 7 # Font with embedded image glyphs icon_font: file: main_font.ttf scale: 16 images: coin: name: "icons/coin.png" x: 0 y: -2 scale: 1.5 heart: name: "icons/heart.png" x: 0 y: 0 scale: 1.0 # Use in patterns: " [vault_money]" # Minecraft default font mimicry minecraft_style: scale: 16 merge-default-bitmap: true use-unifont: true include: - korean - japan - china # Bitmap font from sprite sheet number_sprites: type: bitmap chars: 1: file: "fonts/numbers.png" codepoints: - "0123456789" - "+-=*/%" ``` ## Commands Reference BetterHud provides comprehensive commands for runtime management. ```bash # General commands /hud help [page] # View command list /hud reload # Reload all configurations /hud turn [on/off] # Toggle HUD for self # HUD management /hud hud add # Add HUD to player /hud hud remove # Remove HUD from player # Popup management /hud popup show # Show popup (requires duration set) /hud popup hide # Hide popup /hud popup add # Add default popup /hud popup remove # Remove default popup # Compass management /hud compass add # Add compass /hud compass remove # Remove compass /hud pointer set [icon] # Set compass point /hud pointer clear # Clear compass point ``` ## Skript Integration BetterHud provides native Skript support for compass markers and popups. ```applescript # Compass point management command /addpoint: trigger: # Add location marker to compass point add location at 100, 64, 200 in world "world" named "base" to player # Add with custom icon point add location at 0, 64, 0 in world "world" named "spawn" with icon "spawn_icon" to player command /removepoint: trigger: point remove "base" to player point remove "spawn" to player # Custom popup with variables command /showdamage : trigger: set {_vars::damage} to arg-1 set {_vars::critical} to true show popup "damage_popup" to player with variable of {_vars::*} # Using Skript variables in patterns # pattern: "[skript_variable:%{player_score::%uuid of hud player%}%]" ``` ## Built-in Placeholders Reference BetterHud provides numerous built-in placeholders for common use cases. ```yaml # Usage in patterns: "[placeholder_name]" or "[placeholder_name:arg]" # Player Stats (Number) health # Current health max_health # Maximum health health_percentage # Health as 0-1 food # Hunger level armor # Armor points air # Air bubbles max_air # Maximum air absorption # Absorption hearts level # XP level hotbar_slot # Selected hotbar slot (0-8) last_health # Health before last damage last_health_percentage # Previous health percentage # Player Stats (Boolean) dead # Is player dead frozen # Is player frozen burning # Is player on fire has_main_hand # Has item in main hand has_off_hand # Has item in off hand has_permission:node # Permission check # Player Info (String) name # Player name gamemode # Current gamemode world # Current world name # Vehicle Stats vehicle_health # Mount health vehicle_max_health # Mount max health vehicle_health_percentage # Items total_amount:item_type # Count of item type storage:item_type # Storage count # Potion Effects potion_effect_duration:effect_name # Duration in ticks # Trigger-specific (entity_attack, entity_damage, etc.) entity_name # Target entity name entity_custom_name # Custom name entity_type # Entity type entity_health # Entity current health entity_max_health # Entity max health entity_health_percentage ``` BetterHud excels in creating immersive, animated HUD systems for RPG servers, minigames, and custom game modes. Its integration with MMOCore, MythicMobs, and other popular plugins makes it ideal for servers that need dynamic stat displays, skill cooldowns, party frames, boss health bars, damage numbers, and quest trackers. The trigger system enables contextual popups that respond to combat, achievements, or custom plugin events. For developers, BetterHud's Java API provides full programmatic control over HUD elements, custom placeholders, and event-driven triggers. The configuration-based approach allows server administrators to create complex HUD layouts without coding, while the API enables deep integration with custom plugins. The resource pack auto-generation eliminates manual asset management, and the multi-platform support ensures consistent behavior across Bukkit, Velocity proxies, and Fabric servers.