183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# 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/<eindeutige-id>.lua` verschieben. Alternativ:
|
|
|
|
```text
|
|
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>
|
|
```
|
|
|
|
`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 abwarten.
|
|
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:
|
|
|
|
```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.
|