Skip to main content

Actions

Actions are code snippets executed when a log event is imported from the game server. Use them to process data and automate responses.

Actions now support both a server-local workflow and a marketplace workflow. For the shared model behind marketplace, installed, and custom actions, see Action marketplace concept.

Data source note: actions depend on imported server logs. If logs arrive later, action execution is also delayed. For full import/freshness behavior, see Logs import concept.

  • Server Admin -> Actions

Tabsโ€‹

The actions screen is split into three tabs:

  • Marketplace: browse public creator actions and install them on the current server
  • Installed: manage marketplace actions already installed on this server
  • Custom Actions: create and maintain server-owned actions with local Lua code

Installed marketplace actions inherit their code, event trigger, and config schema from the creator definition. Server staff only manage the active state and configuration values for that server.

Access requirementsโ€‹

Actions now have three permission levels:

  • actions.edit: Allows editing server-side configuration values and enabling or disabling installed or custom actions
  • actions.manage_marketplace: Allows installing marketplace actions and removing installed marketplace actions
  • actions.edit_code: Allows creating and maintaining custom actions, including the Lua code and configuration schema

This separation allows server administrators to distinguish between operational action settings, marketplace management, and local code authoring.

Action language: Luaโ€‹

The actions are written in Lua, a lightweight, high-level, multi-paradigm programming language designed primarily for embedded use in applications.

Action scripts run directly. Two globals are available:

  • data: Event payload of the current log event
  • config: Resolved action configuration values

To preview values while building scripts, use the print function.

Here is an example of a simple action that prints the log event data to the console:

print(data);

Configuration fieldsโ€‹

Actions support user-defined configuration fields that allow you to parameterize your Lua scripts without editing the code. This is useful for:

  • Allowing moderators to adjust action behavior without code access
  • Reusing the same action template with different settings
  • Keeping sensitive values (like channel IDs) separate from the code

For marketplace actions, these fields are defined by the creator. Server admins only set the values in the installed server copy.

Defining configuration fieldsโ€‹

For custom actions, configuration fields are defined in the Code tab using the configuration schema builder.

For marketplace actions, configuration fields are defined in Creator Space by the action author and then appear on each server installation.

Each field has:

  • Name: Key used in Lua (config.<name>)
  • Type: see Field types below
  • Default: Fallback value used when no value is saved in Settings
  • Description: Help text shown in the Settings tab

Recommended naming for field names: camelCase or snake_case without spaces.

Setting configuration valuesโ€‹

After a field is defined in the Code tab, users with actions.edit can set its value in the Settings tab.

At runtime, config values are resolved as:

  1. Saved value from Settings tab (if present)
  2. Otherwise the field default from schema
  3. Otherwise nil

Only fields defined in the schema are exposed in config.

Field typesโ€‹

TypeLua valueNotes
stringstring|nilSingle-line text; empty โ†’ nil
textareastring|nilMulti-line text; identical to string in Lua
emailstring|nilEmail address input; identical to string in Lua
numbernumber|nilParsed as int or float
booleanbool|nilCheckbox; empty โ†’ nil
coordinatetable|nilMap picker โ€” access .x, .z, optionally .y, .radius
generic_selectstring|nil or string[]Dropdown from a fixed list; string[] when multiple: true
faction_selectstring|nil or string[]Server's faction IDs
player_selectstring|nil or string[]Server's player usernames
relation_selectstring[]Always an array; never nil
discord_channel_selectstring|nil or string[]Discord text channel IDs
discord_category_selectstring|nil or string[]Discord category IDs
discord_role_selectstring|nil or string[]Discord role IDs
discord_guild_selectstring|nil or string[]Discord guild IDs
object_listtable[]Repeatable list of objects; never nil โ€” empty returns {}

Empty values are treated as nil unless noted otherwise.

