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

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.

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

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: string, number oder boolean
  • Standard: Fallback-Wert, wenn kein Wert in den Einstellungen gespeichert ist
  • Beschreibung: Hilfetext, der im Einstellungs-Tab angezeigt wird

Empfohlene Benennung: camelCase oder snake_case ohne Leerzeichen.

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. Gespeicherter Wert aus dem Einstellungs-Tab (falls vorhanden)
  2. Andernfalls der Feld-Standard aus dem Schema
  3. Andernfalls nil

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

Typverhalten

Config-Werte werden vor der Skriptausführung nach Feldtyp umgewandelt:

  • string: Wert wird als Zeichenkette verwendet
  • number: Als Integer oder Float geparst
  • boolean: Als true/false geparst

Leere Werte werden als nil behandelt.

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
channelIdstringDiscord-Kanal-ID 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 Serverzeit zurück.

print(server.current_time);

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

Erstellt eine Bann-Einschränkung für den Spieler mit dem angegebenen Benutzernamen. Der Bann ist unter Admin → Einschränkungen sichtbar und verwaltbar.

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

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)

Sendet eine Nachricht an den Spieler mit dem angegebenen Benutzernamen.

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)

Sendet eine Nachricht an den Kanal mit der angegebenen ID.

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

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

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.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

Beispielausgabe:

{
"id": 42,
"name": "Wolves",
"color": "#ff0000",
"image": null,
"balance": 1500,
"discord_role_id": "1234567890"
}
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)

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.

player.get_name("johnnyboy420"); -- gibt den Anzeigenamen des Spielers zurück: "John Doe"
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.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)

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.

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

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.

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

player.list(filters?: table)

Gibt eine Liste aller Spieler auf dem Server zurück, optional gefiltert. Ohne Argumente werden alle Spieler zurückgegeben.

local players = player.list();

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

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

Verfügbare Filter (alle optional):

FilterTypBeschreibung
groupstringNur Spieler, die Mitglied dieser Gruppe sind (nach Name)
whitelistedboolNur auf der Whitelist stehende / nicht stehende Spieler
bannedboolNur Spieler mit aktiver Bann-Einschränkung / Spieler ohne aktive Bann-Einschränkung
has_discordboolNur Spieler mit verknüpftem Discord-Konto
has_balance_abovenumberNur Spieler, deren Guthaben über diesem Wert liegt
has_balance_belownumberNur Spieler, deren Guthaben unter diesem Wert liegt
last_online_afterstringNur Spieler, die nach diesem Zeitpunkt zuletzt gesehen wurden. Unterstützt relative Formate wie "-7d", "-2h", "-1m", "-1y" oder einen absoluten Datums-String
last_online_beforestringNur Spieler, die vor diesem Zeitpunkt zuletzt gesehen wurden. Gleiches Format wie oben
in_factionbool oder stringtrue = in einer beliebigen Fraktion, false = in keiner Fraktion, oder Fraktionsname/-ID für eine bestimmte Fraktion
variablestring oder tableNur Spieler, bei denen eine Variable mit diesem Namen gesetzt ist. Als String wird nur der Name geprüft, als Tabelle { name = "...", value = "..." } zusätzlich der Wert

Rückgabefelder (ein Eintrag pro passendem Spieler):

FeldTypBeschreibung
idintInterne Spieler-ID
usernamestringSpieler-Benutzername
balanceint|nilAktuelles Guthaben
last_onlinestring|nilZuletzt-gesehen-Zeitstempel (Y-m-d H:i:s)
whitelistedboolOb der Spieler auf der Whitelist steht
bannedboolOb der Spieler eine aktive Bann-Einschränkung hat
Beispiel: Tägliche Guthabenbelohnung für alle VIP-Mitglieder
local players = player.list({
group = "vip",
last_online_after = "-30d",
});

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

for _, p in ipairs(inaktive) do
discord.send_to_player(p.username, "Wir vermissen dich auf dem Server! Komm bald wieder.");
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)

Gibt den Wert einer Spieler-Variablen zurück oder nil, wenn die Variable nicht gesetzt ist.

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)

Erstellt oder aktualisiert eine Spieler-Variable. Mit nil als Wert wird die Variable gelöscht.

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
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");

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

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

Verwandte Dokumentation