Zum Hauptinhalt springen

Aktionen

Aktionen sind Code-Snippets, die ausgeführt werden, wenn ein Log-Ereignis vom Spielserver importiert wird. Verwende sie, um Daten zu verarbeiten und Reaktionen zu automatisieren.

Aktionen unterstützen jetzt sowohl einen server-spezifischen Workflow als auch einen Marktplatz-Workflow. Für das gemeinsame Modell hinter Marktplatz-, installierten und eigenen Aktionen siehe das Konzept Aktions-Marktplatz.

Hinweis zur Datenquelle: Aktionen hängen von importierten Server-Logs ab. Kommen Logs später an, wird die Aktionsausführung entsprechend verzögert. Vollständige Informationen zum Import-Verhalten findest du im Konzept des Log-Imports.

  • Server Admin -> Aktionen

Tabs​

Die Aktionsansicht ist in drei Tabs aufgeteilt:

  • Marktplatz: öffentliche Creator-Aktionen durchsuchen und auf dem aktuellen Server installieren
  • Installiert: bereits auf diesem Server installierte Marktplatz-Aktionen verwalten
  • Eigene Aktionen: servereigene Aktionen mit lokalem Lua-Code erstellen und pflegen

Jeder Tab hat ein Suchfeld (Name und Beschreibung) und Filter: der Marktplatz nach Auslöser, Verifizierung und danach, ob die Aktion hier schon installiert ist; die Tabs Installiert und Eigene Aktionen nach Auslöser und danach, ob die Aktion aktiv ist.

Installierte Marktplatz-Aktionen übernehmen Code, Trigger-Ereignis und Konfigurationsschema aus der Creator-Definition. Server-Mitarbeiter verwalten nur den Aktiv-Status und die Konfigurationswerte für diesen Server.

Fertige Belohnungs-Aktionen​

  • Kill-Reward zahlt dem Killer für jeden PvP-Kill eine Belohnung und kann dem Opfer eine Strafe abziehen. Optionale Einstellungen legen fest, welche Kills zahlen: keine Belohnung für Spawnkills, keine Belohnung für ein Opfer, das weniger als eine bestimmte Zahl Minuten am Leben war (ein längerer Schutz für frische Spawns), keine Belohnung unter einer Mindest-K/D des Opfers und eine Staffel nach der K/D des Opfers – entweder ein fester Betrag pro Stufe oder ein Faktor auf die Kill-Belohnung. Jede Stufe gilt ab ihrer K/D bis zur nächsten Stufe. Es zählt die K/D, die dzbot im Profil des Opfers zeigt. Bleiben die Felder leer, zahlt jeder Kill wie bisher die Kill-Belohnung.
  • Group Wage zahlt jedem Mitglied einer dzbot-Gruppe (Admin → Gruppen) einen festen Lohn pro Tag, Woche (ab Montag) oder Monat. Sie läuft über den Scheduler: Lege nach der Installation eine Scheduler-Aufgabe Aktion ausführen für sie an, mit den Zeiten, die du willst, z. B. jeden Montag um 18:00. Jedes Mitglied bekommt höchstens einmal pro Zeitraum in der Zeitzone deines Servers Geld – ein erneuter Lauf, von Hand oder aus einer zweiten Aufgabe, zahlt niemandem doppelt. Auf Wunsch zahlt sie nur Mitgliedern, die in den letzten Tagen auf dem Server waren, schickt jedem eine Discord-DM und postet eine Zusammenfassung in einen Kanal. Den Buchungstext legst du selbst fest. Installiere sie nur auf einem Server: Das Guthaben gilt für die ganze Organisation, eine Kopie auf einem zweiten Server zahlt denselben Mitgliedern noch einmal.

Fertige Spawnkill-Aktionen​

Beide richten sich standardmäßig nach dem Label Spawnkill, also nach der Einstellung PvP-Kill – Mindest-Überlebenszeit des Opfers (2 Minuten, solange du sie nicht geändert hast) – dieselben Kills, die der Killfeed markiert und die Statistiken auslassen. Beide bieten stattdessen auch eine eigene Grenze in Minuten an, gezählt in 5-Minuten-Schritten.

  • Spawn-Trap Detection meldet jeden Spawnkill in einem Moderationskanal und zählt ihn in der Spielervariable fresh_spawn_kills des Killers, die auf der Spieler-Detailseite steht. Kills von Spielern, die selbst erst weniger als eine bestimmte Zahl Minuten am Leben waren (Standard 30), bleiben außen vor – zwei frische Spawns, die sich gegenseitig erschießen, zählen nicht.
  • Fresh Spawn Autoban bannt einen Spieler, der innerhalb eines gleitenden Zeitfensters eine bestimmte Zahl Spawnkills macht (Standard 5 in 10 Minuten), und kann jeden Bann in einem Kanal posten.
  • Raid Autoban bannt einen Spieler, der eine Basisstruktur auf einen Schlag zerstört — eine Kettensäge durch einen Zaun oder ein Tor —, außerhalb der Raid-Zeiten, die du einträgst (z. B. Samstag & Sonntag 18:00 – 23:59; ein Zeitfenster darf über Mitternacht gehen). Ohne Zeitfenster wird jeder solche Raid gebannt. Bann-Dauer, -Umfang und -Grund sind einstellbar, ein Filter begrenzt es auf Zonen, Fraktionen oder Gruppen, und im Nur-melden-Modus postet es Raids in einen Kanal, ohne zu bannen. Abbauen mit Werkzeug zählt nicht: Das Log unterscheidet nicht, ob ein Besitzer seine eigene Wand abbaut oder ein Raider. Außerdem bannt es die Mittäter: alle, die in den 10 Minuten vor dem Raid im Umkreis von 100 m waren und nicht zur geraideten Basis gehören. Zur Basis gehören alle mit mindestens 3 geloggten Bauaktionen an dieser Basis, deren andere Accounts und alle Mitglieder ihrer Fraktionen und der verbündeten Fraktionen. So kann sich nicht einer opfern, während der Rest der Gruppe weiter raidet. Positionen werden nur alle paar Minuten geloggt, deshalb kann auch jemand erwischt werden, der nur vorbeigelaufen ist — die Meldung nennt alle, die mit dem Raider gebannt wurden, und falsche Banns hebst du von Hand auf. Umkreis, Minuten und Mindest-Bauaktionen sind einstellbar; schalte die Mittäter aus, um nur den Raider zu bannen. Steht an der Stelle keine bekannte Basis, wird kein Mittäter gebannt.
  • Explosive Raid Autoban macht dasselbe, wenn jemand außerhalb der Raid-Zeiten Sprengstoff in einer Basis platziert, zu der er nicht gehört. DayZ loggt keine Wand, die von Sprengstoff zerstört wird, nur das Platzieren der Ladung — so lässt sich ein Sprengstoff-Raid überhaupt erkennen. Standardmäßig zählen Plastik- und improvisierter Sprengstoff, Claymores nicht (die legt man auch zur Verteidigung); die Liste der Klassennamen ist einstellbar. Sprengstoff außerhalb jeder bekannten Basis oder in der eigenen Basis wird ignoriert. Mittäter werden wie bei Raid Autoban gebannt.

Fertige Discord-Feeds​

Jeder postet eine Art von Ereignis in einen Discord-Kanal, den du auswählst. Jeder Feed hat einen Ereignis-Filter (welche Ereignisse zählen — siehe Ereignis-Filter-Felder) und einen Discord-Post, den du mit Live-Vorschau gestaltest (siehe Discord-Embed-Felder). Sie laufen neben dem eingebauten Kill- und Schadensfeed aus Einstellungen → Discord, der bleibt, wie er ist.

  • Login Feed und Logout Feed — ein Spieler ist beigetreten oder gegangen. Die Position ist standardmäßig aus; schalte das Feld Location nur in einem Admin-Kanal ein.
  • Build Feed und Placement Feed — jedes gebaute Basenteil und jedes platzierte Zelt, Fass, jede Flagge oder Falle, mit Kartenlink. Gedacht für einen Admin-Kanal: Es zeigt, wo Basen stehen.
  • PvE Death Feed — eine Aktion pro Todesart, installiere die, die du willst: Infected (Infizierte), Animals & Environment (Wölfe, Bären, Minen, Fallen, Sprengstoff, Fahrzeuge, Feuer, Stürze — die Ursache so, wie DayZ sie loggt), Bled Out (verblutet), Drowned (ertrunken) und Suicide (laut auf vollen Servern: viele Spieler bringen sich zum Respawnen um).
  • Long-Range Kill Feed — Spieler-Kills ab 300 m; die Entfernung änderst du im Filter.
  • Kill Streak Feed — postet, wenn ein Spieler eine Killserien-Marke erreicht (standardmäßig 3, 5, 10 …), wenn jemand eine Serie von mindestens 5 beendet, und, wenn du es einschaltest, Todesserien. Die Serien stehen in den Spielervariablen feed_kill_streak und feed_death_streak, zählen ab der Installation der Aktion und enden nur mit einem Tod durch einen anderen Spieler. Der Filter entscheidet, welche Kills zählen.
  • Rich Kill Feed — jeder Spieler-Kill mit seinen Kill-Merkmalen unter dem Titel, Waffe und Entfernung. Gut für einen zweiten Kanal, z. B. nur Kopfschüsse.
  • Admin Kill Feed — jeder Spieler-Kill mit Waffe, Entfernung, Merkmalen, Kartenlink, Zonen, der Position des Killers und beiden Spieler-IDs, für einen Kanal nur für Admins.
  • Raid Feed — jede auf einen Schlag zerstörte Basisstruktur (eine Kettensäge durch einen Zaun oder ein Tor), mit Werkzeug und Kartenlink.

Eine Aktion hat genau einen festen Auslöser, deshalb sind Logins und Logouts, Bauen und Platzieren und jede PvE-Todesart eigene Aktionen. Jeder Lauf steht im Tab Logs der Aktion, auch mit dem Grund, wenn der Filter ein Ereignis übersprungen hat.

Zugriffsvoraussetzungen​