Pick the type by what the field identifies, not by the Lua value it yields: a Discord channel, role, category, or guild โ†’ the matching discord_*_select; a player โ†’ player_select; a faction โ†’ faction_select; a fixed set of choices โ†’ generic_select. A select's value is already the ID (or username) string the action functions expect, while a plain string field leaves an error-prone free-text box. Use string only for genuinely free-form text.

Using configuration in Luaโ€‹

Configuration values are available via the config table:

-- Access configuration values
discord.send_to_channel(config.channelId, "Player killed: " .. data.victim.username);

if config.notifyOnHeadshot and data.weapon == "Mosin" then
discord.send_to_channel(config.channelId, "Mosin kill detected");
end

if data.distance > config.minDistanceForBonus then
player.change_balance(data.killer.username, config.bonusAmount, "Long range kill bonus");
end

Example configurationโ€‹

For a kill notification action, you might define:

NameTypeDefaultDescription
channelIddiscord_channel_selectDiscord channel for notifications
notifyOnHeadshotbooleantrueSend extra message for headshots
minDistanceForBonusnumber200Minimum distance for bonus reward
bonusAmountnumber50Currency bonus for long-range kills

Moderators can then adjust these values in the Settings tab without needing access to the code.

Functionsโ€‹

To customize the action taken on a new log entry, the code editor provides searchable autocomplete using [CTRL] + [SPACE].

The following functions are available to use in the actions:

Prints the data to the console.

print({name = "John", age = 30}); -- this will be printed as json
print("Hello, World!");

Server namespaceโ€‹

The server namespace contains all server-related functions.

server.name()โ€‹

Returns the name of the server.

print(server.name);

server.current_time()โ€‹

Returns the current time on the server.

print(server.current_time);

server.ban_player(username: string, reason: string = "", length: string = "99 years")โ€‹

Creates a ban restriction for the player with the given username. The ban is visible and manageable in Admin โ†’ Restrictions. The ban applies to this server only โ€” it does not propagate to other servers in the organization.

server.ban_player("John");
server.ban_player("John", "Cheating");
server.ban_player("John", "Cheating", "1 day");

server.currency()โ€‹

Returns the organization's in-game currency symbol as configured in Settings โ†’ Economy. The symbol is shared across all servers in the organization.

print(server.currency); -- e.g. "Coins"
discord.send_to_channel(config.channelId, "You earned 100 " .. server.currency .. "!");

Discord namespaceโ€‹

The discord namespace contains all Discord-related functions.

Attention: All Discord ID must be represented as strings (encapsulated in quotes), otherwise the programming language will interpret it as a Number and represents ints mathematically instead of the raw number and your action will fail.

discord.send_to_player(username: string, content: string)โ€‹

Sends a message to the player with the given username.

discord.send_to_player("John", "Hello, John!");
discord.send_to_player("h3eh23ieuh3ei2he3", "Hello, John!"); -- throws an "player not found" exception that stops the action

discord.send_to_channel(channel: string, content: string)โ€‹

Sends a message to the channel with the given ID.

discord.send_to_channel("1234567890", "Hello, World!");

discord.mention(username: string)โ€‹

Returns the mention string for the player with the given username. This can be used within discord messages to reference the discord account. If no player is found, it will return the username with no formatting.

print(discord.mention("John")); -- returns "<@1234567890>"
print(discord.mention("h3eh23ieuh3ei2he3")); -- returns "h3eh23ieuh3ei"

Player namespaceโ€‹

The player namespace contains all player-related functions.

player.change_balance(username: string, amount: number, description: string = "")โ€‹

Changes the balance of the player with the given username. The balance is the player's single, org-wide wallet โ€” shared across every server in the organization, not a per-server total. The player is looked up by username on the current server.

player.change_balance("John", 100);
player.change_balance("John", 100, "Nice shot!");
player.change_balance("John", -100, "Terrible shot!"); -- negative amount to subtract

player.get_balance(username: string)โ€‹

