DeadZone Community Packages
    Preparing search index...

    How to Use 2D and 3D Overlays

    Learn how to display information using the DeadZone overlay system! This comprehensive tutorial covers 2D overlays (text panels on your screen) and 3D overlays (visual markers in the game world), plus how to use utils.js to keep your code clean and maintainable.

    This will cover, main.js, utils.js, config.js and using overlays. This is as technical as it's going to get at the moment.

    Overlays allow you to display information on top of the game client:

    • Text information displayed in a panel on your screen
    • Shows stats, counters, timers, status information
    • Positioned in the top-left by default (user can move them)
    • Updated via setValue() and controlled with setVisible()
    • Visual markers rendered in the 3D game world
    • Highlights NPCs, GameObjects, players, tiles, world points
    • Follows game camera perspective
    • Supports hull highlighting, tile highlighting, and text labels

    Before we begin, let's understand how plugin files work together:

    1. main.js - Your core plugin logic with event handlers
    2. utils.js - Helper functions and utility classes (optional but recommended)
    3. config.js - Configuration items AND overlay definitions
    // config.js structure
    const config = {
    // ConfigItem definitions here
    };

    const overlay = {
    // OverlayItem definitions here
    };

    In this tutorial, you'll create a comprehensive overlay demo plugin featuring:

    • 2D Overlay showing player stats, location, inventory, and runtime
    • 3D Overlays highlighting NPCs, objects, and tiles
    • utils.js with a Logger class to make code cleaner
    • Dynamic updates and proper cleanup
    1. Navigate to https://deadzone.dev
    2. Click "Create Package" in Collection → Private
    3. Fill in:
      • Name: Advanced Overlay Demo
      • Description: Complete demonstration of 2D and 3D overlays with proper code organization
      • Type: Utility
    4. Click "Create"

    Let's start by creating a proper Logger utility in utils.js. This keeps main.js clean and gives us nice logging functions.

    Open utils.js tab and add:

    /**
    * Logger utility class for consistent logging
    * Usage: Logger.info("message"), Logger.error("message"), etc.
    */
    const Logger = (function() {
    const levels = {
    debug: 0,
    info: 1,
    warn: 2,
    error: 3
    };

    let currentLevel = levels.info;

    const log = (message, level = "info") => {
    if (levels[level] >= currentLevel) {
    const timestamp = new Date().toLocaleTimeString();
    const formatted = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
    Utility.print(formatted);
    }
    };

    const Logger = (message, level = "info") => log(message, level);

    // Convenience methods
    Logger.error = (message) => log(message, "error");
    Logger.warn = (message) => log(message, "warn");
    Logger.info = (message) => log(message, "info");
    Logger.debug = (message) => log(message, "debug");

    Logger.setLevel = (level) => {
    currentLevel = levels[level] || levels.info;
    };

    return Logger;
    })();

    /**
    * Utility functions for common tasks
    */
    const Utils = {
    /**
    * Format milliseconds into readable time string
    */
    formatTime: function(milliseconds) {
    const seconds = Math.floor(milliseconds / 1000);
    const minutes = Math.floor(seconds / 60);
    const hours = Math.floor(minutes / 60);

    const s = seconds % 60;
    const m = minutes % 60;

    if (hours > 0) {
    return `${hours}h ${m}m ${s}s`;
    } else if (minutes > 0) {
    return `${m}m ${s}s`;
    } else {
    return `${s}s`;
    }
    },

    /**
    * Count non-null items in inventory
    */
    getInventoryCount: function() {
    const items = Game.info.inventory.getItems();
    let count = 0;
    for (let i = 0; i < items.length; i++) {
    if (items[i] != null) {
    count++;
    }
    }
    return count;
    },

    /**
    * Get player location as formatted string
    */
    getLocationString: function() {
    const player = Client.getLocalPlayer();
    if (!player) return "Unknown";

    const wp = player.getWorldLocation();
    return `${wp.getX()}, ${wp.getY()}, ${wp.getPlane()}`;
    }
    };

    What we created:

    • Logger - A logging utility with debug/info/warn/error levels
    • Utils - Common helper functions we'll use throughout main.js

    Open config.js and add both config AND overlay definitions:

    const config = {
    enable2DOverlay: ConfigItem.createBoolean(
    "enable2DOverlay",
    "Display",
    "Enable 2D Overlay",
    "Show the 2D info panel",
    true
    ),

    enable3DOverlay: ConfigItem.createBoolean(
    "enable3DOverlay",
    "Display",
    "Enable 3D Overlay",
    "Show 3D world markers",
    true
    ),

    highlightNPCs: ConfigItem.createBoolean(
    "highlightNPCs",
    "3D Overlay",
    "Highlight NPCs",
    "Highlight all nearby NPCs",
    false
    ),

    npcHighlightColor: ConfigItem.createString(
    "npcHighlightColor",
    "3D Overlay",
    "NPC Highlight Color",
    "Hex color for NPC highlights (e.g., #FF0000 for red)",
    "#00FF00"
    ),

    highlightDistance: ConfigItem.createIntegerRange(
    "highlightDistance",
    "3D Overlay",
    "Highlight Distance",
    "Maximum distance to highlight objects (tiles)",
    5,
    20,
    10
    ),

    debugMode: ConfigItem.createBoolean(
    "debugMode",
    "Debug",
    "Debug Mode",
    "Enable debug logging",
    false
    )
    };

    // Overlay definitions - part of config.js!
    const overlay = {
    status: OverlayItem.create2d("status", "Status", true, 0),
    runtime: OverlayItem.create2d("runtime", "Runtime", true, 1),
    location: OverlayItem.create2d("location", "Location", true, 2),
    inventory: OverlayItem.create2d("inventory", "Inventory", true, 3),
    health: OverlayItem.create2d("health", "Health", true, 4),
    combat: OverlayItem.create2d("combat", "Combat Lvl", true, 5),
    nearbyNPCs: OverlayItem.create2d("nearbyNPCs", "Nearby NPCs", true, 6)
    };

    Important: Notice that overlay is defined in the same file as config, not separately!

    Now let's build the main plugin logic using our utilities.

    Open main.js and create the basic structure:

    // ============================================
    // Advanced Overlay Demo Plugin
    // Demonstrates 2D and 3D overlays with clean code organization
    // ============================================

    const PLUGIN_NAME = "Overlay Demo";
    const VERSION = "1.0.0";

    // Plugin state
    let isRunning = false;
    let startTime = 0;
    let tickCount = 0;

    // 3D overlay storage
    const overlays3D = new Map();

    function OnStart() {
    isRunning = true;
    startTime = Date.now();
    tickCount = 0;

    // Set logger level based on config
    if (config.debugMode.getValue()) {
    Logger.setLevel("debug");
    }

    Logger.info(`${PLUGIN_NAME} v${VERSION} started`);

    // Initialize 2D overlay
    overlay.status.setValue("Running");
    Game.sendGameMessage("Overlay demo started!", PLUGIN_NAME);
    }

    function OnShutdown() {
    isRunning = false;

    Logger.info(`${PLUGIN_NAME} stopped`);

    // Clean up 2D overlay - DZ Handles this, but why not.
    hideAll2DOverlays();

    // Clean up 3D overlays - DZ also handles this, but why not.
    clearAll3DOverlays();

    Game.sendGameMessage("Overlay demo stopped!", PLUGIN_NAME);
    }

    function OnGameTick() {
    if (!isRunning) return;

    tickCount++;

    try {
    // Update 2D overlay
    if (config.enable2DOverlay.getValue()) {
    update2DOverlay();
    } else {
    hideAll2DOverlays();
    }

    // Update 3D overlays
    if (config.enable3DOverlay.getValue()) {
    update3DOverlays();
    } else {
    clearAll3DOverlays();
    }
    } catch (error) {
    Logger.error(`Error in OnGameTick: ${error}`);
    }
    }

    Add these functions to main.js (using our utils!):

    /**
    * Update all 2D overlay values
    */
    function update2DOverlay() {
    // Status
    overlay.status.setValue("Running");
    overlay.status.setVisible(true);

    // Runtime using our utility function
    const runtime = Date.now() - startTime;
    overlay.runtime.setValue(Utils.formatTime(runtime));
    overlay.runtime.setVisible(true);

    // Location using our utility function
    overlay.location.setValue(Utils.getLocationString());
    overlay.location.setVisible(true);

    // Inventory using our utility function
    const invCount = Utils.getInventoryCount();
    overlay.inventory.setValue(`${invCount}/28`);
    overlay.inventory.setVisible(true);

    // Health
    const currentHP = Client.getBoostedSkillLevels(Skill.HITPOINTS);
    const maxHP = Client.getRealSkillLevels(Skill.HITPOINTS);
    overlay.health.setValue(`${currentHP}/${maxHP}`);
    overlay.health.setVisible(true);

    // Combat level
    const player = Client.getLocalPlayer();
    if (player) {
    const combatLevel = player.getCombatLevel();
    overlay.combat.setValue(combatLevel.toString());
    overlay.combat.setVisible(true);
    }

    // Count nearby NPCs
    const nearbyNPCs = countNearbyNPCs();
    overlay.nearbyNPCs.setValue(`${nearbyNPCs} NPCs`);
    overlay.nearbyNPCs.setVisible(true);
    }

    /**
    * Hide all 2D overlays
    */
    function hideAll2DOverlays() {
    overlay.status.setVisible(false);
    overlay.runtime.setVisible(false);
    overlay.location.setVisible(false);
    overlay.inventory.setVisible(false);
    overlay.health.setVisible(false);
    overlay.combat.setVisible(false);
    overlay.nearbyNPCs.setVisible(false);
    }

    /**
    * Count NPCs within configured distance
    */
    function countNearbyNPCs() {
    const npcs = Game.info.npc.getAll();
    const player = Client.getLocalPlayer();
    if (!player) return 0;

    const playerPos = player.getWorldLocation();
    const maxDistance = config.highlightDistance.getValue();
    let count = 0;

    for (const npc of npcs) {
    const npcPos = npc.getWorldLocation();
    const distance = playerPos.distanceTo(npcPos);

    if (distance <= maxDistance) {
    count++;
    }
    }

    return count;
    }

    What's happening:

    • We're using Logger.error() from utils.js for clean error handling
    • We're using Utils.formatTime() to format the runtime
    • We're using Utils.getLocationString() to format location
    • Code is much more readable than inline logic!

    Now for the powerful part - 3D overlays in the game world!

    The 3D overlay system uses a builder pattern:

    const overlay3D = OverlayItem3D.builder()
    .addGameObject(gameObject) // or .addWorldPoint(wp) or .addNpcByIndex(0) or .addPlayer(player)
    .highlightHull("#FF0000") // Highlight the model outline
    .highlightTile("#00FF00") // Highlight the ground tile
    .addText("Label Text") // Add text label
    .setTickLifetime(10) // Auto-remove after 10 ticks (optional)
    .build(); // Build and activate

    // Later, remove it manually:
    overlay3D.remove();

    Add these functions to main.js:

    /**
    * Update all 3D overlays
    */
    function update3DOverlays() {
    // Clear old overlays first
    clearAll3DOverlays();

    // Highlight NPCs if enabled
    if (config.highlightNPCs.getValue()) {
    highlightNearbyNPCs();
    }

    Logger.debug(`Updated 3D overlays - ${overlays3D.size} active`);
    }

    /**
    * Highlight all nearby NPCs
    */
    function highlightNearbyNPCs() {
    const npcs = Game.info.npc.getAll();
    const player = Client.getLocalPlayer();
    if (!player) return;

    const playerPos = player.getWorldLocation();
    const maxDistance = config.highlightDistance.getValue();
    const color = config.npcHighlightColor.getValue();

    for (const npc of npcs) {
    const npcPos = npc.getWorldLocation();
    const distance = playerPos.distanceTo(npcPos);

    if (distance <= maxDistance) {
    createNPCOverlay(npc, color);
    }
    }
    }

    /**
    * Create a 3D overlay for an NPC
    */
    function createNPCOverlay(npc, color) {
    try {
    const npcPos = npc.getWorldLocation();
    const key = `npc_${npcPos.getX()}_${npcPos.getY()}`;

    const npcName = npc.getName();
    const combatLevel = npc.getCombatLevel();
    const label = `${npcName} (${combatLevel})`;

    const overlay3D = OverlayItem3D.builder()
    .addNpcByIndex(npc.getIndex())
    .highlightHull(color)
    .addText(label)
    .build();

    overlays3D.set(key, overlay3D);

    Logger.debug(`Created overlay for ${npcName} at ${key}`);
    } catch (error) {
    Logger.error(`Failed to create NPC overlay: ${error}`);
    }
    }

    /**
    * Clear all 3D overlays
    */
    function clearAll3DOverlays() {
    overlays3D.forEach((overlay, key) => {
    try {
    overlay.remove();
    } catch (error) {
    Logger.warn(`Failed to remove overlay ${key}: ${error}`);
    }
    });
    overlays3D.clear();
    }
    1. Save all three files (main.js, utils.js, config.js)
    2. Start the plugin in-game
    3. You should see:
      • 2D overlay panel with your stats in bottom-left
      • NPCs highlighted in green (if enabled)
      • Logger messages in console

    Let's add more powerful 3D overlay examples!

    Add this to main.js:

    /**
    * Highlight game objects by ID
    * Example: Highlight all trees, rocks, or specific objects
    */
    function highlightGameObjectsByID(objectIDs, color, label) {
    const objects = Game.info.gameObject.getByID(objectIDs);
    const player = Client.getLocalPlayer();
    if (!player) return;

    const playerPos = player.getWorldLocation();
    const maxDistance = config.highlightDistance.getValue();

    for (const obj of objects) {
    const objPos = obj.getWorldLocation();
    const distance = playerPos.distanceTo(objPos);

    if (distance <= maxDistance) {
    createGameObjectOverlay(obj, color, label);
    }
    }
    }

    /**
    * Create a 3D overlay for a GameObject
    */
    function createGameObjectOverlay(gameObject, color, label) {
    try {
    const objPos = gameObject.getWorldLocation();
    const key = `obj_${objPos.getX()}_${objPos.getY()}_${gameObject.getId()}`;

    const overlay3D = OverlayItem3D.builder()
    .addGameObject(gameObject)
    .highlightHull(color)
    .addText(label)
    .build();

    overlays3D.set(key, overlay3D);
    } catch (error) {
    Logger.error(`Failed to create GameObject overlay: ${error}`);
    }
    }

    // Usage example:
    // highlightGameObjectsByID([1278], "#8B4513", "Tree"); // Highlight trees
    // highlightGameObjectsByID([11387], "#808080", "Rock"); // Highlight granite rocks
    /**
    * Highlight tiles in a radius around the player
    */
    function highlightTilesAroundPlayer(radius, color) {
    const player = Client.getLocalPlayer();
    if (!player) return;

    const playerPos = player.getWorldLocation();
    const playerX = playerPos.getX();
    const playerY = playerPos.getY();
    const playerZ = playerPos.getPlane();

    for (let dx = -radius; dx <= radius; dx++) {
    for (let dy = -radius; dy <= radius; dy++) {
    const x = playerX + dx;
    const y = playerY + dy;
    const wp = new WorldPoint(x, y, playerZ);

    createTileOverlay(wp, color);
    }
    }
    }

    /**
    * Create a 3D overlay for a tile
    */
    function createTileOverlay(worldPoint, color) {
    try {
    const key = `tile_${worldPoint.getX()}_${worldPoint.getY()}`;

    const overlay3D = OverlayItem3D.builder()
    .addWorldPoint(worldPoint)
    .highlightTile(color)
    .build();

    overlays3D.set(key, overlay3D);
    } catch (error) {
    Logger.error(`Failed to create tile overlay: ${error}`);
    }
    }

    // Usage:
    // highlightTilesAroundPlayer(3, "#0000FF"); // Blue tiles in 3 tile radius
    /**
    * Highlight nearby players (useful for PvP or multiplayer areas)
    */
    function highlightNearbyPlayers() {
    const players = Game.info.getPlayers();
    const localPlayer = Client.getLocalPlayer();
    if (!localPlayer) return;

    const localPos = localPlayer.getWorldLocation();
    const maxDistance = config.highlightDistance.getValue();

    for (const player of players) {
    // Skip local player
    if (player === localPlayer) continue;

    const playerPos = player.getWorldLocation();
    const distance = localPos.distanceTo(playerPos);

    if (distance <= maxDistance) {
    createPlayerOverlay(player, "#FF0000"); // Red for other players
    }
    }
    }

    /**
    * Create a 3D overlay for a player
    */
    function createPlayerOverlay(player, color) {
    try {
    const playerName = player.getName();
    const key = `player_${playerName}`;

    const combatLevel = player.getCombatLevel();
    const label = `${playerName} (${combatLevel})`;

    const overlay3D = OverlayItem3D.builder()
    .addPlayer(player)
    .highlightHull(color)
    .addText(label)
    .build();

    overlays3D.set(key, overlay3D);
    } catch (error) {
    Logger.error(`Failed to create player overlay: ${error}`);
    }
    }
    /**
    * Highlight a path of tiles
    * @param {Array<WorldPoint>} path - Array of WorldPoints forming a path
    */
    function highlightPath(path, color) {
    for (let i = 0; i < path.length; i++) {
    const wp = path[i];
    const key = `path_${i}_${wp.getX()}_${wp.getY()}`;

    try {
    const overlay3D = OverlayItem3D.builder()
    .addWorldPoint(wp)
    .highlightTile(color)
    .addText(`Step ${i + 1}`)
    .build();

    overlays3D.set(key, overlay3D);
    } catch (error) {
    Logger.error(`Failed to create path overlay: ${error}`);
    }
    }
    }

    // Usage:
    // const path = [
    // new WorldPoint(3200, 3200, 0),
    // new WorldPoint(3201, 3200, 0),
    // new WorldPoint(3202, 3200, 0)
    // ];
    // highlightPath(path, "#FFFF00"); // Yellow path
    /**
    * Create a temporary marker that auto-removes
    */
    function createTemporaryMarker(worldPoint, color, text, lifetimeTicks) {
    const key = `temp_${Date.now()}_${worldPoint.getX()}_${worldPoint.getY()}`;

    try {
    const overlay3D = OverlayItem3D.builder()
    .addWorldPoint(worldPoint)
    .highlightTile(color)
    .addText(text)
    .setTickLifetime(lifetimeTicks) // Auto-remove after X ticks
    .build();

    overlays3D.set(key, overlay3D);

    Logger.debug(`Created temporary marker at ${worldPoint.getX()}, ${worldPoint.getY()} for ${lifetimeTicks} ticks`);
    } catch (error) {
    Logger.error(`Failed to create temporary marker: ${error}`);
    }
    }

    // Usage:
    // const player = Client.getLocalPlayer();
    // const pos = player.getWorldLocation();
    // createTemporaryMarker(pos, "#FF00FF", "I was here!", 50); // Lasts 50 ticks (~30 seconds)
    function OnShutdown() {
    clearAll3DOverlays(); // DZ usually does this on shutdown - but why not!
    }

    Don't repeat yourself - put common functions in utils.js:

    // Bad: Repeated in main.js multiple times
    const seconds = Math.floor(milliseconds / 1000);
    const minutes = Math.floor(seconds / 60);
    // ... etc

    // Good: Use Utils.formatTime() from utils.js
    const formatted = Utils.formatTime(milliseconds);
    Logger.debug("Detailed info for debugging");  // Only when debugMode enabled
    Logger.info("Normal operational messages"); // General info
    Logger.warn("Something unexpected happened"); // Warnings
    Logger.error("Critical error occurred"); // Errors
    const overlays3D = new Map();

    // Add with unique key
    overlays3D.set("npc_123", overlay);

    // Clear all at once
    overlays3D.forEach(o => o.remove());
    overlays3D.clear();
    const player = Client.getLocalPlayer();
    if (!player) return; // Always check!

    const npc = Game.info.npc.getNearest([1, 2, 3]);
    if (!npc) return; // Always check!
    // Good: Clear what this overlay represents
    const key = `npc_${npcId}_${x}_${y}`;

    // Bad: Can't tell what this is
    const key = `${i}`;

    Use these hex colors for highlighting:

    const COLORS = {
    GREEN: "#00FF00", // Success, available
    RED: "#FF0000", // Danger, error
    BLUE: "#0000FF", // Info, water
    YELLOW: "#FFFF00", // Warning, important
    ORANGE: "#FFA500", // Caution
    PURPLE: "#800080", // Special, rare
    CYAN: "#00FFFF", // Highlight, active
    WHITE: "#FFFFFF", // Neutral
    GRAY: "#808080", // Disabled, depleted
    };

    Here's a complete, working plugin combining everything:

    const config = {
    enable2D: ConfigItem.createBoolean(
    "enable2D", "Display", "Enable 2D Overlay",
    "Show info panel", true
    ),
    enable3D: ConfigItem.createBoolean(
    "enable3D", "Display", "Enable 3D Overlay",
    "Show world markers", true
    ),
    highlightNPCs: ConfigItem.createBoolean(
    "highlightNPCs", "3D", "Highlight NPCs",
    "Highlight all nearby NPCs", false
    ),
    npcColor: ConfigItem.createString(
    "npcColor", "3D", "NPC Color",
    "Hex color for NPCs", "#00FF00"
    ),
    maxDistance: ConfigItem.createIntegerRange(
    "maxDistance", "3D", "Max Distance",
    "Max highlight distance", 5, 20, 10
    ),
    debugMode: ConfigItem.createBoolean(
    "debugMode", "Debug", "Debug Mode",
    "Enable debug logging", false
    )
    };

    const overlay = {
    status: OverlayItem.create2d("status", "Status", true, 0),
    runtime: OverlayItem.create2d("runtime", "Runtime", true, 1),
    location: OverlayItem.create2d("location", "Location", true, 2),
    inventory: OverlayItem.create2d("inventory", "Inventory", true, 3)
    };
    const Logger = (function() {
    const levels = { debug: 0, info: 1, warn: 2, error: 3 };
    let currentLevel = levels.info;

    const log = (message, level = "info") => {
    if (levels[level] >= currentLevel) {
    Utility.print(`[${level.toUpperCase()}] ${message}`);
    }
    };

    const Logger = (message, level = "info") => log(message, level);
    Logger.error = (m) => log(m, "error");
    Logger.warn = (m) => log(m, "warn");
    Logger.info = (m) => log(m, "info");
    Logger.debug = (m) => log(m, "debug");
    Logger.setLevel = (level) => { currentLevel = levels[level] || levels.info; };

    return Logger;
    })();

    const Utils = {
    formatTime: function(ms) {
    const s = Math.floor(ms / 1000);
    const m = Math.floor(s / 60);
    const h = Math.floor(m / 60);
    if (h > 0) return `${h}h ${m % 60}m ${s % 60}s`;
    if (m > 0) return `${m}m ${s % 60}s`;
    return `${s}s`;
    },
    getInventoryCount: function() {
    return Game.info.inventory.getItems().filter(i => i != null).length;
    }
    };
    const PLUGIN_NAME = "Overlay Demo";
    let isRunning = false;
    let startTime = 0;
    const overlays3D = new Map();

    function OnStart() {
    isRunning = true;
    startTime = Date.now();
    if (config.debugMode.getValue()) Logger.setLevel("debug");
    Logger.info(`${PLUGIN_NAME} started`);
    overlay.status.setValue("Running");
    }

    function OnShutdown() {
    isRunning = false;
    Logger.info(`${PLUGIN_NAME} stopped`);
    hideAll2DOverlays();
    clearAll3DOverlays();
    }

    function OnGameTick() {
    if (!isRunning) return;
    try {
    if (config.enable2D.getValue()) {
    update2DOverlay();
    }
    if (config.enable3D.getValue() && config.highlightNPCs.getValue()) {
    highlightNearbyNPCs();
    }
    } catch (error) {
    Logger.error(`Error: ${error}`);
    }
    }

    function update2DOverlay() {
    overlay.status.setValue("Running");
    overlay.runtime.setValue(Utils.formatTime(Date.now() - startTime));
    const player = Client.getLocalPlayer();
    if (player) {
    const wp = player.getWorldLocation();
    overlay.location.setValue(`${wp.getX()}, ${wp.getY()}`);
    }
    overlay.inventory.setValue(`${Utils.getInventoryCount()}/28`);
    }

    function hideAll2DOverlays() {
    overlay.status.setVisible(false);
    overlay.runtime.setVisible(false);
    overlay.location.setVisible(false);
    overlay.inventory.setVisible(false);
    }

    function highlightNearbyNPCs() {
    clearAll3DOverlays();
    const npcs = Game.info.npc.getAll();
    const player = Client.getLocalPlayer();
    if (!player) return;

    const playerPos = player.getWorldLocation();
    const maxDist = config.maxDistance.getValue();
    const color = config.npcColor.getValue();

    for (const npc of npcs) {
    const npcPos = npc.getWorldLocation();
    if (playerPos.distanceTo(npcPos) <= maxDist) {
    createNPCOverlay(npc, color);
    }
    }
    }

    function createNPCOverlay(npc, color) {
    try {
    const key = `npc_${npc.getIndex()}`;
    const overlay3D = OverlayItem3D.builder()
    .addNpcByIndex(npc.getIndex())
    .highlightHull(color)
    .addText(npc.getName())
    .build();
    overlays3D.set(key, overlay3D);
    } catch (error) {
    Logger.error(`Failed to create NPC overlay: ${error}`);
    }
    }

    function clearAll3DOverlays() {
    overlays3D.forEach(o => {
    try { o.remove(); } catch (e) {}
    });
    overlays3D.clear();
    }

    Solutions:

    • Check that config.enable3DOverlay is true
    • Verify .build() is called after setting up the builder
    • Ensure the object/NPC/tile actually exists
    • Check distance - might be too far away
    • Look for errors in Logger output

    Solutions:

    • Always call .remove() on each overlay
    • Clear your Map/storage in OnShutdown
    • Don't forget to call clearAll3DOverlays() before creating new ones

    Solutions:

    • Don't create too many 3D overlays at once (limit to nearby objects)
    • Clear old overlays before creating new ones
    • Use setTickLifetime() for temporary markers
    • Avoid updating 3D overlays every tick if not necessary

    You've mastered the DeadZone overlay system! You now know:

    • ✅ How to organize code with utils.js
    • ✅ How to create and update 2D overlays
    • ✅ How to create powerful 3D overlays with OverlayItem3D
    • ✅ How to highlight NPCs, objects, players, and tiles
    • ✅ How to clean up overlays properly
    • ✅ Best practices for maintainable code
    • Combine overlays with your chatbot plugin
    • Create a skill training helper with visual markers
    • Build a quest helper with path highlighting
    • Make a PvP helper with player tracking
    • Share your creations with the community!

    You now have complete mastery of the DeadZone overlay system! 🎮