Aktionen haben jetzt drei Berechtigungsebenen:

  • actions.edit: Erlaubt das Bearbeiten serverseitiger Konfigurationswerte sowie das Aktivieren und Deaktivieren installierter oder eigener Aktionen
  • actions.manage_marketplace: Erlaubt das Installieren von Marktplatz-Aktionen sowie das Entfernen installierter Marktplatz-Aktionen
  • actions.edit_code: Erlaubt das Erstellen und Pflegen eigener Aktionen einschließlich Lua-Code und Konfigurationsschema

Diese Trennung ermöglicht es Serveradministratoren, operative Aktionsverwaltung, Marktplatz-Verwaltung und lokale Code-Erstellung getrennt zu vergeben.

Aktionssprache: Lua​

Aktionen werden in Lua geschrieben, einer leichtgewichtigen, hochrangigen, multi-paradigmatischen Programmiersprache, die primär für den eingebetteten Einsatz in Anwendungen entwickelt wurde.

Aktionsskripte laufen direkt. Zwei globale Variablen sind verfügbar:

  • data: Ereignis-Payload des aktuellen Log-Ereignisses
  • config: Aufgelöste Aktionskonfigurationswerte

Um Werte beim Erstellen von Skripten zu prüfen, verwende die print-Funktion.

Hier ist ein Beispiel einer einfachen Aktion, die die Log-Ereignisdaten in die Konsole ausgibt:

print(data);

Kill-Kontext in data.flags​

player_kill und player_killed tragen den Kill-Kontext in data.flags: headshot, revenge, trade, team_kill, alliance_violation und spawn_kill sind true oder false; multi_kill ist die Zahl der Kills des Killers innerhalb von 45 Sekunden (2, 3, 4 …) und nil bei einem einzelnen Kill. player_hit_player und player_hit_by_player tragen data.flags.friendly_fire.

player_kill sagt außerdem, wie lange das Opfer am Leben war: data.victim.minutes_alive in 5-Minuten-Schritten und data.victim.seconds_alive, in den ersten 5 Minuten eines Lebens sekundengenau; nil, wenn dzbot nicht weiß, wann das Leben des Opfers begann.

if data.flags.headshot then
player.change_balance(data.killer.username, 50, "Kopfschuss-Bonus");
end

if data.flags.multi_kill and data.flags.multi_kill >= 3 then
discord.send_to_channel(config.channelId, data.killer.username .. " hat " .. data.flags.multi_kill .. " Kills in 45 Sekunden!");
end

Zonen eines Ereignisses​

Jedes Ereignis mit Position (Kills, Treffer, Tode, Bauen, Platzieren, Flaggen, der Fünf-Minuten-Ping player_online und mehr) bringt in data.zones die Zonen mit, in denen es passiert ist — eine Liste mit id und name (in der Serversprache) jeder Zone. Ist das Ereignis in keiner Zone passiert, ist die Liste leer. Inaktive Zonen tauchen nicht auf; eine geänderte Zone gilt nach etwa zehn Sekunden für neue Ereignisse.

for _, zone in ipairs(data.zones) do
print("Passiert in " .. zone.name .. " (#" .. zone.id .. ")");
end

Lass die Zonen mit einem Feld vom Typ zone_select auswählen und vergleiche dessen Wert mit tostring(zone.id):

local wanted = {};
for _, id in ipairs(config.zones or {}) do wanted[tostring(id)] = true end
for _, zone in ipairs(data.zones) do
if wanted[tostring(zone.id)] then
player.change_balance(data.killer.username, 100, "Kill in " .. zone.name);
break;
end
end

Der Log Explorer speichert die Zonen mit dem Ereignis, sodass du auch später siehst, wo es passiert ist.

Konfigurationsfelder​

Aktionen unterstützen benutzerdefinierte Konfigurationsfelder, mit denen du deine Lua-Skripte parametrisieren kannst, ohne den Code zu bearbeiten. Das ist nützlich für:

  • Moderatoren das Anpassen von Aktionsverhalten zu erlauben, ohne Code-Zugriff
  • Dieselbe Aktionsvorlage mit verschiedenen Einstellungen wiederzuverwenden
  • Sensible Werte (wie Kanal-IDs) vom Code zu trennen

Bei Marktplatz-Aktionen werden diese Felder vom Creator definiert. Server-Admins setzen nur die Werte in der installierten Server-Kopie.

Konfigurationsfelder definieren​

Bei eigenen Aktionen werden Konfigurationsfelder im Code-Tab über den Konfigurationsschema-Builder definiert.

Bei Marktplatz-Aktionen werden Konfigurationsfelder im Creator Space vom Autor definiert und erscheinen anschließend auf jeder Server-Installation.

Jedes Feld hat:

  • Name: Schlüssel in Lua (config.<name>)
  • Typ: siehe Feldtypen unten
  • Standard: Fallback-Wert, wenn kein Wert in den Einstellungen gespeichert ist
  • Beschreibung: Hilfetext, der im Einstellungs-Tab angezeigt wird
  • Pflichtfeld: Die Aktion lässt sich nicht speichern und läuft nicht, solange das Feld sichtbar und leer ist

Empfohlene Benennung: camelCase oder snake_case ohne Leerzeichen.

Sektionen und Bedingungen​

Mit einer Sektion fasst du die Felder danach unter einer Überschrift zusammen, bis zur nächsten Sektion. Eine Sektion hat keinen Wert und taucht nie in config auf.

Jedes Feld und jede Sektion kann unter Nur anzeigen, wenn … eine Bedingung bekommen: Wähle ein Feld weiter oben, einen Operator (ist, ist nicht, ist eines von, ist gesetzt, ist größer als, enthält …) und einen Wert, und verknüpfe Regeln mit alle zutreffen / mindestens eine zutrifft, verschachtelten Gruppen und nicht. Eine Bedingung an einer Sektion blendet alle Felder darin aus.

Ein typischer Aufbau: oben ein Block mit Schaltern (bannen, DM schicken, Ticket öffnen), danach pro Schalter eine Sektion, die nur sichtbar ist, solange er an ist, und die Einstellungen dieses Schritts enthält. So sieht der Betreiber nur die Einstellungen dessen, was er wirklich nutzt.

Daraus folgt:

  • Eine Bedingung kann nur Felder weiter oben lesen. Der Builder verweigert ein Verschieben, das eine Bedingung brechen würde, und beim Umbenennen eines Feldes werden alle Bedingungen mitgezogen, die es lesen.
  • Ein ausgeblendetes Feld gilt nicht: Es kommt im Script als nil an, auch wenn es einen Standardwert hat, und ist nie Pflicht. Sein gespeicherter Wert bleibt erhalten; schaltest du das steuernde Feld wieder an, ist er wieder da.
  • Für die Bedingungen der Felder danach zählt ein ausgeblendetes Feld als leer. Blendest du ein Feld aus, verschwindet also auch alles, was davon abhängt.

Konfigurationswerte setzen​

Nachdem ein Feld im Code-Tab definiert wurde, können Benutzer mit actions.edit dessen Wert im Einstellungs-Tab setzen.

Zur Laufzeit werden Config-Werte wie folgt aufgelöst:

  1. nil, wenn das Feld durch seine Bedingung oder seine Sektion ausgeblendet ist
  2. Andernfalls der gespeicherte Wert aus dem Einstellungs-Tab (falls vorhanden)
  3. Andernfalls der Feld-Standard aus dem Schema
  4. Andernfalls nil

Nur im Schema definierte Felder werden in config verfügbar gemacht.

Feldtypen​

TypLua-WertHinweise
stringstring|nilEinzeiliger Text; leer → nil
textareastring|nilMehrzeiliger Text; in Lua identisch zu string
emailstring|nilEingabe einer E-Mail-Adresse; in Lua identisch zu string
numbernumber|nilAls Integer oder Float geparst
booleanbool|nilCheckbox; leer → nil
coordinatetable|nilKartenauswahl — .x, .z, optional .y, .radius
generic_selectstring|nil oder string[]Auswahl aus einer festen Liste; string[] bei multiple: true
faction_selectstring|nil oder string[]Fraktions-IDs des Servers
zone_selectstring|nil oder string[]Zonen-IDs des Servers — mit tostring(zone.id) aus data.zones vergleichen
player_selectstring|nil oder string[]Spielernamen des Servers
relation_selectstring[]Immer ein Array, nie nil
discord_channel_selectstring|nil oder string[]IDs von Discord-Textchannels
discord_category_selectstring|nil oder string[]IDs von Discord-Kategorien
discord_role_selectstring|nil oder string[]IDs von Discord-Rollen
discord_guild_selectstring|nil oder string[]IDs von Discord-Guilds
object_listtable[]Wiederholbare Liste von Objekten, nie nil — leer ergibt {}
event_filtertableBedingungen aus einem Formular — an filter.matches(config.<name>, data) übergeben. Nur für Aktionen
discord_embedtable|nilEin Discord-Post mit Live-Vorschau — mit discord.send_embed(channel, config.<name>, data) senden. Nur für Aktionen

Leere Werte werden als nil behandelt, sofern nicht anders angegeben.

Wähle den Typ danach, was das Feld bezeichnet, nicht nach dem Lua-Wert, den es liefert: Ein Discord-Kanal, eine Rolle, eine Kategorie oder eine Gilde → der passende discord_*_select; ein Spieler → player_select; eine Fraktion → faction_select; eine Zone → zone_select; eine feste Auswahl → generic_select. Der Wert einer Auswahl ist bereits die ID (bzw. der Benutzername) als Zeichenkette, die die Action-Funktionen erwarten — ein reines string-Feld wäre dagegen ein fehleranfälliges Freitextfeld. Verwende string nur für echten Freitext.

Ereignis-Filter-Felder​

Mit einem Feld vom Typ event_filter entscheidet der Betreiber in einem Formular statt in Lua, auf welche Ereignisse die Aktion reagiert. In den Einstellungen fügt er Bedingungen einzeln hinzu — angeboten werden nur die, die das Ereignis der Aktion beantworten kann (ein Login hat keine Entfernung, ein Bau kein Opfer):

  • Zonen: Das Ereignis fand in einer der gewählten Zonen statt — oder, mit Außerhalb, in keiner davon
  • Entfernung: mindestens und/oder höchstens so viele Meter (Kills; bei Treffern die Entfernung zwischen Angreifer und Opfer)
  • Waffe oder Gegenstand: Die Waffe (bzw. beim Bauen, Platzieren und Einpacken der Gegenstand) enthält eines der Wörter, ohne auf Groß- und Kleinschreibung zu achten — mosin trifft Mosin 91/30
  • Kill-Merkmale: jedes Merkmal Egal, Ja (muss gesetzt sein) oder Nein (darf nicht gesetzt sein) — Kopfschuss, Multi-Kill, Rache, Trade, Teamkill, Allianzbruch, Eigenbeschuss, Spawnkill
  • Killer / Opfer (bzw. Angreifer, Absender, Spieler … — wie das Ereignis sie nennt): Mitglied einer der gewählten Fraktionen, einer der gewählten dzbot-Gruppen, und Spielervariablen mit ist, ist nicht, ist größer als, enthält, ist leer und Ähnlichem verglichen

