Files
Viano-Core/doc/clientless/testbots.md
T
Nico 676f566a8c
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s
Fix AdventureBot travel recovery, ranged spacing and companion revival
2026-11-07 00:18:23 +01:00

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 /AI und 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.