Returns the player's current org-wide balance โ€” their single balance across the whole organization โ€” or nil if no matching player exists on the current server.

local balance = player.get_balance("John");
if balance ~= nil then
print("Balance: " .. balance);
end

player.statistics(username: string)โ€‹

Returns a statistics object for the player with the given username. Returns nil if no matching player exists.

local stats = player.statistics("John");
print(stats.online_streak); -- prints the current online day streak

Return fields:

FieldTypeDescription
pvp_killsintTotal PvP kills
last_pvp_killstring|nilTimestamp of last kill
pvp_deathsintTotal PvP deaths
last_pvp_deathstring|nilTimestamp of last PvP death
pvp_kdfloatKill/death ratio
deathsintTotal deaths (all causes)
kill_streakintKills since last death
death_streakintDeaths since last kill
longest_killfloatLongest kill distance in metres
online_streakintConsecutive calendar days with at least one session (including today)

Example output:

{
"pvp_kills": 8,
"last_pvp_kill": "2024-06-19 22:32:23",
"pvp_deaths": 23,
"last_pvp_death": "2024-06-19 22:49:19",
"pvp_kd": 0.35,
"deaths": 58,
"kill_streak": 0,
"death_streak": 2,
"longest_kill": 44.78,
"online_streak": 3
}
Example: Online streak rewardโ€‹

Give a bonus that scales with how many consecutive days the player has been online:

local stats = player.statistics(data.player.username);

if stats.online_streak >= 3 then
local bonus = config.base_reward + (stats.online_streak * config.streak_bonus);
player.change_balance(data.player.username, bonus, "Online-Streak: " .. stats.online_streak .. " Tage");
else
player.change_balance(data.player.username, config.base_reward, "Tรคgliche Belohnung");
end

player.get_faction(username: string)โ€‹

Returns a faction data object for the player's current faction, or nil if the player was not found or is not in a faction.

local faction = player.get_faction("John");

if faction then
print(faction.id); -- e.g. 42
print(faction.name); -- e.g. "Wolves"
end

Return fields:

FieldTypeDescription
idintFaction ID
namestringFaction name
colorstring|nilFaction colour hex code
imagestring|nilFaction image path
balanceint|nilCurrent faction treasury balance
discord_role_idstring|nilLinked Discord role ID

Example output:

{
"id": 42,
"name": "Wolves",
"color": "#ff0000",
"image": null,
"balance": 1500,
"discord_role_id": "1234567890"
}
Example: Reward an entire faction on a killโ€‹
local faction = player.get_faction(data.killer.username);

if faction then
faction.change_balance(faction.id, config.faction_reward, "Kill reward");
discord.send_to_channel(config.channel, faction.name .. " earned " .. config.faction_reward .. " for a kill!");
end
note

player.get_faction previously returned the faction ID as a plain string. It now returns an object โ€” use .id to access the ID.

player.get_name(username: string)โ€‹

Resolves the player's display name. If no name is set for the user or no matching user is found, it returns the input username.

player.get_name("johnnyboy420"); -- outputs the display name of the player: "John Doe"
player.get_faction("nouserwiththatusername"); -- user is not found, so it returns the input: "nouserwiththatusername"

player.get_main_account(username: string)โ€‹

Resolves the main account username for a player. Walks through all players that share at least one device with the given player and returns the username of the first one that has a user account linked. If the player itself has a linked user account, its own username is returned. If no linked account can be found or the player is unknown, the input username is returned unchanged.

player.get_main_account("alt_account");   -- returns "main_account" if the alt shares a device with the main
player.get_main_account("solo_player"); -- returns "solo_player" if no linked main account exists
Example: Credit rewards to the main accountโ€‹
local main = player.get_main_account(data.killer.username);
player.change_balance(main, config.reward or 50, "Kill reward (via " .. data.killer.username .. ")");

player.has_discord_role(username: string, role: string)โ€‹