Ein Ereignis zählt nur, wenn es alle Bedingungen erfüllt; ohne Bedingungen zählt jedes. Das Skript prüft es in einer Zeile mit filter.matches. Spieler-Bedingungen lesen die Datenbank einmal pro Spieler und Lauf, alles andere kostet nichts.

Discord-Embed-Felder​

Ein Feld vom Typ discord_embed ist ein Discord-Post, den der Betreiber in den Einstellungen gestaltet — mit einer Vorschau, wie Discord ihn zeigen wird, gefüllt mit Beispieldaten des Ereignisses der Aktion: Titel (optional als Link), Text, Farbe, Felder zum Ausschalten, Nebeneinanderstellen und Sortieren per Ziehen, Fußzeile, Vorschaubild und der Zeitpunkt des Ereignisses. Ein Klick auf einen Platzhalter fügt ihn an der Cursor-Position ein.

Platzhalter sind die Daten des Ereignisses als Punktpfade — {killer.username}, {weapon}, {distance}, {time.string} — dazu {server.name}, {zone} (erste Zone), {zones} (alle), {flags} (die Kill-Merkmale als Wörter, in der Sprache des Servers) und bei Ereignissen mit Position {location.place}, {location.label}, {location.url} (iZurvive) und {location.link} (ein fertiger Kartenlink). Namen und andere Texte aus dem Ereignis werden maskiert, ein Spieler namens **boss** erscheint also wörtlich; Discords Grenzen werden eingehalten (Titel 256 Zeichen, Text 4096, Feldwert 1024, 25 Felder, 6000 insgesamt). Das Skript sendet ihn mit discord.send_embed. Werte, die das Skript selbst in data schreibt (etwa eine Seriennummer), lassen sich als zusätzliche Platzhalter anbieten: in den Einstellungen des Felds als Zeilen name|Beispielwert.

Konfiguration in Lua verwenden​

Konfigurationswerte sind über die config-Tabelle verfügbar:

-- Konfigurationswerte abrufen
discord.send_to_channel(config.channelId, "Spieler getötet: " .. data.victim.username);

if config.notifyOnHeadshot and data.weapon == "Mosin" then
discord.send_to_channel(config.channelId, "Mosin-Kill erkannt");
end

if data.distance > config.minDistanceForBonus then
player.change_balance(data.killer.username, config.bonusAmount, "Weitschuss-Bonus");
end

Beispielkonfiguration​

Für eine Kill-Benachrichtigungsaktion könntest du folgendes definieren:

NameTypStandardBeschreibung
channelIddiscord_channel_selectDiscord-Kanal für Benachrichtigungen
notifyOnHeadshotbooleantrueZusätzliche Nachricht bei Kopfschüssen
minDistanceForBonusnumber200Mindestdistanz für Bonusbelohnung
bonusAmountnumber50Währungsbonus für Weitschuss-Kills

Moderatoren können diese Werte dann im Einstellungs-Tab anpassen, ohne Code-Zugriff zu benötigen.

Funktionen​

Um die Aktion bei einem neuen Log-Eintrag anzupassen, bietet der Code-Editor durchsuchbare Autovervollständigung mit [STRG] + [LEERTASTE].

Folgende Funktionen stehen in Aktionen zur Verfügung:

Debug-Ausgabe​

Gibt die Daten in die Konsole aus.

print({name = "John", age = 30}); -- wird als JSON ausgegeben
print("Hallo, Welt!");

Server-Namespace​

Der Server-Namespace enthält alle serverbezogenen Funktionen.

server.name()​

Gibt den Namen des Servers zurück.

print(server.name);

server.current_time()​

Gibt die aktuelle Zeit als Unix-Zeitstempel (Sekunden) zurück.

print(server.current_time());

server.ban_player(username: string, reason: string = "", length: string = "99 years", includeAllAccounts: bool = false, orgWide: bool = true)​

Erstellt eine Bann-Einschränkung für den Spieler mit dem angegebenen Benutzernamen. Der Bann ist unter Admin → Einschränkungen sichtbar und verwaltbar. Standardmäßig gilt er auf allen Servern der Organisation und nur für dieses Konto. Mit orgWide auf false gilt er nur auf diesem Server, mit includeAllAccounts zusätzlich für die weiteren bekannten Konten des Spielers. Wenn du nur die Schalter dahinter setzen willst, übergib nil für length.

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

server.currency()​

Gibt das Währungssymbol des Servers zurück, wie es unter Einstellungen → Wirtschaft konfiguriert ist.

print(server.currency()); -- z.B. "Coins"
discord.send_to_channel(config.channelId, "Du hast 100 " .. server.currency() .. " erhalten!");

Discord-Namespace​

Der Discord-Namespace enthält alle Discord-bezogenen Funktionen.

Achtung: Alle Discord-IDs müssen als Zeichenketten (in Anführungszeichen) angegeben werden, da die Programmiersprache sie sonst als Zahlen interpretiert und mathematisch darstellt, was zu Fehlern führt.

discord.send_to_player(username: string, content: string, image?: table)​

Sendet eine Nachricht an den Spieler mit dem angegebenen Benutzernamen. Ein Bild aus map.image als drittes Argument wird angehängt.

discord.send_to_player("John", "Hallo, John!");
discord.send_to_player("h3eh23ieuh3ei2he3", "Hallo, John!"); -- wirft eine "Spieler nicht gefunden"-Exception

discord.send_to_channel(channel: string, content: string, image?: table)​

Sendet eine Nachricht an den Kanal mit der angegebenen ID. Ein Bild aus map.image als drittes Argument wird angehängt. Enthält die Nachricht ein <embed> ohne eigenes <image>, erscheint die Karte im Embed.

discord.send_to_channel("1234567890", "Hallo, Welt!");

local img = map.image({ points = {{ x = data.location.x, z = data.location.z, label = data.victim.username }} });
discord.send_to_channel(config.channel, data.victim.username .. " ist hier gestorben", img);

discord.mention(username: string)​

Gibt den Erwähnungs-String für den Spieler mit dem angegebenen Benutzernamen zurück. Kann in Discord-Nachrichten verwendet werden, um auf den Discord-Account zu verweisen. Wird kein Spieler gefunden, wird der Benutzername ohne Formatierung zurückgegeben.

print(discord.mention("John")); -- gibt "<@1234567890>" zurück
print(discord.mention("h3eh23ieuh3ei2he3")); -- gibt "h3eh23ieuh3ei" zurück

discord.avatar(username: string)​

Gibt den Link zum Discord-Profilbild des Spielers mit dem angegebenen Benutzernamen zurück, etwa als Vorschaubild oder Autor-Icon eines Embeds. Gibt nil zurück, wenn der Spieler unbekannt ist, keinen Discord-Account verknüpft hat oder Discords Standardbild nutzt.

data.killer_avatar = discord.avatar(data.killer.username)
discord.send_embed(config.channel, config.embed, data) -- Vorschaubild im Embed-Feld: {killer_avatar}

discord.send_embed(channel: string, embed: table, data: table)​

Füllt ein Konfigurationsfeld vom Typ discord_embed mit den Werten aus data und postet es in den Kanal. Werte, die das Ereignis nicht mitbringt, können vorher in data gesetzt werden. Gibt true zurück, sobald die Nachricht in der Warteschlange ist; ein nicht eingerichtetes Embed-Feld bricht die Aktion mit einem Fehler ab.

if not filter.matches(config.filter, data) then return end
data.streak = 5
discord.send_embed(config.channel, config.embed, data)

Filter-Namespace​

filter.matches(filter: table, data: table)​

Ob das Ereignis die Bedingungen eines Konfigurationsfelds vom Typ event_filter erfüllt (siehe Ereignis-Filter-Felder). Gibt true zurück, oder false und als zweiten Wert den Grund. nil oder ein leerer Filter trifft jedes Ereignis.

local ok, why = filter.matches(config.filter, data)
if not ok then
print("Übersprungen: " .. why) -- z. B. "distance 120 m is below 300 m"
return
end

Player-Namespace​

Der Player-Namespace enthält alle spielerbezogenen Funktionen.

player.change_balance(username: string, amount: number, description: string = "")​

Ändert das Guthaben des Spielers mit dem angegebenen Benutzernamen.

player.change_balance("John", 100);
player.change_balance("John", 100, "Guter Schuss!");
player.change_balance("John", -100, "Schlechter Schuss!"); -- negativer Betrag zum Abziehen

player.get_balance(username: string)​

Gibt das aktuelle Guthaben des Spielers mit dem angegebenen Benutzernamen zurück, oder nil, wenn kein passender Spieler gefunden wird.

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

player.transfer_balance(from: string, to: string, amount: number, description: string = "")​

Überweist amount vom Guthaben eines Spielers auf das eines anderen, und zwar in einem Schritt: Entweder werden beide Seiten gebucht oder keine, sodass bei einem abgebrochenen Skript kein Geld verschwindet. Nutze diese Funktion statt zweier player.change_balance-Aufrufe, wann immer Geld zwischen Spielern den Besitzer wechselt. Beide Spieler werden auf dem aktuellen Server gesucht, im Kontoauszug erscheint die Buchung als Überweisung. Die Funktion bricht ab, ohne etwas zu bewegen, wenn ein Spieler nicht gefunden wird, amount keine positive ganze Zahl ist, beide Namen zum selben Guthaben gehören oder from zu wenig Geld hat. Rückgabe: debit_transaction_id, credit_transaction_id, from_balance und to_balance (die neuen Guthaben).

local result = player.transfer_balance("John", "Jane", 100, "Autokauf");
print("John hat noch " .. result.from_balance);

