# 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 ```ini 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/.lua` verschieben. Alternativ: ```text clientless test pause clientless test resume clientless test run clientless test submit clientless test status clientless test result clientless test cancel ``` `run` lädt `scripts/.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 abwarten. IDs werden nie wiederverwendet. `results/.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: ```bash # 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 ```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 (0/1) | | `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 | | `move(guid,x,y,z)` | Native MMAP-/MovePoint-Bewegung ohne Gebietskatalog; 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.