Checks if the player with the given username has the specified Discord role. Accepts both the role name and the role ID.

player.has_discord_role("John", "6553535632356535564"); -- works with role IDs
player.has_discord_role("John", "Admin"); -- works with role names as well

player.ensure_discord_role(username: string, roleId: string)โ€‹

Grants the specified Discord role to the player. If the player already has the role, nothing happens. The role must be identified by its Discord role ID.

player.ensure_discord_role("John", "1234567890123456789");

player.remove_discord_role(username: string, roleId: string)โ€‹

Removes the specified Discord role from the player. If the player does not have the role, nothing happens. The role must be identified by its Discord role ID.

player.remove_discord_role("John", "1234567890123456789");

player.list(filters?: table)โ€‹

Returns a list of all players on the server, optionally filtered. Calling it without arguments returns every player.

local players = player.list();

local vip_players = player.list({ group = "vip" });

local active_with_discord = player.list({
has_discord = true,
last_online_after = "-7d",
});

Available filters (all optional):

FilterTypeDescription
groupstringOnly players who are a member of this group (by name)
whitelistedboolOnly whitelisted / non-whitelisted players
bannedboolOnly players who have an active ban restriction / players without one
has_discordboolOnly players with a linked Discord account
has_balance_abovenumberOnly players whose balance is above this value
has_balance_belownumberOnly players whose balance is below this value
last_online_afterstringOnly players last seen after this time. Supports relative formats like "-7d", "-2h", "-1m", "-1y" or an absolute datetime string
last_online_beforestringOnly players last seen before this time. Same format as above
in_factionbool or stringtrue = in any faction, false = in no faction, or a faction name/id to match a specific faction
variablestring or tableOnly players who have a variable with this name set. Pass a string to match by name only, or a table { name = "...", value = "..." } to also match a specific value

Return fields (one entry per matching player):

FieldTypeDescription
idintInternal player ID
usernamestringPlayer username
balanceint|nilCurrent balance
last_onlinestring|nilLast seen timestamp (Y-m-d H:i:s)
whitelistedboolWhether the player is whitelisted
bannedboolWhether the player has an active ban restriction
Example: Daily balance reward for all VIP membersโ€‹
local players = player.list({
group = "vip",
last_online_after = "-30d",
});

for _, p in ipairs(players) do
player.change_balance(p.username, 100, "Monthly VIP bonus");
end
Example: Notify inactive players via Discordโ€‹
local inactive = player.list({
has_discord = true,
last_online_before = "-14d",
banned = false,
});

for _, p in ipairs(inactive) do
discord.send_to_player(p.username, "We miss you on the server! Come back soon.");
end
Example: Filter by variable valueโ€‹
-- All players whose "vip_tier" variable is set to "gold"
local gold_vips = player.list({ variable = { name = "vip_tier", value = "gold" } });

for _, p in ipairs(gold_vips) do
player.change_balance(p.username, 500, "Gold VIP daily bonus");
end

player.get_variable(username: string, name: string)โ€‹

Returns the value of a player variable, or nil if the variable is not set.

local result = player.get_variable("John", "kill_count");
print(result.value); -- prints the stored value, or nil

Return fields:

FieldTypeDescription
namestringVariable name
valuestring|nilStored value, or nil if not set

player.set_variable(username: string, name: string, value: string|nil)โ€‹

Creates or updates a player variable. Pass nil as value to clear it.

player.set_variable("John", "kill_count", "42");
player.set_variable("John", "vip_tier", "gold");
player.set_variable("John", "temp_flag", nil); -- clears the variable
Example: Track kill streaks with variablesโ€‹
local streak_result = player.get_variable(data.killer.username, "kill_streak");
local streak = tonumber(streak_result.value) or 0;
streak = streak + 1;

player.set_variable(data.killer.username, "kill_streak", tostring(streak));