player.statistics(username: string)​

Gibt ein Statistik-Objekt für den Spieler mit dem angegebenen Benutzernamen zurück. Gibt nil zurück, wenn kein passender Spieler gefunden wird.

local stats = player.statistics("John");
print(stats.online_streak); -- gibt die aktuelle Online-Tages-Serie aus

Rückgabefelder:

FeldTypBeschreibung
pvp_killsintGesamte PvP-Kills
last_pvp_killstring|nilZeitstempel des letzten Kills
pvp_deathsintGesamte PvP-Tode
last_pvp_deathstring|nilZeitstempel des letzten PvP-Todes
pvp_kdfloatKill/Death-Verhältnis
deathsintGesamte Tode (alle Ursachen)
kill_streakintKills seit dem letzten Tod
death_streakintTode seit dem letzten Kill
longest_killfloatGrößte Kill-Distanz in Metern
online_streakintAufeinanderfolgende Kalendertage mit mindestens einer Sitzung (einschließlich heute)

Beispielausgabe:

{
"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
}
Beispiel: Online-Streak-Belohnung​

Einen Bonus vergeben, der mit der Anzahl aufeinanderfolgender Online-Tage skaliert:

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)​

Gibt ein Fraktions-Datenobjekt für die aktuelle Fraktion des Spielers zurück, oder nil, wenn der Spieler nicht gefunden wurde oder in keiner Fraktion ist.

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

if faction then
print(faction.id); -- z.B. 42
print(faction.name); -- z.B. "Wolves"
end

Rückgabefelder:

FeldTypBeschreibung
idintFraktions-ID
namestringFraktionsname
colorstring|nilFraktionsfarbe als Hex-Code
imagestring|nilFraktionsbild-Pfad
balanceint|nilAktuelles Fraktionskassen-Guthaben
discord_role_idstring|nilVerknüpfte Discord-Rollen-ID
alliestableFraktionen in einer aktiven Allianz mit dieser, als { id, name }; leer, wenn keine

Beispielausgabe:

{
"id": 42,
"name": "Wolves",
"color": "#ff0000",
"image": null,
"balance": 1500,
"discord_role_id": "1234567890",
"allies": [{ "id": 7, "name": "Bears" }]
}
Beispiel: Gesamte Fraktion bei einem Kill belohnen​
local faction = player.get_faction(data.killer.username);

if faction then
faction.change_balance(faction.id, config.faction_reward, "Kill-Belohnung");
discord.send_to_channel(config.channel, faction.name .. " hat " .. config.faction_reward .. " für einen Kill erhalten!");
end
hinweis

player.get_faction hat früher die Fraktions-ID als einfachen String zurückgegeben. Es wird jetzt ein Objekt zurückgegeben — verwende .id, um auf die ID zuzugreifen.

player.get_name(username: string, withFactionTag?: boolean)​

Löst den Anzeigenamen des Spielers auf. Wenn kein Name für den Benutzer gesetzt ist oder kein passender Benutzer gefunden wird, wird der eingegebene Benutzername zurückgegeben. Mit true als zweitem Argument trägt der Name das Fraktions-Tag des Spielers im Format des Servers; ohne das Argument oder wenn der Server keine Tags zeigt, kommt der reine Name zurück.

player.get_name("johnnyboy420"); -- gibt den Anzeigenamen des Spielers zurück: "John Doe"
player.get_name("johnnyboy420", true); -- mit Fraktions-Tag: "John Doe [WOLF]"
player.get_name("nouserwiththatusername"); -- Benutzer nicht gefunden, gibt die Eingabe zurück: "nouserwiththatusername"

player.get_main_account(username: string)​

Ermittelt den Haupt-Account-Benutzernamen zu einem Spieler. Geht alle Spieler durch, die mit dem angegebenen Spieler mindestens ein Gerät teilen, und liefert den Benutzernamen des ersten Spielers zurück, dem ein Benutzer-Account zugeordnet ist. Hat der Spieler selbst bereits einen verknüpften Benutzer-Account, wird dessen eigener Benutzername zurückgegeben. Kann kein verknüpfter Account gefunden werden oder ist der Spieler unbekannt, wird der eingegebene Benutzername unverändert zurückgegeben.

player.get_main_account("alt_account"); -- gibt "main_account" zurück, falls Alt und Main ein Gerät teilen
player.get_main_account("solo_player"); -- gibt "solo_player" zurück, wenn kein Haupt-Account verknüpft ist
Beispiel: Belohnungen dem Haupt-Account gutschreiben​
local main = player.get_main_account(data.killer.username);
player.change_balance(main, config.reward or 50, "Kill-Belohnung (über " .. data.killer.username .. ")");

player.linked_accounts(username: string)​

Listet die anderen Accounts des Spielers auf diesem Server: jeden Account, der sich von einem Gerät angemeldet hat, das dieser Spieler auch benutzt hat — dieselbe Verknüpfung wie bei player.get_main_account und den Alts auf der Spielerseite. Der Spieler selbst steht nicht in der Liste. Gibt eine Liste von { id, username } zurück, leer, wenn der Spieler unbekannt ist oder keine anderen Accounts hat.

for _, alt in ipairs(player.linked_accounts(data.player.username)) do
print(data.player.username .. " spielt auch als " .. alt.username);
end

player.has_discord_role(username: string, role: string)​

Prüft, ob der Spieler mit dem angegebenen Benutzernamen die angegebene Discord-Rolle hat. Akzeptiert sowohl den Rollennamen als auch die Rollen-ID.

player.has_discord_role("John", "6553535632356535564"); -- funktioniert mit Rollen-IDs
player.has_discord_role("John", "Admin"); -- funktioniert auch mit Rollennamen

player.ensure_discord_role(username: string, roleId: string, durationSeconds?: number)​

Weist dem Spieler die angegebene Discord-Rolle zu. Falls der Spieler die Rolle bereits hat, passiert nichts. Die Rolle muss über ihre Discord-Rollen-ID angegeben werden.

Mit durationSeconds wird die Rolle nur auf Zeit vergeben: dzbot entfernt sie wieder, sobald die Zeit abgelaufen ist (geprüft alle fünf Minuten). Wird sie erneut vergeben, während sie noch läuft, beginnt die Zeit ab jetzt neu – verkürzt wird sie dabei nie. Ruft man die Funktion also bei jeder Spende auf, bleibt die Rolle so lange nach der letzten Spende erhalten. Ohne Dauer vergeben, gilt die Rolle wieder dauerhaft.

player.ensure_discord_role("John", "1234567890123456789");
player.ensure_discord_role("John", "1234567890123456789", 90 * 86400); -- für 90 Tage

player.remove_discord_role(username: string, roleId: string)​

Entfernt die angegebene Discord-Rolle vom Spieler. Falls der Spieler die Rolle nicht hat, passiert nichts. Die Rolle muss über ihre Discord-Rollen-ID angegeben werden. Ein laufendes Ablaufdatum aus ensure_discord_role fällt dabei mit weg.

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

player.list(filters?: table)​

Gibt eine Liste aller Spieler der Organisation des Servers zurück (alle, die auf einem ihrer Server gespielt haben), optional gefiltert; played_here = true behält nur die, die auf diesem Server im Spiel waren. Ohne Argumente werden alle Spieler zurückgegeben (bis zu 5000, mit einer Warnung im Ausführungslog, wenn mehr passten); mit page oder per_page blätterst du selbst durch die Ergebnisse.

local players = player.list();

local vip_spieler = player.list({ group_name = "vip" });

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

Verfügbare Filter (alle optional, mit UND verknüpft):

FilterTypBeschreibung
searchstringEin Name (Username, Ingame- oder Discord-Name, auch ein früherer Name; ähnlich aussehende Zeichen zählen mit) oder eine ID: Spieler-, Discord-, Steam64- oder DayZ-ID
group_idnumberNur Mitglieder der Gruppe mit dieser ID
group_namestringNur Mitglieder der Gruppe mit genau diesem Namen
whitelistedboolNur auf der Whitelist stehende / nicht stehende Spieler
played_hereboolNur Spieler, die auf diesem Server im Spiel waren / nur die, die auf anderen Servern der Organisation gespielt haben
restriction_typesstring oder tableNur Spieler mit einer AKTIVEN Einschränkung dieser Systemtypen (ban, claim_block)
has_user_accountboolNur Spieler mit / ohne verknüpftem App-Konto
has_discord_linkedboolNur Spieler, deren verknüpftes Konto eine Discord-ID hat
balance_minnumberNur Spieler, deren org-weiter Kontostand mindestens diesem Wert entspricht (inklusiv)
balance_maxnumberNur Spieler, deren org-weiter Kontostand höchstens diesem Wert entspricht (inklusiv)
first_seen_afterstringNur Spieler, die ab diesem Zeitpunkt zuerst gesehen wurden. Relativ ("-7d", "-2h", "-1m", "-1y") oder absolutes Datum
first_seen_beforestringNur Spieler, die bis zu diesem Zeitpunkt zuerst gesehen wurden. Gleiches Format
last_online_afterstringNur Spieler, die ab diesem Zeitpunkt zuletzt online waren. Gleiches Format
last_online_beforestringNur Spieler, die bis zu diesem Zeitpunkt zuletzt online waren. Gleiches Format
online_nowboolNur Spieler, die gerade auf dem Server sind / nicht sind. Solange das Admin-Log des Servers dafür zu alt ist, trifft true niemanden und false alle
factionbool oder string/numbertrue = in irgendeiner Fraktion, false = in keiner Fraktion, oder ein Fraktionsname/eine ID für eine bestimmte Fraktion
variablestring oder tableNur Spieler, die eine Variable mit diesem Namen gesetzt haben. String für den Namen allein, oder { name = "...", value = "..." } zusätzlich für einen bestimmten Wert
activity_from / activity_tostringZeitfenster für playtime_seconds / session_count. Nur gemeinsam gültig
sort_bystringusername, firstSeen, lastOnline, balance, playtime oder sessionCount (Standard lastOnline)
sort_directionstringASC oder DESC (Standard DESC)
page / per_pagenumberExplizites Blättern. Ohne beides werden alle Treffer zurückgegeben (bis zu 5000)
group_by_personbooltrue = ein Eintrag pro Person: Alts, die ein Gerät mit einem verknüpften Account teilen, werden ihm zugeordnet und teilen seine Kasse. Standard false = jeder Account einzeln

