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.
Navigation pathโ
Server Admin->Actions
Tabsโ
The actions screen is split into three tabs:
Marketplace: browse public creator actions and install them on the current serverInstalled: manage marketplace actions already installed on this serverCustom 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 eventconfig: 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:
- Saved value from Settings tab (if present)
- Otherwise the field default from schema
- Otherwise
nil
Only fields defined in the schema are exposed in config.
Field typesโ
| Type | Lua value | Notes |
|---|---|---|
string | string|nil | Single-line text; empty โ nil |
textarea | string|nil | Multi-line text; identical to string in Lua |
email | string|nil | Email address input; identical to string in Lua |
number | number|nil | Parsed as int or float |
boolean | bool|nil | Checkbox; empty โ nil |
coordinate | table|nil | Map picker โ access .x, .z, optionally .y, .radius |
generic_select | string|nil or string[] | Dropdown from a fixed list; string[] when multiple: true |
faction_select | string|nil or string[] | Server's faction IDs |
player_select | string|nil or string[] | Server's player usernames |
relation_select | string[] | Always an array; never nil |
discord_channel_select | string|nil or string[] | Discord text channel IDs |
discord_category_select | string|nil or string[] | Discord category IDs |
discord_role_select | string|nil or string[] | Discord role IDs |
discord_guild_select | string|nil or string[] | Discord guild IDs |
object_list | table[] | 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:
| Name | Type | Default | Description |
|---|---|---|---|
| channelId | discord_channel_select | Discord channel for notifications | |
| notifyOnHeadshot | boolean | true | Send extra message for headshots |
| minDistanceForBonus | number | 200 | Minimum distance for bonus reward |
| bonusAmount | number | 50 | Currency 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:
Print debug outputโ
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:
| Field | Type | Description |
|---|---|---|
pvp_kills | int | Total PvP kills |
last_pvp_kill | string|nil | Timestamp of last kill |
pvp_deaths | int | Total PvP deaths |
last_pvp_death | string|nil | Timestamp of last PvP death |
pvp_kd | float | Kill/death ratio |
deaths | int | Total deaths (all causes) |
kill_streak | int | Kills since last death |
death_streak | int | Deaths since last kill |
longest_kill | float | Longest kill distance in metres |
online_streak | int | Consecutive 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:
| Field | Type | Description |
|---|---|---|
id | int | Faction ID |
name | string | Faction name |
color | string|nil | Faction colour hex code |
image | string|nil | Faction image path |
balance | int|nil | Current faction treasury balance |
discord_role_id | string|nil | Linked 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
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):
| Filter | Type | Description |
|---|---|---|
group | string | Only players who are a member of this group (by name) |
whitelisted | bool | Only whitelisted / non-whitelisted players |
banned | bool | Only players who have an active ban restriction / players without one |
has_discord | bool | Only players with a linked Discord account |
has_balance_above | number | Only players whose balance is above this value |
has_balance_below | number | Only players whose balance is below this value |
last_online_after | string | Only players last seen after this time. Supports relative formats like "-7d", "-2h", "-1m", "-1y" or an absolute datetime string |
last_online_before | string | Only players last seen before this time. Same format as above |
in_faction | bool or string | true = in any faction, false = in no faction, or a faction name/id to match a specific faction |
variable | string or table | Only 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):
| Field | Type | Description |
|---|---|---|
id | int | Internal player ID |
username | string | Player username |
balance | int|nil | Current balance |
last_online | string|nil | Last seen timestamp (Y-m-d H:i:s) |
whitelisted | bool | Whether the player is whitelisted |
banned | bool | Whether 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:
| Field | Type | Description |
|---|---|---|
name | string | Variable name |
value | string|nil | Stored 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:
| Field | Type | Description |
|---|---|---|
x | float | East/west world coordinate |
y | float | Height |
z | float | North/south world coordinate |
time | string | When 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):
| Field | Type | Description |
|---|---|---|
id | int | Internal bounty ID |
target | string | Username of the player the bounty is on |
created_by | string | Username of the player who placed the bounty |
amount | int | Bounty amount |
reason | string|nil | Reason given when placing the bounty |
target_all_accounts | bool | Whether killing any account linked to the target's device pays out |
created_at | string | When 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:
| Field | Type | Description |
|---|---|---|
data.target.username | string | Username of the player with the bounty on their head |
data.target.identifier | string | Steam/DayZ ID of the target |
data.placedBy.username | string | Username of the player who placed the bounty |
data.placedBy.identifier | string | Steam/DayZ ID of the placer |
data.amount | int | Bounty size in server currency |
data.reason | string|nil | Optional reason entered by the placer |
data.bountyId | int|nil | Internal 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 .. "*"
);