if streak >= 5 then
player.change_balance(data.killer.username, 250, "Kill streak x" .. streak);
discord.send_to_channel(config.feedChannel, data.killer.username .. " is on a " .. streak .. "-kill streak!");
end

-- Reset the victim's streak
player.set_variable(data.victim.username, "kill_streak", "0");

player.last_position(username: string)โ€‹

Returns the player's last known in-game position, taken from their most recent online ping (recorded roughly every 5 minutes while they are online), or nil if the player is not found or has no recorded position. The position can be minutes or days old โ€” check the time field when freshness matters.

local pos = player.last_position(data.target.username);
if pos then
discord.send_to_channel(config.channel, "Last seen at " .. string.format("%.0f / %.0f", pos.x, pos.z));
end

Return fields:

FieldTypeDescription
xfloatEast/west world coordinate
yfloatHeight
zfloatNorth/south world coordinate
timestringWhen the position was recorded (YYYY-MM-DD HH:MM:SS, UTC)

Faction namespaceโ€‹

The faction namespace contains all faction related action functions.

faction.change_balance(id: string, amount: number, description: string = "")โ€‹

Changes the balance of the faction with the given id.

faction.change_balance("123", 100);
faction.change_balance("123", 100, "Good guys!");
faction.change_balance("123", -100, "Rule penalty!"); -- negative amount to subtract

Bounty namespaceโ€‹

The bounty namespace exposes the bounties of the hitman system.

bounty.list()โ€‹

Returns all open bounties on the server, newest first. Completed or cancelled bounties are never included. Returns an empty list when no bounty is open, so iterating is always safe.