Sortieren nach playtime oder sessionCount erfordert activity_from und activity_to — ohne Fenster würde die Rangfolge stillschweigend „seit jeher" bedeuten.

Rückgabefelder (ein Eintrag pro passendem Spieler):

FeldTypBeschreibung
idintInterne Spieler-ID
usernamestringSpieler-Username
balanceintAktueller org-weiter Kontostand
first_onlinestring|nilZeitpunkt der ersten Sichtung (Y-m-d H:i:s, UTC)
last_onlinestring|nilZeitpunkt der letzten Sichtung (Y-m-d H:i:s, UTC)
whitelistedboolOb der Spieler auf der Whitelist steht
bannedboolOb eine aktive Bann-Einschränkung vorliegt
playtime_secondsint|nilSpielzeit im Aktivitätsfenster; nil ohne Fenster
session_countint|nilSessions im Aktivitätsfenster; nil ohne Fenster
altstableMit group_by_person: die anderen Accounts der Person auf diesem Server als { id, username }; sonst leer

Ohne group_by_person ist jeder Alt einer Person ein eigener Eintrag mit derselben geteilten balance. Ranglisten nach Kontostand deshalb mit group_by_person bilden, ebenso Belohnungen pro Person — wer jedem Alt etwas gutschreibt, zahlt mehrfach in dieselbe Kasse. Mit der Option decken last_online, first_online und die Aktivitätsfelder alle Accounts der Person ab.

Beispiel: Täglicher Bonus für alle VIP-Mitglieder​
local players = player.list({
group_name = "vip",
last_online_after = "-30d",
});

for _, p in ipairs(players) do
player.change_balance(p.username, 100, "Monatlicher VIP-Bonus");
end
Beispiel: Inaktive Spieler per Discord benachrichtigen​
local inaktiv = player.list({
has_discord_linked = true,
last_online_before = "-14d",
});

for _, p in ipairs(inaktiv) do
discord.send_to_player(p.username, "Wir vermissen dich auf dem Server! Komm bald zurück.");
end
Beispiel: Die fünf reichsten Personen​
local reichste = player.list({ group_by_person = true, sort_by = "balance", sort_direction = "DESC", page = 1, per_page = 5 });

for _, p in ipairs(reichste) do
log(p.username .. " (+" .. #p.alts .. " Alts): " .. p.balance);
end
Beispiel: Nach Variablenwert filtern​
-- Alle Spieler, deren Variable "vip_tier" auf "gold" gesetzt ist
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 Tagesbonus");
end

player.get_variable(username: string, name: string, serverOnly: bool = false)​

Gibt den Wert einer Spieler-Variablen zurück oder nil, wenn die Variable nicht gesetzt ist. Eine Variable gehört dem Spieler auf allen Servern deiner Organisation: Eine auf einem Server abgeholte Belohnung gilt auf allen als abgeholt. Mit serverOnly = true liest du eine, die nur auf diesem Server liegt.

local result = player.get_variable("John", "kill_count");
print(result.value); -- gibt den gespeicherten Wert aus, oder nil

Rückgabefelder:

FeldTypBeschreibung
namestringVariablenname
valuestring|nilGespeicherter Wert, oder nil wenn nicht gesetzt

player.set_variable(username: string, name: string, value: string|nil, serverOnly: bool = false)​

Erstellt oder aktualisiert eine Spieler-Variable. Mit nil als Wert wird die Variable gelöscht. Die Variable gilt auf allen Servern deiner Organisation; mit serverOnly = true bleibt sie auf diesem Server, für Zustand, der zu einer Karte gehört. Beide sind getrennte Speicherplätze, auch bei gleichem Namen.

player.set_variable("John", "kill_count", "42");
player.set_variable("John", "vip_tier", "gold");
player.set_variable("John", "temp_flag", nil); -- löscht die Variable
player.set_variable("John", "spawn_point", "4200,7800", true); -- nur dieser Server
Beispiel: Kill-Streaks mit Variablen verfolgen​
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 .. " hat einen " .. streak .. "-Kill-Streak!");
end

-- Streak des Opfers zurücksetzen
player.set_variable(data.victim.username, "kill_streak", "0");

player.last_position(username: string)​

Gibt die letzte bekannte Ingame-Position des Spielers zurück, basierend auf seinem letzten Online-Ping (wird etwa alle 5 Minuten aufgezeichnet, während er online ist), oder nil, wenn der Spieler nicht gefunden wird oder keine Position aufgezeichnet wurde. Die Position kann Minuten oder Tage alt sein — prüfe das Feld time, wenn Aktualität wichtig ist.

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

Rückgabefelder:

FeldTypBeschreibung
xfloatOst/West-Weltkoordinate
yfloatHöhe
zfloatNord/Süd-Weltkoordinate
timestringZeitpunkt der Aufzeichnung (YYYY-MM-DD HH:MM:SS, UTC)

Fraktions-Namespace​

Der Fraktions-Namespace enthält alle fraktionsbezogenen Aktionsfunktionen.

faction.change_balance(id: string, amount: number, description: string = "")​

Ändert das Guthaben der Fraktion mit der angegebenen ID.

faction.change_balance("123", 100);
faction.change_balance("123", 100, "Gute Leute!");
faction.change_balance("123", -100, "Regelverstoß!"); -- negativer Betrag zum Abziehen

Bounty-Namespace​

Der Bounty-Namespace stellt die Kopfgelder des Hitman-Systems bereit.

bounty.list()​

Gibt alle offenen Kopfgelder auf dem Server zurück, neueste zuerst. Abgeschlossene oder stornierte Kopfgelder sind nie enthalten. Gibt eine leere Liste zurück, wenn kein Kopfgeld offen ist — das Iterieren ist also immer sicher.

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

Rückgabefelder (ein Eintrag pro Kopfgeld):

FeldTypBeschreibung
idintInterne Kopfgeld-ID
targetstring|nilBenutzername des Spielers, auf den das Kopfgeld ausgesetzt ist; nil bei einem Kopfgeld auf eine Fraktion
target_idint|nilInterne Spieler-ID des Ziels
target_identifierstring|nilSpiel-ID des Ziels (der identifier in den Ereignisdaten)
target_factionstring|nilName der Fraktion bei einem Fraktions-Kopfgeld; nil bei einem Kopfgeld auf einen Spieler
target_faction_idint|nilID dieser Fraktion
created_bystring|nilBenutzername des Spielers, der das Kopfgeld ausgesetzt und bezahlt hat; nil, wenn der Server es bezahlt
server_fundedbooltrue für ein Kopfgeld, das eine Action mit bounty.create ohne funded_by ausgesetzt hat
amountintKopfgeldbetrag
reasonstring|nilBeim Aussetzen angegebener Grund
target_all_accountsboolOb der Kill eines beliebigen mit dem Gerät des Ziels verknüpften Accounts ausgezahlt wird (bei einem Fraktions-Kopfgeld immer true)
allies_may_collectboolOb Fraktionskollegen und Verbündete des Ziels es kassieren dürfen
created_atstringZeitpunkt des Aussetzens (YYYY-MM-DD HH:MM:SS, UTC)

Auf dasselbe Ziel können mehrere Kopfgelder existieren — gruppiere selbst nach target, wenn du einen Eintrag pro Spieler brauchst. Ein Kopfgeld auf eine Fraktion hat target_faction gesetzt und target ist nil.

Beispiel: Geplanter Kopfgeld-Statusbericht​

Poste die letzte bekannte Position jedes Spielers mit offenem Kopfgeld, z. B. aus einer geplanten benutzerdefinierten Aktion. Wenn kein Kopfgeld offen ist, wird keine Nachricht gesendet:

local bounties = bounty.list();
if #bounties > 0 then
local lines = {};
for _, b in ipairs(bounties) do
local pos = b.target and player.last_position(b.target);
local where = pos and string.format("%.0f / %.0f", pos.x, pos.z) or "unbekannt";
table.insert(lines, (b.target or ("Fraktion " .. b.target_faction)) .. " (" .. b.amount .. ") — zuletzt gesehen: " .. where);
end
discord.send_to_channel(config.channel, "**Offene Kopfgelder:**\n" .. table.concat(lines, "\n"));
end

bounty.create(options: table)​

Setzt ein Kopfgeld auf einen Spieler aus — genau wie eines, das ein Spieler auf der Website aussetzt: Es erscheint in der Kopfgeldliste und auf der Karte, löst das Ereignis bounty_placed aus (eine installierte Ankündigungs-Action postet es also) und zahlt den Hitman-Anteil an den, der das Ziel tötet — standardmäßig auch, wenn einer der verknüpften Accounts des Ziels getötet wird.

Mit target_faction statt target gilt das Kopfgeld einer ganzen Fraktion: Der erste Kill irgendeines Mitglieds kassiert es, Mitglieder (und Spieler mit einem verknüpften Account in der Fraktion) können es nicht, und es löst faction_bounty_placed statt bounty_placed aus — siehe Kopfgelder auf eine Fraktion.

OptionTypBeschreibung
targetint|stringInterne Spieler-ID, Spiel-ID (data.killer.identifier) oder Benutzername
target_factionint|stringStatt target: die Fraktion, per ID, Name oder Kürzel. Gib genau eines der beiden an
amountintPflicht. Ganze Zahl größer 0. Der Mindestbetrag aus den Servereinstellungen gilt für Skripte nicht
reasonstringOptionaler Text, der beim Kopfgeld steht (bis 255 Zeichen)
target_all_accountsboolAuch auszahlen, wenn ein verknüpfter Account des Ziels getötet wird (Standard true; nur bei Kopfgeldern auf Spieler)
allies_may_collectboolAuch Fraktionskollegen und Verbündete des Ziels dürfen kassieren (Standard false) — siehe Verbündete und Fraktionskollegen
funded_byint|stringOptionaler Spieler, der den Betrag bezahlt (gleiche Formen wie target). Darf nicht das Ziel oder einer seiner verknüpften Accounts sein — und bei einem Fraktions-Kopfgeld weder Mitglied der Fraktion sein noch einen verknüpften Account darin haben. Ohne allies_may_collect = true darf er außerdem nicht mit der Zielfraktion oder einer Fraktion des Zielspielers verbündet sein

