17 KiB
Lua-gesteuerte Testbots
Testbots sind ein eigener Verbraucher des Clientless Player Core. Sie laden keine Adventure-Profile, keinen Tagesplan, keine Versorgungs-, Gruppen- oder Kampf-KI. Die Adventure-Population verwaltet diese Charaktere nicht. Ausführung erfolgt im Worldthread durch einen getrennten Lua-Zustand pro Testlauf. Normale Serverphysik und Prüfungen echter Spieleraktionen bleiben wirksam; administrative Vorbereitung ist davon getrennt. DungeonBots bleiben unabhängig.
Einstieg und Ergebnis
Clientless.TestBot.Enabled = 1
Clientless.TestBot.Root = "/path/to/testbots"
Clientless.TestBot.TimeoutSeconds = 300
Der Root enthält requests, scripts, results, archive und registry.
Keine Installation und kein Neustart für neue Lua-Testskripte erforderlich.
Dateiaufträge zuerst außerhalb von requests schreiben und atomar nach
requests/<eindeutige-id>.lua verschieben. Alternativ:
clientless test pause
clientless test resume
clientless test run <eindeutige-id> <skriptname>
clientless test submit <eindeutige-id> <base64-lua>
clientless test status <id>
clientless test result <id>
clientless test cancel <id>
clientless test breathing <guid> limited|unlimited|status
run lädt scripts/<skriptname>.lua. submit nimmt bis64KiB Lua entgegen und
stellt es in die Dateiwarteschlange. Die vorhandenen GM-/SOAP-Rechte schützen
diesen administrativen Einstieg; es wird kein neuer Netzwerkdienst geöffnet.
Skripte sind administrative Testaufträge für vertrauenswürdige Betreiber.
Ein Test läuft zur Zeit, kann aber mehrere Bots steuern. Dateiaufträge warten;
ihre Reihenfolge ist nicht garantiert. Abhängige Fälle erst nach abgeschlossenem
Vorgänger einreichen.
pause hält die Annahme neuer Läufe an; laufende Tests werden separat mit
cancel beendet. Vor Builds Testannahme pausieren, aktive Tests beenden und
dann den sicheren Logout aller Clientless-Sessions anfordern. Der Build startet
parallel; der reguläre Shutdown nach erfolgreichem Build übernimmt die Frist.
IDs werden nie wiederverwendet. results/<id>.json wird per atomarem Umbenennen
veröffentlicht: Schema1, Run-ID, Zustand, Startzeit, Dauer, Prüfungs-/Fehlerzahl,
Fehlermeldung, Bot-GUIDs und Beobachtungen. Terminalzustände sind passed,
failed, cancelled, timed_out, interrupted. Bei Neustart werden zuvor
laufende Tests als interrupted markiert und nicht still wiederholt.
record/check publizieren mit dem nächsten Yield bzw. Abschluss.
Ende/Fehler/Abbruch fordert den Logout ausschließlich der zum Lauf gehörenden
Testbots an. Charaktere und deren regulär gespeicherter Quest-/Inventarstand
bleiben zur Untersuchung und Wiederverwendung erhalten. Eine Ausgabe passed
bedeutet nur, dass das Skript ohne Fehler und ohne fehlgeschlagene check-Prüfung
endete; die Aussagekraft hängt von den tatsächlich formulierten Prüfungen ab.
Der Python-Adapter kann über vorhandenes SOAP ohne tmux-/Datenbankzugriff arbeiten:
# TESTBOT_PASSWORD über die Umgebung des ausführenden Systems bereitstellen.
python3 tools/testbots/client.py quest-001 case.lua \
--endpoint http://127.0.0.1:7878/ --user operator --output result.json
SOAP bleibt standardmäßig deaktiviert; der Adapter verwendet die vorhandene
Trinity-SOAP-Konfiguration und Berechtigungen. Dateieingang und lokale Konsole
funktionieren unabhängig von SOAP. Exitcode0: passed,1: anderer Terminalzustand,
2: Übertragungs-/Clientfehler. --timeout setzt die Client-Wartefrist. Ein Clienttimeout beendet den serverseitigen Lauf
nicht; er kann anschließend abgefragt oder abgebrochen werden.
Charakterinitialisierung vollständig in Lua
local bot = testbot.create(account_id, "Testrion", 1, 1, 10, 0)
-- Parameter: normaler Testaccount, Name, Rasse, Klasse, Level, Geschlecht.
-- Rückkehr erst nach asynchronem Erzeugen und abgeschlossener Anmeldung.
testbot.level(bot, 10)
testbot.clear_slot(bot, 15) -- vorhandene Haupthand bewusst entfernen
assert(testbot.equip(bot, 15, 25))
testbot.stat(bot, 0, 100) -- Grundwert Stärke; 0..4: Stärke bis Willenskraft
-- Lebens-/Ressourcenwerte zuletzt setzen, nach Level/Ausrüstung/Grundwerten:
testbot.stats(bot, 5000, 100)
assert(testbot.teleport(bot, 0, -8912, -138, 80.9, 0))
testbot.await(function() return testbot.online(bot) end, 10000, "Teleport")
testbot.record("start", testbot.state(bot))
testbot.check(testbot.state(bot).level == 10, "Level vorbereitet")
Charaktererzeugung nutzt die generische native CharacterFactory, keine
Adventure-Generierung. Account muss normal sein und einen freien Charakterslot
haben; Rasse/Klasse/Name müssen für den Core gültig sein. Ein erzeugter Charakter
wird im Testbot-Register hinterlegt. Später testbot.login(guid) verwenden.
Nur registrierte Testcharaktere dürfen angemeldet werden; fremde Spieler und
AdventureBots werden nicht übernommen. Keine vorhandenen Figuren werden geklont.
Aufgabenspezifische Stats werden bei Bedarf in jedem Lauf erneut gesetzt.
Lua-API
Alle Bot-GUIDs stammen aus create oder login. Jede Aktion überprüft die
Zugehörigkeit zum aktuellen Lauf. Nach Teleports auf online warten.
| Funktion | Wirkung / Rückgabe |
|---|---|
create(account,name,race,class,level,gender) |
Charakter erzeugen und anmelden; GUID |
login(guid) |
Registrierten Testcharakter anmelden, auf Login warten; GUID |
online(guid) |
Anmeldung und Teleport abgeschlossen |
wait(ms) |
Coroutine abgeben,0..60000ms; blockiert den Server nicht |
await(predicate,timeout_ms,label) |
Bedingung mit echter Laufzeitfrist prüfen |
now_ms() |
Monotone Millisekunden seit Laufbeginn |
state(guid) |
Map,XYZ,Level,HP/Ressource,alive/combat/moving/transport sowie movement_flags, animation_tier, hover_animation, mounted/flying/swimming/falling/in_water/can_swim (0/1), swim_speed und travel_state |
teleport(guid,map,x,y,z,o) |
Administrativer nativer Teleport; bool |
level(guid,level) |
Level1..85 samt Talentgrundinitialisierung |
stat(guid,index,value) |
Grundwert0..4 über natives Stats-System setzen |
stats(guid,max_hp,max_power?) |
Lebens-/aktuelle Klassenressource auffüllen |
learn(guid,spell) |
Vorbereitung: einen gültigen Zauber lernen |
item(guid,item,count) |
Vorbereitung: Inventargegenstände bereitstellen; bool |
clear_slot(guid,slot) |
Vorbereitung: Gegenstand im Ausrüstungs-/Taschenslot0..22 löschen |
equip(guid,slot,item) |
Vorbereitung: leeren Slot nativ ausrüsten; bool |
item_count(guid,item) |
Aktuelle Inventaranzahl ohne Bank |
travel(guid,x,y,z) |
Normale CPC-Navigation samt Gebiets-/Pfad-/Wasserprüfung; bool und Diagnosetext |
liquid(guid,x,y,z) |
Native Flüssigkeitsprobe mit status, surface, bottom, liquid/safe/swimming (0/1) |
move(guid,x,y,z) |
Native MMAP-/MovePoint-Bewegung ohne Gebietskatalog; bool |
flight(guid,x,y,z,following?) |
Nativer CPC-Flug, mit following=true bewegliches Flugziel einschließlich Halten; bool und Fehlertext. Normale Profil-/Mount-/Gebietsprüfung bleibt aktiv |
land_flight(guid) |
Folgenden Flug über die normale geprüfte Landung beenden; bool |
stop(guid) |
Angriff und aktuelle Bewegung stoppen |
creature(guid,spawn) / object(guid,spawn) |
Sichtbarer geladener Spawn; vollständige GUID als String oder nil |
target_state(guid,target) |
Geladenes Unit-Ziel: HP,alive,Distanz,XYZ; sonst nil |
face(guid,target) |
Zum geladenen Ziel ausrichten |
attack(guid,target) |
Nativen Nahkampfangriff beginnen; bool |
cast(guid,spell,target?) |
Nicht getriggerter nativer Cast, Standardziel selbst; SpellCastResult (Erfolg255) |
use_item(guid,bag,slot,spell,target?) |
Nativer Spieler-UseItem-Handler |
use(guid,object) |
Nativer GameObject-Klick |
gossip(guid,target,menu,option,code?) |
Native Gossip-Auswahl, optional Texteingabe |
hello(guid,target) |
Nativen Questgeberdialog öffnen |
accept(guid,target,quest) |
Normale Questannahme über Spieler-Handler |
abandon(guid,quest) |
Quest über nativen Questlog-Handler abbrechen |
request_reward(guid,target,quest) |
Nativen Abgabedialog anfordern; erzwingt keine Zielerfüllung |
reward(guid,target,quest,choice) |
Native Belohnungswahl0..5; Eligibility bleibt erhalten |
quest(guid,quest) |
Status und rewarded; NONE0,COMPLETE1,INCOMPLETE3,FAILED5 |
objectives(guid,quest) |
Vier native Questlog-Zähler (Lua-Indizes1..4) |
loot(guid,target) |
Native Lootöffnung einer Kreaturenleiche; Truhen über den passenden Opening-Zauber öffnen |
loot_item(guid,slot) / loot_release(guid,target) |
Lootgegenstand aufnehmen / Fenster schließen |
record(label,value) |
JSON-Beobachtung; Skalare oder begrenzte Tabellen |
check(condition,label) |
Prüfung protokollieren; false macht Endergebnis failed |
Zusätzliche Eingaben und Beobachtungen für mehrstufige Questtests:
| Funktion | Wirkung / Rückgabe |
|---|---|
skill(guid,id,value,max) |
Vorbereitung: gültige Fertigkeit nativ setzen; bool |
remove_item(guid,item,count) |
Vorbereitung: Gegenstände entfernen |
inventory(guid) |
Gegenstände mit bag/slot/item/count, GUID-String und spells[{id,trigger}] |
nearest_creature(guid,entry,radius) / nearest_object(guid,entry,radius) |
Sichtbares Ziel innerhalb maximal100Yards; GUID-String oder nil |
npc_hello(guid,target) |
Nativen Gossip-Dialog öffnen |
menu(guid) |
Aktuelle id/source_guid und options[{id,text,action,coded}] |
spellclick(guid,target) |
Nativer NPC-Zauberklick, etwa Questfahrzeuge |
exit_vehicle(guid) |
Normalen Fahrzeugausstieg anfordern |
request_cast(guid,spell,target?) |
Normales Client-Cast-Paket, Unit-/GO-Ziel automatisch |
request_cast_at(guid,spell,x,y,z) |
Client-Cast mit Bodenziel |
use_item_at(guid,bag,slot,spell,x,y,z) |
Itembenutzung mit Bodenziel |
area_trigger(guid,id) |
Nativer AreaTrigger-Handler; echte Position wird geprüft |
state enthält zusätzlich area/zone, loot_guid, vehicle_entry/vehicle_guid und
mover_guid. target_state enthält zusätzlich entry/level/max_health. Ein Testbot
bestätigt seine erlaubte kontrollierte Einheit über den normalen ActiveMover-
Handler, auch beim Wechsel in ein Fahrzeug. Das umgeht keine Bewegungsrechte.
Ein GameObject-Klick öffnet nicht automatisch jede Truhe: Dafür muss wie beim
Client der zu Lock.dbc passende Opening-Zauber verwendet werden. Native
Unterbrechungen durch Kampf gelten auch für solche Zauber.
GUID-Strings von creature/object umgehen Lua-Präzisionsverlust bei64Bit-GUIDs.
Aktionen mit void/ohne Rückgabe bestätigen nur den Aufruf: anschließend Status,
Inventar oder Questfortschritt prüfen. Ein versendeter Handleraufruf allein ist
kein Erfolgsnachweis. Quests werden nicht durch künstliche Credits abgeschlossen.
Normale Casts prüfen Kosten, Reichweite, Cooldowns usw.; Vorbereitung darf diese
Bedingungen gezielt durch Level, Stats, Items und gelernte Zauber herstellen.
Native MMAP-Bewegung braucht begehbaren Boden; Teleport steht unabhängig davon
zur Verfügung. Das konkrete Questbeispiel steht unter
src/server/scripts/Custom/clientless/testbots/quest_accept.lua.
Lua hat pro Lauf8MiB und pro Ausführungsschritt100000Instruktionen, danach muss
es mit wait/await abgeben. Keine Datei-/OS-/Package-/Debug-Funktionen und kein
Abfangen des Ressourcenabbruchs durch pcall/eigene Coroutines. Maximal1000
Beobachtungen; Tabellen werden für Ergebnisse begrenzt serialisiert. Laufzeit
ist per Config1..86400Sekunden einstellbar. Das ist eine administrative
Testumgebung, keine Isolation für beliebigen fremden Code oder Corefehler.
Atemluft
AdventureBots haben unbegrenzte Atemluft. Registrierte Testbots verwenden standardmäßig die normale begrenzte Atemluft (drei Minuten ohne Modifikatoren), auch bei einem erhöhten Account-Sicherheitsrang. Echte Unterwasseratemzauber und Atemdauer-Modifikatoren wirken weiterhin nativ. Spieler mit Netzwerkclient bleiben unverändert.
clientless test breathing <guid> limited|unlimited|status schaltet die Atemluft für einen registrierten Testbot oder zeigt dessen gespeicherte Einstellung. Die Einstellung bleibt über Logins und Serverneustarts erhalten (registry/<guid>.breathing); ohne Datei gilt limited. Auch manuelle clientless Logins registrierter Testbots übernehmen die Einstellung. Die Testbot-Steuerung muss aktiviert sein.
Lua: testbot.breathing(guid, true) aktiviert begrenzte Atemluft, false unbegrenzte; ohne zweiten Parameter wird die gespeicherte Einstellung abgefragt. Rückgabewert: true bedeutet begrenzt. Nur dem Testlauf zugeordnete Bots sind zulässig. testbot.state liefert breathing_limited, breath_max_ms, breath_remaining_ms, underwater (letzter nativer Tick; -1 = deaktivierter Timer). Unbegrenzt injiziert keinen Zauber und verändert weder Ermüdung noch Lava-/sonstigen Umweltschaden.
Zielauswahl, GM-Befehle und Chat
Registrierte Testbots erhalten nach dem Login ausschließlich in ihrer Clientless- Sitzung Operatorrechte einschließlich RBAC-Freigaben. Auth-Konten, echte Spieler- Sitzungen und normale AdventureBots bleiben unverändert. GM-Modus, Unsichtbarkeit und Unverwundbarkeit werden nicht automatisch aktiviert. Begrenzte Testbot- Atemluft bleibt auch mit diesen Rechten wirksam. Entfernen der Registrierung oder Deaktivieren des Testcontrollers nimmt die Rechte wieder zurück.
local bot = 148 -- bereits registrierter Testcharakter
testbot.spawn(bot)
testbot.await(function() return testbot.online(bot) end, 15000, "Login")
testbot.select(bot, "self")
local result = testbot.command(bot, ".debug combat")
testbot.record("combat", result)
testbot.chat(bot, "whisper", "/AI Diagnose bereit", "Testrion")
select(bot, target) akzeptiert vollständige GUID-Strings (dezimal/0xhex),
Spielernamen, self und none. NPC-GUIDs liefert beispielsweise
creature(bot, spawnId). Das Ziel muss in derselben geladenen Map/Instanz stehen;
eine Auswahl löst keinen Angriff aus. player(bot, name) liefert die vollständige
GUID eines verbundenen Spielers/Bots. state(bot) enthält selected_guid und
operator. command nutzt den nativen ChatHandler mit dieser Auswahl und gibt
{success, truncated, output} zurück. Ausgabe ist auf100Zeilen/64KiB begrenzt;
asynchrone Befehle können spätere Ergebnisse separat ausgeben.
chat(bot, typ, text, ziel) unterstützt say, yell, emote, party, raid,
raid_warning, battleground, guild, officer, whisper und channel.
Bei whisper/channel ist das Ziel erforderlich. Die nativen Sprach-, Gruppen-
und Kanalprüfungen gelten weiter; ein Aufruf garantiert keine Zustellung.
Für bereits eingeloggte registrierte Testbots auch direkt an der Serverkonsole:
clientless test select 148 self
clientless test command 148 .debug combat
clientless test chat 148 whisper Testrion /AI Diagnose bereit
Live-Chat und Nachrichten an den Beobachter
Clientless.ChatMonitor.Enabled = 1 zeichnet native serverseitige Chatpakete
unter <Clientless.TestBot.Root>/monitor/ auf:
chat.jsonl: Spieler-, Bot-, NPC- und Systemchat mit Zeit und Absender.ai.jsonl: Spielernachrichten, die mit/AIund Wortgrenze beginnen.commands.jsonl: Testbot-GM-Befehle mit Auswahl und synchroner Ausgabe.
Je Datei maximal16MiB plus vier rotierte Vorgänger. Addon-Datenpakete werden
nicht aufgezeichnet. testbot.chat_events(afterSequence, aiOnly) liest die
letzten512Ereignisse; Sequenzen beginnen nach Serverneustart wieder bei1.
Ein aktiver Beobachter liest diese Dateien. Es läuft kein automatischer KI-
Antwortdienst, wenn kein Agent die Überwachung übernimmt.
Der Client sendet unbekannte Slashbefehle nicht an den Server. Für direktes
/AI Text den vorhandenen Ordner /home/wow_admin/wow_cata_server/source/VcoreAI als Interface/AddOns/VcoreAI in den
WoW-Client kopieren und das Addon aktivieren bzw. /reload ausführen. Es sendet
/AI Text als normale Flüsternachricht an den eigenen Charakter. Ohne Addon
funktioniert /s /AI Text (für Spieler in der Nähe sichtbar).
Bei abgelehnten freien Flugsegmenten enthält adventurebot-navigation.log
FLIGHT_CORRIDOR_REJECT mit Bot-GUID, Start-/Rückkehrstatus, Kennzeichnung des
vertikalen Versuchs und Ablehnungszahlen. FLIGHT_CORRIDOR_DETAIL ergänzt die
letzte konkrete Ablehnung und separat den vertikalen Versuch: Prüfpunkt,
Boden-/Terrain-/Höhenobergrenze, Sicherheitsabstand, Abtastindex oder die beiden
Endpunkte des blockierten Körperstrahls (collision-ray). Bei Strahlen bezeichnet
margin die Strahlhöhe über den Füßen. Die Diagnose ändert keine Flugprüfung.
lookahead-surface-invalid bezeichnet eine fehlende gültige Probe für die
nominale Flughöhe. Solche Proben werden aus der Höhenstatistik ausgeschlossen;
die lokale Kandidatensuche und der geprüfte vertikale Fallback bleiben möglich.
Jeder tatsächliche Korridor benötigt weiterhin gültige Oberflächendaten und
freie Körperstrahlen. verticalAttempted zeigt, ob dieser Versuch stattfand.