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:
setValue()
and controlled with setVisible()
Before we begin, let's understand how plugin files work together:
// 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:
Advanced Overlay Demo
Complete demonstration of 2D and 3D overlays with proper code organization
Utility
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:
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:
Logger.error()
from utils.js for clean error handlingUtils.formatTime()
to format the runtimeUtils.getLocationString()
to format locationNow 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();
}
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:
config.enable3DOverlay
is true.build()
is called after setting up the builderSolutions:
.remove()
on each overlayclearAll3DOverlays()
before creating new onesSolutions:
setTickLifetime()
for temporary markersYou've mastered the DeadZone overlay system! You now know:
You now have complete mastery of the DeadZone overlay system! 🎮