Wer bezahlt:

  • Ohne funded_by bezahlt der Server das Kopfgeld. Beim Aussetzen wird nichts abgebucht. Die Auszahlung an den Hitman ist neues Geld und steht in seinem Kontoverlauf als Server-Kopfgeld für …. Wird so ein Kopfgeld zurückgezogen, bekommt niemand etwas erstattet. In der Kopfgeldliste im Adminbereich steht als Auftraggeber Server (Automatisierung).
  • Mit funded_by bezahlt dieser Spieler den Betrag sofort, als hätte er das Kopfgeld selbst ausgesetzt. Reicht sein Guthaben nicht, bricht das Skript mit Not enough balance ab.

Ein unbekannter Spieler oder eine unbekannte Fraktion, ein ungültiger Betrag oder beides bzw. keines von target und target_faction bricht das Skript ebenfalls mit einem Fehler ab.

Gibt eine Tabelle mit id, code, target, target_id, target_faction, target_faction_id (die Zielfelder der anderen Art sind nil), amount, reason, target_all_accounts, allies_may_collect, server_funded und created_by zurück (nil, wenn der Server bezahlt).

-- ein Kill am Flugplatz setzt 500 Kopfgeld auf den Killer aus, bezahlt vom Server
for _, zone in ipairs(data.zones) do
if zone.name == "NWAF" then
local b = bounty.create({
target = data.killer.identifier,
amount = 500,
reason = "Hat " .. data.victim.username .. " am NWAF getötet",
});
print("Kopfgeld " .. b.code .. " auf " .. b.target);
break;
end
end

-- das Opfer bezahlt die Rache aus dem eigenen Guthaben
bounty.create({ target = data.killer.username, amount = 250, funded_by = data.victim.username });

-- ein vom Server bezahltes Kopfgeld von 2000 auf eine ganze Fraktion, per Kürzel
bounty.create({ target_faction = "ALPHA", amount = 2000, reason = "Hat den Händler überfallen" });

Die fertige Action Auto Bounty im Marktplatz erledigt das für dich: ein Kopfgeld auf jeden Spieler, der einen anderen tötet — auf Wunsch nur in den Zonen, die du auswählst, mit Abklingzeit, einer Grenze offener Auto-Kopfgelder pro Spieler und optionalem Discord-Post.

Bookmaker-Namespace​

Der Bookmaker-Namespace stellt die Wetten des Buchmachers bereit. Spieler steigen in Discord mit /bet <code> in eine offene Wette ein — mit dem vierstelligen Code der Wette.

bookmaker.list()​

Gibt die Wetten auf dem Server zurück, die offen sind oder auf ihr Ergebnis warten, neueste zuerst. Abgerechnete und stornierte Wetten sind nie enthalten. Gibt eine leere Liste zurück, wenn es keine gibt — das Iterieren ist also immer sicher.

FeldTypBeschreibung
idintInterne Wett-ID
codestringVierstelliger Wett-Code — das, was Spieler bei /bet <code> angeben
titlestringTitel der Wette
statusstring"open" oder "waiting_for_result"
deadlinestring|nilEinsatzfrist (YYYY-MM-DD HH:MM:SS, Serverzeit)
bookmakerstring|nilBenutzername des Buchmachers
total_potintSumme aller bisherigen Einsätze
outcome_countintAnzahl der Ausgänge
Beispiel: Geplanter Post der offenen Wetten​
local lines = {};
for _, b in ipairs(bookmaker.list()) do
if b.status == "open" then
table.insert(lines, "**#" .. b.code .. "** " .. b.title .. " (Pot: " .. b.total_pot .. ") → `/bet " .. b.code .. "`");
end
end
if #lines > 0 then
discord.send_to_channel(config.channel, "**Offene Wetten:**\n" .. table.concat(lines, "\n"));
end

bookmaker.get(id: int)​

Gibt eine Wette dieses Servers in beliebigem Status zurück, oder nil, wenn es auf diesem Server keine Wette mit dieser ID gibt.

Die Einsätze pro Ausgang werden immer zurückgegeben, auch bei Wetten, die ihre Quoten vor Spielern verbergen — die Aktion gehört dem Serverbetreiber. Prüfe odds_visible, bevor du sie dort postest, wo Spieler sie sehen.

FeldTypBeschreibung
idintInterne Wett-ID
codestringVierstelliger Wett-Code
titlestringTitel der Wette
descriptionstring|nilBeschreibung der Wette
statusstring"open", "waiting_for_result", "settled" oder "cancelled"
deadlinestring|nilEinsatzfrist (YYYY-MM-DD HH:MM:SS, Serverzeit)
min_bet / max_betint|nilEinsatzgrenzen der Wette, nil = Server-Standard
odds_visibleboolOb Spieler die Einsätze pro Ausgang sehen
bookmakerstring|nilBenutzername des Buchmachers
total_potintSumme aller Einsätze
outcomestableListe von { id, label, total_amount, entry_count, is_winner } — is_winner ist nil, bis die Wette abgerechnet ist

Map-Namespace​

Der Map-Namespace macht aus rohen Koordinaten etwas, das Spieler lesen und anklicken können.

map.locate(x: number, z: number)​

Liefert den nächstgelegenen benannten Ort auf der Karte des Servers und einen iZurvive-Link zur Position — derselbe Text und Link wie bei den Kartenlinks auf der Website. Die Karte richtet sich nach der Mission des Servers (Chernarus, Livonia, Sakhal, Nasdara). Gibt nie nil zurück.

local loc = map.locate(data.location.x, data.location.z);
discord.send_to_channel(config.channel, "Gesichtet bei [" .. loc.label .. "](" .. loc.url .. ")");

Rückgabefelder:

FeldTypBeschreibung
placestring|nilNächster benannter Ort, nil wenn keiner im Umkreis von 1,5 km liegt
labelstringplace, oder die gerundeten Koordinaten, wenn es keinen gibt
urlstringiZurvive-Link, zentriert auf die Position

map.players_near(x: number, z: number, radius: number, from?: number, to?: number)​

Listet jeden Spieler, dessen geloggte Position zwischen from und to (Unix-Zeitstempel, z. B. data.time.timestamp) höchstens radius Meter (maximal 2000) von der Stelle entfernt war. Ohne Zeitangaben sind es die letzten 10 Minuten; das Fenster darf höchstens 24 Stunden umfassen. Jede Log-Zeile mit Position zählt — der Online-Ping alle 5 Minuten, aber auch Treffer, Platzieren, Bauen oder Emotes —, ein Spieler, der nur kurz da war und nichts Geloggtes getan hat, kann also fehlen. Zeilen von nach einem Ereignis sind beim Lauf der Action eventuell noch nicht importiert. Gibt eine Liste zurück, der Nächste zuerst, nie nil.

local t = data.time.timestamp;
for _, p in ipairs(map.players_near(data.location.x, data.location.z, 100, t - 600, t)) do
print(p.username .. " war " .. math.floor(p.distance) .. " m entfernt um " .. p.time);
end

Rückgabefelder pro Spieler:

FeldTypBeschreibung
usernamestringBenutzername des Spielers
distancefloatMeter von der Stelle im nächsten geloggten Moment
x / zfloatWo der Spieler in diesem Moment war
timestringWann das war (YYYY-MM-DD HH:MM:SS, UTC)

map.base_at(x: number, z: number)​

Liefert die Basis an der Stelle, oder nil, wenn keine bekannte Basis sie abdeckt. Eine Basis deckt etwa 60 m um jede ihrer Baustellen ab; tun es mehrere, gewinnt die nächste, die nicht verschwunden ist.