local bounties = bounty.list();
print("Open bounties: " .. #bounties);

Return fields (one entry per bounty):

FieldTypeDescription
idintInternal bounty ID
targetstringUsername of the player the bounty is on
created_bystringUsername of the player who placed the bounty
amountintBounty amount
reasonstring|nilReason given when placing the bounty
target_all_accountsboolWhether killing any account linked to the target's device pays out
created_atstringWhen the bounty was placed (YYYY-MM-DD HH:MM:SS, UTC)

Several bounties can exist on the same target โ€” group by target yourself if you need one entry per player.

Example: Scheduled bounty status postโ€‹

Post the last known position of every player with an open bounty, e.g. from a scheduled custom action. No message is sent when no bounty is open:

local bounties = bounty.list();
if #bounties > 0 then
local lines = {};
for _, b in ipairs(bounties) do
local pos = player.last_position(b.target);
local where = pos and string.format("%.0f / %.0f", pos.x, pos.z) or "unknown";
table.insert(lines, b.target .. " (" .. b.amount .. ") โ€” last seen: " .. where);
end
discord.send_to_channel(config.channel, "**Open bounties:**\n" .. table.concat(lines, "\n"));
end

Math namespaceโ€‹

The math namespace contains utility functions for calculations.

math.distance(x1: number, z1: number, x2: number, z2: number)โ€‹

Calculates the 2D distance between two points. Useful for checking if a position is within a certain radius of another position.

-- Calculate distance between two coordinates
local dist = math.distance(1000, 2000, 1500, 2500);
print("Distance: " .. dist); -- outputs approximately 707.1
Example: Radius checkโ€‹

Check if a kill happened within a certain radius of a location (e.g., a safezone or base):

local safezone_x = 5000;
local safezone_z = 5000;
local safezone_radius = 100;

local kill_distance = math.distance(data.x, data.z, safezone_x, safezone_z);

if kill_distance < safezone_radius then
discord.send_to_channel(config.alertChannel, "Kill detected inside safezone!");
server.ban_player(data.killer, "Killing in safezone", "1 day");
end
Example: Reward long-distance killsโ€‹
-- Calculate distance between killer and victim
local kill_range = math.distance(data.killerX, data.killerZ, data.victimX, data.victimZ);

if kill_range > 500 then
player.change_balance(data.killer, 100, "Long range kill bonus (" .. math.floor(kill_range) .. "m)");
discord.send_to_channel(config.feedChannel, data.killer .. " scored a " .. math.floor(kill_range) .. "m kill!");
end

HTTP namespaceโ€‹

The http namespace allows actions to make outbound HTTP requests to external services. Requests have a 5-second timeout.

http.get(url: string, headers?: table)โ€‹

Performs an HTTP GET request. Returns two values: the HTTP status code and the response body as a string.

local status, body = http.get("https://api.example.com/data");
print(status); -- e.g. 200
print(body); -- raw response body string

With custom headers:

local status, body = http.get("https://api.example.com/data", {
["Authorization"] = "Bearer mytoken",
["Accept"] = "application/json",
});

http.post(url: string, body?: string, headers?: table)โ€‹

Performs an HTTP POST request. Returns two values: the HTTP status code and the response body as a string.

local status, body = http.post("https://api.example.com/webhook", "payload=hello");

Sending a JSON body:

local payload = json.encode({ event = "kill", player = data.killer.username });
local status, body = http.post("https://api.example.com/events", payload, {
["Content-Type"] = "application/json",
["Authorization"] = "Bearer mytoken",
});
Example: Notify an external webhook on a kill eventโ€‹
local payload = json.encode({
killer = data.killer.username,
victim = data.victim.username,
weapon = data.weapon,
});

local status, body = http.post(config.webhookUrl, payload, {
["Content-Type"] = "application/json",
});

if status ~= 200 then
print("Webhook failed with status: " .. status);
end

JSON namespaceโ€‹

The json namespace provides JSON encoding and decoding to work with API responses and payloads.

json.encode(value)โ€‹

Encodes a Lua value (table, string, number, boolean) as a JSON string.

local encoded = json.encode({ name = "John", score = 42 });
print(encoded); -- {"name":"John","score":42}

json.decode(json: string)โ€‹

Parses a JSON string and returns a Lua table.

local decoded = json.decode('{"name":"John","score":42}');
print(decoded.name); -- John
print(decoded.score); -- 42
Example: Call a JSON API and use the responseโ€‹
local status, body = http.get("https://api.example.com/players/" .. data.player.username);

if status == 200 then
local result = json.decode(body);
if result.vip == true then
player.change_balance(data.player.username, config.vipBonus, "VIP bonus");
end
end

In-app eventsโ€‹

Most action triggers come directly from game server logs (kills, connections, base building, etc.). A small number of events are generated by dzbot itself when something happens inside the platform. These synthetic events go through the same action pipeline as game log events.

bounty_placedโ€‹

Fires whenever a player places a bounty on another player via the shop. Use this trigger to notify the target, post to a Discord channel, or run any other automation when a bounty goes live.

Event payload:

FieldTypeDescription
data.target.usernamestringUsername of the player with the bounty on their head
data.target.identifierstringSteam/DayZ ID of the target
data.placedBy.usernamestringUsername of the player who placed the bounty
data.placedBy.identifierstringSteam/DayZ ID of the placer
data.amountintBounty size in server currency
data.reasonstring|nilOptional reason entered by the placer
data.bountyIdint|nilInternal bounty ID

Example โ€” DM the targeted player:

local reason = data.reason or "No reason given";
discord.send_to_player(
data.target.username,
"A bounty of **" .. data.amount .. "** has been placed on your head by "
.. data.placedBy.username .. ".\n> " .. reason
);

Example โ€” Post to a channel:

local channelId = config.channel or "";
if channelId == "" then return end;

local reason = data.reason or "No reason given";
discord.send_to_channel(
channelId,
"**Bounty placed!** " .. discord.mention(data.placedBy.username)
.. " put a bounty of **" .. data.amount .. "** on "
.. discord.mention(data.target.username) .. ".\n> *" .. reason .. "*"
);