local base = map.base_at(data.location.x, data.location.z);
if base then
print(data.player.username .. " ist bei " .. base.name .. " (" .. #base.contributors .. " Erbauer)");
end

Rückgabefelder:

FeldTypBeschreibung
codestringDer Code der Basis, wie im Admin-Panel
namestringAnzeigename (von Hand gesetzt oder aus dem Besitzer erzeugt)
statusstringactive, decaying oder gone
ownerstring|nilBenutzername des führenden Erbauers
factiontable|nil{ id, name }, wenn eine Fraktion mindestens 75 % der Arbeit gemacht hat
contributorstableAlle, die dort gebaut haben, die Stärksten zuerst: { username, share, events } — share ist ihr Anteil an der Arbeit (0 bis 1), events, wie viele ihrer Bau-, Platzier- oder Flaggenaktionen dort geloggt wurden

Wer an einer Basis nur einen einzigen Gegenstand platziert hat, ist auch Erbauer, mit winzigem share und events von 1. Prüfe events oder share, wenn nur die echten Erbauer zählen sollen.

map.image(options: table)​

Zeichnet ein 800 × 600 großes Bild der Serverkarte (Chernarus, Livonia, Sakhal oder Nasdara, je nach Mission) mit beschrifteten Markierungen und optional einer Route und gibt ein Bild zurück, das du an discord.send_to_channel oder discord.send_to_player übergibst. Der Ausschnitt passt sich an alle Punkte und die ganze Route an, höchstens so nah, dass etwa 1,5 km zu sehen sind.

OptionTypBeschreibung
pointstableMarkierungen: Liste von { x = number, z = number, label = "Text", color = "red" } (Beschriftung und Farbe optional), bis zu 25
routetableOptionale Linie durch eine Liste von { x, z }-Positionen in Reihenfolge, bis zu 500 — grüner Punkt am Anfang, roter am Ende
zoomnumberOptional fester Zoom: 1 zeigt die ganze Karte, 8 ist der nächste

Farben: red (Standard), blue, green, yellow, orange, purple, grey, white, black oder "#rrggbb". Beschriftungen werden nach 40 Zeichen abgeschnitten. Ein Punkt ohne numerisches x und z bricht die Action mit einem Fehler ab — prüfe Positionen, die fehlen können, also vorher.

Rückgabefelder:

FeldTypBeschreibung
typestringImmer "map_image"
idstringKennzeichnet das Bild; gleiche Eingaben ergeben dieselbe ID
zoomnumberZoom, mit dem das Bild gezeichnet wurde
width, heightnumberGröße in Pixeln
render_msnumberWie lange das Zeichnen gedauert hat, 0, wenn dasselbe Bild in den letzten 15 Minuten schon gezeichnet wurde

Eine Action kann pro Lauf bis zu 5 Bilder zeichnen. Das Zeichnen dauert einige hundert Millisekunden, bis zu einigen Sekunden, wenn der Kartenausschnitt länger nicht gezeichnet wurde; das Action-Log zeigt die Zeit jedes Bildes.

Beispiel: Kill-Ort (player_kill)​
local img = map.image({
points = {{ x = data.location.x, z = data.location.z, label = data.victim.username }},
});
discord.send_to_channel(config.channel, data.killer.username .. " hat " .. data.victim.username .. " mit " .. data.weapon .. " getötet", img);
Beispiel: Kopfgeld-Ziel gesichtet (player_online)​

Die Karte erscheint im Embed:

for _, b in ipairs(bounty.list()) do
if b.target_identifier == data.player.identifier then
local img = map.image({
points = {{ x = data.location.x, z = data.location.z, label = b.target .. " (" .. b.amount .. ")", color = "orange" }},
});
discord.send_to_channel(config.channel, "<embed><title>Kopfgeld-Ziel gesichtet</title><description>"
.. b.target .. " bei " .. map.locate(data.location.x, data.location.z).label .. "</description></embed>", img);
break;
end
end
Beispiel: Basis-Alarm (base_alarm_created)​
local img = map.image({ points = {{ x = data.posX, z = data.posZ, label = data.faction.name, color = "yellow" }}, zoom = 6 });
discord.send_to_channel(config.channel, "Basis-Alarm von " .. data.faction.name .. " ausgelöst von " .. data.player.username, img);
Beispiel: Route mit Ziel als DM​
local img = map.image({
route = {{ x = 4500, z = 10200 }, { x = 5200, z = 9800 }, { x = 6100, z = 9300 }},
points = {{ x = 6100, z = 9300, label = "Treffpunkt", color = "green" }},
});
discord.send_to_player(data.player.username, "Deine Route zum Treffpunkt", img);

Math-Namespace​

Der Math-Namespace enthält Hilfsfunktionen für Berechnungen.

math.distance(x1: number, z1: number, x2: number, z2: number)​

Berechnet die 2D-Distanz zwischen zwei Punkten. Nützlich, um zu prüfen, ob eine Position innerhalb eines bestimmten Radius einer anderen Position liegt.

-- Distanz zwischen zwei Koordinaten berechnen
local dist = math.distance(1000, 2000, 1500, 2500);
print("Distanz: " .. dist); -- gibt ungefähr 707.1 aus
Beispiel: Radius-Prüfung​

Prüfen, ob ein Kill innerhalb eines bestimmten Radius einer Location stattgefunden hat (z.B. einer Safezone oder Basis):

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 innerhalb der Safezone erkannt!");
server.ban_player(data.killer, "Kill in Safezone", "1 day");
end
Beispiel: Weitschuss-Kills belohnen​
-- Distanz zwischen Killer und Opfer berechnen
local kill_range = math.distance(data.killerX, data.killerZ, data.victimX, data.victimZ);

if kill_range > 500 then
player.change_balance(data.killer, 100, "Weitschuss-Bonus (" .. math.floor(kill_range) .. "m)");
discord.send_to_channel(config.feedChannel, data.killer .. " hat einen " .. math.floor(kill_range) .. "m-Kill erzielt!");
end

HTTP-Namespace​

Der HTTP-Namespace ermöglicht es Aktionen, ausgehende HTTP-Anfragen an externe Dienste zu stellen. Anfragen haben ein Timeout von 5 Sekunden.

http.get(url: string, headers?: table)​

Führt eine HTTP-GET-Anfrage durch. Gibt zwei Werte zurück: den HTTP-Statuscode und den Antwort-Body als String.

local status, body = http.get("https://api.example.com/data");
print(status); -- z.B. 200
print(body); -- roher Antwort-Body als String

Mit benutzerdefinierten Headern:

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

http.post(url: string, body?: string, headers?: table)​

Führt eine HTTP-POST-Anfrage durch. Gibt zwei Werte zurück: den HTTP-Statuscode und den Antwort-Body als String.

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

JSON-Body senden:

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 meintoken",
});
Beispiel: Externen Webhook bei einem Kill-Event benachrichtigen​
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 fehlgeschlagen mit Status: " .. status);
end

JSON-Namespace​

Der JSON-Namespace stellt JSON-Kodierung und -Dekodierung bereit, um mit API-Antworten und Payloads zu arbeiten.

json.encode(value)​

Kodiert einen Lua-Wert (Tabelle, String, Zahl, Boolean) als JSON-String.

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

json.decode(json: string)​

Parst einen JSON-String und gibt eine Lua-Tabelle zurück.

local decoded = json.decode('{"name":"John","score":42}');
print(decoded.name); -- John
print(decoded.score); -- 42
Beispiel: JSON-API aufrufen und Antwort verarbeiten​
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

Geplante Aktionen​

Der Auslöser Geplant ist gar kein Ereignis: Eine Aktion damit läuft nur, wenn eine Scheduler-Aufgabe Aktion ausführen sie ausführt – zu den Zeiten, die du in der Aufgabe festlegst. Nimm ihn für alles, was nach der Uhr passieren soll – ein Wochenlohn, ein täglicher Post, ein monatliches Aufräumen –, während die Einstellungen im Formular der Aktion bleiben, wo auch Teammitglieder ohne Code-Zugriff sie ändern können.

Eine solche Aktion bekommt wie gewohnt ihre config. data beschreibt den Lauf:

FeldTypBeschreibung
data.time.stringstringWann die Aktion lief (Serverzeit, Y-m-d H:i:s)
data.time.timestampintDasselbe als Unix-Zeitstempel
data.scheduled_at.stringstringWann die Aufgabe fällig war. Ein Lauf startet ein paar Sekunden danach; bei Jetzt ausführen ist es der Moment des Klicks
data.scheduled_at.timestampintDasselbe als Unix-Zeitstempel
data.task_idintID der Scheduler-Aufgabe
data.task_descriptionstring|nilDie Beschreibung der Aufgabe
data.recurringbooltrue bei einer wiederkehrenden Aufgabe, false bei einer einmaligen

Eine Aufgabe kann eine Aktion öfter ausführen als geplant – Jetzt ausführen, eine duplizierte Aufgabe. Zahlt die Aktion etwas aus, sollte sie sich merken, was sie schon bezahlt hat, zum Beispiel mit einer Spielervariable (player.set_variable) pro Tag:

local day = string.sub(data.scheduled_at.string, 1, 10);
for _, p in ipairs(player.list({ group_name = config.group })) do
if player.get_variable(p.username, "bonus_paid").value ~= day then
player.set_variable(p.username, "bonus_paid", day);
player.change_balance(p.username, config.amount, "Daily bonus " .. day);
end
end

Eine deaktivierte Aktion wird übersprungen, und die Aufgabe geht zu ihrem nächsten Lauf über. Eine geplante Aktion hat kein Spielereignis, auf das sie reagieren könnte, und läuft deshalb nie von selbst.

In-App-Events​

Die meisten Action-Trigger stammen direkt aus den Spielserver-Logs (Kills, Verbindungen, Basisbau usw.). Eine kleine Anzahl von Events wird von dzbot selbst erzeugt, wenn etwas innerhalb der Plattform passiert. Diese synthetischen Events durchlaufen dieselbe Action-Pipeline wie Game-Log-Events.

bounty_placed​

Wird ausgelöst, sobald ein Kopfgeld aktiv wird — ausgesetzt von einem Spieler über den Shop oder von einer Action mit bounty.create. Verwende diesen Trigger, um das Ziel zu benachrichtigen, in einem Discord-Kanal zu posten oder eine andere Automatisierung zu starten, sobald ein Kopfgeld aktiv wird.

Event-Payload:

FeldTypBeschreibung
data.target.usernamestringBenutzername des Spielers, auf den das Kopfgeld ausgesetzt wurde
data.target.identifierstringSteam/DayZ-ID des Ziels
data.placedBy.usernamestringBenutzername des Spielers, der das Kopfgeld ausgesetzt hat; der Servername, wenn der Server bezahlt
data.placedBy.identifierstringSteam/DayZ-ID des Auftraggebers; leer, wenn der Server bezahlt
data.serverFundedbooltrue, wenn eine Action das Kopfgeld ohne zahlenden Spieler ausgesetzt hat
data.amountintHöhe des Kopfgelds in der Server-Währung
data.reasonstring|nilOptionale Begründung des Auftraggebers
data.bountyIdint|nilInterne Kopfgeld-ID

Beispiel — DM an den Ziel-Spieler senden:

local reason = data.reason or "Kein Grund angegeben";
discord.send_to_player(
data.target.username,
"Ein Kopfgeld von **" .. data.amount .. "** wurde von "
.. data.placedBy.username .. " auf dich ausgesetzt.\n> " .. reason
);

Beispiel — Nachricht in einen Kanal senden:

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

local reason = data.reason or "Kein Grund angegeben";
discord.send_to_channel(
channelId,
"**Kopfgeld ausgesetzt!** " .. discord.mention(data.placedBy.username)
.. " hat ein Kopfgeld von **" .. data.amount .. "** auf "
.. discord.mention(data.target.username) .. " ausgesetzt.\n> *" .. reason .. "*"
);

faction_bounty_placed​

Wird ausgelöst, wenn ein Kopfgeld auf eine ganze Fraktion live geht — von einem Spieler gesetzt oder von einer Action mit bounty.create({ target_faction = … }). Ein Kopfgeld auf einen Spieler löst stattdessen bounty_placed aus; eine Ankündigungs-Action für Spieler-Kopfgelder postet diese also nicht.

FeldTypBeschreibung
data.faction.idintID der Fraktion
data.faction.namestringName der Fraktion
data.faction.tagstring|nilKürzel der Fraktion
data.memberslistDie Mitglieder beim Aussetzen, jeweils mit username und identifier
data.placedBy.usernamestringWer es gesetzt hat; der Servername, wenn der Server bezahlt
data.placedBy.identifierstringSteam/DayZ-ID des Auftraggebers; leer, wenn der Server bezahlt
data.serverFundedbooltrue, wenn eine Action es ohne zahlenden Spieler gesetzt hat
data.amountintKopfgeldbetrag in Serverwährung
data.reasonstring|nilOptionaler Grund
data.bountyIdintInterne Kopfgeld-ID

Beispiel — in einen Kanal posten und jedem Mitglied eine DM schicken:

local reason = data.reason or "Kein Grund angegeben";
discord.send_to_channel(config.channel,
"**Kopfgeld auf eine Fraktion!** " .. data.placedBy.username .. " setzt **" .. data.amount
.. "** auf " .. data.faction.name .. ". Der erste Kill irgendeines Mitglieds kassiert es.\n> *" .. reason .. "*");
for _, member in ipairs(data.members) do
discord.send_to_player(member.username, "Auf deine Fraktion " .. data.faction.name .. " ist ein Kopfgeld von " .. data.amount .. " ausgesetzt.");
end

bounty_completed​

Wird ausgelöst, wenn ein Kill ein Kopfgeld einlöst — auf einen Spieler oder auf eine Fraktion. Der Spieler des Ereignisses ist der Auftragskiller.

FeldTypBeschreibung
data.hitman.username / .identifierstringDer Killer, der es kassiert hat
data.victim.username / .identifierstringDer getötete Spieler
data.targettable|nilBei einem Kopfgeld auf einen Spieler: username und identifier des Accounts, auf den es gesetzt war (das Opfer oder dessen dort genannter Account); nil bei einem Fraktions-Kopfgeld
data.targetFactiontable|nilBei einem Kopfgeld auf eine Fraktion: id, name, tag; nil bei einem Spieler-Kopfgeld
data.placedBy.username / .identifierstringWer es gesetzt hat; der Servername und eine leere ID, wenn der Server bezahlt hat
data.serverFundedbooltrue, wenn der Server es bezahlt hat
data.amountintBetrag des Kopfgelds
data.payoutintWas der Auftragskiller bekommen hat (sein Anteil am Betrag)
data.bountyIdintInterne Kopfgeld-ID
local on = data.targetFaction and ("die Fraktion " .. data.targetFaction.name) or data.target.username;
discord.send_to_channel(config.channel,
"**Kopfgeld kassiert!** " .. data.hitman.username .. " hat " .. data.victim.username
.. " getötet und " .. data.payout .. " für das Kopfgeld auf " .. on .. " kassiert.");

faction_alliance_formed, faction_alliance_joined, faction_alliance_left, faction_alliance_dissolved​

Werden ausgelöst, wenn sich eine Allianz ändert. Sie hängen an keinem Spieler und posten von sich aus nichts – installiere oder schreib eine Action, um sie anzukündigen.

EreignisWanndata.faction ist
faction_alliance_formedDie erste eingeladene Fraktion hat zugesagt, die Allianz gilt jetztdie Fraktion, die zugesagt hat
faction_alliance_joinedEine weitere Fraktion ist beigetreten, nachdem alle Mitglieder zugestimmt habendas neue Mitglied
faction_alliance_leftEine Fraktion hat eine Allianz verlassen, die mit zwei oder mehr Fraktionen weiterbestehtdie Fraktion, die gegangen ist
faction_alliance_dissolvedWeniger als zwei Fraktionen sind übrig, die Allianz ist beendetdie Fraktion, die zuletzt gegangen ist
FeldTypBeschreibung
data.alliance.idintID der Allianz
data.alliance.namestringName der Allianz
data.faction.id / .name / .tagint / string / string|nilDie Fraktion, um die es bei der Änderung geht (siehe oben)
data.factionslistDie Mitgliedsfraktionen nach der Änderung, jeweils mit id, name und tag. Bei faction_alliance_dissolved die eine Fraktion, die noch drin war
data.reasonstringNur faction_alliance_left: left (ausgetreten), removed (von den anderen Mitgliedern hinausgewählt) oder faction_deleted

Beispiel — neues Mitglied ankündigen (Auslöser faction_alliance_joined):

local names = {};
for _, f in ipairs(data.factions) do
table.insert(names, f.name);
end
discord.send_to_channel(config.channel,
"🤝 " .. data.faction.name .. " ist **" .. data.alliance.name .. "** beigetreten. Mitglieder jetzt: " .. table.concat(names, ", "));

bet_opened, bet_closed, bet_settled, bet_cancelled​

Werden im Lebenslauf einer Buchmacher-Wette ausgelöst — egal ob ein Spieler, ein Admin oder ein Event-Match sie angelegt hat. Verwende sie, um neue Wetten in einem Discord-Kanal anzukündigen, an das Ende der Einsatzfrist zu erinnern oder das Ergebnis zu posten. data.bookmaker ist der Spieler, der die Wette führt; bookmaker.get(data.bet_id) liefert die vollständige Wette.

EventWann
bet_openedEine Wette wurde angelegt
bet_closedEine Wette nimmt keine Einsätze mehr an und wartet auf ihr Ergebnis — von Hand geschlossen oder weil die Frist abgelaufen ist
bet_settledDer Gewinner-Ausgang wurde gesetzt und die Gewinner wurden ausgezahlt
bet_cancelledEine Wette wurde storniert und jeder Einsatz erstattet

Event-Payload (alle vier):

FeldTypBeschreibung
data.bookmaker.usernamestringBenutzername des Buchmachers
data.bookmaker.identifierstringSteam/DayZ-ID des Buchmachers
data.bet_idintInterne Wett-ID, für bookmaker.get(data.bet_id)
data.bet_codestringVierstelliger Wett-Code — das, was Spieler bei /bet <code> angeben
data.titlestringTitel der Wette

Zusätzliche Felder:

EventFeldTypBeschreibung
bet_openeddata.descriptionstring|nilBeschreibung der Wette
bet_openeddata.outcomestableListe von { id, label }
bet_openeddata.deadlinestring|nilEinsatzfrist (YYYY-MM-DD HH:MM:SS, Serverzeit)
bet_openeddata.min_bet / data.max_betint|nilEinsatzgrenzen, nil = Server-Standard
bet_closeddata.reasonstring"manual" oder "deadline"
bet_closeddata.total_potintSumme aller Einsätze
bet_closeddata.entry_countintAnzahl der Einsätze
bet_settleddata.winning_outcome.id / .labelint / stringDer Gewinner-Ausgang
bet_settleddata.total_potintSumme aller Einsätze
bet_settleddata.winner_countintAnzahl der ausgezahlten Spieler
bet_settleddata.total_payoutintSumme der Auszahlungen an die Gewinner
bet_cancelleddata.refunded_totalintErstattete Summe
bet_cancelleddata.entry_countintAnzahl der Einsätze

Beispiel — Neue Wette ankündigen (Trigger bet_opened):

local options = {};
for _, o in ipairs(data.outcomes) do
table.insert(options, "• " .. o.label);
end
local deadline = data.deadline and ("\nEinsätze bis " .. data.deadline) or "";
discord.send_to_channel(
config.channel,
"**Neue Wette #" .. data.bet_code .. ": " .. data.title .. "**\n" .. table.concat(options, "\n")
.. deadline .. "\nMitwetten mit `/bet " .. data.bet_code .. "`"
);

Beispiel — Ergebnis posten (Trigger bet_settled):

discord.send_to_channel(
config.channel,
"**" .. data.title .. "** ist entschieden: " .. data.winning_outcome.label .. " gewinnt. "
.. data.winner_count .. " Gewinner teilen sich " .. data.total_payout .. " " .. server.currency() .. "."
);

discord_member_joined​

Feuert, wenn jemand dem Discord-Server beitritt, mit dem dein Gameserver verbunden ist – der Auslöser für eine Willkommensnachricht. Es feuert für jedes neue Mitglied, egal ob es schon auf deinem Server spielt; Bots lösen es nie aus. Teilen sich mehrere Gameserver einen Discord, feuert es auf jedem von ihnen.

Event-Daten:

FeldTypBeschreibung
data.mentionstringErwähnung (<@id>) – in eine Nachricht eingesetzt, wird das neue Mitglied gepingt
data.discord_idstringDiscord-Nutzer-ID
data.discord_usernamestringDiscord-Nutzername
data.discord_namestringAnzeigename, oder der Nutzername, wenn keiner gesetzt ist
data.discord_created_atstring|nilWann das Discord-Konto erstellt wurde (ISO 8601)
data.usernamestring|nilIngame-Name auf diesem Server – nil, bis das Mitglied ein Spielkonto verknüpft
data.rejoinedbooltrue, wenn das Mitglied schon vor diesem Beitritt verknüpft war, also zurückkehrt

Beispiel – Neue Mitglieder in einem Channel begrüßen:

if data.rejoined then
discord.send_to_channel(config.channel, "Willkommen zurück, " .. data.mention .. "!");
return;
end

discord.send_to_channel(
config.channel,
"Willkommen auf " .. server.name() .. ", " .. data.mention .. "! Lies die Regeln und verknüpfe dein Spielkonto mit `/link`."
);

Die fertige Action Discord Welcome Message im Marktplatz macht genau das, mit einstellbarem Channel und Text.

player_warned​

Wird ausgelöst, wenn ein Spieler eine Verwarnung erhält — von deinem Team (Website, Discord-Spielerkarte, KI-Companion) oder automatisch für einen Verstoß gegen eine Einschränkung, die Verstöße als Verwarnung zählt. Die Verwarnungsstufen bannen von selbst; nutze dieses Event für alles andere, etwa einen Hinweis im Team-Kanal.

Event-Daten:

FeldTypBeschreibung
data.player.usernamestringDer verwarnte Spieler
data.noteIdnumberDie Verwarnungs-Notiz
data.reasonstringWofür die Verwarnung ist
data.warnedBystringBenutzername des Teammitglieds — leer bei einer automatischen Verwarnung
data.automaticbooltrue, wenn ein Verstoß gegen eine Einschränkung die Verwarnung erteilt hat
data.restrictionIdnumber|nilDie Einschränkung bei einer automatischen Verwarnung
data.activeWarningsnumberNicht abgelaufene Verwarnungen des Spielers auf diesem Server, diese eingeschlossen
data.expiresAtstring|nilWann die Verwarnung abläuft (ISO 8601), nil wenn nie

Beispiel — Hinweis im Team-Kanal:

discord.send_to_channel(
config.channel,
"⚠️ " .. data.player.username .. " wurde verwarnt (" .. data.activeWarnings .. " aktiv): " .. data.reason
);

Verwandte Dokumentation​