# War3AI / OpenWar3 Vollständige Dokumentation > Quelle: https://war3ai.com/de — Offene API für Warcraft III 1.27 für KI-Agents. Beim Schreiben eines Bots nur die Game-Methoden aus dem „API-Katalog“ am Ende verwenden. --- # Überblick > OpenWar3-Doku: was es ist, was es kann; Schnellstart, dein erster Bot, KI schreibt Bots, APIs und Protokoll, Gateway und MCP – je nach Situation, wo du am besten einsteigst. **OpenWar3** ist die offene Schnittstellenschicht von War3AI: eine Runtime, die in Warcraft III 1.27 injiziert wird, plus ein Python-SDK. - Die Runtime schreibt alle **50 ms** den vollständigen Zustand der ganzen Karte in Shared Memory: Ressourcen und Nahrung aller Spieler; für jede Einheit HP/Mana, Order, wen sie gerade angreift, Abklingzeiten, Buffs und Inventar; dazu Gegenstände am Boden, Bäume, Produktionswarteschlangen und Tageszeit. Außerdem gibt es einen **Event-Stream**: Einheiten erscheinen und sterben, jeder einzelne Treffer, fertige Produktion … - Externe Programme senden **semantische Befehle** mit einer Latenz von **etwa einem Frame**: bewegen, angreifen, abbauen, bauen, ausbilden, zaubern, Fähigkeiten lernen, wiederbeleben, Gegenstände benutzen, einkaufen … Jeder Befehl erhält eine **Quittung**: ob die Engine ihn angenommen hat und, falls nicht, mit welchem Reason-Code. - Du sagst nur, *was* passieren soll: Einheiten per Rawcode, Fähigkeiten per Order-String, genau wie im Spiel. *Wie* es umgesetzt wird, übernimmt die Runtime. Ein LLM braucht deshalb weder Low-Level-Wissen noch das Bild auf dem Bildschirm. Nach dem Lesen der Docs kann es einen Bot schreiben, der seine Wirtschaft führt und kämpft – und ihn nach dem Einsatz anhand von Quittungen und Events selbst verbessern. Und nicht nur Matches: Mit dem [Canvas](https://war3ai.com/de/docs/canvas/) zeichnest du eigene Panels und Markierungen ins Spielbild, mit [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/) werden die gezeichneten Buttons klickbar und Hotkeys reagieren, über den [JASS-Kanal](https://war3ai.com/de/docs/jass/) rufst du von außen die 1291 Funktionen des Spiels auf, und auf RPG-Karten holst du dir einen [KI-Begleiter](https://war3ai.com/de/docs/companion/) an die Seite. Fertige KIs lassen sich als [KI-Schemata](https://war3ai.com/de/docs/schemes/) verpacken – per Klick gewechselt, exportiert und geteilt; ein ganzes neues Spielprinzip schreibst du als [Gameplay-Mod](https://war3ai.com/de/docs/mods/). Es geht auch ohne Python: Über das [Gateway](https://war3ai.com/de/docs/gateway/) rufen jede Sprache und jede Browserseite dieselben APIs per WebSocket / JSON auf, und der [MCP-Server](https://war3ai.com/de/docs/mcp/) lässt Agents wie Claude Code direkt Tools aufrufen, um die Lage zu sehen und Befehle zu erteilen. - [Schnellstart](https://war3ai.com/de/docs/quickstart/): Umgebung einrichten, mit einem Befehl ein Spiel starten und zusehen, wie der Beispiel-Bot übernimmt. - [Einen Bot mit einem LLM schreiben](https://war3ai.com/de/docs/ai-bot/): Auch ohne Programmierkenntnisse: Prompt kopieren, Spielweise beschreiben, an einen Agent übergeben. - [Mentales Modell](https://war3ai.com/de/docs/concepts/): Snapshots, Befehle, Quittungen, Events, Ticks. Nimm dir vor dem ersten Bot fünf Minuten dafür. - [API-Katalog](https://war3ai.com/de/api/): Alle APIs, jeweils mit Teststatus, Latenzstufe und zugrunde liegendem Mechanismus. ## Wähle deinen Einstieg | Du … | Lies zuerst | Danach | |---|---|---| | spielst Warcraft, programmierst aber nicht | [Schnellstart](https://war3ai.com/de/docs/quickstart/) → [Einen Bot mit einem LLM schreiben](https://war3ai.com/de/docs/ai-bot/) | Bei Problemen: [FAQ](https://war3ai.com/de/docs/faq/) | | kannst Python | [Dein erster Bot](https://war3ai.com/de/docs/first-bot/) → [Mentales Modell](https://war3ai.com/de/docs/concepts/) → [Die 15 Regeln](https://war3ai.com/de/docs/rules/) | [Profi-Kochbuch](https://war3ai.com/de/docs/cookbook/), [Beispiel-Bots](https://war3ai.com/de/docs/examples/) | | baust Coding Agents / Automatisierung | [Agent-Selbstiteration](https://war3ai.com/de/docs/agent-loop/) | [Quittungen & Reason-Codes](https://war3ai.com/de/docs/reason-codes/), [`llms-full.txt`](https://war3ai.com/de/llms-full.txt) | | willst ein LLM im Spiel entscheiden lassen | [LLM als Strategie-Coach](https://war3ai.com/de/docs/llm-coach/) | [Sprechblasen & lokale Modelle](https://war3ai.com/de/docs/speech/) | | willst einen Agent direkt selbst steuern lassen (Claude Code usw.) | [LLM ruft Tools direkt auf (MCP)](https://war3ai.com/de/docs/mcp/) | [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/) | | nutzt eine andere Sprache (JS, C#, Go, Rust …) | [Gateway](https://war3ai.com/de/docs/gateway/) | Tiefer: [W3P-Protokoll](https://war3ai.com/de/docs/protocol/) | | willst KIs verschiedener Leute gegeneinander antreten lassen | [Fair-Modus](https://war3ai.com/de/docs/fair-mode/) | [Arena](https://war3ai.com/de/arena/) | | willst auf RPG- / Custom Maps eigene Spielideen bauen | [Gameplay-Mods](https://war3ai.com/de/docs/mods/) | [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/), [Canvas](https://war3ai.com/de/docs/canvas/), [JASS-Kanal](https://war3ai.com/de/docs/jass/), [RPG-Begleiter](https://war3ai.com/de/docs/companion/) | | willst deine KI mit anderen teilen | [KI-Schemata](https://war3ai.com/de/docs/schemes/) | [Farsight-Konsole](https://war3ai.com/de/docs/console/) | ## Was steckt im Repo? ```text start.bat der einzige Einstiegspunkt: Installation von null + Farsight öffnen; stop.bat stoppt alles vollständig sdk/python/ API-Schicht. openwar3/ ist die öffentliche Fassade (Game + Bot) – hier anfangen brains/ Entscheidungsschicht examples/ hello_bot (Wirtschaft) → rush_bot (Armee) → macro_bot (Makro) → micro_bot (Mikro + Creepen); buddy (RPG-Begleiter); mod_hero_roguelike / mod_endless_defense (Gameplay-Mods) xwar3/ Referenz-Brain: Strategie-Schicht (Sekunden) + Reflex-Schicht (4 Prozesse) + Siegmodell console/ Farsight-Webkonsole (FastAPI + React) gateway/ Gateway (WebSocket / JSON) + JS-Client + Demoseite für den Browser director/ automatische Kameraführung, HP-Balken über Einheiten speech/ Chat-Sprechblasen über Einheiten + lokales LLM runtime/ Multi-Instanz-Orchestrierung (jedes Spiel startet nach Einstellungen neu) data/ order-ids.txt; Tools, um Daten aus deinem eigenen Spiel zu extrahieren schemes/ deine KI-Schemata (mine/) und von anderen geteilte (installed/), nicht im Repo tools/ play.py (Spiel mit einem Befehl starten), run_scheme.py (Schema-Runner), war3_mcp.py (MCP-Server), run_tests.py, Live-Prüfskripte docs/ API-Katalog api.json (aus dem Code generiert), Protokoll, Handbuch ``` Zwischen der Runtime und deinem Code liegt nur ein versioniertes [W3P-Protokoll](https://war3ai.com/de/docs/protocol/): Mit dem Python-SDK geht es am einfachsten, aber jede andere Sprache kann sich anhand des Protokolls ebenfalls anbinden. ## Was bedeutet der „Teststatus“ einer API? Jede API im Katalog trägt einen von drei Status: - **Im Live-Spiel verifiziert**: Der zugrunde liegende Pfad (Aktions-ID, Parameterform, zurückgelesene Wirkung) wurde in echten Spielen verifiziert und ist durch ein Prüfskript abgesichert. - **Experimentell**: Eine neu hinzugefügte API, die auf der Testinstanz bereits läuft und noch Punkt für Punkt live verifiziert wird. Sie ist nutzbar, Details der Schnittstelle können sich aber noch ändern. - **Abgeleitet / nicht vollständig getestet**: Der Mechanismus folgt dem Vorgehen der Engine selbst (etwa der entsprechenden JASS-Funktion), wurde aber noch nicht Punkt für Punkt im Spiel geprüft. Prüf vor der Verwendung die Quittung. > **Info** > > Derzeit wird nur **Warcraft III 1.27** (The Frozen Throne) unterstützt. 1.24–1.28 teilen dieselbe Engine-Struktur; die Unterstützung mehrerer Versionen ist Phase P4 der [Roadmap](https://war3ai.com/de/roadmap/). Ab 1.29 und bei Reforged handelt es sich um eine andere Engine – dafür gibt es keine Zusage. --- # Schnellstart > Doppelklick auf start.bat installiert alles automatisch; in Farsight das Spielverzeichnis festlegen, eine Partie starten und zusehen, wie der Beispiel-Bot übernimmt. Etwa 15 Minuten. ## Was du brauchst | | Anforderung | Hinweis | |---|---|---| | System | Windows 10 / 11, 64 Bit | Derzeit wird nur Windows unterstützt | | Spiel | Warcraft III **1.27a** (The Frozen Throne, `Game.dll` 1.27.0.52240) | Ein Client, den du rechtmäßig besitzt; keine Spieldatei auf der Festplatte wird verändert | **Sonst musst du nichts vorab installieren.** `start.bat` lädt nur eine Sache herunter: Python 3.13 (offizielles portables Paket, ca. 14 MB), das ins `bin\env\`-Verzeichnis im Repository kommt – ohne Administratorrechte und ohne Änderung am System-PATH; in Netzen in China wird automatisch auf Mirror-Server umgeschaltet. Ist auf dem Rechner schon eine brauchbare Python-Version vorhanden, wird sie direkt verwendet. PowerShell nutzt die in Windows enthaltene Version; die Weboberfläche von Farsight liegt dem Repository bereits fertig gebaut bei, Node.js wird nicht benötigt. ## Installation 1. **Code holen** ```bash git clone https://github.com/OPENXXAI/OpenWar3AI.git ``` Oder das [ZIP-Archiv](https://github.com/OPENXXAI/OpenWar3AI/archive/refs/heads/main.zip) herunterladen und entpacken. Die Runtime (Injektions-DLL und Starter) liegt dem Repository bei und muss nicht separat heruntergeladen werden. 2. **`start.bat` doppelklicken** Beim ersten Mal erledigt es selbst: - Python 3.13 herunterladen; - Python-Pakete installieren, die Runtime-Dateien prüfen und einrichten; - AMAI herunterladen und daraus die Strategiedaten für das Referenz-Brain erzeugen (AMAI steht unter einer eigenen Lizenz, die erzeugten Dateien landen nicht in git; ein Fehlschlag betrifft nur das Referenz-Brain); - die Startseite von Farsight öffnen, das „Kontrollzentrum“: `http://127.0.0.1:8866`. Jeder Schritt gibt sein Ergebnis aus; bei einem fehlgeschlagenen Schritt erfährst du, wie du ihn nachholst. Danach prüft jeder Doppelklick nur ein, zwei Sekunden lang und öffnet dann Farsight. Das schwarze Fenster schließt sich nach ein paar Sekunden von selbst: Farsight läuft im Hintergrund weiter und stoppt auch nicht, wenn du den Browser schließt. 3. **Im Kontrollzentrum das Spielverzeichnis festlegen** Ganz oben im Kontrollzentrum kannst du „Automatisch suchen“ oder über „Durchsuchen …“ das Verzeichnis von Warcraft III selbst auswählen. Farsight prüft die Spielversion und **extrahiert Daten aus deinem eigenen Spiel** (Einheitentabelle, Fähigkeiten, Gegenstände, Kontertabelle … die Dateien von Blizzard werden nicht mit dem Code verteilt). Ist die Version nicht 1.27a, bekommst du einen Hinweis. Karten und die Einstellungen für die nächste Partie beziehen sich auf dieses Verzeichnis (Karten aus allen Ordnern unter `\Maps` stehen zur Auswahl); das Verzeichnis wechselst du später auf der Seite „Einstellungen“. 4. **Partie starten und den Beispiel-Bot übernehmen lassen** Am einfachsten geht es in Farsight auf der Seite „Instanzen & Start“: eine Instanznummer ankreuzen, ein KI-Schema wählen und auf „Test starten“ klicken. Oder über die Kommandozeile: ```bash python tools/play.py --bot brains/examples/hello_bot.py ``` Dieser Befehl startet eine Spielinstanz, injiziert die Runtime, beginnt automatisch eine Partie und startet dann den Bot. **Wenn die Bauern Gold sammeln gehen und das Hauptgebäude Bauern trainiert, hat alles geklappt.** Mit `python` im Befehl ist das in `openwar3.json` eingetragene Python gemeint; das von `start.bat` selbst installierte liegt unter `bin\env\python\python.exe`. ## start.bat und stop.bat ```bash start.bat # Installation prüfen + Farsight öffnen start.bat setup # vollständige Prüfung: Python-Pakete neu installieren, AMAI erneut versuchen start.bat restart # nur das Farsight-Backend neu starten (Spiele und Dienste laufen weiter) start.bat node # installiert nebenbei eine Kopie von Node.js (nur nötig, um die Website in der Vorschau zu bauen – sonst nicht gebraucht) start.bat 5 6 # startet zusätzlich Tests auf Instanz 5 und 6 (Spiel + Referenz-Brain) stop.bat # alles vollständig stoppen; stop.bat --keep-llm lässt das lokale Modell im VRAM ``` Gateway, Sprechblasen und lokales LLM startest und stoppst du ebenfalls im „Kontrollzentrum“ von Farsight, ohne nach weiteren Skripten suchen zu müssen. **Alles vollständig stoppen**: `stop.bat` doppelklicken oder im Kontrollzentrum oben rechts auf „Alle stoppen“ klicken – Spielinstanzen, KI, Gateway, Sprechblasen, das von diesem System genutzte lokale Modell und das Farsight-Backend werden der Reihe nach beendet. MCP-Server werden von Clients wie Claude verwaltet und nicht gestoppt. > **Konfigurationsdatei** > > `openwar3.json` wird von `start.bat` und Farsight automatisch geschrieben, enthält nur lokale Pfade und landet nicht in git. Willst du Ports, Adresse oder Modellnamen des lokalen LLM ändern, trag nach dem Muster von `openwar3.example.json` nur die Einträge ein, die davon abweichen. ## Parameter von play.py ```bash python tools/play.py --bot my_bot.py --inst 9 --race 2 --enemy-race 1 --difficulty 3 --speed 200 python tools/play.py --bot my_bot.py --inst 9 --attach # Spiel läuft schon, nur den Bot anhängen python tools/play.py --bot my_bot.py --fair # Fair-Modus: nur sehen, was in Sichtweite ist ``` | Parameter | Standard | Beschreibung | |---|---|---| | `--bot` | Pflicht | Pfad zur Bot-Datei (sie muss eine Unterklasse von `Bot` enthalten) | | `--inst` | `9` | Instanznummer. Darf nicht mit einer laufenden Instanz kollidieren (welche Nummern belegt sind, siehst du in Farsight auf der Seite „Instanzen & Start“) | | `--race` | `1` | Eigenes Volk: 1 Menschen, 2 Orcs, 3 Untote, 4 Nachtelfen | | `--enemy-race` | `0` | Volk des Gegners | | `--difficulty` | `2` | Schwierigkeit des Computergegners: 2 Leicht, 3 Normal, 4 Wahnsinnig | | `--speed` | `100` | Spielgeschwindigkeit (in Prozent, 200 = doppelte Geschwindigkeit) | | `--map` | `default_map` aus der Konfiguration | Karte | | `--attach` | | Kein Spiel starten, nur an eine bereits laufende Instanz anhängen | | `--hz` | `5` | Wie oft pro Sekunde `on_tick` aufgerufen wird | | `--minutes` | `60` | Maximale Laufzeit in Minuten (Echtzeit) | | `--fair` | | [Fair-Modus](https://war3ai.com/de/docs/fair-mode/) | | `--player` | | Als welcher Spieler befehligt wird (für KI gegen KI) | > **Achtung** > > Starte das Spiel nicht mit `--minimize`: **Ist das Fenster minimiert, steht die Spielsimulation still** (die Uhr läuft nicht), und der Bot wartet endlos auf den Partiebeginn. Du kannst `play.py` auch umgehen und dich direkt über die Kommandozeile des SDK an eine laufende Instanz anhängen: ```bash python -m openwar3 run brains/examples/hello_bot.py --inst 5 # Bot ausführen python -m openwar3 status --inst 5 # verbinden, Status von Snapshot / Schnellspur ausgeben python -m openwar3 catalog # API-Katalog ausgeben ``` ## Wenn alles läuft - [Deinen ersten Bot schreiben](https://war3ai.com/de/docs/first-bot/): Starte mit einem minimalen Bot aus 10 Zeilen und ergänze Schritt für Schritt Truppen und Angriffe. - [Das LLM schreiben lassen](https://war3ai.com/de/docs/ai-bot/): Prompt-Vorlage kopieren und die Spielweise in Alltagssprache beschreiben. ## Selbsttest ```bash python tools/run_tests.py # SDK / Referenz-Brain / Reflexschicht / Konsole / Sprechblasen / Beispiele, jede Suite in einem eigenen Unterprozess ``` Für die Offline-Tests muss das Spiel nicht laufen. Auch im „Kontrollzentrum“ von Farsight gibt es eine Umgebungsprüfung, die zeigt, ob alle Teile installiert sind. --- # Dein erster Bot > Starte mit einem minimalen Bot aus 10 Zeilen, ergänze Bauern, Nahrung, Truppen, Helden und Angriffe und lerne zum Schluss, Quittungen zu lesen. Ein Bot ist eine Klasse, die von `openwar3.Bot` erbt. Du überschreibst nur die Hooks, die du brauchst; `g` (`Game`) ist fürs „Sehen“ und „Handeln“ zuständig. ## Der minimale Bot ```python title="my_bot.py" from openwar3 import Bot class MyBot(Bot): def on_start(self, g): # einmal nach Partiebeginn aufgerufen g.message("Da bin ich") def on_tick(self, g): # etwa 5-mal pro Sekunde for w in g.idle_workers(): g.gather(w, g.nearest(g.gold_mines(), w)) ``` ```bash python tools/play.py --bot my_bot.py ``` Untätige Bauern gehen zur nächsten Goldmine. Die vier Hooks: | Hook | Wann er aufgerufen wird | |---|---| | `on_start(g)` | Einmal nach Partiebeginn, vor dem ersten Tick | | `on_tick(g)` | In jedem Tick (standardmäßig 5-mal pro Sekunde). Dauert ein Tick zu lange, verschiebt sich der nächste automatisch, statt dass sich Ticks aufstauen | | `on_event(g, ev)` | Vor jedem `on_tick`; übergibt dir alle Events seit dem letzten Tick, eines nach dem anderen | | `on_end(g, reason)` | Einmal, wenn die Partie endet (Spielprozess beendet / keine eigenen Einheiten mehr / manuell gestoppt) | > **Tipp** > > Eine Exception in `on_tick` bricht nicht die ganze Partie ab: Der Runner gibt den Stacktrace aus und macht im nächsten Tick weiter; erst nach **20 Fehlern in Folge** hält er an. ## Wirtschaft ergänzen: Bauern und Nahrung ```python from openwar3 import Bot class Economy(Bot): def on_tick(self, g): res = g.resources() # nicht lesbar = None, nicht 0 halls = g.my_buildings({"htow", "hkee", "hcas"}) if res is None or not halls: return home = halls[0] # 1. Untätige Bauern sammeln Gold for w in g.idle_workers(): mine = g.nearest(g.gold_mines(), w) if mine: g.gather(w, mine) # 2. Bauern trainieren: nur 1 in die Warteschlange (eine volle Warteschlange bindet Gold) if len(g.my_workers()) < 15 and not g.queue(home): g.train(home, "hpea") # 3. Nahrung fast voll: einen Bauern suchen, der gerade nicht baut, und beim Hauptgebäude einen Bauernhof bauen if res["food_cap"] - res["food_used"] <= 6: builder = next((w for w in g.my_workers() if not g.is_constructing(w)), None) if builder: g.build_near(builder, "hhou", home.x, home.y) ``` Drei Muster, auf die es ankommt: - **Nur trainieren, wenn `g.queue(home)` leer ist.** Gibst du jeden Tick einen Trainingsbefehl, füllst du die 7 Plätze der Warteschlange und bindest dein Gold (gemessen: 4 Bauern in der Warteschlange des Hauptgebäudes, 300 Gold gebunden – die Eröffnung wird deutlich langsamer). - **`build_near` statt fester Koordinaten.** Es sucht von nah nach fern einen freien Platz und verfolgt das Ergebnis über mehrere Ticks; reicht das Geld nicht, tut es nichts. Feste Koordinaten liegen womöglich genau in einem Wald. - **Keinen Bauern nehmen, der gerade baut.** Ein Bauernhof der Menschen braucht 35 Sekunden; wird der Arbeiter unterwegs abgezogen, steht die Baustelle still. Eine vollständige Version, die mit allen vier Völkern läuft, ist `brains/examples/hello_bot.py`: 5 Arbeiter pro Mine, bei voller Mine Holz fällen, stillstehende Baustellen weiterbauen. ## Kaserne, Held und Angriff ergänzen ```python from openwar3 import Bot WAVE = 8 class Rush(Bot): def on_start(self, g): self.attacking = False def on_tick(self, g): halls = g.my_buildings({"htow", "hkee", "hcas"}) if not halls: return home = halls[0] # Held: Altar vorhanden, aber kein Held -> zuerst wiederbeleben, sonst trainieren (Helden sind einzigartig; einen toten neu trainieren wird abgelehnt) altars = g.my_buildings({"halt"}) if altars and not g.my_heroes(): if not g.revive(altars[0]): g.train(altars[0], "Hpal") for h in g.my_heroes(): info = g.hero_info(h) if info and info["skill_points"]: g.learn(h, "AHhb") # Heiliges Licht # Die Kaserne produziert ständig Fußsoldaten (nur 1 in der Warteschlange) for b in g.my_buildings({"hbar"}): if not g.queue(b): g.train(b, "hfoo") # Bei voller Welle angreifen; nach schweren Verlusten zurück zur Basis army = g.my_army() if len(army) >= WAVE: self.attacking = True elif len(army) < WAVE // 2: self.attacking = False if self.attacking: target = g.nearest([e for e in g.enemies() if g.is_building(e)], home) if target: idle = [u for u in army if not g.order_of(u)] # nur untätigen Einheiten Befehle geben g.attack_move(idle, target.x, target.y) ``` Die vollständige Version findest du in `brains/examples/rush_bot.py` (erbt von `hello_bot` und baut Kaserne / Altar, falls sie fehlen). ## Quittungen verstehen Jeder Befehl liefert eine Quittung. `if r:` heißt „die Engine hat angenommen“; wenn nicht, steht in `r.reason` der Grund: ```python r = g.train(barracks, "hfoo") if not r: print(r.reason) # rejected(人口不够) (= nicht genug Nahrung) print(r.verdict) # 3 ``` Häufige Grundcodes: `3` nicht genug Nahrung, `8` nicht genug Gold, `9` nicht genug Holz, `32` Warteschlange voll, `183` Voraussetzung fehlt, `221` nicht vorhanden / im Bau / Held existiert bereits, `1001` Ziel nicht sichtbar. Die vollständige Tabelle findest du unter [Quittungen und Grundcodes](https://war3ai.com/de/docs/reason-codes/). > **Angenommen ≠ erledigt** > > Die Quittung sagt nur, dass „die Engine diesen Befehl angenommen hat“. Auch einen Bauplatz im Wald nimmt die Engine sofort an, der Arbeiter scheitert erst, wenn er dort ankommt; Zauber können unterbrochen werden. Die Wirkung siehst du in Snapshot und Events: Zum Bauen `build_near` verwenden (es verfolgt, ob die Baustelle erscheint), bei Zaubern prüfen, ob `g.cooldown()` eine Abklingzeit zeigt. ## Nächste Schritte - [Mentales Modell](https://war3ai.com/de/docs/concepts/): Snapshot, Befehl, Event, Tick, Batch – warum es so gebaut ist. - [Profi-Kochbuch](https://war3ai.com/de/docs/cookbook/): 21 Rezepte: gesättigtes Sammeln, nie an der Nahrung hängen, Fokusfeuer, Verwundete zurückziehen, nachts creepen … --- # Einen Bot mit einem LLM schreiben > Geht auch ohne Programmierkenntnisse: Du beschreibst, wie gespielt werden soll, das LLM schreibt den Code. Prompt-Vorlage kopieren, Spielweise beschreiben, laufen lassen und nachbessern lassen. Für alle, die Warcraft III spielen, aber nicht programmieren – und für Entwickler, die Zeit sparen wollen. Der ganze Ablauf ist ein Dialog: **Du beschreibst die Spielweise → das Modell schreibt Code → du spielst eine Partie → du erzählst dem Modell, was passiert ist → es bessert nach**. > **Tipp** > > Richte zuerst die Umgebung nach dem [Schnellstart](https://war3ai.com/de/docs/quickstart/) ein und bring `hello_bot` zum Laufen (die Bauern gehen Gold sammeln). So kannst du bei Problemen unterscheiden, ob es an der Umgebung oder am Bot liegt. ## 1. Material für das Modell vorbereiten Wie gut das Modell schreibt, hängt zu achtzig Prozent davon ab, ob es das richtige Material gelesen hat. Wähle je nach Werkzeug eine Variante: | Du nutzt | So gibst du das Material | |---|---| | **Coding Agent mit Repository-Zugriff** (Claude Code, Cursor, Codex usw.) | Im Repository-Verzeichnis öffnen und zuerst `docs/BOT_HANDBOOK_ZH.md`, `docs/api.json` und ein Beispiel lesen lassen (Wirtschaft: `brains/examples/macro_bot.py`, Kampf: `micro_bot.py`) | | **Chatmodell mit Internetzugang** | Zuerst [`https://war3ai.com/llms-full.txt`](https://war3ai.com/de/llms-full.txt) lesen lassen – die gesamte Dokumentation steckt in dieser einen Datei | | **Web-Chat ohne Internetzugang** | Handbuch, [`api.json`](https://war3ai.com/de/api.json) und eine Beispieldatei hinter den Prompt einfügen | | **Lokales Modell** (LM Studio, Ollama) | Wie oben. Empfohlen ist ein Kontextfenster ab 32K Token, sonst passen Handbuch und API-Referenz nicht hinein | Willst du eine bestimmte Profi-Technik, füge zusätzlich das passende Rezept aus dem [Profi-Kochbuch](https://war3ai.com/de/docs/cookbook/) ein. ## 2. Diesen Prompt kopieren Ersetze am Ende „Meine Spielweise“ durch deine eigenen Worte – je konkreter, desto besser: ```text Du schreibst eine KI (Python) für Warcraft III 1.27. Verwende nur die Game-Methoden aus api.json, erfinde keine Methoden, die es nicht gibt. Orientiere dich an rush_bot.py: von openwar3.Bot erben, on_start(g) und on_tick(g) implementieren. Regeln: - on_tick wird etwa 5-mal pro Sekunde aufgerufen und muss schnell sein (kein sleep darin). - Nicht lesbare Werte sind None, nicht 0 – vor der Verwendung prüfen. - Jeder Befehl liefert eine Quittung (Receipt); `if r:` heißt "die Engine hat angenommen"; wenn nicht, steht in `r.reason` der Grund (nicht genug Nahrung, nicht genug Gold, Ziel nicht sichtbar, diesen Helden gibt es schon …) – im nächsten Tick erneut versuchen oder anders vorgehen. - Einen bestimmten Gegner greifst du mit g.attack(truppen, gegner) an; der Gegner muss in Sicht sein, Unsichtbares wird abgelehnt. - Tote Helden werden mit g.revive(altar) wiederbelebt, nicht neu trainiert. - Gebäude baust du mit g.build_near(arbeiter, gebäudecode, x, y): Es sucht selbst einen freien Platz, verfolgt das Ergebnis und tut nichts, wenn das Geld nicht reicht. - Um zu erfahren, "was gerade passiert ist" (wer gestorben ist, wer Schaden nimmt, Heldenstufe gestiegen, Gegenstand fallen gelassen), implementiere on_event(g, ev). - Gib derselben Einheit nicht jeden Tick denselben Befehl erneut (das unterbricht, was sie gerade tut); gib nur "untätigen" Einheiten Befehle. - Zum Sammeln nur Arbeiter aus idle_workers() einteilen. Höchstens 5 Arbeiter pro Goldmine. - In die Trainingswarteschlange nur 1 Eintrag (erst nachlegen, wenn g.queue(gebäude) leer ist); ob die Nahrung blockiert, zeigt g.production(gebäude).blocked. - Viele Befehle in einem Tick in with g.batch(): packen (nur einmal auf den Spiel-Thread warten). - Wen du angreifst, entscheidest du mit g.time_to_kill(meine_gruppe, gegner) (berücksichtigt Konter und Rüstung); wohin du gehst, mit g.path_distance (unerreichbar = None). - Im Fair-Modus ist nur sichtbar, was in Sichtweite ist; früher gesehene Gegner liefert g.last_seen(). - Einheiten werden als Vier-Zeichen-Codes angegeben (Bauer der Menschen hpea, Fußsoldat hfoo, Kaserne hbar …), Zauber über Order-Strings (thunderbolt Sturmblitz, blizzard Blizzard, holybolt Heiliges Licht …, vollständige Liste in data/order-ids.txt), das Erlernen von Fähigkeiten über Vier-Zeichen-Codes (AHtb, AHbz …). Meine Spielweise: ``` ### So beschreibst du die Spielweise klar Vage Anforderungen sind für das Modell das größte Problem. Statt „spiel aggressiver“ helfen diese Angaben viel mehr: - **Volk und Helden**: welcher Held zuerst, in welcher Reihenfolge er Fähigkeiten lernt (z. B. Erzmagier: Wasserelementar, Blizzard, Wasserelementar …). - **Bauabfolge**: beim wievielten Bauern die Kaserne, wann der Tier-up, wie viele Kasernen. - **Armeezusammensetzung**: Fußsoldaten + Büchsenschützen? Ab welcher Anzahl geht es los? - **Angriff und Rückzug**: ab wie vielen Einheiten angreifen, unter wie viel TP sich der Held zurückzieht, nach schweren Verlusten zurück zur Basis und neu sammeln. - **Creepen**: überhaupt ja oder nein, wann (nach Einbruch der Nacht?), nur Lager, die man sicher schafft? - **Fair oder nicht**: Wenn du später in die Arena willst, sag „nur Gegner verwenden, die in Sichtweite sind“. ## 3. Laufen lassen Speichere den Code des Modells als `brains/my_bot.py` und führe dann aus: ```bash python tools/play.py --bot brains/my_bot.py --race 1 --difficulty 2 ``` Für schnellere Ergebnisse `--speed 200` (doppelte Geschwindigkeit) ergänzen. ## 4. Nachbessern lassen - **Es gibt einen Fehler**: Füge die **komplette Fehlermeldung** unverändert beim Modell ein und sag „bitte korrigieren“. - **Es spielt schlecht**: Beschreibe, **was du im Spiel siehst**, nicht deine Vermutung über die Ursache. Zum Beispiel „der Held steht die ganze Zeit in der Basis“, „die Einheiten laufen einzeln in den Gegner“, „die Bauern drängen sich an einer Mine“. - **Neue Spielweise hinzufügen**: Immer nur eine Sache auf einmal, eine Partie spielen, prüfen, dass nichts kaputt ist, dann die nächste. > **Info** > > Ein Coding Agent, der selbst Befehle ausführen kann, übernimmt auch die Schritte 3 und 4: eine Partie spielen, Logs und Quittungen lesen, Code ändern, erneut spielen. Wie er genug Informationen bekommt, steht unter [Agent-Selbstiteration](https://war3ai.com/de/docs/agent-loop/). ## 5. Häufige Probleme | Symptom | Meistens liegt es daran | |---|---| | Nichts bewegt sich | Falsche Instanznummer (`--inst`) oder das Spiel ist noch nicht in einer Partie | | Bauern sammeln nicht | Befehle an bereits beschäftigte Bauern; nur `idle_workers()` einteilen | | Es wird nie ein Gebäude fertig | `build_near` verwenden, keine festen Koordinaten; in der Quittung prüfen, ob `reason` „nicht genug Gold“ sagt | | Kein Held erscheint | Quittung von `train` prüfen: zu wenig Nahrung? Oder ist der Held tot (dann `revive`)? | | Held wirkt keine Zauber | Nicht erlernt (`learn`) oder kein Mana; nach dem Wirken prüfen, ob `cooldown()` eine Abklingzeit zeigt | | Einheiten zucken Tick für Tick | Jeder Tick erteilt die Befehle neu; nur untätigen Einheiten Befehle geben | | Keine Einheiten, das Gold steigt und steigt | Die Nahrung blockiert: `g.production(kaserne).blocked` prüfen | | Das Modell nutzt Methoden, die es nicht gibt | Im Prompt noch einmal betonen: „nur Methoden aus api.json verwenden“, und api.json vollständig einfügen | ## Fortgeschritten - Alle Schnittstellen und der jeweils zugrunde liegende Mechanismus: [API-Referenz](https://war3ai.com/de/api/); - Das Referenz-Brain (`brains/xwar3/strategy`) ist eine vollständige KI, die expandiert, creept und angreift. Das Modell kann seine Ideen lesen, es nutzt aber tiefer liegende Schnittstellen – direkt abschreiben ist nicht empfehlenswert; - In der [Arena](https://war3ai.com/de/arena/) siehst du später nur Gegner in Sichtweite – verwende schon jetzt `--fair` als Selbstbeschränkung, dann musst du später nichts ändern. --- # Agent-Selbstiteration > Lass einen Coding Agent selbst Partien spielen, Ergebnisse lesen, Code ändern und erneut spielen. Dafür braucht er einen Befehl, der unbeaufsichtigt läuft, einen strukturierten Spielbericht und ein klares Ziel. In [Einen Bot mit einem LLM schreiben](https://war3ai.com/de/docs/ai-bot/) übernimmst du den Schritt „Partie spielen → beobachten → dem Modell berichten“ selbst. Ein Coding Agent, der Befehle ausführen kann (Claude Code, Codex, der Agent-Modus von Cursor usw.), kann auch diesen Schritt übernehmen und so den Kreis schließen: ```text Code ändern ──► Partie spielen (unbeaufsichtigt) ──► Spielbericht lesen ──► wichtigstes Problem finden ──┐ ▲ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` Damit diese Schleife wirklich konvergiert, braucht der Agent drei Dinge. ## 1. Ein Befehl, der unbeaufsichtigt läuft ```bash python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair ``` - `--minutes` sorgt dafür, dass die Partie garantiert endet (Minuten Echtzeit), damit der Agent nicht in einer Partie hängen bleibt; - `--speed 200` spart mit doppelter Geschwindigkeit Zeit – aber im Bot **nach der Spieluhr warten** (`g.clock()`), nicht per `sleep` nach Echtzeit; - `--fair` sorgt dafür, dass er vom ersten Tag an nach Arena-Regeln schreibt und nur sieht, was in Sichtweite ist; - Am Ende gibt das Terminal den Grund für das Ende aus, z. B. `我方没有单位了` (keine eigenen Einheiten mehr) oder `到时间了` (Zeit abgelaufen); was der Bot selbst per `print` ausgibt, steht ebenfalls im Terminal. > **Achtung** > > Ist das Fenster minimiert, steht die Spielsimulation still. Lass den Agent das Spiel im Standard-Fenstermodus starten und achte darauf, dass seine Instanznummer (`--inst`) nicht mit der Instanz kollidiert, die du gerade benutzt. ## 2. Ein strukturierter Spielbericht Die Terminalausgabe ist für Menschen gedacht. Für den Agent sollte es ein JSON sein: was passiert ist, was nicht geklappt hat und warum. Das SDK liefert dir alle Rohdaten – Quittungen tragen Grundcodes, der Event-Stream liefert fertige Produktionen und Verluste. Du musst sie nur einsammeln: ```python title="recorder.py" import collections, json, time from openwar3 import Bot class Recorder(Bot): """Ergänzt einen Bot um einen Spielbericht. Davon erben und in eigenem on_start / on_event super() aufrufen.""" def on_start(self, g): self.rejects = collections.Counter() # "train hfoo: rejected(人口不够)" -> Anzahl self.timeline = [] # [Spielsekunde, Kategorie, Vier-Zeichen-Code]: Training / Forschung / Bau / Aufwertung fertig self.lost = collections.Counter() # was bei uns gestorben ist self.killed = collections.Counter() # was wir getötet haben def check(self, r, what): """Umhüllt einen Befehl und zählt Ablehnungsgründe: self.check(g.train(b, "hfoo"), "train hfoo")""" if r is not None and not r: self.rejects[f"{what}: {r.reason}"] += 1 return r def on_event(self, g, ev): me = g.me() if ev.kind == "production.done" and ev.owner == me: self.timeline.append([round(ev.clock), ev.done_kind, ev.done_code]) elif ev.kind == "unit.died": (self.lost if ev.owner == me else self.killed)[ev.type] += 1 def on_end(self, g, reason): report = {"reason": reason, "timeline": self.timeline, "lost": self.lost, "killed": self.killed, "rejects": self.rejects.most_common(10)} try: # das Spiel ist evtl. schon beendet; nicht lesbar -> egal report |= {"clock": g.clock(), "resources": g.resources(), "army": len(g.my_army()), "workers": len(g.my_workers())} except Exception: pass with open(f"run_{int(time.time())}.json", "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=1) ``` Fragen, die dieser Bericht beantwortet: | Signal | Quelle | Was man daran erkennt | |---|---|---| | Häufigste Ablehnungsgründe | Quittung `reason` / `verdict` | Nahrung blockiert ständig (3), Befehle trotz Geldmangel (8 / 9), Angriffe auf Ziele im Kriegsnebel (1001), Helden werden trotz Tod neu trainiert (221) | | Produktions-Timeline | `production.done`-Events (mit benötigten Spielsekunden) | In welcher Sekunde der erste Held kam, wann der Tier-up fertig war, ob die Kaserne durchgehend produziert hat; lässt sich mit den Eröffnungen von Profis vergleichen | | Verluste beider Seiten | `unit.died`-Events | Ob ständig Einheiten verschenkt werden, wie oft der Held gestorben ist, ob sich das Creepen gelohnt hat | | Grund für das Ende | `on_end(g, reason)` | `我方没有单位了` (keine eigenen Einheiten mehr) = verloren; `到时间了` (Zeit abgelaufen) = noch kein Sieger | | Armee und Ressourcen am Ende | Einmal den Snapshot in `on_end` lesen | Gold gespart statt ausgegeben = Produktion kommt nicht hinterher; zu wenige Arbeiter = Wirtschaft kommt nicht in Gang | > **Info** > > Den Sieger programmatisch zu bestimmen ist eines der Grundlagenexperimente für die [Arena](https://war3ai.com/de/arena/) und steht noch auf der Roadmap. Bis dahin kannst du „keine eigenen Einheiten mehr“ als Niederlage werten und „alle sichtbaren gegnerischen Gebäude zerstört“ näherungsweise als Sieg. ## 3. Ein klares Ziel und ein paar Regeln Gib dem Agent den folgenden Text und passe ihn an dein Ziel an: ```text Ziel: brains/my_bot.py soll auf Echo Isles zuverlässig gegen den Computer auf Schwierigkeit "Leicht" gewinnen (Menschen gegen zufälliges Volk). In jeder Runde: 1. python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair ausführen 2. Terminalausgabe und die neueste run_*.json lesen: Grund für das Ende, Produktions-Timeline, häufigste Ablehnungsgründe, Verluste beider Seiten 3. Das EINE Problem finden, das das Ergebnis am stärksten beeinflusst, und nur diese Stelle ändern; im Code-Kommentar Grund der Änderung und die zugrunde liegenden Daten festhalten 4. Zurück zu Schritt 1. Gibt es in 3 Partien hintereinander keinen Fortschritt, anhalten und mir Bericht und deine Einschätzung geben Regeln: - Nur Methoden aus docs/api.json verwenden, keine Schnittstellen erfinden - Derselben Einheit nicht jeden Tick denselben Befehl erneut geben; nur untätigen Einheiten Befehle erteilen - --fair beibehalten (nur Gegner verwenden, die in Sichtweite sind) - Vor jeder Codeänderung python tools/run_tests.py ausführen und sicherstellen, dass die Beispiele nicht kaputt sind ``` ## Gewohnheiten, mit denen die Schleife schneller konvergiert - **Immer nur eine Stelle ändern.** Änderst du drei Stellen gleichzeitig, weißt du bei einem Sieg nicht, welche geholfen hat, und bei einer Niederlage nicht, welche geschadet hat. - **Genug Partien vergleichen.** Dieselbe Ausgangslage streut stark; zwei Partien zeigen nur große Unterschiede. Um zu beurteilen, ob es Fortschritt gibt, schau dir den Trend über mehrere Partien an. - **Erst Ablehnungen beheben, dann die Strategie tunen.** Der häufigste Ablehnungsgrund in den Quittungen ist oft der größte Bug des Bots. - **Einschätzungen in Kommentare schreiben.** Der Agent in der nächsten Runde (oder im nächsten Gespräch) sieht dann in den Kommentaren, warum etwas so geschrieben ist, und macht Korrekturen nicht wieder rückgängig. - **Offline-Tests als Absicherung.** Schreib für die zentrale Logik Unit-Tests, die ohne laufendes Spiel auskommen (die Tests der Beispiel-Bots liegen in `brains/examples/tests/`), und lass den Agent sie nach jeder Änderung zuerst ausführen. --- # LLM als Strategie-Coach > Überlass dem LLM, worauf gespart wird, wohin die Arbeiter gehen und ob in dieser Minute angegriffen oder abgewartet wird – die Regelschicht führt nur aus und legt bei Bedarf ein Veto ein. Das Referenz-Brain arbeitet bereits so; diese Seite erklärt das Muster und die Fallstricke. Ab einer gewissen Größe merkst du, dass die Wirtschaftsregeln deines Bots Schicht um Schicht aufeinandergestapelt sind: eine Regel für die Zahl der Holzfäller, eine für 5 Arbeiter pro Mine, eine, die das Holzfällen halbiert, wenn zu viel Holz da ist, eine, die mehr Arbeiter aufs Gold schickt, wenn Gold knapp und Holz reichlich ist … Jede Regel ist für sich richtig, zusammen erzeugen sie aber Situationen wie „an der Mine fehlen Arbeiter, während alle Bauern Holz hacken“ – Situationen, für die **keine einzelne Regel zuständig ist**. Urteile wie „das Gesamtbild betrachten und Prioritäten setzen“ lassen sich ohnehin schlecht als `if / else` schreiben – genau darin sind LLMs aber gut. Das Referenz-Brain (`brains/xwar3/strategy/brain/coach.py`) nutzt die folgende Schichtung. ## Schichten ```text LLM (Berater) Alle 20 Spielsekunden, asynchron, blockiert nie einen Tick Eingabe: ein einseitiger Snapshot der Partie (Ressourcen, Nahrung, Verteilung der Bauern, Minen, Einheitentypen, Tech, Helden, Feindlage, jüngste Ereignisse) Ausgabe: striktes JSON – Diagnose in einem Satz + Arbeiterverteilung + was zuerst kommt + Haltung für diese Minute + was zu vermeiden ist │ ▼ Whitelist + Min/Max-Clamping + Veto Regelschicht (Bot, jeder Tick) Übersetzt die Empfehlungen in „Biases“ auf vorhandene Fähigkeiten: Arbeiterverteilung, Bau- / Trainingspriorität, Angriffshaltung │ ▼ Ausführungsschicht (SDK / Reflexschicht) Befehle erteilen, Quittungen lesen, Mikro ``` ## Ausgabevertrag Lass das Modell nur JSON mit festen Feldern ausgeben – nichts hinzufügen, nichts weglassen: ```json { "diagnosis": "Ein Satz: das größte Problem der Partie, das sich in den Eingabedaten belegen lassen muss", "workers": { "gold": 10, "lumber": 6 }, "priority": ["hpea", "hhou", "hbar"], "posture": "creep", "avoid": ["Bei Holzmangel nicht zuerst Iron Plating erforschen"] } ``` | Feld | So nutzt es die Regelschicht | Clamping im Referenz-Brain | |---|---|---| | `workers` | Zielzahl der Arbeiter auf Gold und auf Holz | Gold 2 ~ 25, Holz 1 ~ 20; die Summe darf die Gesamtzahl der Bauern nicht überschreiten | | `priority` | Prioritätsreihenfolge für Training / Bau / Forschung | Höchstens 4; nur Vier-Zeichen-Codes, die in der Tabelle „erlaubte Codes“ vorkommen | | `posture` | Haltung für diese Minute | Muss eines von `attack` `defend` `creep` `expand` `recover` `hold` sein | | `avoid` | Was in dieser Minute nicht getan werden soll | Höchstens 2 Einträge | | `diagnosis` | Nur für Logs und die Anzeige in der Konsole | — | Schreib pro Volk einen eigenen Prompt, der nur die volksspezifischen Abwägungen enthält (gemeinsames Bauen und Miliz bei den Menschen, Burrows bei den Orcs, Haunted Gold Mine bei den Untoten, Entangled Gold Mine bei den Nachtelfen …). Allgemeine Regeln gehören in einen gemeinsamen Teil – nicht viermal kopieren. ## Vier harte Regeln Für jede dieser vier Regeln hat das Referenz-Brain in der Praxis Lehrgeld bezahlt: 1. **Der Berater erteilt nie direkt Einheitenbefehle.** Er sieht nicht, was im 150-ms-Takt passiert, und er halluziniert. Er ändert nur Ziele und Prioritäten; wer wohin geht und wen angreift, entscheiden weiterhin Regelschicht und Reflexschicht – die Befehlsgewalt kann nur einen Besitzer haben. 2. **Asynchron.** Ein Beratungsaufruf dauert etwa 1 Sekunde und läuft in einem Hintergrund-Thread; das neueste Ergebnis gilt, und er **blockiert nie einen Tick**. Läuft das Modell nicht, gibt es einen Timeout oder antwortet es Unsinn, tu so, als gäbe es diese Schicht nicht – das Verhalten fällt auf reine Regeln zurück. Zu alte Empfehlungen (älter als 3 Intervalle) werden ebenfalls nicht verwendet. 3. **Whitelist + Clamping.** Jedes Feld muss sich auf eine vorhandene Fähigkeit abbilden lassen, Zahlenwerte werden auf einen sinnvollen Bereich begrenzt. Unbekanntes wird **gezählt und dann verworfen**, nicht stillschweigend ignoriert. 4. **Alles zählen.** Wie oft gefragt, wie oft erfolgreich, wie viele Timeouts, wie oft geclampt, wie oft jedes Feld übernommen wurde – veröffentliche das alles zusammen mit der letzten Eingabe an das Modell. Sonst ist „bringt diese Schicht überhaupt etwas?“ eine Frage, die niemand beantworten kann. > **Was sicher degradiert, degradiert am leichtesten unbemerkt** > > Der Berater ist so gebaut, dass „Fehler = als gäbe es diese Schicht nicht“ gilt. Läuft der Modelldienst nicht, verhält sich der Bot also exakt wie mit reinen Regeln, und von außen merkt man nichts. Beim Referenz-Brain konnte der Berater einmal einen ganzen Tag lang auf allen 6 Instanzen keine Verbindung herstellen, ohne dass es jemand bemerkte. Veröffentliche unbedingt „Zeitpunkt des letzten Erfolgs“ und „Grund des letzten Fehlers“ – genau dafür gibt es die Seite „Strategie-Coach“ in der [Farsight-Konsole](https://war3ai.com/de/docs/console/). ## In deinem eigenen Bot umsetzen Unten findest du ein minimales Gerüst, das mit jeder OpenAI-kompatiblen API funktioniert (LM Studio, Ollama oder eine Cloud-API) und nur die Standardbibliothek nutzt: ```python title="coached_bot.py" import collections, json, threading, urllib.request from openwar3 import Bot BASE = "http://127.0.0.1:1234/v1" # LM Studio / Ollama / jeder OpenAI-kompatible Dienst MODEL = "your-model" POSTURES = {"attack", "defend", "creep", "expand", "recover", "hold"} SYSTEM = """Du bist ein Makro-Coach für Warcraft III. Du kümmerst dich nur um Wirtschaft und Strategie, nicht um Mikro. Gib nur JSON aus, mit festen Feldern: {"diagnosis": ein Satz, "workers": {"gold": Ganzzahl, "lumber": Ganzzahl}, "priority": [Vier-Zeichen-Codes, höchstens 4, nur aus allowed], "posture": eine von sechs, "avoid": [höchstens 2]} Stütze dich nur auf die Spieldaten, die du bekommst; erfinde nichts, was nicht in den Daten steht.""" def ask(state: dict) -> dict: body = {"model": MODEL, "temperature": 0.3, "max_tokens": 260, "messages": [{"role": "system", "content": SYSTEM}, {"role": "user", "content": json.dumps(state, ensure_ascii=False)}]} req = urllib.request.Request(f"{BASE}/chat/completions", json.dumps(body).encode(), {"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=8) as r: text = json.load(r)["choices"][0]["message"]["content"] return json.loads(text[text.index("{"): text.rindex("}") + 1]) class CoachedBot(Bot): EVERY = 20.0 # Spielsekunden: Makro-Entscheidungen laufen im Minutentakt, nicht jeden Tick fragen allowed = {"hpea", "hfoo", "hrif", "hkni", "hhou", "hbar", "hbla", "Rhme", "Rhar"} def on_start(self, g): self.plan, self.plan_at, self.asked_at, self.busy = {}, -1e9, -1e9, False self.stats = collections.Counter() def summary(self, g) -> dict: # Snapshot im Haupt-Thread lesen; der Hintergrund-Thread fasst g nie an res = g.resources() or {} return {"clock": round(g.clock() or 0), "gold": res.get("gold"), "lumber": res.get("lumber"), "food": [res.get("food_used"), res.get("food_cap")], "workers": len(g.my_workers()), "idle_workers": len(g.idle_workers()), "army": collections.Counter(u.type for u in g.my_army()), "enemy_seen": collections.Counter(u.type for u, _t, _age in g.last_seen(max_age=90)), "night": g.is_night(), "allowed": sorted(self.allowed)} def consult(self, state, now): try: p = ask(state) self.stats["ok"] += 1 posture = p.get("posture") if posture not in POSTURES: self.stats["bad_posture"] += 1 # zählen, dann verwerfen – nie stillschweigend posture = "hold" self.plan = {"gold": min(25, max(2, int(p["workers"]["gold"]))), # clampen "lumber": min(20, max(1, int(p["workers"]["lumber"]))), "priority": [c for c in p.get("priority", []) if c in self.allowed][:4], "posture": posture} self.plan_at = now except Exception as e: # Timeout / Unsinn: als gäbe es diese Schicht nicht self.stats[f"error:{type(e).__name__}"] += 1 finally: self.busy = False def on_tick(self, g): now = g.clock() or 0.0 if not self.busy and now - self.asked_at >= self.EVERY: self.busy, self.asked_at = True, now threading.Thread(target=self.consult, args=(self.summary(g), now), daemon=True).start() plan = self.plan if now - self.plan_at <= 3 * self.EVERY else {} # zu alte Empfehlungen nicht verwenden # ↓ Regelschicht: ohne plan gelten die Standardregeln; mit plan nur Verteilung, Prioritäten und Haltung anpassen – die konkreten Befehle entscheiden weiterhin die Regeln ... ``` ## Welches Modell? | Situation | Empfehlung | |---|---| | Lokal, muss schnell sein | MoE-Modelle (die pro Aufruf nur einen kleinen Teil der Parameter aktivieren) sind deutlich schneller als dichte Modelle gleicher Größe. Das Referenz-Brain nutzt Qwen3.6-35B-A3B (LM Studio, Q4): Median **1.09 s**, langsamster Aufruf 1.45 s, 5/5 Ausgaben lassen sich direkt mit `json.loads` parsen | | Lokal, „denkendes“ Modell | **Den Thinking-Abschnitt unbedingt abschalten**, sonst gehen alle Tokens fürs Denken drauf und es kommt nie ein JSON heraus. LM Studio ignoriert `/no_think`; das Referenz-Brain nutzt stattdessen `/v1/completions`, baut das ChatML selbst zusammen und füllt ein leeres `` plus ein `{` vor | | Cloud-Modelle | Die Latenz ist meist höher, aber diese Schichtung ist ohnehin asynchron; Makro-Entscheidungen bemessen sich in Minuten, ein paar Sekunden Latenz sind in Ordnung | > **Info** > > Dasselbe Modell kann deinen Einheiten auch eine Stimme geben: siehe [Sprechblasen und lokale Modelle](https://war3ai.com/de/docs/speech/). Wenn das Modell direkt jeden Tick Befehle erteilen soll (statt als Berater zu arbeiten), warte auf das JSON-Gateway der [Arena](https://war3ai.com/de/arena/). --- # LLM ruft Tools direkt auf (MCP) > tools/war3_mcp.py ist ein MCP-Server. Hängst du ihn in Claude Code, Claude Desktop oder einen anderen MCP-fähigen Client, kann das LLM direkt die Lage sehen, Befehle erteilen, auf dem Bildschirm mit dem Spieler sprechen, ihm Karten zur Auswahl zeigen und Screenshots ansehen – ohne vorher Code zu schreiben. `tools/war3_mcp.py` ist ein **MCP-Server** (stdio). Claude Code, Claude Desktop, Agent-Frameworks für lokale Modelle – hängst du ihn in einen beliebigen MCP-fähigen Client, kann das LLM **direkt** die Lage sehen, Befehle erteilen, auf dem Spielbildschirm mit dem Spieler sprechen, dem Spieler Fragen stellen und Screenshots ansehen, ohne vorher Code zu schreiben. Neben Bot schreiben, Coach spielen und Einheiten sprechen lassen ist das ein weiterer Weg: **Das LLM benutzt die Tools selbst.** ## Einbinden ```bash claude mcp add war3 -- python \tools\war3_mcp.py --inst 9 # Claude Code; durch deinen openwar3-Ordner ersetzen ``` Andere Clients konfigurierst du nach diesem Schema: ```json {"mcpServers": {"war3": {"command": "python", "args": ["\\tools\\war3_mcp.py", "--inst", "9"]}}} ``` Mit dem Spiel verbunden wird erst beim ersten Tool-Aufruf – das Spiel kann also später gestartet werden; wird es geschlossen und neu gestartet, verbindet sich der nächste Aufruf automatisch neu. Mit `--role` legst du fest, was das LLM darf: | Rolle | Darf | |---|---| | `dev` (Standard) | alle Tools, einschließlich `war3_jass` | | `player --player N` | nur Einheiten von Spieler N befehligen und nur dessen Sicht sehen (Fair-Modus); kein JASS | | `observer` | nur lesen, nichts auf den Bildschirm zeichnen, keine Einheiten sprechen lassen; die Runtime lehnt seine Befehle direkt ab | Für die Rolle `player` gelten dieselben Grenzen wie im [Gateway](https://war3ai.com/de/docs/gateway/): kein Spiel beenden, kein Tempo ändern, kein Pausieren, keine APIs, die die verdeckten Karten der anderen zeigen, und Abfragen mit Spielernummer gehen nur für den eigenen Spieler. Ein paar Obergrenzen: Ein Tool-Ergebnis hat höchstens 200.000 Zeichen – was darüber hinausgeht, wird abgeschnitten, mit einem Hinweis, wie man den Umfang eingrenzt; `war3_ask_player` wartet höchstens 120 Sekunden; `scale` beim Screenshot liegt zwischen 0.1 und 1. ## Tools | Tool | Was es tut | |---|---| | `war3_overview` | Lage auf einer Seite: Zeit, Ressourcen, Nahrung, Anzahl eigener Einheiten pro Typ, Helden (HP, Mana, Stufe, Abklingzeiten), sichtbare gegnerische Einheitentypen, Produktion. **Zuerst aufrufen** | | `war3_units` | Einheitenliste (`owner` = me / enemy / creep / all, Filter per `types`); mit `addr` erteilst du Befehle | | `war3_events` | Was seit dem letzten Aufruf passiert ist: Tode, Stufenaufstiege, Zauber, fertige Produktion, Chat, angeklickte Buttons … (standardmäßig ohne die Arten, die das Log fluten) | | `war3_call` | Beliebige öffentliche API aufrufen (`move`, `attack_move`, `train`, `build`, `cast`, `learn`, `ui.button`, `canvas.text` …); Einheiten als `{"unit": addr}` | | `war3_api` | APIs nachschlagen: Namen und Beschreibungen nach Stichwort durchsuchen | | `war3_toast` / `war3_say` | eine Textzeile oben auf dem Bildschirm / ein Satz über dem Kopf einer Einheit | | `war3_ask_player` | zeigt dem Spieler in der Bildschirmmitte ein paar Auswahlkarten, wartet auf den Klick und gibt die Wahl zurück (kann das Spiel pausieren) | | `war3_screenshot` | Screenshot des Spielbilds (PNG; funktioniert auch, wenn das Fenster verdeckt ist, ohne den Fokus zu stehlen) | | `war3_jass` | ein Stück JASS ausführen (nur dev; die Welt ändern nur im Einzelspieler) | Was sich damit machen lässt: - **Mitspieler / Trainer**: Mit `war3_overview` die Lage lesen, mit `war3_toast` Tipps auf den Bildschirm geben; - **Den Spieler mitten im Spiel fragen**: `war3_ask_player` zeigt drei Karten, und es geht mit der Karte weiter, die der Spieler anklickt; - **Kommentar**: `war3_events` liest, was passiert ist, `war3_say` lässt die Einheiten es selbst aussprechen; - **Direkt eine Truppe befehligen**: Rolle `player` + `war3_call`, nur die eigenen Einheiten lassen sich bewegen; - **Oberfläche per Bild prüfen**: Mit `war3_screenshot` einen Screenshot machen und nachsehen, ob die selbst gezeichneten Buttons richtig sitzen. ## So sieht ein Gespräch etwa aus ```text Du: Schau dir die aktuelle Lage an und frag mich dann auf dem Bildschirm: als Nächstes expandieren, Armee aufbauen oder Hauptgebäude aufwerten? → war3_overview {} ← Lage auf einer Seite: Spielzeit, Gold 500, Nahrung 10/12, eigene htow 1 · hpea 5 · Hpal 1, keine Gegner in Sicht, keine laufende Produktion → war3_ask_player {"question": "Wie geht's weiter?", "options": ["Armee aufbauen", "Expandieren", "Aufwerten"], "pause": true} ← {"picked": 1, "option": "Expandieren"} Modell: Du hast „Expandieren“ gewählt. Ich suche zuerst mit war3_units einen untätigen Bauern und schaue dann, wo die nächste Goldmine ist … ``` ## Gemessen 2026-09-25: - Eigener MCP-Client an einer echten Partie, 7/7: Handshake → Tools auflisten (10) → `war3_overview` (`htow` 1, `hpea` 5) → `war3_units` → `war3_toast` → `war3_screenshot` (PNG, ca. 200.000 Bytes) → `war3_ask_player` (drei Karten, simulierter Klick auf die zweite → `{"picked": 1, "option": "开矿"}`, also „Expandieren“). - Live in Claude Code 2.1 eingebunden: Es startet den Server selbst, führt den Handshake durch, Status `connected`, und alle 10 Tools erscheinen als `mcp__war3__*` in seiner Tool-Liste. ## Implementierung - Zeilengetrenntes JSON-RPC 2.0 (`initialize` / `tools/list` / `tools/call` / `ping`), Protokollversion 2025-06-18, kompatibel mit 2025-03-26 und 2024-11-05. - Tool-Fehler landen nach MCP-Konvention im Ergebnis (`isError: true`), die Verbindung bleibt bestehen. - Nutzt dieselbe Rollen-Whitelist, dasselbe Format für Einheitenparameter und dieselbe „Lage auf einer Seite“ wie das [Gateway](https://war3ai.com/de/docs/gateway/). - Logs gehen nach stderr, auf stdout steht nur das Protokoll. --- # Mentales Modell > Snapshot, Befehl, Quittung, Event, Tick, Batch. Wer diese sechs Konzepte versteht, versteht, warum die API so aussieht und wie man schnellen Code schreibt. ## Snapshot: lesen, ohne Wartezeit Die Runtime erfasst alle **50 ms** im Spiel-Thread die gesamte Welt und schreibt sie ins Shared Memory. `g.snapshot()` liefert dir eine **vollständige, in sich konsistente** Welt: - 16 Spieler-Slots: Gold, Holz, Nahrung, Nahrungslimit, insgesamt gesammelte Menge, Volk; - bis zu 1024 Einheiten: Typ, Besitzer, Koordinaten, TP / Mana (mit Maximum), aktuelle Order und Order-Ziel, **was sie tatsächlich angreift** (Aufgabenziel), Heldenstufe / EP / Fertigkeitspunkte, Sichtbarkeit für jeden Spieler; - bis zu 256 Einheitendetails: 12 Fähigkeiten (Stufe, verbleibende Abklingzeit), 8 Buffs, 6 Inventarplätze; - Gegenstände am Boden, Bäume (alle 2 Sekunden aktualisiert), Produktionstabelle (Fortschritt von Training / Forschung / Bau / Aufwertung), Spieluhr, Tageszeit im Spiel. Eine Kopie zu lesen dauert etwa **0.4 ms** (Parsing in Python), ohne auf den Spiel-Thread zu warten. Deshalb gilt: **Lies, so oft du willst.** Schnittstellen wie `g.units()`, `g.my_army()`, `g.cooldown()` und `g.inventory()` lesen alle aus demselben Snapshot – egal wie oft du sie in einem Tick aufrufst, es kostet kaum etwas. > **Tipp** > > Die Veröffentlichungsperiode ist einstellbar: `g.set_publish_period(ms)`, 16 ~ 1000 Millisekunden. Eine Erfassung kostet im Spiel-Thread etwa 0.5 ~ 0.9 ms, auch 33 ms sind also kein Problem. Der Wert gilt für den ganzen Rechner; wer zuletzt schreibt, gewinnt. ## Befehl: schreiben, etwa ein Frame `g.move / attack / gather / build / train / cast …` werden vom Spiel-Thread ausgeführt. Die Runtime arbeitet die von Clients eingereichten Befehle gebündelt im **Event-Dispatch** des Spiel-Threads ab, ein Befehl wartet also etwa **ein Frame** (fällt er in einen Event-Cluster, ca. 0.1 ms, sonst bis zum nächsten Dispatch). - Befehle nehmen **eine Einheit oder eine Liste** entgegen; alle Einheiten einer Liste bekommen den Befehl im selben Frame; - Mit `queue='after'` stellst du wie mit Shift an: erst die aktuelle Aufgabe erledigen, dann die neue; - Für die Einheiten im Befehl reichen die Objekte aus dem Snapshot; das SDK prüft ihre Identität über das **Handle-Paar** (Adressen werden von neuen Einheiten wiederverwendet, Handles nicht). ## Quittung: für jeden Befehl ```python r = g.build(worker, "hbar", x, y) if r: # die Engine hat angenommen ... else: r.reason # 'rejected(金不够)' (= nicht genug Gold) r.verdict # 8 r.exec_us # wie viele Mikrosekunden dieser Befehl im Spiel-Thread lief ``` Die Quittung wird **im selben Frame** zurückgelesen: Order der Einheit vor und nach dem Befehl, Rückgabewert der Engine-Funktion, Grundcode der Machbarkeitsprüfung. Sie beantwortet, „ob die Engine den Befehl angenommen hat und warum nicht“, aber **nicht**, „ob er am Ende erfolgreich war“ – das zeigen Snapshot und Events. Alle Statuscodes und Grundcodes findest du unter [Quittungen und Grundcodes](https://war3ai.com/de/docs/reason-codes/). ## Event: was passiert ist `on_event(g, ev)` übergibt dir vor jedem `on_tick` alle Events seit dem letzten Tick, eines nach dem anderen: | Event | Bedeutung | |---|---| | `unit.appeared` / `unit.died` / `unit.removed` | Einheit erscheint, stirbt, verschwindet (in eine Goldmine gehen, umgewandelt werden, Verwesen der Leiche zählen auch als Verschwinden – das heißt nicht, dass sie gestorben ist) | | `unit.damaged` / `order.changed` / `owner.changed` | TP-Verlust, neue Order, neuer Besitzer | | `hero.levelup` | Held steigt eine Stufe auf | | `item.appeared` / `item.removed` | Gegenstand am Boden erscheint, wird aufgehoben oder verbraucht | | `damage` | Engine-Ebene: **jeder einzelne** Treffer. Quelleinheit, Angriffstyp, Schadenstyp, tatsächlicher TP-Verlust, Schaden vor Rüstung | | `killed` | Engine-Ebene: Dieser Treffer hat sie getötet, mit Killer | | `production.done` | Training / Forschung / Bau / Aufwertung fertig, mit Vier-Zeichen-Code und benötigten Spielsekunden. Auch für Gegner | | `spell.cast` | Eine Einheit hat eine Fähigkeit gewirkt: Vier-Zeichen-Code der Fähigkeit, Stufe, Abklingzeit in Sekunden, Zielpunkt | | `message` | In einem Nachrichtenfeld auf dem Bildschirm erscheint eine Zeile: Spielhinweis (etwa, dass mehr Farmen nötig sind), Chat (`.chat` enthält Sprecher und Text), Systemnachricht | | `selection.changed` / `player.left` | Die Auswahl des lokalen Spielers hat sich geändert / ein Spieler hat das Spiel verlassen oder wurde als besiegt entfernt | | `game.started` / `game.ended` | Eine neue Partie beginnt / die Partie wurde verlassen | Eingabe-Events wie Klicks auf Canvas-Buttons, Hotkeys und Klicks auf den Boden findest du unter [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/). > **Achtung** > > Der Event-Stream ist **global**: Fertige Produktionen des Gegners und getötete Creeps sind ebenfalls enthalten. Filtere nach `ev.owner` oder nach dem Handle der Einheit. ## Tick: der Takt des Bots `on_tick` wird standardmäßig 5-mal pro Sekunde aufgerufen (Echtzeit). Die Dauer eines Ticks ist im Wesentlichen deine eigene Rechenzeit: Snapshots ohne Wartezeit, Befehle etwa ein Frame. Überschreitet ein Tick die Periode, verschiebt sich der nächste automatisch, statt dass sich Ticks aufstauen. - **Bei doppelter Geschwindigkeit nicht nach Echtzeit warten.** Wenn du 3 Spielsekunden warten willst, prüf, ob `g.clock()` um 3 gestiegen ist – kein `sleep(1.5)`. - **Kein `sleep` in `on_tick`.** Soll etwas „etwas später“ passieren, merk dir die aktuelle Spielzeit und prüf im nächsten Tick erneut. ## Batch: Dutzende Befehle, nur einmal warten Wenn du in einem Tick viele Befehle erteilst, pack sie in `with g.batch():`: ```python with g.batch(): g.attack(melee, target_a) g.attack(ranged, target_b) g.move(wounded, home.x, home.y) g.cast(hero, "thunderclap") # am Ende des Blocks wird der ganze Batch eingereicht: im selben Frame ausgeführt, nur einmal auf den Spiel-Thread warten ``` - Befehle im Block liefern `Pending`, das nach dem Block zur Quittung wird; wer es vor dem Ende des Blocks liest, bekommt eine Exception; - Wird im Block eine Exception ausgelöst, **verfällt der ganze Batch** (halb ausgeführte Befehle sind gefährlicher als gar keine); - Gemessen mit 8 Bewegungen: einzeln 68 ~ 99 ms, als Batch **6.5 ~ 10 ms**. Dasselbe Prinzip gilt für Abfragen: `g.can_do_many([(u, code), ...])` und `g.tech_many([...])` fragen viele Dinge auf einmal ab. ## Das eben Geschriebene lesen Innerhalb desselben Ticks sieht der Snapshot deine gerade erteilten Befehle noch nicht (erst die nächste Veröffentlichung holt sie ein). Zwei Logikteile können sich so um denselben Arbeiter streiten: Der eine hat ihn gerade zum Bau eines Bauernhofs geschickt, der andere hält ihn laut Snapshot noch für untätig. `g.order_of(u)` löst das: Bis der Snapshot aufgeholt hat, gilt die neue Order aus der Quittung. **Ob eine Einheit untätig ist, prüfst du mit `g.order_of(u)`, nicht mit `u.order`.** `g.idle_workers()` schließt Arbeiter, die in diesem Tick gerade eine Aufgabe bekommen haben, bereits aus. ## Latenzstufen | Stufe | Kanal | Latenz | Verwendet für | |---|---|---|---| | 0 | Push-Snapshot + Event-Stream | Eine Kopie lesen ca. 0.4 ms; alle 50 ms neue Daten | Alle „lesenden“ Schnittstellen | | 1 | Schnellspur | Ca. 1 Frame; bei 6 parallelen Prozessen Median 0.06 ms | Alle Befehle und Abfragen (Standard im SDK) | | 2 | Steuerkanal | 20 ~ 40 ms | Fallback, einige UI-Aktionen (Spielgeschwindigkeit, Sprechblasen, Nachrichten) | | 3 | [Gateway](https://war3ai.com/de/docs/gateway/) (WebSocket / JSON) | Stufe 1 + ca. 1 ms | Beliebige Sprachen, Browser, LLMs, Programme auf anderen Rechnern | In der [API-Referenz](https://war3ai.com/de/api/) ist bei jeder Schnittstelle angegeben, über welche Stufe sie läuft. --- # Die 15 Regeln > Jede einzelne stammt aus echten Spielen. Prüf deinen Bot beim Schreiben dagegen – das spart den Großteil der Fehlersuche. > **Tipp** > > Gib diese Seite zusammen mit [`api.json`](https://war3ai.com/de/api.json) an dein LLM – der Bot, den es schreibt, erspart dir viele Umwege. ## Zustand lesen ### 1. Nicht lesbar heißt `None`, nicht 0 `resources()`, `time_of_day()`, `production()` und `cooldown()` können alle `None` zurückgeben (Ladebildschirm, Einheit ohne Detaildaten, Gebäude produziert gerade nichts …). Erst prüfen, dann verwenden: ```python res = g.resources() if res is None: return ``` ### 2. Einheiten per Handle erkennen, nicht per Adresse Adressen werden für neue Einheiten wiederverwendet: Eine alte Adresse kann auf eine frisch erschienene Einheit zeigen. Um dir eine Einheit über mehrere Ticks zu merken, speichere `u.handle` und hol sie mit `g.unit(handle)` zurück. ### 3. Der Event-Stream ist global In `production.done` und `unit.died` stecken auch Events von Gegnern und Creeps. Filtere nach `ev.owner` (oder dem Gebäude-Handle): ```python if ev.kind == "production.done" and ev.owner == g.me(): ... ``` ### 4. Arbeiter in der Goldmine fehlen im Snapshot In dem Moment, in dem ein Arbeiter die Goldmine betritt, verschwindet er aus dem Snapshot (`unit.removed`, nicht tot). Um zu zählen, wie viele an jeder Mine arbeiten, **führ selbst Buch** und streich keine Einträge anhand des Snapshots – sonst schickst du zusätzliche Arbeiter an volle Minen. ## Befehle erteilen ### 5. „Angenommen“ ≠ erledigt Auch einen Bauplatz im Wald nimmt die Engine sofort an; scheitern tut der Befehl erst, wenn der Arbeiter ankommt. Fähigkeiten können unterbrochen werden. Die Wirkung siehst du in Snapshot und Events: Zum Bauen `build_near` (verfolgt, ob das Fundament erscheint), bei Fähigkeiten prüfen, ob `g.cooldown()` läuft. ### 6. Unsichtbare Ziele kannst du nicht angreifen Zielbefehle auf Gegner im Nebel werden mit Reason-Code **1001** abgelehnt. Um einen Gegner im Nebel zu verfolgen, schick `attack_move` an seine zuletzt gesehene Position. ### 7. Nur untätigen Einheiten Befehle geben Denselben Befehl in jedem Tick erneut an dieselbe Einheit zu senden, unterbricht sie: Soldaten zucken auf der Stelle, der Abbauzyklus der Arbeiter beginnt von vorn. Ob eine Einheit untätig ist, prüfst du mit `g.order_of(u)` (enthält auch die Befehle, die du in diesem Tick gerade erteilt hast), nicht mit `u.order` aus dem Snapshot (der hinkt noch hinterher). ### 8. Shift heißt nur „direkt hinter den aktuellen Befehl“ Die Engine kennt kein „ans Ende anhängen“: Sendest du nacheinander B und C mit `queue='after'`, bekommst du A, C, B. Für eine Reihe von Punkten in fester Reihenfolge nimm `g.path(units, points)`, für mehrere Gebäude mit einem Arbeiter `g.build_queue(worker, plan)` – beide fügen in umgekehrter Reihenfolge ein und erledigen das für dich. ### 9. Alle Befehle eines Ticks als Batch Dutzende Befehle einzeln gesendet heißt dutzendmal auf den Spiel-Thread warten; in `with g.batch():` verpackt nur einmal. ## Wirtschaft & Produktion ### 10. Maximal 5 Arbeiter pro Goldmine Mehr bringt kein Einkommen. Das Arbeiterziel richtet sich nach der Zahl der Minen: 5 für Gold pro Mine, plus ein paar für Holz. ### 11. Nur 1 Einheit in der Ausbildungswarteschlange Eine volle Warteschlange (7 Plätze) bindet Geld (gemessen: 4 Bauern im Hauptgebäude eingereiht, 300 Gold gebunden, die Eröffnung deutlich langsamer). Erst nachlegen, wenn `g.queue(b)` leer ist. ### 12. Bei Nahrungsengpass in die Produktionstabelle schauen `g.production(b).blocked` = etwas steht in der Warteschlange, hat aber nicht begonnen – meistens fehlt Nahrung. Das ist einen Schritt früher als „bauen, wenn die Nahrung fast voll ist“: Verlierst du im Kampf viele Einheiten und stockt beim Nachbauen die Warteschlange, weißt du es sofort. ### 13. Helden sind einzigartig; kein Tier-Up bei belegter Warteschlange des Hauptgebäudes - Ein toter Held lässt sich nur mit `g.revive(altar)` zurückholen; erneutes Ausbilden wird abgelehnt (221). Auch Wiederbeleben kostet Nahrung (ein Held belegt 5). - Solange noch etwas in der Warteschlange des Hauptgebäudes steht, kann es nicht aufgewertet werden (Reason-Code 185, „Gebäude beschäftigt“). ## Zeit & Raum ### 14. Bei 2× Geschwindigkeit nicht nach der Wanduhr warten Willst du 3 Spielsekunden warten, warte, bis `g.clock()` um 3 gestiegen ist – nicht `sleep(1.5)`. Bei erhöhter Geschwindigkeit läuft die Engine-Uhr schneller als die Wanduhr. ### 15. Auf Insel- und Waldkarten keine Luftlinie Für Creep-Lager und Expansionen `g.path_distance(a, b)` verwenden (A* am Boden, um Wald, Klippen und Gebäude herum); ist ein Ort unerreichbar, kommt `None` zurück. Der Punkt mit der kürzesten Luftlinie liegt womöglich auf der anderen Seite des Meeres. ## Und noch eine: Schreib für den Fair-Modus Mit `--fair` siehst du nur Einheiten, Gegenstände, Produktion und Events in deiner Sicht – genau die Regel der Arena. Schreib schon jetzt für den Fair-Modus, dann musst du für die [Arena](https://war3ai.com/de/arena/) nichts ändern. Mehr dazu unter [Fair-Modus](https://war3ai.com/de/docs/fair-mode/). --- # Fair-Modus > Ein ins Spiel injizierter Client kann die ganze Karte lesen. Im Fair-Modus sieht dein Bot nur, was in seiner Sicht liegt – wie ein menschlicher Spieler und wie nach den Regeln der Arena. Die Beobachtungsfähigkeit dieses Projekts beruht darauf, dass „der Client den Zustand aller Spieler hält“: Der Snapshot enthält alle Einheiten der ganzen Karte, auch Gegner im Nebel. Fürs Debugging ist das praktisch, für Wettkämpfe unfair. Der **Fair-Modus** lässt das SDK nach deiner Sicht filtern: ```bash python tools/play.py --bot my_bot.py --fair python -m openwar3 run my_bot.py --inst 5 --fair ``` ```python from openwar3 import Game, run g = Game(inst=5, fair=True) # Game direkt verwenden run(MyBot, inst=5, fair=True) # oder dem Runner überlassen ``` ## Was gefiltert wird | Inhalt | Im Fair-Modus | |---|---| | Einheiten | Alle eigenen + Gegner und neutrale Einheiten, die du gerade siehst | | Gegenstände am Boden | Nur in Sichtweite eigener Einheiten (Sichtweite bei Tag / Nacht getrennt laut Datentabelle) | | Produktionstabelle | Nur sichtbare Gebäude (du siehst nicht, was der Gegner ausbildet) | | Events | Deine eigenen; sichtbare (oder innerhalb der letzten Sekunde sichtbare); Schaden, den deine Seite verursacht | ## Woher die Sicht kommt - Jede Einheit im Snapshot trägt eine **Sichtbarkeitsmaske**: Bit p = Spieler p sieht sie gerade (nur Spieler 0–11 mit Einheiten auf dem Feld; eigene Einheiten sind für dich immer sichtbar). `u.visible_to(g.me())` liest sie direkt, ohne Wartezeit. - Für beliebige Punkte fragt `g.visible(x, y)` die Engine (sichtbar / Nebel / unerforscht) – über die Schnellspur, etwa ein Frame pro Aufruf. Musst du in einem Tick viele Einheiten prüfen, nimm `u.visible_to()` aus dem Snapshot statt `g.visible()` einzeln aufzurufen. ## Das Gedächtnis für Gegner: `last_seen` Ein menschlicher Spieler merkt sich „eben habe ich dort drüben eine Gruppe Wolfsreiter gesehen“. Das SDK merkt es sich auch: Bei jeder Snapshot-Aktualisierung werden gegnerische Einheiten und Creeps, die du gerade siehst, festgehalten (letzte Position, HP, Zeit); siehst du eine sterben, wird sie gelöscht, bei einem neuen Spiel wird alles geleert. ```python for u, t, age in g.last_seen(max_age=60): # Gegner, die in den letzten 60 Spielsekunden gesehen wurden print(u.type, u.x, u.y, f"vor {age:.0f}s") heroes = [r for r in g.last_seen() if r[0].is_hero] # wo die gegnerischen Helden zuletzt waren camps = g.last_seen(owner="creep") # bereits gesehene Creeps ``` Im Fair-Modus ist das deine einzige „Informationsquelle über den Gegner“ – genau wie bei menschlichen Spielern. Auch im normalen Modus wird nach Sicht gespeichert, du kannst also denselben Code verwenden. ## Als welcher Spieler befehligen ```bash python tools/play.py --bot my_bot.py --player 1 --attach ``` `--player N` (oder `Game(player=N)`) lässt den Bot als Spieler N befehligen – er kann nur Einheiten von Spieler N steuern. Für zwei KIs gegeneinander öffnest du im selben Spiel zwei solche Kanäle. > **Lokal ist Fairness eine Absprache, keine Sicherheitsgrenze** > > Auf deinem eigenen Rechner kann nichts ein Programm daran hindern, die ganze Karte zu lesen. `--fair` ist eine Selbstverpflichtung; echte Wettkämpfe sichert der Schiedsrichterprozess der [Arena](https://war3ai.com/de/arena/): Bots kommen nie an den Shared Memory, erhalten nur vom Schiedsrichter nach Sicht gefilterte Beobachtungen, können nur Aktionen einreichen, und bei jeder Aktion wird zuerst geprüft, ob die Einheit dem Bot gehört. ## Warum du ihn schon jetzt einschalten solltest - Auf der Arena gelten genau diese Regeln – schreibst du jetzt für den Fair-Modus, musst du später keine Zeile ändern; - erst ohne Informationen über die ganze Karte zeigt sich, wie gut dein Bot wirklich ist (das Referenz-Brain stützt sich derzeit stark darauf, etwa auf den Zielpunkt des Anführers der Computer-KI – ein guter Test); - Aufklärung, Gedächtnis und Lageeinschätzung, die du im Fair-Modus entwickelst, sind die KI-Fähigkeiten, die wirklich zählen. --- # Profi-Kochbuch > Der Vorsprung guter Spieler steckt größtenteils in Dutzenden kleiner Gewohnheiten. Diese Seite übersetzt gängige Profi-Techniken eine nach der anderen in SDK-Code – jeder Abschnitt lässt sich direkt in on_tick kopieren. Konventionen: `g` ist das `Game`, `home` ist unsere Hauptbasis (`g.my_buildings({"htow", "hkee", "hcas"})[0]`), `now = g.clock()`. Details zur API findest du in der [API-Referenz](https://war3ai.com/de/api/), vollständige lauffähige Beispiele unter [Beispiel-Bots](https://war3ai.com/de/docs/examples/). > **Tipp** > > Wenn ein LLM eine bestimmte Technik einbauen soll, gib ihm das passende Rezept samt Code mit. Das wirkt viel besser als die Bitte, „professioneller zu spielen“. ## I. Wirtschaft ### 1. Arbeiter nie untätig, 5 pro Mine ```python for w in g.idle_workers(): # nur untätige zuweisen (neue Befehle an beschäftigte unterbrechen das Sammeln) mine = g.nearest([m for m in g.gold_mines() if crew[m.addr] < 5], w) g.gather(w, mine) if mine else g.gather(w, g.trees(w.x, w.y, limit=1)[0]) ``` Merk dir selbst, wie viele du jeder Mine zugewiesen hast (`crew`): Arbeiter in einer Goldmine sind nicht im Snapshot. Vollständiges Beispiel in `hello_bot.py`. ### 2. Nur 1 in die Warteschlange, kein gebundenes Gold ```python for b in g.my_buildings({"hbar"}): if not g.queue(b): # erst den nächsten einreihen, wenn sie leer ist g.train(b, "hfoo") ``` ### 3. Nie an der Nahrung hängen bleiben ```python stuck = any(p.blocked for _b, p in g.all_production("me")) # eingereiht, aber nicht gestartet = nicht genug Nahrung res = g.resources() if stuck or res["food_cap"] - res["food_used"] <= 6: g.build_near(builder, "hhou", home.x, home.y) ``` `blocked` greift einen Schritt früher als „fast voll“: Verlierst du im Kampf einen Teil der Armee und stockt beim Nachbauen die Warteschlange, weißt du es sofort. ### 4. Bauabfolge + danach selbst zur Mine zurück (Shift-Rückkehr zur Mine) ```python spot = g.build_near(w, "hbar", home.x, home.y) if spot: g.gather(w, mine, queue="after") # nach dem Bau zurück zur Mine, im nächsten Tick nicht erneut suchen ``` Ein Bauer baut mehrere Gebäude nacheinander: `g.build_queue(w, [("hhou", x1, y1), ("hhou", x2, y2)])`. Das Gold wird erst bei Baubeginn abgezogen. ### 5. Tier-up-Timing, Angriffs-/Rüstungs-Upgrades ```python if not g.queue(hall) and g.can_do(hall, "hkee") in (0, 220): # das Hauptgebäude kann nur mit leerer Warteschlange ausbauen (sonst 185) g.upgrade(hall, "hkee") p = g.production(hall) # Tier-up-Fortschritt if p and p.kind == "upgrade": print(f"Hauptgebäude braucht noch {p.remaining:.0f} Sekunden") for sm in g.my_buildings({"hbla"}): if not g.queue(sm): ok = [u for u, v in zip(UPS, g.can_do_many([(sm, u) for u in UPS])) if v in (0, 220)] if ok: g.research(sm, ok[0]) ``` ### 6. Expansion: die nächste Mine nach Laufweg wählen ```python mines = [m for m in g.gold_mines() if g.dist(m, home) > 1500 and not taken(m)] best = min(mines, key=lambda m: g.path_distance(home, m) or 1e9) # Minen auf Inseln liefern None -> landen ganz hinten ``` ## II. Aufklärung und Informationen ### 7. Sehen, was der Gegner macht ```python for b, p in g.all_production("enemy"): # was sichtbare gegnerische Gebäude trainieren / erforschen / aufwerten print(b.type, p.kind, p.queue, f"{p.progress:.0%}" if p.progress is not None else "hängt") ``` Kombiniert mit Events: `ev.kind == "production.done" and ev.owner != g.me()` – was der Gegner gerade fertiggestellt hat. ### 8. Merken, was du gesehen hast (Kriegsnebel) ```python for u, t, age in g.last_seen(max_age=60): # Gegner, die in den letzten 60 Spielsekunden gesehen wurden (letzte Position und TP) ... hero_seen = [r for r in g.last_seen() if r[0].is_hero] # wo der gegnerische Held zuletzt war ``` Im Fair-Modus ist das deine einzige Informationsquelle über den Gegner – genau wie bei einem menschlichen Spieler. ### 9. Wo der Computergegner angreifen will (nur Computer-KI) ```python plan = g.enemy_ai_plan(some_enemy_soldier) # wohin sein Computer-Captain unterwegs ist ``` Der Computer legt seinen Zielpunkt fest, bevor er losmarschiert – zieh deine Armee rechtzeitig dorthin zurück. ## III. Creepen ### 10. Nachts creepen ```python if g.is_night(): # 18 ~ 6 Uhr: Creeps schlafen (du triffst zuerst, ohne umzingelt zu werden), alle sehen weniger weit ... wait = g.seconds_until(18) # Spielsekunden bis zum Einbruch der Nacht (ein Tag hat 480 Sekunden) ``` ### 11. Nur Lager angreifen, die du schaffst ```python from openwar3 import combat mine = [g.stats(u) for u in army] def ttk(target): return combat.time_to_kill(mine, g.stats(target), target_hp=target.hp) or 1e9 camp = [c for c in g.creeps() if g.dist(c, center) < 600] ours = max(ttk(c) for c in camp) # wie lange es dauert, dieses Lager zu räumen (grob) theirs = g.time_to_kill(camp, weakest_of_mine) or 1e9 # wie lange sie brauchen, um unsere schwächste Einheit zu töten if ours < theirs and g.reachable(center, camp[0]): g.attack_move(army, camp[0].x, camp[0].y) ``` Vollständiges Beispiel: `_maybe_creep` in `micro_bot.py`. ## IV. Mikro ### 12. Fokusfeuer: das angreifen, was am schnellsten stirbt, nicht das Nächste ```python target = min(visible_enemies, key=lambda e: g.time_to_kill(fighters, e) or 1e9) g.attack([u for u in fighters if (g.current_target(u) or target).handle != target.handle], target) ``` Befiehl nur den Einheiten, die es noch nicht angreifen (`current_target`), damit du die anderen nicht unterbrichst. ### 13. Verwundete zurückziehen ```python for u in army: if u.hp < u.hp_max * 0.35: g.move(u, *toward(home, u, 500)) # 500 Richtung Basis zurückziehen; dieselbe Einheit 3 Sekunden lang nicht erneut ziehen ``` Fokusfeuer erkennen: Trifft dieselbe Einheit in `damage`-Events kurz hintereinander Schaden aus mehreren Quellen, ist sie umzingelt. ### 14. Helden am Leben halten, keine EP verschenken ```python for h in g.my_heroes(): if h.hp < h.hp_max * 0.4: g.move(h, home.x, home.y) g.use_item(h, slot_of(h, "phea")) # Heiltrank: Platznummer über inventory(h) finden ``` ### 15. Konter: die richtige Einheit auf das richtige Ziel ```python s = g.stats(u) best = max(enemies, key=lambda e: s.dps_vs(g.stats(e))) # Büchsenschützen auf Greifenreiter (Durchbohrend gegen leichte Rüstung ×2), Greifenreiter auf Fußsoldaten (Magie gegen schwere Rüstung ×2) ``` Die Kontertabelle stammt aus den Spieldaten: `combat.damage_multiplier("pierce", "small") == 2.0`. ### 16. Flanken und Wegführung: Türme umgehen ```python route = g.walk_path(army_center, target) # Knickpunkte des kürzesten Bodenwegs g.path(army, route, attack=True) # nacheinander per Angriffsbewegung durch jeden Punkt ``` Um Türme zu umgehen, markierst du den Bereich um jeden Turm im Wegfindungsraster als unpassierbar und berechnest dann die Route: ```python grid = g.grid().copy() for t in towers: grid.block_area(t.x, t.y, 800) # Turmreichweite 700 + Puffer route = grid.path((army_x, army_y), (target.x, target.y)) ``` ### 17. Die Befehle eines Ticks als ein Batch senden ```python with g.batch(): g.attack(melee, target_a) g.attack(ranged, target_b) g.move(wounded, *home_xy) g.cast(hero, "thunderclap") ``` Dutzende Befehle warten nur einmal auf den Spiel-Thread (gemessen: 8 Bewegungen 68 ms → 6.5 ms). ### 18. Belagerung: Artillerie greift den Boden an ```python g.attack_ground(mortars, tower.x, tower.y) # Mörsertrupps / Katapulte feuern auf eine Fläche (hinter Bäumen, unsichtbare Einheiten) ``` ## V. Helden ### 19. Skill-Reihenfolge ```python SKILLS = {"Hamg": ["AHwe", "AHbz", "AHwe", "AHbz", "AHwe", "AHmt"]} # Wasserelementar, Blizzard … Stufe-6-Ultimate Massenteleportation info = g.hero_info(h) if info and info["skill_points"]: g.learn(h, SKILLS[h.type][learned_count]) # abgelehnt (Stufe zu niedrig fürs Ultimate)? auf die nächste Stufe warten ``` ### 20. Wurde der Zauber wirklich gewirkt? ```python r = g.cast(h, "thunderbolt", target=enemy_hero) # im nächsten Tick: if g.cooldown(h, "AHtb"): # Abklingzeit läuft = wirklich gewirkt; angenommen ≠ gewirkt ... ``` ### 21. Tränke kaufen, zurück in die Stadt ```python g.buy(shop, "phea") # der Held steht neben dem Laden g.use_item(hero, slot, x=home.x, y=home.y) # Stadtportal-Schriftrolle (Gegenstand auf einen Punkt benutzen) ``` ## VI. Nachbesprechung - Schreib die Entscheidungen jedes Ticks ins Log (`print` landet im Ausführungsfenster) und nutze `g.say(einheit, "Rückzug")`, um sie im Spiel zu sehen; - `production.done`-Events enthalten „wie viele Sekunden es gedauert hat“ – erstelle daraus deine eigene Bau-Timeline (in welcher Sekunde der erste Held kam, wann dein Tier-up fertig war) und vergleiche sie mit der von Top-Spielern; - Lass den Agent sich selbst auswerten: siehe den Spielbericht in [Agent-Selbstiteration](https://war3ai.com/de/docs/agent-loop/). --- # Beispiel-Bots > Vier Beispiele von einfach bis komplex – jedes sofort lauffähig, jeder Logikblock entspricht einer SDK-Fähigkeit. Dazu ein vollständiges Referenz-Brain. Alle Beispiele liegen in `brains/examples/`. Jedes baut auf dem vorherigen auf und ergänzt nur Neues. Lies sie am besten der Reihe nach: | Beispiel | Was du lernst | Ausführen | |---|---|---| | `hello_bot.py` | Abbauen (5 pro Goldmine, bei voller Mine ab ins Holz), Arbeiter ausbilden (nur 1 in der Warteschlange), Nahrungsgebäude bauen, liegengebliebene Fundamente weiterbauen; läuft mit allen vier Völkern | `python tools/play.py --bot brains/examples/hello_bot.py` | | `rush_bot.py` | Kaserne und Altar (fehlen sie, per `build_near` bauen), Held zuerst (ist er tot, wiederbeleben), Fähigkeitspunkte sofort vergeben, eine Welle sammeln und per Angriffsbewegung losschicken | `… --bot brains/examples/rush_bot.py` | | `macro_bot.py` | Bauplan + nach dem Bau selbst zurück zur Mine (Shift), Nahrungsengpass sofort beheben, Kasernen-Warteschlange mit 1 Einheit, Angriffs-/Rüstungs-Upgrades, Tier-Up und High-Tech-Einheiten, Ziel nach **echter Laufdistanz** wählen und dem Pfad folgen | `… --bot brains/examples/macro_bot.py --speed 200` | | `micro_bot.py` | Übernimmt aufbauend auf dem Makro die Kämpfe: Fokusfeuer auf das Ziel, das am schnellsten fällt, Verwundete zurückziehen, Helden retten, nachts schaffbare Creep-Lager wählen, bei Angriffen auf die Basis zurückverteidigen; alle Befehle eines Ticks als ein Batch | `… --bot brains/examples/micro_bot.py --fair` | > **Info** > > In den Kommentaren von `hello_bot` und `rush_bot` stehen die Stolperfallen aus echten Spielen, etwa „jedes Mal wurde der erste Arbeiter zum Bauen geschickt – am Ende standen 3 halbfertige Farmen herum“ oder „die fest eingetragenen Kasernen-Koordinaten lagen mitten im Wald, nach 3 Minuten war keine einzige gebaut“. Die Kommentare bringen mehr als der Code. ## hello_bot: Wirtschaft ```python # Volk -> (Arbeiter, Hauptgebäude, Nahrungsgebäude) RACES = { "h": ("hpea", {"htow", "hkee", "hcas"}, "hhou"), "o": ("opeo", {"ogre", "ostr", "ofrt"}, "otrb"), "u": ("uaco", {"unpl", "unp1", "unp2"}, "uzig"), "e": ("ewsp", {"etol", "etoa", "etoe"}, "emow"), } MINE_CAP = 5 # max. 5 Arbeiter pro Goldmine (mehr bringt kein Einkommen) LUMBER_CREW = 5 # Holzfäller: pro Mine 5 auf Gold + so viele aufs Holz = Arbeiterziel ``` Drei Aufgaben: Untätige Arbeiter bauen Gold ab (der Bot zählt selbst mit, wie viele an jeder Mine sind; ist eine Mine voll, geht es ans Holz); fehlen Arbeiter, werden welche ausgebildet (nur 1 in der Warteschlange); ist die Nahrung fast voll, baut ein Arbeiter, der nicht schon baut, ein Nahrungsgebäude neben dem Hauptgebäude (bei Menschen und Orcs werden außerdem liegengebliebene Fundamente weitergebaut). ## rush_bot: Armee aufstellen und angreifen Auf `hello_bot` kommen drei Dinge dazu: Fehlen Kaserne und Altar, werden sie gebaut; der Altar bildet einen Helden aus (**ist er tot, zuerst wiederbeleben** – Helden sind einzigartig), und Fähigkeitspunkte werden sofort vergeben; bei 8 Einheiten geht die ganze Armee per Angriffsbewegung auf das gegnerische Hauptgebäude los – ist sie aufgerieben, sammelt sie sich zu Hause neu. Befehle gehen nur an untätige Einheiten, damit Kämpfe nicht Tick für Tick unterbrochen werden. ## macro_bot: Makro-Grundlagen ```python TECH = { "h": dict(order=["halt", "hbar", "hbla", "hlum"], altar="halt", hero="Hamg", skills=["AHwe", "AHbz", "AHab"], barracks="hbar", soldiers=["hfoo", "hrif", "hkni"], smith="hbla", upgrades=["Rhme", "Rhar", "Rhra", "Rhla"], tiers=["hkee", "hcas"]), ... } ``` Die Dinge, die Profis in jedem Spiel tun, jeweils mit der passenden SDK-Fähigkeit: Bauplan + `gather(..., queue="after")`, damit Arbeiter nach dem Bau zurück zur Mine gehen; `production().blocked` erkennt Nahrungsengpässe; `g.queue` sorgt dafür, dass in der Kaserne nur 1 Einheit wartet; `can_do` fragt die Engine, ob die nächste Angriffs-/Rüstungsstufe erforschbar ist; Tier-Up und High-Tech-Einheiten (Lektion aus dem Live-Spiel: auf Tier 1 hängen geblieben, nach 23 Minuten von Rittern und Greifenreitern auf Tier 3 überrollt); Ziele per `path_distance` wählen und mit `path()` den Wegpunkten folgen. ## micro_bot: wenn es zum Kampf kommt ```python def _fight(self, g, army, foes, home, now): ... visible = [e for e in foes if e.visible_to(me)] # unsichtbare Ziele werden abgelehnt (1001) atk = [s for s in (g.stats(u) for u in fighters) if s] target = min(visible, key=lambda e: _ttk(g, atk, e)) # das Ziel, das am schnellsten fällt, nicht das nächste idle_or_other = [u for u in fighters if g.current_target(u) is None or g.current_target(u).handle != target.handle] if idle_or_other: g.attack(idle_or_other, target) ``` Im Live-Spiel: 5 Minuten, 1497 Ticks, 3023 Befehle, 0 Fehler. ## Referenz-Brain: eine vollständige KI `brains/xwar3/` ist eine vollständige KI, die expandiert, creept und angreift – aufgebaut in drei Schichten: | Schicht | Ort | Takt | Aufgabe | |---|---|---|---| | Strategie-Schicht | `strategy/` | Sekunden | Wahl und Wechsel zwischen mehreren Strategien im AMAI-Stil, Baupläne, Konter-Einheiten, Heldenwahl; optional ein [LLM-Strategie-Coach](https://war3ai.com/de/docs/llm-coach/) | | Reflex-Schicht | `reflex/` (4 eigenständige Prozesse) | ~100 ms | Überleben, Zaubern, Fokusfeuer, Gegenstände aufsammeln | | Siegmodell | `worldmodel/` | — | Können wir den Kampf gewinnen? (Inferenz-Teilmenge) | Mehrere Prozesse teilen sich Einheiten über eine **Claim-Tabelle**; die Priorität entscheidet, wer das Sagen hat: Mensch 95 > Überleben 90 > Ausweichen 85 > Zaubern 80 > Gegenstände aufsammeln 70 > … > Strategie 50 > Arbeitsverteilung 45. Dein eigener Bot steht in der Tabelle als `bot`, mit Standardpriorität 50. > **Achtung** > > Das Referenz-Brain nutzt direkt die Low-Level-Schicht des SDK (`w3cmd` / `act`) und stützt sich stark auf Informationen über die ganze Karte. Es taugt als Ideenquelle, sollte aber nicht 1:1 von einem LLM kopiert werden. Es braucht AMAI-Daten: `start.bat` holt sie bei der ersten Installation aus dem öffentlichen AMAI-Repo und generiert sie (AMAI steht unter einer eigenen Lizenz, die generierten Dateien kommen nicht ins Git; hat das nicht geklappt, mit `start.bat setup` erneut versuchen). Am einfachsten startest du das Referenz-Brain über die [Farsight-Konsole](https://war3ai.com/de/docs/console/): Auf der Seite „Instanzen & Start“ die Instanznummer ankreuzen und auf „Test starten“ klicken. --- # Debugging & Performance > Warum ein Tick langsam ist, warum ein Befehl nicht greift, warum das Spiel steht: nach Symptom eingrenzen, dann mit den mitgelieferten Live-Prüfskripten bestätigen. ## Quittungen lesen Die Quittung jedes Befehls ist die erste Spur: ```python r = g.cast(hero, "blizzard", x=tx, y=ty) if not r: print(r.reason, r.verdict) # rejected(…) und der Reason-Code print(r.exec_us, r.engine_us) # Mikrosekunden auf dem Spiel-Thread / davon in der Befehlsfunktion der Engine selbst ``` Normalerweise kostet ein Befehl auf dem Spiel-Thread einige bis einige hundert Mikrosekunden. Nach einem Batch-Block enthält `g.last_receipts` die Quittung jedes einzelnen Befehls dieses Batches. ## Im Spiel beobachten ```python g.say(unit, "Rückzug") # Chat-Blase über der Einheit (beeinflusst das Spiel nicht) g.message("Creepen beginnt") # eine Zeile im Nachrichtenbereich unten links (nur lokal sichtbar) ``` Ausgaben von `print` landen im Terminal, in dem der Bot läuft. Die wichtigsten Entscheidungen jedes Ticks ausgeben und dazu die Sprechblasen nutzen – das geht viel schneller, als den Code zu lesen. ## Ein Tick ist langsam Prüf zuerst, ob einer dieser Fälle vorliegt: | Ursache | Lösung | |---|---| | Befehle einzeln gesendet, jeder wartet einen Frame | In `with g.batch():` packen – Dutzende Befehle warten nur einmal | | `g.visible()` / `g.can_do()` einzeln aufgerufen (jeder Aufruf geht über die Schnellspur und wartet einen Frame) | Sichtbarkeit per `u.visible_to()` aus dem Snapshot; Machbarkeit gebündelt per `g.can_do_many([...])` | | `sleep` oder Warten in `on_tick` | Spielzeit merken und im nächsten Tick erneut prüfen | | Teures in jedem Tick neu berechnen (Wegfindung, Scans der ganzen Karte) | Ergebnisse cachen, nur alle paar Ticks neu rechnen. `g.grid()` hat einen eigenen 2-s-Cache, die Technologiestufen in `g.stats()` werden 5 s gecacht | ## Spiel steht / Bot wartet ewig auf Spielbeginn | Symptom | Meistens | |---|---| | Hängt dauerhaft bei „warte auf Spiel“ | Falsche Instanznummer; oder das Spielfenster ist **minimiert** – minimiert steht die Spielsimulation (die Uhr läuft nicht) | | Spiel läuft, Bot-Befehle bewirken nichts | Befehle an fremde Einheiten (Quittung `not_owner`); oder der Bot ist als observer verbunden (`forbidden`) | | Befehl ist `held` | Die Einheit wird von einer höher priorisierten Schicht gehalten (Reflex-Schicht des Referenz-Brains, manueller Befehl aus der Konsole) und der Befehl nicht gesendet | | Nach einer Pause gehen Befehle noch durch | Normal: In der Pause steht die Engine-Uhr, aber Event-Dispatch und Befehlsausführung laufen weiter | ## Verbinden und Status prüfen ```bash python -m openwar3 status --inst 5 ``` Gibt den Verbindungsstatus aus: Spiel-PID, Veröffentlichungsintervall der Welt und Dauer jeder Erfassung, Zähler der Schnellspur, ob ein Spiel läuft, Einheitenzahl, Spieluhr. ## Live-Prüfskripte Starte eine Testinstanz und prüfe Punkt für Punkt, ob die SDK-Fähigkeiten auf deinem Rechner funktionieren: ```bash python tools/sdk_live_check.py --inst 20 # alles python tools/sdk_live_check.py --inst 20 --only prod # nur einen Abschnitt prüfen ``` Abschnitte: Batch, Zeit, Produktion, Befehle mit Warteschlange, Kampfwerte, Wegfindung, Fair-Modus. Jeder Abschnitt erteilt in einem echten Spiel Befehle, liest die Wirkung zurück und gibt die Zahl der bestandenen Prüfungen aus. Offline-Tests brauchen kein laufendes Spiel: ```bash python tools/run_tests.py ``` ## Typische „sieht aus wie ein Bug“-Fälle - **Bau-Quittung angenommen, aber nie ein Fundament**: Auch einen Punkt im Wald nimmt die Engine sofort an; scheitern tut der Befehl erst, wenn der Arbeiter ankommt. `build_near` verfolgt das und sperrt fehlgeschlagene Punkte eine Zeit lang. - **Fähigkeit angenommen, aber nicht gewirkt**: Unterbrochen, oder kein Mana. Im Tick danach prüfen, ob `g.cooldown()` läuft. - **Angriff angenommen, aber die Einheiten greifen etwas anderes an**: Für ein konkretes Ziel `g.attack(units, enemy)` verwenden (Rechtsklick-Semantik). Der rohe Angriffsbefehl ändert bei einem Ziel nur die Order, merkt sich das Ziel aber nicht und greift dann etwas anderes in der Nähe an. - **Arbeiterzahl stimmt nicht**: Arbeiter in der Goldmine fehlen im Snapshot. - **Toter Held lässt sich nicht ausbilden**: Helden sind einzigartig, du brauchst `g.revive(altar)`; Wiederbeleben kostet Nahrung und geht erst etwa 3 Spielsekunden nach dem Tod. --- # RPG-Begleiter > Ein KI-Begleiter für RPG- und benutzerdefinierte Karten: Er folgt dir, kämpft mit, heilt dich bei niedrigen HP und plaudert mit dir. Vier Modi – eine Klasse erben, ein paar Attribute ändern, fertig ist dein eigener Begleiter. Nicht nur für Melee-Partien. In RPG- und benutzerdefinierten Karten kannst du dir einen **KI-Begleiter** an die Seite stellen: Er folgt dir, hilft dir gegen Monster, heilt dich, wenn deine HP knapp werden, und plaudert mit dir, wenn gerade nichts los ist – die Sätze können auch von einem lokalen LLM kommen. **Wie du es nutzt, entscheidest du selbst.** Das Ganze besteht aus drei API-Schichten, von unten nach oben; jede davon lässt sich direkt verwenden: | Schicht | Was es ist | Geeignet für | |---|---|---| | **JASS-Kanal** `g.jass` | Die 1291 JASS-Funktionen, die Kartenautoren zur Verfügung stehen, direkt per Name aufrufbar (Einheiten erstellen, Bündnisse setzen, Gegenstände geben, umbenennen, Text anzeigen, Helden wiederbeleben …) | eigene Spielideen bauen | | **Komfort-APIs** | `g.spawn`, `g.set_alliance`, `g.player_slots`, `g.show_text`, `g.map_data`: die häufigsten Aufgaben fertig verpackt | eigene Hilfsskripte schreiben | | **Begleiter-Framework** | `openwar3.companion.Companion` + `openwar3.talk.Talk`: erben, ein paar Attribute ändern – und du hast einen Begleiter, der folgt, mitkämpft, heilt und plaudert | einfach einen Begleiter haben wollen | > **Achtung** > > Nur für **Offline-Spiele und selbst gehostete LAN-Spiele**. Einheiten erstellen, Bündnisse setzen und Ähnliches verändert die Welt einseitig auf deinem Rechner: In Einzelspieler-Partien (gegen den Computer) ist das kein Problem; in Mehrspieler-Partien geraten die anderen Spieler dadurch aus dem Takt (Desync). Deshalb lässt der JASS-Kanal in Mehrspieler-Partien nur lesende Funktionen zu, und der Begleiter fällt automatisch auf „nur reden“ zurück. ## Am schnellsten: in Farsight mit einem Klick starten 1. **Karte wählen**: in Farsight auf der Seite „Instanzen & Start“ → „Einstellungen fürs nächste Spiel“ → Karte eine RPG-Karte wählen (alle Karten unter `Scenario` und `Download` im Ordner `Maps` des Spielverzeichnisses werden aufgelistet, z. B. `(4)WarChasers`). 2. **Schema wählen**: auf der Instanzkachel im Dropdown „KI-Schema“ **Begleiter-Beispiel (buddy)** wählen → „Festlegen“. 3. **Test starten**: Sobald das Spiel läuft, **spielst du selbst im Spielfenster**. Der Begleiter – ein Paladin namens „Lumi“ – erscheint neben dir. Auch per Kommandozeile möglich: ```bash python tools/play.py --bot brains/examples/buddy.py --inst 20 --rpg --map "\Maps\Scenario\(4)WarChasers.w3m" ``` `--rpg` (im Schema-Manifest `"judge": false`) bedeutet, dass Sieg und Niederlage nicht nach Melee-Regeln entschieden werden: In RPGs lassen sich Helden wiederbeleben, und „alle Gebäude weg = verloren“ gibt es dort auch nicht. Viele RPG-Karten bleiben nach dem Laden bei „Beliebige Taste drücken, um fortzufahren“ stehen; erkennt das SDK „in der Partie, aber die Spieluhr steht dauerhaft bei 0“, drückt es selbst einmal die Leertaste (`g.press_to_continue()`; schickt nur eine Tastennachricht ans Spielfenster und stiehlt nicht den Fokus). ## Einen eigenen Begleiter schreiben ```python from openwar3.companion import Companion from openwar3.talk import Talk class MyBuddy(Companion): mode = "ally" # Modus, siehe Tabelle unten unit = "Hpal" # was erstellt wird: jeder Vier-Zeichen-Code, auch von der Karte definierte nickname = "Lumi" heal = ("holybolt", "AHhb", 0.55) # (Order-String, zu lernende Fähigkeit, heilen, wenn die HP des Meisters unter diesen Anteil fallen); None = nicht heilen follow_distance = 350 talk = Talk(persona="Fröhlicher kleiner Paladin, der seinen Meister gern anfeuert") ``` ### Vier Modi | mode | Wer der Begleiter ist | Beschreibung | |---|---|---| | `ally` (Standard) | belegt einen freien Spielerplatz als dein **Verbündeter** | Hat eigene Farbe und eigenen Namen (Punktetafel und Verbündeten-Fenster zeigen `nickname`); du kannst ihn nicht steuern, er kämpft selbstständig. Das Framework setzt automatisch Bündnis + geteilte Sicht | | `own` | wird **unter deinem Namen** erstellt | Du kannst ihn jederzeit selbst befehligen; wenn du dich nicht um ihn kümmerst, steuert ihn die KI für dich | | `adopt` | übernimmt eine **vorhandene** Einheit der Karte | `adopt(g)` überschreiben und diese Einheit zurückgeben (ein Haustier oder Gefolgsmann, den die Karte dir gibt) | | `voice` | erstellt keine Einheit, **redet nur** | Gesellschaft und Hinweise; verändert die Welt nicht, funktioniert auch in Mehrspieler-Partien | Ohne freien Platz fällt `ally` automatisch auf `own` zurück; in Mehrspieler-Partien oder wenn sich keine Einheit erstellen lässt, auf `voice`. > **Info** > > Ein Begleiter im Modus `ally` hält sich an „die beste Einheit, die dieser Platz gerade hat“ (Helden zuerst), nicht stur an eine bestimmte Einheit. Im Test hat eine Karte den Begleiter für einen echten Spieler gehalten, den Paladin gelöscht und stattdessen einen Kartenhelden ausgegeben – der Begleiter übernimmt diesen Helden dann einfach und lernt auch die Fähigkeiten, die die Karte für ihn vorsieht. Stirbt der Held, wird er möglichst an Ort und Stelle wiederbelebt; belebt ihn die Karte selbst wieder, macht der Begleiter mit ihm weiter. ### Was in jedem Tick passiert Die Regeln werden der Reihe nach geprüft; die erste zutreffende wird ausgeführt: | Reihenfolge | Verhalten | Bedingung | Einstellbar | |---|---|---|---| | 1 | Rückzug | Eigene HP unter 25 % und Gegner in der Nähe: hinter den Meister zurückziehen | `retreat_at` | | 2 | Heilen | HP des Meisters unter dem eingestellten Wert, Fähigkeit bereit, Entfernung höchstens 900 | `heal` (None schaltet es ab) | | 3 | Mitkämpfen | Gegner in der Nähe des Meisters: **wer den Meister angreift > wen der Meister angreift > der nächste** | `assist_radius`, oder `pick_target` überschreiben | | 4 | Folgen | Zu weit vom Meister entfernt: aufschließen; ab einer gewissen Entfernung direkt zurücklaufen, ohne sich in Kämpfe verwickeln zu lassen | `follow_distance`, `leash` | | 5 | Plaudern | Keine Gegner in der Nähe: alle 1–2.5 Minuten ein Satz | Sprüche | „Gegner“ richtet sich nach den Bündnissen im Spiel (alle 20 Sekunden aktualisiert). RPG-Karten haben oft mehrere verbündete Parteien; man kann also nicht einfach „alle Spieler außer mir“ als Gegner behandeln. Überschreibbare Hooks: `find_master` (wer der Meister ist; Standard: der Held mit der höchsten Stufe des lokalen Spielers), `adopt`, `pick_target`, `on_poke` (der Meister hat den Begleiter mit Rechtsklick angeklickt) sowie die Bot-Hooks `on_start` / `on_tick` / `on_event` / `on_end`. Wie oft geheilt, mitgekämpft, getötet, gefolgt, zurückgezogen, gesprochen und wiederbelebt wurde, steht in `self.stats` und wird am Ende ausgegeben. ### So sprichst du ihn an - **Chat-Befehle**: im Chatfeld `-follow` (folge mir), `-stay` (bleib hier und halte die Stellung), `-heal` (sofort heilen) oder `-hi` (begrüßen) eintippen. Die Befehlsliste änderst du in `commands`, die Reaktionen durch Überschreiben von `on_command`. - **Rechtsklick auf den Begleiter**: löst `on_poke` aus. Im Beispiel heilt er den Meister einmal, falls dieser nicht volle HP hat, sonst sagt er etwas. - **Porträt-Dialog**: Begrüßung, Meister gefallen, Meister steigt auf, Begleiter wieder da – diese Sätze laufen über den Porträt-Dialog des Spiels (unten erscheint das Porträt des Begleiters, auf dem Bildschirm ein Untertitel); alles andere als Sprechblase über dem Kopf. - **Statusanzeige**: ein Panel links am Bildschirmrand mit HP-Balken des Begleiters, was er gerade tut, Stimmung (fröhlich / aufgeregt / angespannt / ängstlich / traurig), Kills und Heilungen. Es wird mit dem [Canvas](https://war3ai.com/de/docs/canvas/) gezeichnet und ist auch in Mehrspieler-Partien sicher. ### Sprechen – und lokale LLMs `Talk` wählt Sprüche passend zum Ereignis und zeigt sie als Sprechblase über dem Kopf; im Modus `voice` oder wenn keine Sprechblase möglich ist, unten links auf dem Bildschirm. Jeder Satz landet außerdem im Schema-Log, sodass du später nachlesen kannst, was der Begleiter gesagt hat. | Ereignis | Wann | Ereignis | Wann | |---|---|---|---| | `hello` | gerade angekommen | `master_low` | Meister hat kaum noch HP | | `poke` | Meister klickt ihn mit Rechtsklick an | `master_levelup` | Meister steigt eine Stufe auf | | `fight` | Kampf beginnt | `master_died` / `master_back` | Meister fällt / ist wiederbelebt | | `kill` | Monster getötet (nennt den Namen des Monsters) | `buddy_low` / `buddy_died` / `buddy_back` | Begleiter selbst kaum noch HP / gefallen / wieder da | | `healed` | Meister geheilt | `idle` / `item` | Plaudern / etwas aufgehoben | In den Sprüchen kannst du die Platzhalter `{master}`, `{me}`, `{map}`, `{enemy}`, `{level}` und `{item}` verwenden; die Sprüche selbst änderst du direkt in `talk.lines`, die Abklingzeit in `talk.cooldown`. **Lokales LLM anbinden**: `Talk(llm=LocalLLM(url, model))` – jede OpenAI-kompatible API funktioniert (LM Studio, Ollama …). Das Modell antwortet in einem Hintergrund-Thread, gesprochen wird erst, wenn die Antwort da ist; läuft es nicht, gibt es ein Timeout oder einen Fehler, kommt ein fester Spruch – das Spiel hängt nie. Anfragen gehen nur an die lokale Adresse, die du angibst; Inhalt ist, was im Spiel passiert (wie der Meister heißt, gegen welche Monster gekämpft wurde). ## Einheitennamen in benutzerdefinierten Karten Einheiten, Gegenstände und Helden in RPG-Karten sind meist von der Karte selbst angelegt (Vier-Zeichen-Codes wie `HC07`, `I00A`) und stehen nicht in der eingebauten Namenstabelle. `g.map_data` liest direkt die Kartendatei der aktuellen Partie: ```python md = g.map_data md.name_of("HC07") # 'Optimus Primo' – von der Karte geänderte Namen haben Vorrang md.hero_names("HC07") # Liste der Heldentitel md.hero_skills("OC10") # Fähigkeiten, die die Karte für diesen Helden vorsieht md.tooltip("I00A") # Beschreibungstext ``` Geschützte oder optimierte Karten (viele beliebte RPGs) enthalten keine Standard-Objektdatendateien; die Namen werden dann aus den Textdaten der Karte gelesen. Im Test wurden alle 38 RPG- und benutzerdefinierten Karten auf dem Rechner erfolgreich geparst, bei 37 davon kamen Einheitennamen heraus. ## Als Schema teilen Ein Begleiter ist einfach eine Unterklasse von `openwar3.Bot` und lässt sich als [KI-Schema](https://war3ai.com/de/docs/schemes/) mit anderen teilen. Im Manifest stehen zwei zusätzliche Einträge: ```json {"id": "my-buddy", "name": "Mein Begleiter", "entry": "my_buddy.py", "fair": false, "judge": false} ``` `"fair": false`: nötig für den JASS-Kanal (Einheiten erstellen, Bündnisse setzen); `"judge": false`: Sieg und Niederlage nicht nach Melee-Regeln entscheiden. ## Testprotokoll 2026-09-24, Testinstanz, Karte WarChasers, 2× Geschwindigkeit: - JASS-Kanal: alle 18 Prüfungen bestanden – Spielerplätze, Umrechnung zwischen Einheit und Handle hin und zurück, Rückgabewerte vom Typ real, String-Parameter, Einheit für einen freien Platz erstellen, Bündnis setzen, umbenennen, Einheit löschen; Aufrufe über eine Spieler-Spur und eine falsche Parameteranzahl wurden korrekt abgelehnt. - Begleiter: bestätigt „Beliebige Taste drücken“ selbst → erscheint neben dem Meister und grüßt → folgt in den Kreis der Macht zur Heldenwahl, bekommt von der Karte einen Helden und übernimmt ihn → folgt (200–400 vom Meister entfernt) → kämpft gegen Monster und sagt beim Kill „Sauber!“ → zieht sich bei wenig HP zurück → wird nach dem Tod von der Karte wiederbelebt und folgt weiter. ## Was noch fehlt 1. **Beliebiger Chattext des Spielers lässt sich nicht lesen.** Feste Chat-Befehle funktionieren bereits; damit der Begleiter wirklich frei mit dir plaudern kann, muss erst der Text selbst ausgelesen werden. 2. **Der Begleiter kennt die Spielmechanik einer bestimmten Karte nicht** (Quests, Läden, Story). Er kann allgemein folgen, mitkämpfen und heilen; soll er eine bestimmte Karte verstehen, schreibst du das für diese Karte in die Unterklasse – `g.map_data` liefert Namen, `g.jass` ruft jede Funktion auf. Genau dieser Teil bleibt dir überlassen. --- # Canvas > Textfelder, Panels, Fortschrittsbalken, Bilder, Kreise am Boden und Routen mit Pfeil aufs Spielbild zeichnen. Die Runtime zeichnet selbst in jedem Frame und ändert den Spielzustand nicht – auch in Mehrspieler-Partien sicher; per Python, HTTP oder direkt über Shared Memory. Externe Programme können **Textfelder, Panels, Fortschrittsbalken, Bilder, Kreise am Boden und Routen am Boden (mit Pfeil)** aufs Spielbild zeichnen; die Runtime zeichnet sie in jedem Frame selbst. Ideal für eigene HUDs, Hilfslinien, Hinweise, Lernmarkierungen und Info-Tafeln für Streams. ## Canvas oder JASS-Grafikfunktionen? | | Canvas (diese Seite) | [JASS-Grafikfunktionen](https://war3ai.com/de/docs/jass/) | |---|---|---| | Wer zeichnet | die Runtime selbst | das Spiel selbst (schwebender Text, Effekte, Panels, Porträt-Dialog …) | | Mehrspieler | **sicher**: wird nur auf deinem eigenen Bildschirm gezeichnet, erzeugt keine Spielobjekte, ändert keinen Spielzustand | nur Einzelspieler | | Stil | frei: eigene Schriften (auch Chinesisch), abgerundete Ecken, Halbtransparenz, Rahmen, beliebige Farben, lokale Bilder | nativer Spielstil | | Folgt Objekten | Einheiten, Weltkoordinaten, Bildschirmpositionen; Kreise am Boden folgen dem Geländeprofil | je nach Funktion | | Kosten | gemessen 0.2–0.35 ms pro Frame (9 Elemente) | ca. 13 ms pro Aufruf | Beide Wege lassen sich kombinieren: Effekte im nativen Stil per JASS, eigene Panels, Hilfslinien und Hinweise per Canvas. ## Python ```python c = g.canvas # beim ersten Zugriff installiert die Runtime den Zeichen-Hook (ca. 0.1 s) c.text("title", "Hallo, das ist das Canvas", screen=(40, 110), color=(255, 220, 80), bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True) c.panel("status", "Begleiter · Lumi", ["Stimmung: fröhlich", "Kills: 12"], screen=(16, 330)) c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%") # folgt der Einheit c.text("tag", "Boss lädt sein Ultimate auf!", unit=boss, lift=320, color=(255, 80, 80), size=22, bold=True) c.circle("danger", (x, y), 300, color=(255, 60, 60), fill=(255, 60, 60, 60), width=3) # Gefahrenzone am Boden c.circle("aura", hero, 450, color=(80, 200, 255, 220)) # Kreis, der der Einheit folgt c.path("route", [(x1, y1), (x2, y2), (x3, y3)], color=(255, 220, 0), width=5, arrow=True) c.image("icon", "icon.png", screen=(40, 170), width=64, height=64) c.remove("danger"); c.hide("tag"); c.clear() # clear entfernt nur, was du selbst gezeichnet hast c.expire("tag", 5) # verschwindet nach 5 s von selbst with c.batch(): ... # viele Änderungen auf einmal, Shared Memory wird nur einmal geschrieben c.stats() # drawnFrames steigt = es wird wirklich gezeichnet ``` Jedes Element wird über einen `key` identifiziert: Mit demselben key erneut zeichnen heißt aktualisieren. **Klickbar**: Gib Textfeldern und Panels `clickable=True` (die Hover-Farbe legst du mit `hover=` fest). Bei einem Klick darauf kommt ein `ui.click` im Event-Stream an, `ev.key` ist genau dieser key, und das Spiel bekommt diesen Klick nicht mit. Fertige Buttons, Auswahlkarten, Hotkeys und Klicks auf den Boden findest du unter [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/). **Position** (eine pro Element): - `screen=(x, y)`: Bildschirmpixel; negative Werte zählen vom rechten / unteren Rand aus; `center=True` richtet an der Mitte aus; - `frac=(0.5, 0.1)`: Anteil der Bildschirmgröße; - `world=(x, y)`: Weltkoordinaten; - `unit=Einheit`: folgt der Einheit. Text und Balken in der Welt oder an Einheiten sitzen mit der Mitte ihrer Unterkante auf diesem Punkt, `lift` hebt sie an. Elemente in der Welt und an Einheiten weichen standardmäßig der Bedienleiste unten und der Tag-/Nacht-Uhr oben aus (`over_ui=True` legt sie darüber). **Farben** schreibst du als `(r, g, b)`, `(r, g, b, a)`, `"#RRGGBB"` oder `"#RRGGBBAA"`. | Methode | Zeichnet | Wichtige Parameter | |---|---|---| | `text(key, Text, ...)` | Textfeld, mehrzeilig mit `\n` | `color`, `bg` Hintergrund (ohne = transparent), `border`, `size`, `bold`, `shadow`, `width` (Umbruch bei dieser Breite), `radius` abgerundete Ecken | | `panel(key, Titel, [Zeilen...], ...)` | Panel (dunkler halbtransparenter Hintergrund, goldener Rahmen) | wie `text` | | `bar(key, 0..1, ...)` | Fortschrittsbalken: HP, Abklingzeit, Zauberbalken | `width`, `height`, `color`, `bg`, `border`, `text` | | `image(key, Pfad, ...)` | lokales Bild (png / jpg / bmp / gif) | `width`, `height` (ohne = Originalgröße) | | `circle(key, Einheit oder Punkt, Radius, ...)` | Kreis am Boden, folgt dem Gelände | `color` Linienfarbe, `fill` Füllung (mit Transparenz), `width` Linienbreite | | `path(key, [Punkte...], ...)` | Linienzug am Boden | `color`, `width`, `arrow` Pfeil am Ende; Punkte können Koordinaten oder Einheiten sein | ## HTTP (jede Sprache) Farsight-Backend (lauscht nur lokal): ```http POST /api/instances/20/canvas {"set": [ {"key": "banner", "kind": "text", "text": "Canvas per HTTP", "frac": [0.5, 0.12], "center": true, "color": "#FFDC50", "bg": [0, 0, 0, 180]}, {"key": "hp", "kind": "bar", "value": 0.8, "unit": 596125988, "lift": 260, "text": "80%"}, {"key": "zone", "kind": "circle", "center": 596125988, "radius": 600, "color": [255, 200, 0], "width": 4}, {"key": "route", "kind": "path", "points": [[-4587, -9092], [-5387, -8792]], "color": "#50C8FF"} ], "remove": ["old"], "clear": false} GET /api/instances/20/canvas welche Elemente gerade gezeichnet werden + wie viele Frames gezeichnet wurden ``` `kind` ist der Name der Python-Methode, die Parameternamen sind identisch; Einheiten gibst du über ihre Adresse `addr` aus dem Snapshot an. ## Direkt in Shared Memory schreiben Es geht auch ohne Python und Farsight: Einmal den semantischen Befehl `canvas_enable` (W3P-Opcode 73) senden, dann legt die Runtime den Shared-Memory-Block `Local\War3Canvas_` an: 64-Byte-Header + 256 Einträge × 112 Bytes + 64 KB Pool für Text und Punkte. Geschrieben wird per seqlock (Sequenznummer ungerade → Einträge und Pool schreiben → Sequenznummer gerade); die Runtime liest einmal pro Frame, behält bei halb geschriebenen Daten den Stand des vorigen Frames bei und schreibt die Zahl gezeichneter Frames, die Elementzahl und den Ausnahmezähler zurück. Die Python-Referenzimplementierung ist `sdk/python/w3canvas.py`; die Strukturen sind im Protokoll-Header definiert, siehe [W3P-Protokoll](https://war3ai.com/de/docs/protocol/). ## Mehrere Programme zeichnen gleichzeitig Mods, Farsight, MCP und Gateway zeichnen unter Umständen gleichzeitig in dieselbe Partie, es gibt aber nur ein Canvas. Die Regel: **Jedes Programm fasst nur seine eigenen Elemente an.** - Vor dem Schreiben eine benannte Sperre holen, die vorhandenen Elemente lesen, die der anderen behalten, die eigenen ersetzen und dann zurückschreiben; - Jedes Element merkt sich, wer es gezeichnet hat (Prozess-ID + laufende Nummer im Prozess). Hat sich das zeichnende Programm beendet, räumt der nächste Schreibvorgang das Element nebenbei weg, und seine Buttons fangen keine Klicks mehr ab; - Elementnummern kommen aus einem gemeinsamen Zähler, es gibt also keine Kollisionen. Das Python-SDK macht das bereits so, und auch `clear()` entfernt nur die eigenen Elemente. Wenn du selbst direkt in Shared Memory schreibst, halte dich daran, sonst überschreibst du die Elemente anderer. Details zum Speicherlayout siehe [W3P-Protokoll](https://war3ai.com/de/docs/protocol/). ## Messwerte und Hinweise - Messung vom 2026-09-25 (1920×1080, 2× Geschwindigkeit): 9 Elemente 0.27–0.34 ms pro Frame, ca. 63 Frames pro Sekunde, 0 Ausnahmen; 9 Einträge zu schreiben dauert 6 ms; wenn der Held läuft, halten Kreise, Text und HP-Balken, die an Einheiten hängen, mit. Texturen werden nur neu gerendert, wenn sich der Inhalt ändert, nicht bei reinen Positionsänderungen. - Das Canvas wird nach der Spieloberfläche und vor dem Mauszeiger gezeichnet: Es liegt über den HP-Balken, Einheiten und der Oberfläche des Spiels, der Mauszeiger liegt darüber. Es weicht der Bedienleiste unten und der Tag-/Nacht-Uhr oben aus, **aber nicht den Panels der Karte selbst** (Rangliste oben rechts, Countdown) – eigene Panels also nicht oben rechts platzieren. - Außerhalb einer Partie (Hauptmenü, Ergebnisbildschirm) werden Elemente in Weltkoordinaten und an Einheiten nicht gezeichnet, Elemente mit Bildschirmposition schon. - Kreise am Boden entstehen, indem 64 Punkte auf dem Umfang einzeln auf den Boden projiziert werden; bei unebenem Gelände verformt sich der Kreis mit – das ist richtig so: Er liegt auf dem echten Boden. - Beim ersten Öffnen werden der Hook installiert und Schriften vorgewärmt, ca. 1 Sekunde; in dieser Zeit werden Textelemente noch nicht gezeichnet, Kreise und Linien schon. - Tritt beim Zeichnen auch nur eine Ausnahme auf, wird in dieser Sitzung nichts mehr gezeichnet (derselbe Schutz wie bei den Sprechblasen über Einheiten); `faults` in `stats()` wird dann 1. - Text, Bildpfade und Punkte teilen sich 64 KB, maximal 256 Elemente; Bildpfade müssen lokale Pfade sein, die der Spielprozess lesen kann. Das Statuspanel des [KI-Begleiters](https://war3ai.com/de/docs/companion/) wird mit dem Canvas gezeichnet: HP-Balken, was er gerade tut, Stimmung, Kills und Heilungen. --- # Oberfläche & Eingabe > Buttons und Auswahlkarten auf dem Canvas sind klickbar und leuchten beim Hover auf; Hotkeys registrieren, per Klick auf den Boden eine Position wählen, lesen, worauf die Maus zeigt, und wissen, was der lokale Spieler ausgewählt hat. Klicks, Hotkeys, gewirkte Fähigkeiten, voller Chat-Text und Spieler, die gehen – alles landet im Event-Stream. Was das [Canvas](https://war3ai.com/de/docs/canvas/) zeichnet, ist jetzt **klickbar**. Die Runtime übernimmt die Eingabe des Spielfensters, und externe Programme können: | Fähigkeit | Kurz gesagt | Bekommt das Spiel es mit? | |---|---|---| | **Klickbare Canvas-Elemente** | Buttons, Auswahlkarten, Panels: ein Klick löst `ui.click` aus, beim Hover leuchten sie automatisch auf | Der Klick auf den Button kommt **nicht** an | | **Hotkeys** | Kombinationen wie `F5` oder `ctrl+shift+Q` registrieren; Drücken löst `hotkey` aus | Optional verschlucken (samt dem Zeichen, das die Taste erzeugt) | | **Klick auf den Boden** | Ein Klick in die Welt löst `mouse.world` aus, mit Bodenkoordinaten | Optional verschlucken („Klick auf eine Position, um einen Turm zu setzen“) | | **Mausposition** | Jedes Frame aktualisiert: Bildschirmpixel, Bodenpunkt unter dem Mauszeiger, Canvas-Element unter dem Mauszeiger | – | | **Auswahl** | Was der lokale Spieler ausgewählt hat; bei jeder Änderung kommt `selection.changed` | – | Alles ist **lokale Eingabe + lokales Zeichnen**: Nichts geht in den Befehlsstrom, auch im Multiplayer sicher. Verändert dein Callback aber die Welt (Einheiten erzeugen, Werte ändern), gilt wieder: nur Einzelspieler. ## Python: g.ui ```python ui = g.ui # beim ersten Zugriff übernimmt die Runtime die Fenstereingabe ui.button("shop", "Heiltrank kaufen (50 Gold)", screen=(40, 300), on_click=lambda g, ev: buy(g)) c = ui.choice("Stufe aufgestiegen! Wähl eine Belohnung", [("Stärke +5", "hält mehr aus"), ("Angriffstempo +20 %", "teilt mehr aus"), ("Wolf beschwören", "ein Helfer mehr")], pause=True, on_pick=lambda g, i: give(g, i)) # Kartenreihe in der Bildschirmmitte; pause=True pausiert das Spiel während der Wahl i = c.wait(timeout=30) # auch blockierend warten möglich (Events werden dabei weiter abgeholt, nichts geht verloren) ui.hotkey("F5", lambda g, ev: g.say(hero, "Verstanden!")) # standardmäßig verschluckt ui.hotkey("ctrl+shift+Q", on_press=..., swallow=False) ui.mouse(on_click, capture=True, buttons=("left", "right")) # Klicks auf den Boden abfangen: links und rechts melden und verschlucken xy = ui.pick_point("Klick auf den Boden: Wohin mit dem Turm?") # blockierend: nächster Linksklick auf den Boden -> (x, y); Esc oder Timeout -> None ui.cursor() # {'screen': (x, y), 'world': (x, y, z) oder None, 'hover': 'shop'} ui.toast("Welle 3 kommt!", seconds=3) ui.close() # eigene Bedienelemente und Hotkeys entfernen; Fenstereingabe erst zurückgeben, wenn kein anderes Programm sie nutzt g.close() # oder ganz trennen (geht auch als with Game(...) as g:) ``` Callbacks erhalten `(g, ev)` und werden ausgelöst, wenn du `g.events()` aufrufst – die Runner für Bots und [Gameplay-Mods](https://war3ai.com/de/docs/mods/) tun das in jedem Tick. Klicks ohne Callback landen in `ui.clicks`. Wirft ein Callback eine Ausnahme, wird sie nur geloggt; andere Callbacks und Events sind nicht betroffen. Du kannst auch direkt die Canvas-Ebene nutzen: `g.canvas.text(..., clickable=True, hover=Farbe)`; Klicks kommen über den Event-Stream, `ev.key` ist der key, den du beim Zeichnen vergeben hast. Sobald du ein klickbares Element zeichnest, wird die Eingabe automatisch aktiviert – `g.ui` musst du vorher nicht anfassen. **Hotkey-Schreibweise**: `F1` ~ `F24`, `A` ~ `Z`, `0` ~ `9`, `numpad0` ~ `numpad9`, `space enter esc tab backspace insert delete home end pageup pagedown left up right down`, davor optional `ctrl+`, `shift+`, `alt+`. > **Achtung** > > Buchstaben und Ziffern ohne Modifikatortaste kommen der Chat-Eingabe und den Tastenkürzeln des Spiels in die Quere. Nimm lieber Tasten, die das Spiel nicht belegt, etwa F5 ~ F8, oder Tastenkombinationen. ## Neue Events In `g.events()` kommen diese hinzu (alle Felder siehe [W3P-Protokoll](https://war3ai.com/de/docs/protocol/)): | kind | Wann | Komfortfelder | |---|---|---| | `ui.click` | Ein interaktives Canvas-Element wurde angeklickt | `.key` Canvas-key, `.button` (`'left'` / `'right'`), `.mods` Modifikatortasten | | `ui.hover` | Maus fährt auf ein Canvas-Element / verlässt es | `.key` (beim Verlassen `None`) | | `hotkey` | Ein registrierter Hotkey wurde gedrückt | `.key` Hotkey-Schreibweise, `.mods` | | `mouse.world` | Klick in die Welt, wenn Bodenklicks aktiviert sind | `.x .y` Bodenkoordinaten, `.button`, `.value` (1 = verschluckt) | | `selection.changed` | Die Auswahl des lokalen Spielers hat sich geändert | Einheiten mit `g.selection()` holen | | `spell.cast` | Eine Einheit hat eine Fähigkeit gewirkt (Abklingzeit startet) | `.spell` Vier-Zeichen-Code, `.b` Stufe, `.value` Abklingzeit in Sekunden, `.x .y` Zielpunkt | | `message` | In einem Nachrichtenfeld auf dem Bildschirm erscheint eine Zeile | `.text` voller Text, `.frame` welches Feld, `.chat` (bei Chat-Nachrichten) | | `player.left` | Ein Spieler hat das Spiel verlassen oder wurde als besiegt entfernt | `.player` | | `game.ended` | Partie verlassen | – | ## Chat und Bildschirmnachrichten Was der Spieler ins Chatfeld tippt, liest du direkt aus `.chat` des `message`-Events: ```python for ev in g.events(): if ev.kind == "message" and ev.chat and ev.chat["text"] == "-follow": ... # ev.chat = {'channel': 'Alle', 'sender': 'Spielername', 'text': '-follow'} ``` `g.messages()` hat einen eigenen, unabhängigen Cursor und enthält auch Spielhinweise (etwa, dass mehr Farmen nötig sind oder dass dort nicht gebaut werden kann). Beim Schreiben eines Bots erfährst du so, warum ein Befehl nicht geklappt hat. ## Aus anderen Sprachen - **Gateway**: `ui.button`, `ui.choice`, `ui.hotkey`, `ui.mouse`, `ui.cursor` stehen im [Gateway](https://war3ai.com/de/docs/gateway/) unter denselben Namen zur Verfügung. Von entfernt lassen sich keine Callback-Funktionen übergeben; Klicks und Hotkeys kommen über die Event-Pushes (das `ui.click`-Event trägt den `key`). - **Direkt in Shared Memory schreiben**: Zuerst den semantischen Befehl `input_enable` (W3P-Opcode 74) senden, dann übernimmt die Runtime die Eingabe. Im Eingabeblock `Local\War3Input_` schreibst du die Hotkey-Tabelle und die Maus-Schalter, die Runtime schreibt Mausposition, den Bodenpunkt unter dem Mauszeiger und das Element unter dem Mauszeiger zurück. Das Flag `0x40` eines Canvas-Elements bedeutet „interaktiv“. Layout siehe [W3P-Protokoll](https://war3ai.com/de/docs/protocol/). ## Mehrere Programme gleichzeitig Mods, Farsight, MCP und jede Gateway-Sitzung können gleichzeitig Buttons in dieselbe Partie setzen und Hotkeys registrieren, ohne sich in die Quere zu kommen: - Jedes Programm registriert seine eigenen Hotkeys und seinen eigenen Schalter für Bodenklicks; das SDK führt alles zu einer Tabelle zusammen und übergibt sie der Runtime. Jede Taste steht nur einmal darin, das Event geht an alle, und jeder erkennt seine eigenen Hotkeys an der Taste; - `ui.close()` entfernt nur die eigenen Einträge; die Fenstereingabe wird erst zurückgegeben, wenn das letzte Programm weg ist; - Wird ein Programm hart beendet und kann nicht mehr aufräumen: Die Runtime prüft alle 2 Sekunden; haben sich alle registrierten Programme beendet, entfernt sie deren übrig gebliebene Hotkeys und das Abfangen von Bodenklicks, und ihre Buttons fangen keine Klicks mehr ab. ## Gemessen 2026-09-25, Live-Prüfung auf der Testinstanz: 16/16. - Klick auf einen Button → `ui.click` + Callback, der Abfangzähler der Runtime steigt um 1 (das Spiel hat den Klick nicht bekommen); ein Klick neben den Button löst nichts aus; - F6 → `hotkey`; Klick auf den Boden → `mouse.world` (verschluckt); - Einen Paladin erzeugen und auswählen → `selection.changed`, `g.selection()` stimmt überein; Gottesschild wirken → `spell.cast('AHds', 1, 35.0)`; - Kartentext → `message`; Chat → `message`, `.chat` liefert Sprecher und Inhalt; - Computergegner als besiegt werten → `player.left`; Partie beenden → `game.ended`. Echte Mausklicks auf Buttons und das Aufleuchten beim Hover wurden ebenfalls einzeln geprüft. ## Grenzen und Hinweise - **Die Position kommt von der echten Maus**: Das Spiel liest die Position über den System-Cursor, Hover und `cursor()` spiegeln also die echte Maus wider. Abgefangen werden nur die Tasten. - **Gezeichnet unter dem Mauszeiger**: Warcraft zeichnet den Mauszeiger in jedem Frame als Teil des Bildes. Canvas und Sprechblasen werden vor dem Schritt gezeichnet, in dem das Spiel den Mauszeiger zeichnet: Sie liegen über der Spieloberfläche, der Mauszeiger liegt über ihnen. Nur wenn in einem Frame kein Mauszeiger gezeichnet wird (ausgeblendet oder Zwischensequenz), wird wieder im letzten Schritt gezeichnet. - **Systemskalierung**: Wenn du eigene Tests schreibst und Klicks per Fensternachricht einspeist, werden die Koordinaten aus einem Prozess ohne DPI-Awareness vom System hochskaliert (bei 150 % Skalierung gemessen ×1.5). Das Testprogramm sollte sich vorher als DPI-aware deklarieren. Echte Klicks sind nicht betroffen. - **Beim ersten Mal werden Schriften vorgewärmt**, ca. 1 Sekunde. In dieser Zeit sind die Buttons noch nicht gezeichnet und lassen sich nicht anklicken. - **Außerhalb einer Partie keine Bodenklicks**: Im Hauptmenü und auf dem Ergebnisbildschirm wird `mouse.world` weder gemeldet noch verschluckt. - Wurde ein Klick beim Drücken verschluckt und wechselst du vor dem Loslassen zu einem anderen Programm oder ziehst die Maus aus dem Fenster, wird der Zustand trotzdem zurückgesetzt – das nächste Loslassen wird nicht ebenfalls verschluckt. - 1.27 hat keine Funktionen, um neue Frames der Spieloberfläche anzulegen (die gibt es erst ab 1.31): Buttons und Karten hier zeichnet die Runtime selbst – im Stil frei, aber sie tauchen nicht in der Menühierarchie des Spiels auf. --- # JASS-Kanal > Die 1291 JASS-Funktionen, die Kartenautoren nutzen können, lassen sich jetzt von außerhalb des Spiels direkt per Name aufrufen: Einheiten erstellen, Eigenschaften ändern, Effekte, Panels, Dialoge, Sounds, Kamera, Nebel … Vier Wege: Farsight-Konsole, Kommandozeile, HTTP und Python. Alle **1291 JASS-Natives**, die Kartenautoren in Kartenskripten verwenden können, lassen sich jetzt von außerhalb des Spiels direkt per Name aufrufen: Einheiten erstellen, Eigenschaften ändern, Effekte zeichnen, Panels und Dialoge öffnen, Sounds abspielen, die Kamera bewegen, den Nebel ändern … Damit lässt sich das Spiel weiter anpassen – RPG-Helfer, [KI-Begleiter](https://war3ai.com/de/docs/companion/), kleine eigene Spielmodi, Debug-Werkzeuge. | Weg | Geeignet für | Einstieg | |---|---|---| | **Farsight-Seite „JASS-Konsole“** | manuell ausprobieren, zuschauen und anpassen | Seitenleiste „System → JASS-Konsole“: Skript schreiben, auf Ausführen klicken; rechts Funktionen nach Kategorie nachschlagen, ein Klick fügt sie ins Skript ein | | **Kommandozeile** | manuell ausprobieren oder als Skriptdatei immer wieder ausführen | `python -m openwar3 jass --inst 20` (interaktiv), `-e "Code"`, `my_script.j`, `--list Stichwort` | | **HTTP** | externe Programme in jeder Sprache | `POST /api/instances/{n}/jass` usw. (siehe unten); das Farsight-Backend lauscht nur lokal | | **Python** | Schemata, Begleiter und Werkzeuge schreiben | `g.jass.BeliebigeFunktion(...)`; häufige Grafik- und Interaktionsfunktionen sind in `openwar3.visual` gekapselt | > **Achtung** > > Drei Grenzen, alle durch den Mechanismus bedingt: > > - Nur in **Einzelspieler-Partien** (gegen den Computer auf deinem Rechner) lässt sich die Welt verändern. Legt dein Rechner einseitig Objekte an oder ändert Einheiten, geraten die anderen Spieler in Mehrspieler-Partien aus dem Takt (Desync) – dort sind nur lesende Funktionen erlaubt (`Get*`, `Is*`, `Count*` …). > - Nur für lokale Werkzeuge; Aufrufe über eine Spielerverbindung (`Game(player=N)`) oder im Fair-Modus werden abgelehnt. > - Nur für Offline-Spiele und selbst gehostete LAN-Spiele. > > Um in Mehrspieler-Partien etwas aufs Spielbild zu bringen, nimm das [Canvas](https://war3ai.com/de/docs/canvas/): Die Runtime zeichnet es selbst, der Spielzustand bleibt unverändert. ## Skriptsyntax Konsole, Kommandozeile und HTTP verwenden dieselbe Skriptsprache. Eine Anweisung pro Zeile; **JASS lässt sich direkt einfügen** (`call` / `set` / `local`, `true` / `false` / `null`, Vier-Zeichen-Codes wie `'Hpal'`, `//`-Kommentare), Python-Schreibweise geht auch: ```text set h = hero() // eingebaut: eigener Haupt-Held local texttag t = CreateTextTag() call SetTextTagText(t, "|cffffcc00+128 Kritisch!|r", 0.024) call SetTextTagPosUnit(t, h, 60) call SetTextTagVelocity(t, 0, 0.03) call SetTextTagPermanent(t, false) call SetTextTagLifespan(t, 4) call SetTextTagVisibility(t, true) call PingMinimapEx(h.x, h.y + 300, 3, 255, 0, 0, false) set u = CreateUnit(Player(0), 'hfoo', h.x + 200, h.y, 270) print("Erstellt:", u, "Heldenstufe", GetHeroLevel(h)) ``` - **Variablen bleiben erhalten**: In derselben Instanz und derselben Partie kann der nächste Abschnitt die mit `set` gesetzten Variablen weiterverwenden; bei einer neuen Partie werden sie automatisch geleert, du kannst sie auch manuell leeren. - **Eingebaute Funktionen**: `hero()` eigener Haupt-Held, `me()` lokaler Spieler, `unit('hfoo')` sucht eine Einheit, `unit_at(x, y)`, `wait(Sekunden)`, `print(...)`. Bei Einheiten lassen sich `.x`, `.y`, `.hp`, `.hp_max`, `.mana`, `.type`, `.owner` und `.level` lesen; Grundrechenarten und Vergleiche werden unterstützt. - **Nicht unterstützt** werden `if`, `loop` und `function` – für Logik nimm `g.jass` in Python (das sind ganz normale Funktionsaufrufe) oder schreib ein [Schema](https://war3ai.com/de/docs/schemes/). - Bei Fehlern erfährst du Zeile und Grund (Funktion existiert nicht, falsche Parameteranzahl, Variable nicht definiert …); Anweisungen vor dem Fehler sind bereits ausgeführt. Parameter und Rückgabewerte: | In der Signatur | Was du übergibst | Hinweise | |---|---|---| | Ganzzahl (integer) | Zahl; Vier-Zeichen-Codes wie `'Hpal'` werden automatisch umgewandelt | | | Gleitkommazahl (real) | Zahl | die Runtime wandelt sie ins Format der Engine um | | Wahrheitswert (boolean) | `true` / `false` | | | Zeichenkette (string) | `"..."` | chinesischer Text und die Farbcodes des Spiels werden unterstützt; Zeichenketten, die das Spiel speichert (schwebender Text, Panels, Buttons, Chat-Befehle), werden sofort kopiert – also sicher | | Handle | ein Handle aus einer Variablen oder eine Einheit (etwa `hero()` wird automatisch in ein Handle umgewandelt) | | | Funktion (code) | nur `null` | Von außen lässt sich keine JASS-Funktion übergeben; so etwas wie `TimerStart(t, 60, false, null)` funktioniert | | Rückgabewert string | — | Die Engine liefert eine Nummer in der String-Tabelle, der Text lässt sich nicht zurücklesen. Für Einheitennamen `g.map_data.name_of` verwenden | ## Kategorien Die Funktionen sind nach ihrem Namen in Kategorien eingeteilt; die rechte Seite der Konsole und `--list` verwenden diese Einteilung: | Kategorie | Anzahl | Beispiele | |---|---|---| | Grafikeffekte | 80 | schwebender Text, Blitzverbindungen, Spezialeffekte, Bodenbilder, Bodenmarkierungen, Einheiten einfärben / skalieren / Animation abspielen | | Oberfläche & Panels | 146 | Multiboards, Ranglisten, Countdown-Fenster, Dialoge, Quests, Bildschirmtext, Pings auf der Minikarte, Porträt-Dialog, Vollbildfilter | | Kamera | 44 | Kamerafelder, Schwenks, Kamerawackeln | | Sound & Musik | 50 | Sounds erstellen und abspielen, Musik abspielen | | Nebel & Sicht | 25 | Sichtbereiche, Nebel ein-/ausschalten | | Gegenstände / Helden / Einheiten | 63 / 32 / 161 | Gegenstände erstellen, Heldenstufe setzen, Besitzer wechseln, Fähigkeiten hinzufügen | | Spieler / Bündnisse / Ressourcen | 71 | Bündnisse setzen, Gold und Holz ändern | | Trigger / Ereignisse / Timer | 62 | Trigger erstellen, Ereignisse registrieren, Timer | | Gelände / Wetter / Zerstörbare | 45 | Wettereffekte, Gelände ändern, zerstörbare Objekte erstellen | | Spielablauf | 57 | Spielgeschwindigkeit, Pause, Tageszeit | | Sonstiges | … | Einheitengruppen und Regionen, Datenspeicherung, Computer-KI-Skripte, Typumwandlung und Mathematik, Ereignisantworten … | Am 2026-09-24 wurden **94** davon einzeln im Live-Spiel aufgerufen und ihre Wirkung mit eigenen Augen geprüft; die übrigen laufen über denselben Weg, nur wurde ihre Wirkung nicht einzeln angesehen. > **Info** > > Funktionen der Kategorie „Ereignisantworten“ (`GetTriggerUnit`, `GetClickedButton` …) haben nur in dem Moment einen Wert, in dem ein Trigger ausgeführt wird; von außen aufgerufen liefern sie 0 oder nichts. Um zu erfahren, ob etwas passiert ist, nutze die Ereigniszähler weiter unten. ## HTTP Farsight-Backend (Standard `127.0.0.1:8866`, lauscht nur lokal): ```http GET /api/jass/natives?q=TextTag&cat=visual POST /api/instances/20/jass {"code": "set h = hero()\ncall PingMinimapEx(h.x, h.y, 3, 255, 0, 0, false)"} -> {"ok": true, "rows": [...], "printed": [...], "vars": {...}} -> bei Fehler: {"ok": false, "error": "第 2 行:...", "line": 2} POST /api/instances/20/jass/call {"name": "SetUnitScale", "args": [{"unit": 599669636}, 1.4, 1.4, 1.4]} POST /api/instances/20/jass/reset gemerkte Variablen löschen ``` Einheitenparameter schreibst du als `{"unit": Adresse}`; die Adresse ist das `addr` der Einheit im Snapshot. Gemessen: 60–90 ms pro Anfrage. ## Python: g.jass und openwar3.visual ```python j = g.jass t = j.CreateTextTag() j.SetTextTagText(t, "Hallo", 0.024) # gleiche Parameterregeln wie im Skript; Einheiten- und Gegenstandsobjekte aus dem Snapshot lassen sich direkt übergeben j.signature("CreateImage") # Signatur nachschlagen ``` `openwar3.visual.Visual(g)` verpackt erprobte, häufig genutzte Grafikeffekte in jeweils einen Aufruf (pro Tick einmal `v.tick()` aufrufen: Abgelaufenes wird entfernt, Linien und Kreise, die Einheiten folgen, werden nachgezogen; `v.clear()` entfernt alles): | Methode | Effekt | |---|---| | `float_text(Text, Einheit oder Punkt, ...)` | schwebender Text: Schadenszahlen, Hinweise über dem Kopf, auch mit chinesischem Text und Farben | | `link(a, b, kind)` | eine Linie zwischen zwei Einheiten, die ihnen folgt: Fessel / Geistverbindung / Lebensentzug / Heilwelle | | `effect(Modell, Einheit oder Punkt, ...)` | Effektmodell: über dem Kopf, unter den Füßen oder einmalig abgespielt (Explosion, Lichtsäule) | | `ring(Einheit oder Punkt, Radius, color)` | Bereichskreis am Boden: Zauberreichweite, Gefahrenzone, Sammelpunkt; kann einer Einheit folgen | | `ping(Punkt, color)` | Ping auf der Minikarte | | `board(Titel, Zeilen...)` | mehrzeiliges Panel oben rechts (mit Symbolen), Felder einzeln änderbar | | `countdown(Titel, Sekunden)` | Countdown-Fenster oben rechts, das Spiel zählt selbst herunter | | `scene(Name, Text, portrait)` | Porträt-Dialog: unten erscheint das Porträt einer sprechenden Einheit, auf dem Bildschirm der Untertitel „Name: Text“ | | `screen_tint(color, alpha)` | Vollbildfilter (Standard: roter Rand – Warnung bei niedrigen HP) | | `sound(Pfad)` / `reveal(Punkt, Radius, Sekunden)` / `look(Einheit, ...)` | Sound abspielen / Nebel in einem Bereich aufdecken / Einheit einfärben, vergrößern, Animation abspielen, aufblitzen lassen | ## Interaktion: wissen, was der Spieler tut – ohne JASS-Funktionen zu schreiben Um in JASS auf den Spieler zu reagieren, schreibt man Trigger-Funktionen – und von außen lässt sich keine Funktion übergeben. Die Lösung: **einen leeren Trigger ohne Bedingungen und Aktionen anlegen, nur Ereignisse registrieren und zählen, wie oft er ausgeführt wurde.** Im Test zählt auch ein leerer Trigger zuverlässig mit. | Methode | Zweck | |---|---| | `chat_commands(["-follow", "-stay"])` → `.poll()` | Befehle, die der Spieler ins Chatfeld tippt (exakte Übereinstimmung oder Präfix) | | `menu(Titel, [Buttons...])` → `.clicked()` | Button-Menü in der Bildschirmmitte: welcher Button geklickt wurde | | `hotkeys(("left", "right", "up", "down", "esc"))` → `.poll()` | wie oft die Pfeiltasten und Esc gedrückt wurden | | `on("TriggerRegister...Event", Parameter...)` → `.poll()` | wie oft ein beliebiges JASS-Ereignis eingetreten ist: Einheit stirbt, betritt eine Region, nimmt Schaden, Timer … | Die Einschränkung: Du erfährst nur, „wie oft“ etwas passiert ist, nicht „wer, und was getippt wurde“. Um zu unterscheiden, wer es war, legst du für jedes Objekt einen eigenen Zähler an. Genau so sind die Chat-Befehle des [KI-Begleiters](https://war3ai.com/de/docs/companion/) angebunden. ## Hinweise - **Was du erstellst, musst du selbst wieder löschen**: Schwebender Text, Linien, Bodenbilder, Panels, Trigger … bleiben sonst für immer (`Visual.clear()` löscht, was es selbst angelegt hat). Im Spiel gibt es gleichzeitig höchstens etwa 100 schwebende Texte. - **BJ-Funktionen sind keine Natives**: `CreateTextTagUnitBJ` und Co. werden im Kartenskript aus Natives zusammengesetzt und stehen hier nicht zur Verfügung – ruf die Natives so auf, wie es ihre Implementierung tut. - **Manche Konstanten müssen erst umgewandelt werden**: etwa `ConvertPlayerColor(1)`, `ConvertFogState(4)` (Werte siehe common.j). - Ein Aufruf dauert ca. 13 ms (inkl. Handle-Umrechnung); auf Protokollebene sind das die W3P-Opcodes 70–72, siehe [W3P-Protokoll](https://war3ai.com/de/docs/protocol/). --- # Gameplay-Mods > Ein Schema ist nicht nur eine KI, die für dich spielt – es kann auch ein Regelwerk sein: Du spielst selbst im Spielfenster, die Mod richtet den Start ein, schickt Gegnerwellen, verteilt Belohnungen, gibt dir Buttons und Auswahlkarten auf dem Bildschirm und entscheidet über Sieg und Niederlage. Von openwar3.Mod erben – eine Datei ist ein ganzes Spielprinzip. [KI-Schemata](https://war3ai.com/de/docs/schemes/) gibt es in zwei Arten: `kind: bot` ist eine KI, die für dich spielt; `kind: mod` ist **ein Regelwerk** – du spielst selbst im Spielfenster, und die Mod stellt die Aufgaben: wie der Start aussieht, wann nach Zeit oder Event Gegner erscheinen, welche Belohnungen es gibt, welche Buttons und Auswahlkarten du auf dem Bildschirm bekommst und wann du gewonnen hast. Eine Mod nutzt ausschließlich vorhandene Fähigkeiten: [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/) (klickbare Buttons, Karten, Hotkeys, Klicks auf den Boden), [Canvas](https://war3ai.com/de/docs/canvas/) (Panels, Fortschrittsbalken, Routen), [JASS-Kanal](https://war3ai.com/de/docs/jass/) (Einheiten erzeugen, Werte ändern, Gegenstände vergeben) und den Event-Stream (Tode, Stufenaufstiege, gewirkte Fähigkeiten, Chat). ## Zwei Beispiele In Farsight unter „KI-Schemata“ → „Eingebaut“ auswählbar: | Mod | Spielprinzip | Genutzte Fähigkeiten | |---|---|---| | **Helden-Roguelike** `builtin/hero-roguelike` | Du hast nur einen Paladin, und Welle um Welle rücken Gegner von allen Seiten an; bei jedem Stufenaufstieg wählst du in der Bildschirmmitte eine von drei Verstärkungen (das Spiel pausiert während der Wahl); überstehst du 10 Wellen, gewinnst du, stirbt der Held, verlierst du | `g.ui.choice` (klickbare Karten + Pause), Events `hero.levelup` / `killed` / `spell.cast`, Chat `-help`, Heldenwerte per JASS ändern, Gegenstände vergeben | | **Endlose Verteidigung** `builtin/endless-defense` | Gegner laufen vom gegenüberliegenden Startpunkt entlang der roten Linie am Boden auf dein Hauptgebäude zu; für jede abgewehrte Welle gibt es Gold; per Button auf dem Bildschirm oder mit F7 rufst du die nächste Welle früher, Belohnung ×1.5; F8 und dann Linksklick auf den Boden setzt einen kostenlosen Wachturm (Rechtsklick bricht ab) | `g.ui.button`, `g.ui.hotkey`, `g.ui.mouse` (Klicks auf den Boden abfangen), Canvas-Panel / Fortschrittsbalken / Route, Gegner erzeugen und Gold geben per JASS | Beide Beispiele haben je etwa 150 Zeilen; der Code liegt in `brains/examples/mod_hero_roguelike.py` und `brains/examples/mod_endless_defense.py`. ```bash python tools/play.py --bot brains/examples/mod_hero_roguelike.py --inst 9 # Spiel starten, die Mod übernimmt, du spielst im Spielfenster ``` ## Eine Mod schreiben ```python from openwar3 import Mod class Survive(Mod): name = "survive" def on_start(self, g): super().on_start(g) # Einzelspieler-Prüfung + Computergegner ruhigstellen self.foe = self.wave_player(g) # ein leerer Slot als „Wellenspieler“: mit niemandem verbündet, keine Computer-KI self.every(30, self.wave) # alle 30 Spielsekunden eine Welle (steht während der Pause) g.ui.hotkey("F7", lambda g, ev: self.wave(g)) def wave(self, g): self.spawn_ring(g, self.foe, "ugho", 6, self.home(g), 1400, attack_to=self.home(g)) def on_event(self, g, ev): if ev.kind == "unit.died" and ev.type == "htow": self.finish("loss", "Hauptgebäude zerstört") ``` `Mod` bietet zusätzlich zu `Bot`: | Methode / Attribut | Beschreibung | |---|---| | `on_start / on_tick / on_event / on_end` | wie beim Bot; wenn du `on_start` / `on_tick` überschreibst, zuerst `super()` aufrufen | | `every(Sekunden, fn, first=)` / `after(Sekunden, fn)` | Timer nach **Spielzeit**, der Callback ist `fn(g)` | | `finish(result, reason)` | beendet die Partie (`'win'` / `'loss'` / `'unknown'`): Der Runner stoppt im nächsten Tick, in der Bildschirmmitte erscheint ein Ergebnis-Panel, und das Ergebnis des Schemas wird entsprechend erfasst | | `wave_player(g)` | erster leerer Spieler-Slot, als Wellenspieler gedacht | | `spawn_ring(g, Spieler, Einheit, Anzahl, Mitte, Radius, attack_to=)` | erzeugt Einheiten auf einem Kreis, auch Dutzende pro Welle ohne Ruckeln; gibt JASS-Handles zurück | | `alive_of(g, Spieler)` / `attack_move_all(g, Spieler, Punkt)` | lebende Einheiten eines Spielers / alle per Angriffsbewegung dorthin schicken (alle paar Sekunden aufrufen, dann verfolgen die Gegner dich) | | `home(g)` / `hud(g, Titel, Zeilen)` | Position deines Hauptgebäudes / Info-Panel oben rechts | | `neutralize_ai = True` | stellt den Computergegner beim Start ruhig: Seine Einheiten werden alle 5 Sekunden angehalten, Gold und Holz auf null gesetzt. Auf Melee-Karten gibt es immer einen Computergegner – wenn die Mod eigene Regeln macht, soll er nicht dazwischenfunken | | `single_player_only = True` | verweigert den Start, wenn weitere menschliche Spieler da sind (JASS, das die Welt verändert, würde bei anderen einen Desync auslösen) | | `linger_s = 6` | wie viele Sekunden der Ergebnisbildschirm nach der Entscheidung stehen bleibt, bevor die Partie endet | `finish()` gibt es auch auf `Bot`: Auch ein normaler Bot kann das Ende selbst verkünden. ## Als Schema verpacken und teilen In `scheme.json` `"kind": "mod"` eintragen und in der Einstiegsdatei eine Unterklasse von `Mod` definieren: ```json {"id": "survive", "name": "10 Wellen halten", "kind": "mod", "entry": "survive.py", "class": "Survive"} ``` Eine Mod läuft grundsätzlich **nicht im Fair-Modus** (sie ist der Schiedsrichter, der die Aufgaben stellt, und muss die ganze Karte sehen und die Welt verändern) und entscheidet **Sieg und Niederlage nicht nach Melee-Regeln** (das Ergebnis meldet `finish`); `fair` / `judge` im Manifest haben keine Wirkung. zip-Export, Import, Vertrauen und Ergebnisse funktionieren genau wie bei Bot-Schemata, siehe [KI-Schemata](https://war3ai.com/de/docs/schemes/). Auch eine Mod ist Code: Fremde Mods müssen vor dem ersten Start ebenfalls als vertrauenswürdig bestätigt werden. ## Gemessen 2026-09-25, auf der Testinstanz: - **Helden-Roguelike**: Die erste Welle erscheint, das Panel oben rechts läuft mit; Held auf Stufe 3 gebracht → Karten erscheinen in der Bildschirmmitte, die Spieluhr steht; zweimal eine Karte angeklickt → beide Verstärkungen wirken (Stärke 22 → 27), die Uhr läuft weiter. - **Endlose Verteidigung**: Panel, Route am Boden und Buttons sind da; F8 + Klick auf den Boden → neben dem Hauptgebäude steht ein Turm mehr; Button geklickt, bevor die Welle erledigt ist → Hinweis „Diese Welle ist noch nicht erledigt“. ## Grenzen - **Nur Einzelspieler**: Einheiten erzeugen und Werte ändern läuft über den JASS-Kanal und führt im Multiplayer zu Desyncs. Das folgt aus dem Lockstep-Modell; Multiplayer-Spielprinzipien müssen auf einen Synchronisationskanal warten (siehe [Roadmap](https://war3ai.com/de/roadmap/)). - Eine Mod sieht die ganze Karte – sie stellt die Aufgaben, sie ist kein Spieler. - Der Computergegner auf Melee-Karten wird nur „ruhiggestellt“, nicht entfernt (ein Entfernen würde nach Melee-Regeln einen Sieg auslösen). --- # Farsight-Konsole > Lokale Web-Konsole und zugleich der einzige Einstiegspunkt: Spielverzeichnis festlegen, Dienste starten und stoppen, Spielinstanzen starten und stoppen, das nächste Spiel einrichten, sehen, was die KI denkt, manuelle Befehle, Regie, Spielprotokolle. Farsight ist die Web-Konsole, die lokal auf deinem Rechner läuft, und **lauscht nur auf 127.0.0.1**. Sie ist auch der einzige Einstiegspunkt ins gesamte System: Partien starten, KI wechseln, Gateway, Sprechblasen, lokales LLM – all das klickst du hier an, ohne nach weiteren Skripten suchen zu müssen. ```bash start.bat # Installation prüfen, dann Farsight öffnen: http://127.0.0.1:8866 start.bat 5 6 # startet zusätzlich Tests auf Instanz 5 und 6 (Spiel + Referenz-Brain) start.bat restart # nur das Farsight-Backend neu starten (nach Änderungen am Servercode; Spiele und Dienste laufen weiter) stop.bat # alles vollständig stoppen ``` Den Port änderst du unter `ports.console` in `openwar3.json` (Standard 8866). ## Kontrollzentrum Die Startseite von Farsight. - **Spielverzeichnis**: automatisch suchen oder selbst auswählen; Farsight prüft die Spielversion und extrahiert Daten aus deinem Spiel. - **Lokale Dienste**: [Gateway](https://war3ai.com/de/docs/gateway/), [Chat-Sprechblasen](https://war3ai.com/de/docs/speech/), lokales LLM (LM Studio), Website-Vorschau – auf jeder Karte lässt sich der Dienst starten, stoppen und neu starten, und du kannst die Logs ansehen; außerdem siehst du, ob ein Client den [MCP](https://war3ai.com/de/docs/mcp/)-Server eingebunden hat. - **Umgebungsprüfung**: ob Python, Runtime-Dateien, Spieldaten, AMAI-Daten usw. installiert sind. - **Alle stoppen** (oben rechts): Spielinstanzen, KI, Gateway, Sprechblasen, das von diesem System genutzte lokale Modell und das Farsight-Backend werden der Reihe nach beendet – genau wie mit einem Doppelklick auf `stop.bat`. MCP-Server werden von Clients wie Claude verwaltet und nicht gestoppt; auch das Programm LM Studio selbst wird nicht geschlossen. ## Seiten | Gruppe | Seite | Funktion | |---|---|---| | Zentrale | Kontrollzentrum | Siehe Abschnitt oben | | Spiel | Übersicht | Überblick über das Spiel der aktuellen Instanz | | | Schlachtfeld | Kartenansicht; manuelle Befehle möglich (manuelle Befehle haben in der Claim-Tabelle die höchste Priorität: 95) | | | Einheitendaten | Order, Aufgabenziel, Mana, Heldenstufe und Erfahrung, Abklingzeiten, Inventar jeder Einheit | | | KI-Entscheidungen / Kampfentscheidungen | Was das Referenz-Brain in diesem Tick denkt, Details zu jeder Kampfentscheidung | | | Strategie-Coach | Status des [LLM-Coaches](https://war3ai.com/de/docs/llm-coach/): ob der Modellserver läuft, ob jede Instanz verbunden ist, der letzte Ratschlag und die Eingaben, die er gesehen hat | | | Regiepult | Automatische Kameraführung, HP-Balken über Einheiten | | | Sprechblasen | Einheiten sprechen lassen, mit dem lokalen Modell chatten, Bauern-Kaffeeklatsch, Kamera-Dialoge, ereignisgesteuerte Kommentare, Modelleinstellungen. Siehe [Sprechblasen](https://war3ai.com/de/docs/speech/) | | | Befehlstempo | APM und Befehlsdurchsatz | | | Events & Eingabe | Was in dieser Partie passiert ist: gewirkte Fähigkeiten, Chat und Bildschirmnachrichten, Button-Klicks, Hotkeys, Klicks auf den Boden, Auswahl, Spieler, die gehen – nach Kategorie filterbar; daneben Mausposition, das Element unter dem Mauszeiger und die lokale Auswahl. Siehe [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/) | | Protokolle | Logs / Spielprotokolle | Log-Quellen jeder Instanz; Ergebnis, Dauer und maximale Truppenstärke jedes Spiels | | | Problemnotizen | Im Spiel mit Pause/Break pausieren und den Zeitpunkt festhalten, hier später die Beschreibung ergänzen | | System | Instanzen & Start | Instanzen starten und stoppen; Karte (Melee-Karte, wahlweise auch RPG- / Custom Map), Völker beider Seiten, Schwierigkeit und Geschwindigkeit für das **nächste Spiel** festlegen; pro Instanz ein KI-Schema wählen; „Test starten“ startet Spiel + KI mit einem Klick | | | KI-Schemata | Schemata importieren, exportieren, kopieren, als vertrauenswürdig markieren, löschen; das Schema einer Instanz wechseln (auch in der laufenden Partie übernimmt sofort ein anderes); Ergebnisse jedes Schemas ansehen. Siehe [KI-Schemata](https://war3ai.com/de/docs/schemes/) | | | JASS-Konsole | JASS-Skript schreiben und auf Ausführen klicken; rechts die 1291 Funktionen nach Kategorie durchsuchen und per Klick ins Skript einfügen; Variablen bleiben während derselben Partie erhalten. Siehe [JASS-Kanal](https://war3ai.com/de/docs/jass/) | | | Anbindung & Erweiterung | Gateway-Status und Start per Klick; Verbindungsadressen pro Rolle (Dev / Spieler / Zuschauer), MCP-Einbindungsbefehle und -Konfiguration; Beispiele in JS und Python. Siehe [Gateway](https://war3ai.com/de/docs/gateway/) und [MCP](https://war3ai.com/de/docs/mcp/) | | | Daten & Speicherplatz | Wie viel Speicherplatz Laufzeitdaten wie Aufnahmen, Spielprotokolle und Logs belegen, wie viel am letzten Tag hinzugekommen ist und was gelöscht werden kann (Farsight löscht nie etwas automatisch) | | | Einstellungen | Spielverzeichnis, Sprache der Oberfläche, Darstellung (dunkel / hell, modern / Warcraft-Stil) usw. | | | Feedback & Vorschläge | Probleme und Vorschläge direkt an uns senden; Diagnoseinformationen werden nur angehängt, wenn du sie ankreuzt, und lassen sich vor dem Senden ansehen | Mit Strg + K öffnest du die Befehlspalette: Seite wechseln, Instanz wechseln, das laufende Spiel beenden, ein neues Brain starten. Unten in der Seitenleiste zeigt „Neuigkeiten“, was bei Farsight und der Plattform zuletzt hinzugekommen ist. Beim Start (und danach alle 6 Stunden) fragt Farsight bei War3AI.com nach, ob es eine neue Version gibt, und weist dich gegebenenfalls darauf hin. Nach einem Update von Farsight erscheint oben auf der Seite ein Banner: Sichere zuerst, was du gerade eingibst, und klicke dann auf „Neu laden“. ## Mehrere Instanzen `runtime/farm.py` übernimmt das Multi-Boxing (Farsight ruft es für dich auf, wenn Instanzen gestartet oder gestoppt werden): Es kopiert den Original-Starter `War3.exe` und benennt ihn in `War3-.exe` um (keine Spieldatei wird verändert); jede Instanz hat eine Nummer und ein eigenes Verzeichnis (`bin/inst/`). Nach jedem Spiel startet das nächste automatisch gemäß `next_game.json` (genau diese Datei bearbeitest du auf der Konsolenseite „Instanzen & Start“). > **Tipp** > > Dein Bot verbindet sich mit `--inst N` zu einer bestimmten Instanz. Auf der Konsolenseite „Instanzen & Start“ siehst du, welche Nummern belegt sind – komm dem Referenz-Brain nicht in die Quere. ## Live-Seite `http://127.0.0.1:8866/live` ist eine scrollende Log-Seite für die „Browserquelle“ in OBS und zeigt die Entscheidungen der KI und das Kampfgeschehen. ## API Das Backend der Konsole ist eine Reihe lokaler REST- und WebSocket-APIs (Instanzstatus, Einstellungen fürs nächste Spiel, Einheitendetails, manuelle Befehle, Logs, Spielprotokolle, Regie, KI-Schemata, JASS-Aufrufe, Canvas …); die Webseite ist nur einer von mehreren Clients, Programme in jeder Sprache können sie direkt aufrufen. Die Liste der APIs steht im Dateikopf von `console/server/app.py`; wie die drei Gruppen Schemata, JASS und Canvas benutzt werden, steht unter [KI-Schemata](https://war3ai.com/de/docs/schemes/), [JASS-Kanal](https://war3ai.com/de/docs/jass/) und [Canvas](https://war3ai.com/de/docs/canvas/). --- # KI-Schemata > Ein Schema ist eine komplette KI. In Farsight mit einem Klick wechseln – auch in einer laufenden Partie übernimmt sofort die neue KI. Als zip exportieren und teilen, fremde Schemata importieren und testen; die Ergebnisse jedes Schemas werden automatisch erfasst. Ein **Schema** = eine komplette KI: ein Ordner + ein Manifest `scheme.json` + Code. Jede Spielinstanz verwendet ein Schema; in Farsight wechselst du mit einem Klick, und **selbst in einer laufenden Partie übernimmt sofort das neue Schema**. Von anderen geteilte Schemata liegen nach dem Import **in einem eigenen Bereich** und beeinflussen deine eigenen nicht; willst du eins ändern, nimm „In Meine kopieren“. ```text schemes/ mine// Meine: selbst geschrieben oder von einem anderen Schema kopiert und angepasst (frei änderbar, wirkt ab der nächsten Partie) installed// Installiert: hier werden zips entpackt, die andere geteilt haben (vor dem ersten Start muss Vertrauen bestätigt werden) brains/xwar3/ Eingebaut: Referenz-Brain (vollständige KI) brains/examples/ Eingebaut: vier Lehrbeispiele hello / rush / macro / micro, das Begleiter-Beispiel buddy und zwei Gameplay-Mods (Helden-Roguelike, Endlose Verteidigung) ``` Ein Schema muss keine KI sein, die für dich spielt: Ein Schema mit `kind: mod` ist ein Satz **Spielregeln** – du spielst selbst, es stellt die Aufgaben; siehe [Gameplay-Mods](https://war3ai.com/de/docs/mods/). ## In Farsight verwenden Seite „KI-Schemata“ (Seitenleiste „System → KI-Schemata“): | Aktion | Was passiert | |---|---| | Schema importieren (zip) | wird in `installed/` installiert; ist dieselbe id schon installiert, wirst du gefragt, ob sie ersetzt werden soll (danach muss Vertrauen neu bestätigt werden) | | Für Instanz verwenden … | Instanz wählen + „Sofort wirksam“ (aktuelle KI stoppen, das neue Schema übernimmt diese Partie) oder „Ab dem nächsten Teststart“ | | In Meine kopieren | legt eine Kopie in `mine/` an, Autor „ich“, Version 0.1.0, und merkt sich, von welcher Version welches Schemas kopiert wurde | | Als zip exportieren | packt es als `-.zip` – an andere schicken heißt teilen | | Ordner öffnen | öffnet das Schemaverzeichnis im Explorer, um den Code direkt zu bearbeiten | | Vertrauen | muss bei fremden Schemata vor dem ersten Start geklickt werden (siehe „Vertrauen und Sicherheit“ unten) | | Letzte Ergebnisse | Sieg/Niederlage, Dauer und Endgrund jeder Partie dieses Schemas | | Löschen | nur für „Meine“ und „Installiert“; Schemata, die gerade von einer Instanz verwendet werden, lassen sich nicht löschen | Auf der Instanzkachel gibt es außerdem eine Zeile „KI-Schema“: Schema im Dropdown wählen → „Wechseln (sofort wirksam)“. Läuft die Instanz nicht, heißt der Button „Festlegen“, und beim nächsten „Test starten“ wird die KI mit diesem Schema gestartet. ## Das Manifest scheme.json ```json { "format": 1, "id": "fast-rush", "name": "Drei-Minuten-Rush", "version": "1.2.0", "author": "Jemand", "description": "Ein Satz dazu, welche Strategie diese KI spielt", "entry": "rush_bot.py", "class": "RushBot", "fair": true, "hz": 5, "races": ["human", "orc"], "license": "MIT" } ``` | Feld | Pflicht | Beschreibung | |---|---|---| | `id` | ✔ | Kleinbuchstaben, Ziffern, `-`, `_`, 2–41 Zeichen | | `entry` | ✔ | eine `.py`-Datei im Schemaverzeichnis (keine absoluten Pfade, kein `..`) | | `kind` | | Standard `bot` (Unterklasse von `openwar3.Bot`, spielt für dich); `mod` = [Gameplay-Mod](https://war3ai.com/de/docs/mods/) (Unterklasse von `openwar3.Mod`, läuft nie im Fair-Modus und entscheidet Sieg und Niederlage nicht nach Melee-Regeln) | | `class` | | Name der Bot- (oder Mod-)Unterklasse in der Einstiegsdatei; ohne Angabe wird die letzte `openwar3.Bot`-Unterklasse der Einstiegsdatei genommen | | `fair` | | Standard `true`: sieht nur, was in Sicht ist – dieselbe Regel wie in der Arena. `false` = ganze Karte sichtbar, und nur dann ist der [JASS-Kanal](https://war3ai.com/de/docs/jass/) nutzbar (Begleiter brauchen ihn) | | `judge` | | Standard `true`: Sieg und Niederlage nach Melee-Regeln. RPG- und Begleiter-Schemata setzen `false` | | `hz` | | wie oft pro Sekunde `on_tick` aufgerufen wird, Standard 5 | | `format` | | Version des Manifest-Formats, derzeit 1; ein neueres Format, als das lokale OpenWar3 kennt, wird mit dem Hinweis auf ein Update abgelehnt | | Sonstige | | `name`, `version`, `author`, `description`, `races`, `license`, `homepage`, `forked_from` dienen nur der Anzeige | Das Schemaverzeichnis wird dem Python-Modulsuchpfad hinzugefügt, die Einstiegsdatei kann also andere Dateien im selben Verzeichnis per `import` laden. Drittanbieter-Pakete (numpy, torch …) werden nicht automatisch installiert – schreib in `description`, was gebraucht wird. **Das kleinste Schema besteht aus zwei Dateien**: ```python # my_bot.py from openwar3 import Bot class MyBot(Bot): def on_tick(self, g): for w in g.idle_workers(): mine = g.nearest(g.gold_mines(), w) if mine: g.gather(w, mine) ``` ```json {"id": "my-first", "name": "Meine erste KI", "entry": "my_bot.py"} ``` Leg es unter `schemes/mine/my-first/` ab und lade Farsight neu – schon erscheint es. Noch bequemer: unter „Eingebaut“ ein Beispiel wählen und auf „In Meine kopieren“ klicken. ## Ausführung und Ergebnisse Schemata werden vom **Schema-Runner** ausgeführt (genau den startet Farsight bei „Test starten / Wechseln“): ```bash python tools/run_scheme.py --inst 20 --scheme builtin/micro --hours 6 ``` - Pro Instanz läuft ein dauerhafter Überwachungsprozess, und **für jede Partie wird ein eigener Kindprozess gestartet**, der das Schema ausführt: Stürzt der Schemacode ab, reißt er den Überwachungsprozess nicht mit; änderst du den Code eines Schemas unter „Meine“, wird ab der nächsten Partie automatisch die neue Version verwendet. - Am Ende jeder Partie wird eine Ergebniszeile festgehalten: Schema, Version, Autor, Sieg/Niederlage, Grund, Spieldauer, Anzahl der Fehler. Daraus berechnet Farsight die Siegquote. So wird über Sieg und Niederlage entschieden: | Situation | Eintrag | |---|---| | Alle gegnerischen Gebäude zerstört | Sieg | | Alle eigenen Gebäude zerstört (auch wenn noch Truppen leben – so wird in Melee-Partien eine Niederlage entschieden) | Niederlage | | Alle eigenen Einheiten tot | Niederlage | | In Farsight manuell beendet / gestoppt | offen | | Beim Wechsel lief die Partie schon länger als 60 Spielsekunden (Übernahme mitten im Spiel) | separat gezählt, **nicht in der Siegquote** | | Spieluhr bewegt sich lange nicht | offen | Nach der Entscheidung schließt der Runner den Ergebnisbildschirm, startet die nächste Partie gemäß „Einstellungen fürs nächste Spiel“, und das Schema übernimmt wieder – du kannst es eine ganze Nacht laufen lassen und Ergebnisse sammeln. Eine Pause ist kein Ende: Während der Pause läuft der Bot normal weiter, nur die Spieluhr steht. ## Vertrauen und Sicherheit **Ein Schema ist Code und läuft mit denselben Rechten wie du selbst** (kann Dateien lesen und schreiben, kann aufs Netz zugreifen). Deshalb: - Schemata in `installed/` sind standardmäßig **nicht vertrauenswürdig**; Farsight und der Runner verweigern die Ausführung, bis du auf „Vertrauen“ klickst; - wird ein Schema mit derselben id durch eine neue Installation ersetzt, wird **das Vertrauen zurückgesetzt** (neue Version = neuer Code); - beim Import wird geprüft: zip höchstens 50 MB und höchstens 2000 Dateien; keine absoluten Pfade und kein `..` (damit nichts außerhalb des Schemaverzeichnisses geschrieben wird); ein ungültiges Manifest oder eine fehlende Einstiegsdatei führt zur sofortigen Ablehnung. > **Achtung** > > Bevor du vertraust: mit „Ordner öffnen“ den Code einmal durchlesen. Nimm Schemata nur von Leuten, denen du vertraust. ## API (für Skripte) | API | Beschreibung | |---|---| | `GET /api/schemes` | Schemaliste + Ergebnisse + das pro Instanz gewählte und gerade laufende Schema | | `GET /api/schemes/results?ref=` | die letzten 30 Partien eines Schemas | | `POST /api/schemes/import` | zip importieren | | `GET /api/schemes/export?ref=` | zip herunterladen | | `POST /api/schemes/fork` | In Meine kopieren | | `POST /api/schemes/trust` | Vertrauen | | `DELETE /api/schemes?ref=` | Löschen (abgelehnt, solange eine Instanz es verwendet) | | `POST /api/instances/{n}/scheme` | Schema einer Instanz wechseln: diese Partie sofort übernehmen oder ab dem nächsten Teststart | In Python direkt über die Bibliothek: `from openwar3 import schemes` (`list_schemes`, `install_zip`, `export_zip`, `fork`, `trust`, `stats` …). ## Später: Schema-Website Die exportierte zip ist die Einheit zum Teilen; die Website muss nur eine Schicht darüberlegen: aus Farsight mit einem Klick hochladen; auf der Website herunterladen – mit genau denselben Prüfungen wie beim „Schema importieren“ und ebenfalls mit Vertrauensbestätigung; optional Ergebnisse melden, die Website fasst die Siegquote pro Version zusammen. Der Button „Auf der Schema-Website teilen“ hat in Farsight schon seinen Platz. Den aktuellen Stand findest du in der [Roadmap](https://war3ai.com/de/roadmap/). --- # Sprechblasen & lokale Modelle > Lass jede Einheit im Spiel in beliebiger Rolle eine Sprechblase zeigen. Mit einem lokalen LLM geht ein Satz rein, und die Antwort erscheint über der Einheit. Sprechblasen sind eine Zuschauerschicht: Sie beeinflussen den Spielausgang nicht und eignen sich für Streams, Kommentar und Debugging. - Jede Einheit kann in jeder Rolle sprechen, auch mehrere Einheiten gleichzeitig; - Schriftgröße, Farbe, Breite, Zeiger, Transparenz und Tippgeschwindigkeit sind pro Blase einstellbar; - direkte Anbindung an ein lokales LLM (LM Studio) mit Streaming: Die Blase aktualisiert sich, während der Text entsteht. ## Aus einem Bot heraus Am einfachsten geht es mit `say` aus dem SDK: ```python g.say(hero, "Mir nach!", seconds=4) ``` ## Start und Oberfläche **Am einfachsten: das „Kontrollzentrum“ auf der Startseite von Farsight** – zuerst „Lokales LLM → Starten und Modell laden“ (lokaler LM-Studio-Server + das konfigurierte Modell in den VRAM laden), dann „Chat-Sprechblasen → Starten“. Auf den Karten kannst du Logs ansehen, stoppen und neu starten. Die Oberfläche ist die Seite „Sprechblasen“ links in Farsight: Einheiten sprechen lassen (Einheit wählen, Text schreiben, Stil anpassen, mit dem Modell chatten), Bauern-Kaffeeklatsch, Kamera-Dialoge, ereignisgesteuerte Kommentare, Modelleinstellungen – jeweils für die Instanz, die oben in der Leiste ausgewählt ist. Auch über die Kommandozeile: ```bash python speech/speak_launch.py # startet lokalen Modellserver + lädt und wärmt das Modell auf + startet Sprechblasen-API python speech/speak_launch.py --restart # API nach Codeänderungen neu starten python speech/speak_launch.py --stop # API stoppen und das Modell aus dem VRAM entladen ``` Jeder Schritt wird übersprungen, wenn er schon läuft – mehrfaches Ausführen hat keine Nebenwirkungen. ## HTTP-API Standard ist `http://127.0.0.1:8872/` (Port unter `ports.speech` in `openwar3.json`); jedes Programm kann sie aufrufen. ### Eine Einheit sprechen lassen `POST /api/say` ```json { "inst": 16, "bubbles": [ { "unit": "0x14A12614", "name": "Bergkönig", "text": "Mir nach!" }, { "unit": "0x14A12924", "name": "Erzmagier", "text": "Ich zaubere einen Blizzard.", "style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } }, { "screen": [960, 110], "key": 1, "name": "Erzähler", "text": "Die erste Orc-Welle kommt in 30 Sekunden.", "style": { "tail": false, "type_ms": 0 } }, { "world": [-4684, 2644], "key": 2, "text": "Sammelpunkt", "style": { "font_px": 16 } } ] } ``` | Feld | Beschreibung | |---|---| | `unit` / `world` / `screen` | Eins von dreien: folgt der Einheit (sitzt direkt über dem HP-Balken, falls vorhanden) / Kartenkoordinaten / Bildschirmpixel (für Erzähltext) | | `name` | Sprecher in der ersten Zeile – frei wählbar, muss nicht zur Einheit passen | | `text` | Inhalt, wird automatisch umbrochen | | `duration_ms` | Anzeigedauer; 0 = automatisch 3–5 Sekunden | | `key` | Nummer für Welt- / Bildschirmblasen; eine neue Nachricht mit gleichem key ersetzt die alte | | `update` | Gibt es dieselbe Blase schon, nur den Text tauschen, ohne den Timer zurückzusetzen (für Streaming) | | `style` | `font_px`, `max_width_px`, `text_color`, `bg_color`, `border_color`, `tail`, `side_px`, `opacity`, `type_ms`, `font` … | Maximal 32 Blasen gleichzeitig; Kosten pro Frame im Schnitt etwa 0.1–0.2 ms. ### Mit einem lokalen Modell chatten `POST /api/chat` ```json { "inst": 16, "unit": "0x14A12614", "name": "Bergkönig", "persona": "Du spielst Muradin, den Bergkönig aus Warcraft: rau, herzlich, trinkfest. Ein, zwei Sätze Umgangssprache, höchstens 15 Wörter.", "message": "Da vorne ist ein Haufen Oger. Greifen wir an?", "stream": true } ``` Zurück kommt `{"reply": "...", "first_token_ms": 283, "total_ms": 342}` – und die Antwort steht bereits über der Einheit. Jede Einheit merkt sich die letzten 6 Gesprächsrunden. ### Sonstiges | API | Beschreibung | |---|---| | `GET /api/instances` | Laufende Spiele | | `GET /api/units?inst=16&mine=true&heroes=true` | Einheitenliste (mit chinesischem Namen, Koordinaten, HP) | | `POST /api/clear` | Eine oder alle Blasen entfernen | | `GET /api/llm`, `POST /api/llm` | Modellkonfiguration lesen / ändern (`base_url`, `model`, `max_tokens`, `temperature`) | | `POST /api/banter` | Bauern-Kaffeeklatsch: Die Arbeiter zu Hause lästern reihum nach ihrer Persona, dazu eine Ansage zum Spielstart (alle Spieldaten sind echt) | | `POST /api/camtalk` | Kamera-Dialog: Helden und Gefolge im Bild reden ihrer Rolle entsprechend | | `POST /api/events` | Ereignisgesteuert: Kampfbeginn, Kampfende, Held gefallen, Tier-Up, Gebäude zerstört … gesprochen wird nur, wenn etwas passiert | ## Welches lokale Modell? Gemessen auf einer RTX 5090 (5 Spielzeilen): | Modell | VRAM | Tempo | Eine Antwort | Fazit | |---|---|---|---|---| | **Qwen3.6-35B-A3B** (MoE, pro Token nur 3B aktiv), Q4, Thinking aus | 20.6 GB | ca. 142 Token/s | **ca. 0.3 s** (erstes Token nach ca. 0.27 s) | Empfehlung: schnell, natürliches Rollenspiel auf Chinesisch | | gpt-oss-20b (MXFP4), Reasoning low | 11.3 GB | ca. 280 Token/s | 0.3–0.8 s | bei knappem VRAM; Chinesisch etwas flach | | Qwen3.6-27B (dense), Q4 | 17.2 GB | ca. 39 Token/s | nach 5.5 s noch am Denken | nicht für Echtzeit-Dialoge geeignet | - **Das Tempo hängt von den aktiven Parametern ab, nicht von der Gesamtgröße**: Das 35B-MoE aktiviert nur 3B und ist 3- bis 4-mal schneller als das dichte 27B. - **„Thinking“ unbedingt abschalten**: Sonst gehen alle Tokens ins Denken, und es kommt kein einziges Wort Antwort. - Die Blase tippt etwa 22 Zeichen pro Sekunde, die Generierungsgeschwindigkeit ist also kein Engpass mehr. Was das Erlebnis wirklich bestimmt, ist die **Latenz bis zum ersten Token**. > **Damit die Zeilen „echt“ wirken** > > Gib dem Modell ausschließlich echte Spieldaten (Anzahl Spiele, Siege/Niederlagen, Truppenstärke, Vorräte) und sag ausdrücklich: „Verwende nur diese Fakten.“ Ohne diese Einschränkung hat das Modell im Test Kämpfe erfunden, die nie stattgefunden haben. --- # Gateway > WebSocket-/JSON-Gateway: Die öffentlichen APIs, die das Python-SDK aufrufen kann, stehen auch JS, C#, Go, Rust, Browserseiten und Programmen auf anderen Rechnern offen. Drei Rollen, mit JS-Client und Demoseite für den Browser; die Latenz ist die der Schnellspur plus ca. 1 ms. Das Gateway verpackt Schnellspur und gepushten Zustand als **WebSocket / JSON**. Die öffentlichen APIs aus dem [API-Katalog](https://war3ai.com/de/api/), die das Python-SDK aufrufen kann, stehen damit auch JS, C#, Go, Rust, Browserseiten, Programmen auf anderen Rechnern und LLMs offen – mit denselben Methodennamen und Parametern. Die Latenz ist die der Schnellspur plus ca. 1 ms. **Am einfachsten: „Kontrollzentrum“ auf der Startseite von Farsight → Gateway → Starten** (Stoppen, Neu starten, Logs ansehen und die Demoseite öffnen gehen ebenfalls über diese Karte). Über die Kommandozeile: ```bash python gateway/server.py # ws://127.0.0.1:8870/ws (Port unter ports.gateway in openwar3.json) python gateway/server.py --open # wie oben, öffnet die Demoseite http://127.0.0.1:8870/demo, sobald der Port lauscht python gateway/server.py --host 0.0.0.0 # fürs LAN: verlangt automatisch ein Token (bin/gateway/token.txt) python gateway/server.py --allow-origin http://localhost:5173 # damit sich auch deine eigene Webseite verbinden kann ``` ## Verbindung und Rollen Verbindungsadresse: `ws://127.0.0.1:8870/ws?inst=9&role=dev` (statt `inst=` geht auch `pid=`; wird ein Token verlangt, `&token=` anhängen). | Rolle | Darf aufrufen | Geeignet für | |---|---|---| | `dev` | alles: Beobachten, Befehle, Spielsteuerung, Sandbox (Welt per JASS verändern), Oberfläche zeichnen | lokale Tools, [Gameplay-Mods](https://war3ai.com/de/docs/mods/), Begleiter | | `player` (mit `&player=N`) | Beobachten, Einheiten von Spieler N befehligen, Oberfläche zeichnen; **standardmäßig im Fair-Modus**, sieht nur, was in der Sicht von Spieler N liegt (`&fair=0` schaltet das ab) | Bots oder LLMs, die für einen bestimmten Spieler antreten | | `observer` | nur lesen (die Runtime lehnt seine Befehle direkt ab) | Zuschauen, Kommentar, Datenerfassung | `player` bekommt nicht: Spielsteuerung wie Spiel beenden, Tempo ändern oder Pausieren, `players` und `enemy_ai_plan`, die die verdeckten Karten der anderen zeigen, `canvas.image`, das den Spielprozess eine lokale Datei öffnen ließe, und JASS. Abfragen mit Spielernummer wie `resources`, `tech` und `stats` gehen nur für den eigenen Spieler. Eine Verbindung ist eine Sitzung und belegt eine Schnellspur (die Runtime hat insgesamt 16). Das Gateway erlaubt höchstens 12 Sitzungen gleichzeitig, damit ein paar für Bots, Mods und Farsight frei bleiben. Beim Trennen wird nur entfernt, was diese Sitzung selbst gezeichnet hat, samt ihren Hotkeys; was andere Programme gezeichnet haben, bleibt. ## Nachrichten Nach dem Verbinden kommt zuerst `hello`: Protokollversion, Rolle, Prozess-ID des Spiels und die Liste der Methoden, die diese Rolle aufrufen darf. Danach trägt jede Anfrage eine `id`, und die Antwort trägt dieselbe `id`: ```json → {"id": 1, "op": "call", "method": "units", "args": ["me"]} ← {"id": 1, "ok": true, "result": [{"addr": 123456, "type": "hpea", "owner": 0, "x": -4480, "y": 2120, "hp": 220, "hp_max": 220}]} → {"id": 2, "op": "call", "method": "move", "args": [[{"unit": 123456}], 100, 200]} → {"id": 3, "op": "call", "method": "ui.button", "args": ["shop", "Trank kaufen"], "kwargs": {"screen": [40, 300]}} → {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true} ← {"type": "state", ...} {"type": "events", ...} danach laufend gepusht → {"id": 5, "op": "overview"} Lage auf einer Seite: Ressourcen, Einheitenzahlen, Helden, sichtbare Gegner, Produktion → {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"} nur für dev → {"id": 7, "op": "api"} Methodenkatalog (außerdem ping / unsubscribe) ``` - **Einheitenparameter** schreibst du als `{"unit": Adresse}`; die Adresse ist `addr` aus dem Einheiten-JSON. Optional mit `"handle": [lo, hi]`, um zu prüfen, dass die Adresse nicht von einer anderen Einheit wiederverwendet wurde. - **Methodennamen** sind die öffentlichen Methoden von Game, dazu `ui.*` (button / choice / toast / hotkey / mouse / cursor …), `canvas.*` (text / panel / bar / image / circle / path / remove …) und `jass.` (nur für dev). - Callback-Funktionen lassen sich von entfernt nicht übergeben: Klicks und Hotkeys kommen über die Event-Pushes, das `ui.click`-Event trägt den `key`. Siehe [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/). - Schlägt ein Aufruf fehl, betrifft die Antwort nur diesen einen (`ok: false` plus `error`); die Verbindung bleibt bestehen. Das gilt auch, wenn das Gesendete kein JSON ist. - Die Felder im Event-JSON entsprechen dem [W3P-Protokoll](https://war3ai.com/de/docs/protocol/), dazu kommen Komfortfelder (`spell`, `key`, `text`, `chat`, `button`, `player`, `mods`). HTTP geht auch, praktisch für einzelne Aufrufe und curl: `GET /api?role=player` listet den Methodenkatalog, `POST /call` mit `inst`, `role`, `method`, `args`, `kwargs` ruft einmal auf. `/call` verwendet Sitzungen wieder: Startet das Spiel neu und läuft in einem neuen Prozess, wird automatisch eine neue Sitzung geöffnet; Sitzungen, die 10 Minuten untätig waren, werden geschlossen. ## Clients **JS** (Browser oder Node 22+, ohne Abhängigkeiten): `gateway/clients/js/openwar3.mjs` ```js import { OpenWar3, unit } from "./openwar3.mjs"; const ow = new OpenWar3("ws://127.0.0.1:8870/ws", { inst: 9, role: "dev" }); await ow.connect(); const mine = await ow.api.units("me"); await ow.api.move(mine.slice(0, 3).map(unit), 100, 200); await ow.api.ui.button("hi", "Klick mich", { screen: [40, 300] }); // letztes einfaches Objekt = Keyword-Argumente ow.on("event:ui.click", (e) => console.log("Geklickt:", e.key)); await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true }); ``` Node 20 / 21 braucht `--experimental-websocket`. Ein vollständiges Beispiel liegt in `gateway/clients/js/example.mjs`. **Demoseite für den Browser** `http://127.0.0.1:8870/demo`: Lage, Tabelle der eigenen Einheiten, einen Button ins Spiel setzen, Event-Stream – alles auf einer Seite. **Andere Sprachen**: Eine beliebige WebSocket-Bibliothek + das JSON oben genügt; Shared Memory musst du nicht anfassen. **LLMs**: Nimm direkt den [MCP-Server](https://war3ai.com/de/docs/mcp/) – er stellt die häufigen Aufgaben als fertige Tools bereit. ## Gemessen 2026-09-25, Punkt für Punkt an einer echten Partie geprüft: 16/16 (Gateway 9 + MCP 7): Handshake (Rolle dev mit 121 Methoden), `units('me')`, Lage auf einer Seite, Bildschirmhinweis, Button setzen; nach dem Abonnieren den Button im Spiel angeklickt → `ui.click` wird zum Client gepusht; JASS; eine ungültige Einheit führt nur bei diesem einen Aufruf zu einem Fehler; HTTP `/call` (Rolle observer). Auch der JS-Client (Node) und die Demoseite wurden getestet: Der von der Webseite gesetzte Button wurde im Spiel angeklickt, und das Event-Log der Webseite hat `ui.click` empfangen. ## Sicherheit - Standardmäßig lauscht es nur lokal auf `127.0.0.1`, ohne Token (wie Farsight). Ist `--host` keine lokale Adresse, wird automatisch ein Token verlangt; `--auth` verlangt es auch lokal. - **Andere Websites im Browser kommen nicht rein**: Verbindungen aus dem Browser tragen immer eine Herkunft (`Origin`); das Gateway akzeptiert nur seine eigene Demoseite und die mit `--allow-origin` angegebenen Adressen. Programme wie Python, Node oder curl senden keine Herkunft und verbinden sich ganz normal. Lauscht es nur lokal, prüft es zusätzlich `Host` und blockt so Angriffe, die eine externe Domain auf den lokalen Rechner auflösen lassen. - Die Rolle deklariert der Client beim Verbinden selbst: Im lokalen Modus ist sie eine Konvention, keine Sicherheitsgrenze. In der Arena muss der Schiedsrichter-Prozess entscheiden, wer welche Rolle bekommt, siehe [Arena](https://war3ai.com/de/arena/). --- # W3P-Protokoll > Der komplette Vertrag zwischen Runtime und externen Programmen: acht Shared-Memory-Blöcke, Weltzustand lesen, Events lesen, Befehle erteilen, Quittungen, Spur-Rollen, Canvas sowie Oberfläche & Eingabe. Lies diese Seite, wenn du aus einer anderen Sprache als Python andockst. Runtime und externe Programme tauschen Daten **ausschließlich über Shared Memory** aus. Das Folgende ist alles, was es dazu gibt. - Die Referenzimplementierung ist in Python: `sdk/python/w3world.py` (Lesen) und `sdk/python/w3fast.py` (Schreiben); Größe und Offsets jeder Struktur sind dort definiert und durch Tests festgenagelt; - **Das Protokoll beschreibt nur Semantik und ist unabhängig von der Spielversion.** Wechselt die Spielversion, passt sich die Runtime selbst an, das Protokoll bleibt gleich; neue Felder werden nur ans Blockende angehängt, alte Clients funktionieren weiter. > **Info** > > Die meisten brauchen diese Seite nicht – nimm einfach das Python-SDK. Du brauchst sie nur, wenn du direkt aus C++ / C# / Rust / Go oder einer anderen Sprache andocken willst oder wissen möchtest, was unter dem SDK passiert. ## 1. Acht Shared-Memory-Blöcke `` ist die Prozess-ID des Spiels. | Name | Richtung | Inhalt | Synchronisation | |---|---|---|---| | `Local\War3World_` | Runtime → du | Weltzustand: Header + 16 Spieler + bis zu 1024 Einheiten + 256 Einheitendetails + 256 Gegenstände am Boden + Erweiterungsbereich + Produktionstabelle | seqlock | | `Local\War3Trees_` | Runtime → du | Bis zu 4096 zerstörbare Objekte (Bäume usw.), alle 2 Sekunden aktualisiert | seqlock | | `Local\War3Events_` | Runtime → du | Event-Ring, 8192 Einträge | jeder Eintrag trägt eine eigene Sequenznummer | | `Local\War3Map_` | Runtime → du | Karte: Geländezellen (128 pro Zelle, bis zu 256×256) + Grenzen des spielbaren Bereichs + Startpositionen; wird in den ersten Sekunden der Partie in Etappen berechnet | seqlock (ändert sich nach der Berechnung nicht mehr) | | `Local\War3Fast_` | beide Richtungen | Befehlsspuren: 16 Spuren × 16 Slots; jeder Slot enthält einen Befehl + Quittung; jede Spur hat eine Rolle | pro Slot ein Schreiber, ein Leser | | `Local\War3Canvas_` | du → Runtime | [Canvas](https://war3ai.com/de/docs/canvas/): Header 64 Bytes + 256 Elemente × 112 Bytes + 64 KB Text- / Punkt-Pool; wird erst nach einem `canvas_enable` angelegt | seqlock (du schreibst, die Runtime liest in jedem Frame) | | `Local\War3Msgs_` | Runtime → du | Ring der Bildschirmnachrichten: voller Text von Spielhinweisen, Chat und Systemnachrichten, 128 Einträge × 256 Bytes | jeder Eintrag trägt eine eigene Sequenznummer | | `Local\War3Input_` | beide Richtungen | [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/): Die Runtime schreibt Mausposition, den Bodenpunkt unter dem Mauszeiger und das Element unter dem Mauszeiger zurück; du schreibst die Hotkey-Tabelle und die Maus-Schalter; die Runtime übernimmt die Eingabe erst nach einem `input_enable` | Hotkey-Tabelle per seqlock | **Mehrere Clients nutzen Canvas und Eingabe gleichzeitig**: Beide Blöcke gibt es nur einmal; schreibt jeder für sich, überschreiben sie sich gegenseitig. Es gelten diese Konventionen, auch für deinen eigenen Client: - **Canvas**: Den benannten Mutex `Local\War3CanvasMutex_` halten und lesen – ändern – schreiben: nur die eigenen Elemente ersetzen, die der anderen unverändert lassen (Pool-Offsets neu anordnen); Elemente, deren Besitzerprozess beendet ist oder die keinen Besitzer haben, entfernen. Im Element ist `reserved[1]` = Prozess-ID des Besitzers, `reserved[2]` = laufende Nummer im Prozess; Elementnummern vergibt der Zähler bei Offset 60 im Blockheader (ab `0x10000`). - **Eingabe**: Jeder Client trägt seine Hotkeys und Maus-Schalter in `Local\War3InputClients_` ein (Header 16 Bytes + 16 Clients × 528 Bytes), aktualisiert unter `Local\War3InputMutex_` seinen eigenen Eintrag und schreibt dann die lebenden Clients zusammengeführt in den Eingabeblock: Hotkeys werden nach „Tastencode + Modifikatortasten“ dedupliziert, Maus-Schalter vereinigt. Events gehen an alle Clients, jeder erkennt seine Hotkeys an „Tastencode + Modifikatortasten“. Solange im Register noch andere lebende Clients stehen, kein `input_enable 0` senden. - **Runtime**: Klickbare Elemente, deren Besitzerprozess beendet ist, fangen keine Klicks mehr ab; alle 2 Sekunden prüft die Runtime das Register, und haben sich alle eingetragenen Clients beendet, setzt sie Hotkey-Tabelle und Maus-Schalter im Eingabeblock auf null. ## 2. Weltzustand lesen (seqlock) ```text loop: s1 = block.seq (Offset 8, int32) if s1 ungerade: erneut (Runtime schreibt gerade) kopiere Header + players + units[unitCount] + details[detailCount] + items[itemCount] if block.seq != s1: erneut ``` - **Header**: Veröffentlichungszähler (steigt er nicht, ist die Veröffentlichung abgerissen), Spieluhr der Engine, eine Epoche, die pro Partie um 1 steigt, eigene Spielernummer, ob gerade eine Partie läuft, Spielgeschwindigkeit, Veröffentlichungsperiode, Mikrosekunden, die das Erfassen dieser Kopie im Spiel-Thread gekostet hat, Event-Sequenznummer, Zeiten pro Abschnitt. Clients können `requestedPeriodMs` schreiben, um eine Veröffentlichungsperiode anzufordern (16 ~ 1000 ms). - **Einheit** (112 Bytes): Handle-Paar (**Einheiten am Handle-Paar erkennen** – Adressen werden wiederverwendet), Typ als Vier-Zeichen-Code, Besitzer, Flags, Koordinaten, TP / Mana (mit Maximum), aktuelle Order + Order-Ziel, Aufgabenziel (was sie tatsächlich angreift), Heldenstufe / EP / Fertigkeitspunkte, Detail-Index, `visibleTo` (Bit p = Spieler p sieht sie gerade). - **Details** (288 Bytes, Helden > Spielereinheiten > Creeps, bis zu 256): 12 Fähigkeiten (Code / Stufe / Flags / verbleibende Abklingzeit in Sekunden), 8 Buff-Codes, 6 Inventarplätze. - **Erweiterungen am Blockende** (nur angehängt, frühere Offsets verschieben sich nie, alte Clients funktionieren weiter): Erweiterungsbereich `EXT1` (Tageszeit im Spiel, Tag-/Nacht-Geschwindigkeit, Anzahl der Einträge in der Produktionstabelle) und Produktionstabelle `prods[128]` (Gebäude, die gerade trainieren / forschen / bauen / aufwerten, Warteschlange, Gesamtdauer, bereits vergangene Zeit, ob sie hängen). **Nur verwenden, wenn die Magic passt.** ## 3. Events lesen ```text head = ring.writeSeq (Offset 8) for seq in (cursor, head]: e = ring.events[(seq - 1) % 8192] if e.seq > seq: einer verloren (zu langsam gelesen, überschrieben) elif e.seq != seq: noch nicht fertig geschrieben, beim nächsten Mal lesen else: e verarbeiten ``` Die Event-Struktur hat 64 Bytes: `seq kind clockMs addr handleLo handleHi typeId owner a b x y value extra`. - Aus dem Vergleich zweier aufeinanderfolgender Veröffentlichungen abgeleitet (Genauigkeit = Veröffentlichungsperiode): `unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started`; - Auf Engine-Ebene (die Runtime zeichnet sie sofort im Spiel-Thread auf, es gibt also einen für **jeden einzelnen Treffer**): `damage` (Quelle, Schadenstyp, Angriffstyp, Position, tatsächlicher TP-Verlust, Schaden vor Rüstung), `killed` (Killer); - Aus dem Verfolgen der Produktionstabelle abgeleitet: `production.done` (Vier-Zeichen-Code des Fertiggestellten, Kategorie, benötigte Spielsekunden; wird auch für Gegner ausgegeben); - Von der Runtime bei jeder Veröffentlichung nebenbei geprüft: `spell.cast` (Abklingzeit einer Fähigkeit startet: `a` Vier-Zeichen-Code der Fähigkeit, `b` Stufe, `value` Abklingzeit in Sekunden, `x/y` Zielpunkt), `player.left` (`a` Spielernummer, `b` neuer Slot-Status), `selection.changed` (Auswahl des lokalen Spielers; die vollständige Liste steht im Erweiterungsbereich des Weltblocks), `game.ended` (Partie verlassen); - Bildschirmnachrichten: `message` (`a` = Nachrichten-Sequenznummer; den vollen Text schlägst du im Shared Memory `Local\War3Msgs_` nach: 128 Einträge × 256 Bytes, mit Spielhinweisen, Chat und Systemnachrichten; `b` = Nummer des Nachrichtenfelds); - Oberfläche & Eingabe (nach `input_enable`): `ui.click` (`a` ID des Canvas-Elements, `b` 1 linke / 2 rechte Maustaste), `ui.hover`, `hotkey` (`a` Hotkey-ID, `b` virtueller Tastencode), `mouse.world` (`x/y` Bodenkoordinaten, `value` = 1 heißt verschluckt); Modifikatortasten stehen jeweils in `extra`. ## 4. Befehle erteilen 1. **Ein Client-Objekt belegt eine Spur**: `Local\War3FastMutex_` halten, eine freie Spur suchen (oder eine, deren Besitzerprozess tot ist), Rolle, Spielernummer und die eigene pid eintragen. Braucht derselbe Prozess zwei Rollen, öffnet er zwei Spuren; 2. Slots füllen: Flag für semantischen Befehl, Opcode, `args[11]`, Deadline `deadlineMs`; 3. Sind alle Slots geschrieben, als eingereicht markieren und `submitSeq` der Spur um 1 erhöhen; 4. Auf das Event `Local\War3FastDone__` warten (oder pollen), Quittungen lesen, Slots zurückgeben. Die Runtime führt die Befehle gebündelt im Event-Dispatch des Spiel-Threads aus: Ein Abarbeitungsdurchlauf hat ein Zeitbudget von **4 ms** (echter hochauflösender Timer); was darüber hinausgeht, bleibt für den nächsten Dispatch liegen. **Slots mit abgelaufener Deadline werden nie mehr ausgeführt** – es kann also nicht passieren, dass alte Befehle nach dem Fortsetzen einer Pause noch einmal laufen. `args`-Indizes: `0..2` Einheit (Adresse, Handle lo, Handle hi), `3` Order-ID oder Vier-Zeichen-Code, `4..6` Ziel, `7/8` x / y (Float-Bits), `9` extra (Spielernummer / Slotnummer / Schalter / Warteschlangenposition), `10` mode (0 ohne Ziel / 1 auf Punkt / 2 auf Ziel). ### Opcodes | Opcode | Name | Beschreibung | |---|---|---| | 1 | `point` | Order einer Einheit auf einen Punkt (Bewegen / Angriffsbewegung / Patrouillieren / Boden angreifen / Zauber auf Punkt). extra bit0 = anstellen (hinter die aktuelle Order einreihen) | | 2 | `target` | Order einer Einheit auf ein Ziel (Rechtsklick-Angriff / Ernten / Reparieren / Zauber auf Ziel / Gegenstand aufheben); das Ziel muss sichtbar sein | | 3 | `immediate` | Befehl ohne Ziel (Stopp / Position halten / Trainieren / Forschen / Aufwerten / Zauber ohne Ziel) | | 4 | `build` | Arbeiter errichtet ein Gebäude (Koordinaten auf 32 ausgerichtet) | | 5 | `learn` | Held erlernt eine Fähigkeit | | 6 | `use_item` | Inventarplatz Nummer extra benutzen | | 7 | `revive` | Held am Altar wiederbeleben | | 8 | `rally` | Sammelpunkt (auf Punkt / auf Ziel) | | 9 | `buy` | Ein Laden verkauft einem Helden in der Nähe einen Gegenstand | | 10 | `item_drop` | Gegenstand abgeben: einem Verbündeten geben, an einen Laden verkaufen (`code` = empfangende Einheit) oder auf den Boden legen | | 20 ~ 25 | Abfragen | `q_tech` Tech-Zähler, `q_feasible` Machbarkeit, `q_visible` Sichtbarkeit, `q_mine_gold` Restgold einer Mine, `q_captain` Computer-Captain, `q_dead_heroes` Liste toter Helden | | 30 | `pause` | Pause / Fortsetzen | | 40 ~ 50 | Kamera | Kamerazustand lesen, Feld setzen, auf einen Punkt blicken, folgen, zurücksetzen, drehen, Grenzen, Glättung, UI ein-/ausblenden, sauberes Bild, Nebel | | 60 ~ 63 | HUD | Text des Quest-Buttons, Titel und Beschreibung des Quest-Panels, aktualisieren, lesen, ob das Panel geöffnet ist | | 70 | `jass` | JASS-Native per Name aufrufen (1291 Stück): Name und String-Parameter stehen im Zusatzbereich des Slots, die übrigen Parameter laut Signatur in `args`; der Rückgabewert steht in `value[0]`. Nur für die Spur lokaler Tools; Aufrufe mit Funktionsparametern oder solche, die den Skript-Thread anhalten würden, werden grundsätzlich abgelehnt. Siehe [JASS-Kanal](https://war3ai.com/de/docs/jass/) | | 71 / 72 | `jass_handle_of` / `jass_unit_of` | Einheit im Snapshot ↔ JASS-Handle umrechnen (das Handle-Paar im Snapshot ist kein JASS-Handle) | | 73 | `canvas_enable` | Legt den Canvas-Shared-Memory an und installiert den Zeichen-Hook; jede Spur darf es senden (das Canvas zeichnet nur ins lokale Bild). Beim ersten Mal muss der Hook installiert werden – Timeout von mindestens 2 Sekunden vorsehen | | 74 | `input_enable` | `extra` = 1 übernimmt die Eingabe des Spielfensters (Klicks / Hover auf Canvas-Elemente, Hotkeys, Klicks auf den Boden), 0 = zurückgeben. Eingabeblock `Local\War3Input_`: Header 128 Bytes + 32 Hotkeys × 16 Bytes; du schreibst die Hotkey-Tabelle und die Maus-Schalter, die Runtime schreibt Mausposition, den Bodenpunkt unter dem Mauszeiger und das Element unter dem Mauszeiger zurück. Jede Spur darf es senden (betrifft nur die lokale Eingabe). Siehe [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/) | ## 5. Quittungen Eine Quittung hat 52 Bytes (+8 Bytes Zeitmessung): `status`, `engineReturn`, `verdict` (Grundcode der Ablehnung), `orderBefore / orderAfter` (Order der Einheit, im selben Frame zurückgelesen), `value[8]` (Abfrageergebnisse), `execUs` (wie viele Mikrosekunden dieser Befehl im Spiel-Thread lief), `engineUs` (davon in der Order-Funktion der Engine selbst). Alle Statuscodes und Grundcodes findest du unter [Quittungen und Grundcodes](https://war3ai.com/de/docs/reason-codes/). ## 6. Spur-Rollen | Rolle | Was sie darf | |---|---| | `dev` | Lokale Tools: semantische Befehle (für Einheiten des lokalen Spielers) + JASS-Kanal | | `player` | Nur semantische Befehle, und nur für Einheiten des Spielers, dem die Spur gehört (fremde = `not_owner`) | | `observer` | Nur Abfragen, Kamera, Lesen des HUD-Panel-Zustands, Canvas und lokale Eingabe aktivieren; alles andere ist `forbidden` | Zwei KIs gegeneinander = zwei `player`-Spuren in derselben Partie (player 0 / player 1). > **Achtung** > > Im lokalen Modus deklariert der Client seine Rolle selbst (eine Konvention, keine Sicherheitsgrenze). In der [Arena](https://war3ai.com/de/arena/) legt der Schiedsrichter-Prozess die Spuren an und übergibt jedem Teilnehmer nur die `player`-Spur. ## 7. Im Live-Spiel verifizierte Semantik - Rechtsklick (smart) auf einen Gegner = **genau diesen** angreifen (Order-Ziel und Aufgabenziel sind beide diese Einheit); ein roher Angriffsbefehl als Zielbefehl setzt nur die Angriffs-Order, ohne sich das Ziel zu merken – die Einheit greift dann etwas anderes in der Nähe an; - Die Engine lässt keine Zielbefehle auf Einheiten zu, die du nicht siehst: Nach Einbruch der Nacht liegen entfernte Lager im Kriegsnebel, und jeder Rechtsklick wird abgelehnt (1001); - Ein „angenommener“ Bau bedeutet nur, dass der Arbeiter den Befehl übernommen hat: Auch ein Punkt im Wald wird sofort angenommen, der Arbeiter scheitert erst, wenn er dort ankommt; offensichtlich belegte Punkte werden sofort abgelehnt; - Ein Held lässt sich erst etwa 3 Spielsekunden nach seinem Tod wiederbeleben; auch bei zu wenig Nahrung wird abgelehnt (Helden kosten Nahrung); - Gegenstände im Inventar zählen nicht als Gegenstände am Boden; beim Aufheben wird `item.removed` ausgegeben; - Während der Pause steht die Engine-Uhr still, Befehle lassen sich aber weiterhin erteilen; - Wird das Spiel minimiert gestartet, steht die Simulation (die Uhr läuft nicht). --- # Quittungen & Reason-Codes > Jede Befehlsquittung enthält einen Statuscode und einen Reason-Code. Daran korrigieren sich Bots und Agents selbst: Aus „Warum hat es nicht geklappt?“ wird eine maschinenlesbare Zahl. ```python r = g.train(barracks, "hfoo") bool(r) # False r.status # 1 -> rejected r.verdict # 3 -> nicht genug Nahrung r.reason # 'rejected(人口不够)' (= nicht genug Nahrung) r.exec_us # wie viele Mikrosekunden dieser Befehl auf dem Spiel-Thread lief ``` `if r:` entspricht `r.status == 0` (von der Engine angenommen). ## Statuscode `status` | Code | Name | Bedeutung | Häufige Ursache | |---|---|---|---| | 0 | `accepted` | Von der Engine angenommen | — (aber angenommen ≠ erledigt, siehe unten) | | 1 | `rejected` | Von der Engine abgelehnt | siehe `verdict` | | 2 | `bad_unit` | Einheit existiert nicht oder Handle passt nicht | Einheit ist schon tot; veraltetes Einheitenobjekt verwendet | | 3 | `not_owner` | Nicht deine Einheit | Als `player` fremde Einheiten befehligt | | 4 | `fault` | Ausnahme bei der Ausführung (von der Runtime abgefangen, das Spiel läuft weiter) | Bitte mit Reproduktionsschritten melden | | 5 | `bad_args` | Falsche Argumente | Koordinaten, Slot-Nummer oder Rawcode falsch | | 6 | `unsupported` | Nicht unterstützt | Diese Runtime-Version hat die Fähigkeit nicht | | 7 | `bad_target` | Ungültiges Ziel | Ziel existiert nicht mehr; falscher Zieltyp | | 8 | `forbidden` | Rolle der Spur erlaubt das nicht | Befehl als `observer` erteilt | | 97 | `cancelled` | Im Batch-Block wurde eine Ausnahme geworfen, der ganze Batch wurde nicht gesendet | Fehler im Code innerhalb von `with g.batch():` | | 98 | `held` | Einheit wird von einer höher priorisierten Schicht gehalten, Befehl nicht gesendet | Die Reflex-Schicht des Referenz-Brains oder ein manueller Befehl aus der Konsole hält die Einheit gerade | | 99 | `timeout` | Zeitüberschreitung | Spiel pausiert / hängt, Deadline überschritten (abgelaufene Befehle werden nicht mehr ausgeführt) | ## Reason-Code `verdict` Bei einer Ablehnung nennt die Runtime den Grund anhand der engine-eigenen Machbarkeitsprüfung. Du kannst auch vorher fragen, statt direkt zu befehlen: `g.can_do(unit, code)` liefert dieselben Codes. | Code | Bedeutung | Was tun | |---|---|---| | 0 / 220 | Möglich | — | | 3 | Nicht genug Nahrung | Nahrungsgebäude bauen; mit `g.production(b).blocked` früh erkennen | | 8 | Nicht genug Gold | Warten; vor dem Befehl `g.can_afford(code)` prüfen | | 9 | Nicht genug Holz | Mehr Arbeiter ins Holz schicken | | 32 | Ausbildungswarteschlange voll (7 Plätze) | Nur 1 Einheit einreihen: nachlegen, wenn `g.queue(b)` leer ist | | 183 | Voraussetzung (Technologie / Gebäude) fehlt | Erst das vorausgesetzte Gebäude bauen, Tier-Up | | 185 | Gebäude beschäftigt | Der Altar belebt gerade einen Helden wieder; das Hauptgebäude kann nicht aufwerten, solange seine Warteschlange nicht leer ist | | 221 | Nicht verfügbar / im Bau / in Aufwertung / existiert bereits | Held existiert schon (ist er tot: `revive`); dieser Laden verkauft das nicht | | 89 | Laden hat die Ware noch nicht | Zu Spielbeginn gibt es Waren erst nach der Lagerzeit aus der Gegenstandstabelle; bei einem neu gebauten Laden zählt die Zeit ab Fertigstellung | | 1001 | Ziel nicht sichtbar | Ziel steht im Nebel oder in unerforschtem Gebiet; `attack_move` auf seine Position | ## Angenommen ≠ erledigt Die Quittung sagt nur, dass die Engine den Befehl angenommen hat – sie wird im selben Frame zurückgelesen. Was danach passiert, erfasst sie nicht: | Befehl | Kann nach „angenommen“ noch scheitern | So prüfst du es | |---|---|---| | Bauen | Ein Punkt im Wald wird sofort angenommen, scheitert aber erst, wenn der Arbeiter ankommt | `build_near` verwenden (verfolgt, ob das Fundament erscheint), oder auf `production.done` warten | | Zaubern | Unterbrochen, kein Mana | Im nächsten Tick prüfen, ob `g.cooldown(u, ability)` läuft | | Ausbilden | In der Warteschlange, aber zu wenig Nahrung – startet nie | `g.production(b).blocked` | | Bewegen / Angreifen | Von anderer Logik (oder einer höher priorisierten Schicht) überschrieben | `g.current_target(u)`, `g.order_of(u)` | ## Abfrage-APIs Diese APIs erteilen keine Befehle, sondern fragen nur die Engine; das Ergebnis steckt im `value` der Quittung (das SDK gibt den Wert direkt zurück): | API | Rückgabe | |---|---| | `g.can_do(u, code)` / `g.can_do_many([(u, code), ...])` | Reason-Code aus der Tabelle oben | | `g.tech(code, player=None)` / `g.tech_many([...])` | Forschungsstufe / Anzahl fertiger Gebäude (Aufwertungskette eingerechnet) | | `g.visible(x, y)` | Ob dieser Punkt für uns sichtbar ist | | `g.gold_left(mine)` | Restgold der Goldmine | | `g.enemy_ai_plan(enemy_unit)` | Wohin der Anführer des Computergegners seine Truppen führen will (nur bei Computer-KI) | --- # Datenquellen > Herkunft und Genauigkeit jeder Datenart. Wenn etwas „komisch aussieht“, schau zuerst hier nach. | Daten | Quelle | Genauigkeit | |---|---|---| | Einheiten, Ressourcen, Orders, Fähigkeiten, Buffs, Inventar | Weltblock, den die Runtime alle 50 ms pusht | Veröffentlichungsintervall (bis 16 ms einstellbar) | | Schadens- und Kill-Events | Von der Runtime sofort im Spiel-Thread aufgezeichnet, jeder einzelne Treffer | Sofort | | Andere Events (erscheinen, sterben, Order-Wechsel, Level-up …) | Vergleich zweier aufeinanderfolgender Veröffentlichungen | Veröffentlichungsintervall | | Produktionstabelle (ausbilden / erforschen / bauen / aufwerten) | Timer-Felder der Produktionsfähigkeiten der Engine + von der Runtime aufsummierte verstrichene Zeit | ca. ±0.2 Spielsekunden | | Kampfwerte, Konter-Tabelle | Datentabellen des Spiels (aus deinem lokalen Spiel extrahiert) | Ohne Modifikatoren durch Gegenstände, Auren, Buffs | | Wegfindung | Begehbarkeit des Engine-Geländes (128 pro Zelle) + Bäume + Gebäudeflächen, A* im SDK | Eine Zelle; Lücken schmaler als eine Zelle gelten als unpassierbar | | Tageszeit im Spiel | JASS `GetFloatGameState(GAME_STATE_TIME_OF_DAY)` | Veröffentlichungsintervall | | Sichtbarkeit | Von der Runtime pro Einheit und Spieler berechnete Sichtbarkeitsmaske | Veröffentlichungsintervall | | Technologiezähler, Machbarkeit, Restgold | Abfrage über die Schnellspur, direkt an die Engine | Sofort | ## Spieldaten werden nicht mit dem Code verteilt Einheitentabellen, Fähigkeiten, Gegenstände, Helden, Buffs, Schadens-/Konter-Tabellen usw. stammen aus Blizzards Spieldateien und **liegen nicht im Repo**. Sobald du im „Kontrollzentrum“ von Farsight das Spielverzeichnis festgelegt hast, werden sie automatisch aus deinem eigenen Spiel extrahiert; du kannst das auch manuell ausführen: ```bash python data/tools/extract_game_data.py ``` Die Ergebnisse landen in `data/game/` (nicht im Git): die rohen `.slk`-/`.txt`-Dateien sowie die aufbereiteten `units.json`, `names.json`, `skills.json`, `items.json`, `heroes.json` und `buffs.json`. ## Ein paar konkrete Zahlen | Größe | Wert | |---|---| | Ein Tag | 480 Spielsekunden (je 240 s Tag und Nacht), eine Stunde = 20 Spielsekunden; das Spiel beginnt um 8 Uhr morgens | | Tag | 6:00–18:00 | | Rüstungsfaktor | 0.06 (aus den Datentabellen des Spiels) | | Kapazität des Weltblocks | 16 Spieler, 1024 Einheiten, 256 Einheitendetails, 256 Gegenstände am Boden, 128 Produktionseinträge | | Bäume | Max. 4096 zerstörbare Objekte, alle 2 s aktualisiert | | Event-Ring | 8192 Einträge; wer zu langsam liest, verliert Events (das SDK erkennt das) | | Kartenraster | 128 Spieleinheiten pro Zelle, max. 256 × 256 | ## Im Live-Spiel kalibrierte Beispiele - Produktionszeiten: Bauer 14.9, Farm 34.9, Iron Forged Swords 59.9 Spielsekunden – deckungsgleich mit den Werten, die die Runtime pusht (Abweichung unter 0.2 s); - Kampfwerte gegen die Anzeige im Spiel geprüft: Paladin 650 HP, 255 Mana, 3.9 Rüstung, Angriff 24–34; Fußsoldat mit Angriffs-Upgrade Stufe 1: 13–15; - der Schaden vor Rüstung aus den Schadens-Events der Engine (14 / 15 / 15) liegt im von `stats()` berechneten Bereich; - Wegfindung: Echo Isles 116 × 88 Zellen, Bodenweg zum gegnerischen Hauptgebäude 10642 (Luftlinie 9856), Aufbau des Rasters 18 ms, ein A*-Lauf ca. 1 ms. --- # FAQ > Ist das ein Cheat? Welche Versionen werden unterstützt? Was sieht die KI, was kann sie tun? Geht es ohne Programmierkenntnisse? … ## Ist das ein Cheat? Nein. Es ist eine Entwicklerschnittstelle für KI-Forschung und Unterhaltung, ausschließlich für **Clients, die du rechtmäßig besitzt** – lokal, im LAN oder in selbst gehosteten Spielen gegen den Computer oder andere KIs. Es **darf nicht auf Battle.net oder Servern mit Anti-Cheat eingesetzt werden** und bietet keinerlei Funktionen für Spiele gegen echte Menschen. Details unter [Nutzungsgrenzen](https://war3ai.com/de/docs/legal/). ## Welche Spielversionen werden unterstützt? Derzeit nur **Warcraft III 1.27** (The Frozen Throne). 1.24–1.28 teilen dieselbe Engine-Struktur; die Unterstützung mehrerer Versionen (Symboltabelle je Version, Signatur-Scan als Fallback, Selbsttest beim Start mit Fähigkeitsliste) ist Phase P4 der [Roadmap](https://war3ai.com/de/roadmap/). Versionen ab 1.29 und Reforged nutzen eine andere Engine, bräuchten eine eigene Anpassung und sind derzeit nicht zugesagt. ## Werden meine Spieldateien verändert? Nein. Die Runtime wird zur Laufzeit injiziert und **verändert weder die Game.dll auf der Festplatte** noch andere Spieldateien. Für mehrere Instanzen wird nur der Original-Starter `War3.exe` unverändert kopiert und umbenannt. Spieldaten (Einheitentabellen usw.) werden aus deinem eigenen Spiel extrahiert und nicht mit dem Code verteilt. ## Was sieht die KI? Im Grunde alles, was ein Profi wissen will – aktualisiert alle 50 ms: - Gold, Holz und Nahrung aller Spieler; für alle Einheiten Position, HP/Mana, aktuelle Order, **wen sie gerade angreifen**, Stufe und Erfahrung; - Fähigkeitsstufen und verbleibende Abklingzeiten von Helden und Einheiten, aktive Buffs, Inventar; - was jedes Gebäude gerade ausbildet / erforscht / baut / aufwertet, mit Fortschritt und ob es wegen fehlender Nahrung blockiert ist; - Gegenstände am Boden, Bäume, das begehbare / bebaubare Raster der Karte, Startpositionen, die Tageszeit im Spiel (Tag/Nacht); - den Event-Stream: Einheiten erscheinen und sterben, **jeder einzelne Treffer** (von wem, Angriffstyp, Schaden vor Rüstung), Kills, fertige Produktion, Helden-Level-ups … - außerdem kannst du die Engine direkt fragen: Ist etwas gerade möglich, und wenn nicht, warum? Welche Stufe hat eine Technologie? Ist ein Punkt sichtbar? Wie viel Gold hat eine Mine noch? Wohin will der Computergegner seine Armee führen? Darauf aufbauend berechnet das SDK Kampfwerte (Konter, Rüstung, Angriffs-/Rüstungs-Upgrades), „wie viele Sekunden bis zum Kill“ und die Wegfindung am Boden. Alle APIs findest du im [API-Katalog](https://war3ai.com/de/api/). ## Was kann die KI tun? Praktisch alles, was ein Spieler kann: bewegen, Angriffsbewegung, gezielt angreifen, stoppen, Position halten, patrouillieren, Boden angreifen, abbauen, reparieren, bauen (mit automatischer Platzsuche), ausbilden / erforschen / aufwerten, abbrechen, Fähigkeiten lernen, zaubern (auf Einheit / auf Punkt / ohne Ziel), Sammelpunkt, Helden wiederbeleben, Gegenstände aufheben / benutzen / ablegen / geben / verkaufen, einkaufen, „Zu den Waffen“; dazu Shift-Warteschlange, Marsch über Wegpunkte und mehrere Gebäude nacheinander mit einem Arbeiter. Außerdem Spielgeschwindigkeit, Pause und Sprechblasen. Jeder Befehl liefert eine Quittung. Über die Aktionen eines Spielers hinaus kann sie eigene Panels und Markierungen ins Spielbild zeichnen ([Canvas](https://war3ai.com/de/docs/canvas/)) und im Einzelspieler die 1291 JASS-Funktionen aufrufen, die auch Kartenautoren zur Verfügung stehen ([JASS-Kanal](https://war3ai.com/de/docs/jass/)). ## Funktioniert das auch auf RPG- / Custom Maps? Ja. Wähle eine RPG-Karte, gib der Instanz das Schema „Begleiter-Beispiel“, und spiel nach dem Start selbst: An deiner Seite läuft ein KI-Partner, der mitkämpft, dich heilt und mit dir plaudert – siehe [RPG-Begleiter](https://war3ai.com/de/docs/companion/). `g.map_data` liest die Namen der eigenen Einheiten einer Karte aus; über den [JASS-Kanal](https://war3ai.com/de/docs/jass/) kannst du Einheiten erzeugen, Bündnisse setzen, Panels einblenden … wie du spielst, entscheidest du. Aktionen, die die Welt verändern, gehen nur im Einzelspieler (im Multiplayer käme es zu Desyncs); das Canvas ist auch im Multiplayer sicher. ## Geht es ohne Programmierkenntnisse? Ja. Richte die Umgebung nach dem [Schnellstart](https://war3ai.com/de/docs/quickstart/) ein und lies dann [Einen Bot mit einem LLM schreiben](https://war3ai.com/de/docs/ai-bot/): Du beschreibst die Spielweise in Alltagssprache, das LLM schreibt den Code. Läuft etwas schief, gib ihm die Fehlermeldung oder beschreib, was du im Spiel siehst, und lass es nachbessern. ## Geht nur Python? Das SDK ist in Python geschrieben. Zwischen Runtime und externen Programmen liegt nur ein Shared-Memory-Protokoll ([W3P](https://war3ai.com/de/docs/protocol/)); jede Sprache, die unter Windows Shared Memory lesen und schreiben kann, kann sich anbinden. Noch einfacher geht es über das [Gateway](https://war3ai.com/de/docs/gateway/) (WebSocket / JSON): JS, C#, Go, Rust, Browserseiten und Programme auf anderen Rechnern können dieselben APIs aufrufen; LLM-Agents können direkt über [MCP](https://war3ai.com/de/docs/mcp/) eingebunden werden. ## Welches LLM ist am besten? Jedes gängige Modell, das programmieren kann. Entscheidend ist nicht das Modell, sondern **das richtige Material** (Handbuch + `api.json` + ein Beispiel) und die Vorgabe, nur Methoden aus dem API-Katalog zu verwenden. Echtzeitentscheidungen im Spiel (Coach, Einheiten-Dialoge) sind latenzkritisch; lokale MoE-Modelle schneiden dort sehr gut ab, siehe [LLM als Strategie-Coach](https://war3ai.com/de/docs/llm-coach/) und [Sprechblasen & lokale Modelle](https://war3ai.com/de/docs/speech/). ## Bremst es das Spiel aus? Eine Erfassung des Weltzustands kostet auf dem Spiel-Thread im Median 0.5–0.9 ms (100–120 Einheiten), einmal alle 50 ms. Befehle kosten auf dem Spiel-Thread jeweils wenige Mikrosekunden; ein Abarbeitungsdurchlauf hat ein Zeitbudget von 4 ms, was nicht fertig wird, kommt beim nächsten Mal dran – das Spiel wird nicht aufgehalten. Alle Aufrufe ins Spiel sind gegen Ausnahmen abgesichert: Stürzt ein Bot ab, stoppt nur diese Seite, das Spiel läuft weiter. ## Kann ich mehrere Spiele gleichzeitig laufen lassen? Ja. `runtime/farm.py` orchestriert mehrere Instanzen, jede mit eigener Nummer; gestartet und gestoppt wird in der [Farsight-Konsole](https://war3ai.com/de/docs/console/). Dein Bot verbindet sich mit `--inst N` zu einer bestimmten Instanz. ## Können zwei KIs gegeneinander spielen? Öffne im selben Spiel zwei `player`-Kanäle (`--player 0` / `--player 1`) – schon spielt KI gegen KI. Lokal beruht Fairness auf Absprache; offizielle Matches mit Schiedsrichter, Sichtfilterung und Besitzprüfung gibt es auf der [Arena](https://war3ai.com/de/arena/) (Phase P6). ## Werden Mac und Linux unterstützt? Derzeit nur Windows 10 / 11. ## Welche Lizenz gilt? Die Lizenz wird mit dem offiziellen Release veröffentlicht. Drittkomponenten behalten ihre eigenen Lizenzen (z. B. MinHook unter BSD-2); AMAI steht unter einer eigenen Lizenz, abgeleitete Daten werden nicht mit dem Projekt verteilt, sondern bei der Installation aus dem öffentlichen AMAI-Repo geholt und generiert. ## Wo melde ich Probleme? Mit dem offiziellen Release öffnen wir einen Kanal für Feedback. Gib dabei bitte die Instanznummer, die Ausgabe von `python -m openwar3 status` und die Schritte zum Reproduzieren an. Schau vorher in [Debugging & Performance](https://war3ai.com/de/docs/debugging/), ob sich das Problem damit schon lösen lässt. --- # Nutzungsgrenzen > Was erlaubt ist, was nicht, die Besucherstatistik dieser Website, dazu Hinweise zu Marken und Drittlizenzen. Wer dieses Projekt nutzt, erklärt sich mit diesen Grenzen einverstanden. ## Erlaubt - Nutzung mit einem Client von Warcraft III 1.27, **den du rechtmäßig besitzt**; - KI gegen Computergegner oder andere KIs antreten lassen – lokal, offline, im LAN oder in selbst gehosteten Spielen; - Forschung, Lehre, Unterhaltung und Streaming deiner eigenen KI-Spiele; - Weiterentwicklung auf Basis von SDK, Referenz-Brain, Beispielen und Tools unter Beachtung ihrer Lizenzen. ## Nicht erlaubt - **Keine Nutzung auf Battle.net oder auf Servern und Plattformen mit Anti-Cheat**, auch nicht parallel zu einer aktiven Anti-Cheat-Sitzung; - keine Nutzung, um sich in Spielen gegen echte Menschen einen unfairen Vorteil zu verschaffen; - keine Weitergabe von Blizzards Spieldateien oder daraus extrahierten Daten (auch dieses Projekt verteilt sie nicht: Nutzer extrahieren die Spieldaten aus ihrem eigenen Spiel); - die Nutzungslizenz der Runtime ist einzuhalten. ## Unsere technischen Zusagen - Weder `Game.dll` noch andere Spieldateien auf der Festplatte werden verändert; alle Änderungen passieren zur Laufzeit; - für mehrere Instanzen wird nur der Original-Starter `War3.exe` unverändert kopiert und umbenannt; - das Projekt enthält keinerlei Code oder Spieldateien von Blizzard. ## Deine Verantwortung Die Gesetze zu Reverse Engineering und Spielmodifikationen unterscheiden sich je nach Land. **Du musst selbst prüfen, ob die Nutzung dieses Projekts in deinem Land zulässig ist, und trägst die Folgen selbst.** Das Projekt wird „wie besehen“ bereitgestellt, ohne jegliche ausdrückliche oder stillschweigende Gewährleistung. ## Besucherstatistik dieser Website Diese Website (war3ai.com) erfasst Besuche mit Microsoft Clarity: welche Seiten angesehen werden, woher Besucher kommen, wie lange sie bleiben, wohin geklickt und wie weit gescrollt wird, dazu anonyme Sitzungswiedergaben und Heatmaps. Wir nutzen das ausschließlich, um Dokumentation und Seiten zu verbessern. - Es ist keine Registrierung nötig, und es werden keine Identitätsdaten wie Name oder E-Mail-Adresse erfasst; Text in Eingabefeldern wird standardmäßig maskiert und nicht aufgezeichnet; - Clarity speichert Cookies im Browser, um mehrere Besuche desselben Besuchers zu erkennen; die Daten verarbeitet Microsoft, siehe [Datenschutzerklärung von Microsoft](https://privacy.microsoft.com/privacystatement); - Wenn du nicht erfasst werden möchtest: Hänge an eine beliebige Adresse dieser Website `?stats=off` an und öffne sie einmal – danach wird in diesem Browser nichts mehr erfasst (`?stats=on` macht das rückgängig). Du kannst auch `clarity.ms` mit dem Tracking-Schutz deines Browsers blockieren; die Website funktioniert weiterhin normal. Farsight, SDK und Runtime auf deinem Rechner enthalten keine solche Statistik. Farsight verbindet sich nur in zwei Fällen mit war3ai.com: beim Start und danach alle 6 Stunden, um die Versionsliste zu lesen und nach einer neuen Version zu sehen; und wenn du auf der Seite „Feedback & Vorschläge“ auf Senden klickst – dann werden dein Feedback und eine Kennung dieses Rechners übertragen (ein gesalzener Hash der System-ID, aus dem sich der ursprüngliche Wert nicht zurückrechnen lässt; er dient zum Schutz vor Spam). Diagnoseinformationen werden nur angehängt, wenn du sie ankreuzt, und lassen sich vor dem Senden ansehen. Wenn diese beiden Anfragen bei war3ai.com eingehen, zeichnet der Server die IP-Adresse, das von Cloudflare bestimmte Land oder die Region sowie die Client-Version (User-Agent) auf, um Missbrauch vorzubeugen und zu zählen, wie viele Farsight-Instanzen im Einsatz sind. Die Aufzeichnungen der Versionsprüfung werden nach 90 Tagen automatisch gelöscht; Feedback wird zusammen mit diesen Informationen aufbewahrt, bis die Maintainer es bearbeitet und gelöscht haben. Diese Daten sind nur für die Projekt-Maintainer im Backend einsehbar und werden nicht an Dritte weitergegeben. ## Marken Warcraft® und 魔兽争霸® (der chinesische Titel) sind Marken oder eingetragene Marken von Blizzard Entertainment, Inc. War3AI / OpenWar3 sind unabhängige Community-Projekte, nicht mit Blizzard Entertainment verbunden und weder von Blizzard Entertainment gebilligt noch gesponsert. Andere erwähnte Produktnamen (Claude, GPT, Gemini, Qwen usw.) gehören ihren jeweiligen Inhabern und werden nur zur Angabe der Kompatibilität genannt. ## Drittkomponenten und Daten | Komponente / Daten | Lizenz | Umgang | |---|---|---| | MinHook | BSD-2-Clause | Wird mit der Runtime verwendet, der Lizenzhinweis bleibt erhalten | | AMAI | Eigene Lizenz | Wird nicht mit dem Projekt verteilt; `start.bat` holt es bei der Installation aus dem öffentlichen AMAI-Repo, ein Tool erzeugt daraus die Daten für das Referenz-Brain | | Spieldaten (Einheiten, Fähigkeiten, Gegenstände usw.) | Blizzard | Werden nicht mit dem Projekt verteilt; Nutzer extrahieren sie aus ihrem eigenen Spiel | | Aus öffentlichen Turnier-Replays gewonnene Fakten (Bauplätze, Eröffnungsreihenfolgen) | — | Nur Faktendaten, für das Referenz-Brain | --- # API-Katalog (api.json) Status: verified = zugrunde liegender Pfad im Live-Spiel verifiziert; experimental = neue API, läuft bereits, wird noch Punkt für Punkt live verifiziert; inferred = abgeleitet / nicht vollständig getestet. Latenz:Gepushter Snapshot(Liest Shared Memory, ohne auf den Spiel-Thread zu warten (ca. 0.05 ms)); Schnellspur(ca. 1 Frame: Batch-Ausführung auf dem Spiel-Thread); Steuerkanal(20~40 ms (alter Weg für UI-Operationen)); Direktes Schreiben(Nicht über die Warteschlange des Spiel-Threads: schreibt Shared Memory (Canvas) oder schickt eine Nachricht ans Spielfenster); Lokal berechnet(Reine Berechnung oder Datei lesen, berührt das Spiel nicht) ## Beobachten Liest Zustand, ohne das Spiel zu verändern. Fast alles liest direkt den gepushten Snapshot, ohne Wartezeit. - `snapshot(max_age: 'float' = 0.05)` [verified] [Gepushter Snapshot] Vollständiger Zustand der gesamten Karte (WorldState): .units .players .items .clock .me; wiederholte Aufrufe innerhalb von max_age Sekunden liefern dieselbe Kopie. ⚠ Arbeiter in einer Goldmine sind nicht in der Liste; standardmäßig ist die ganze Karte sichtbar (im Lockstep-Modell liegt lokal alles vor), erst Game(fair=True) filtert nach Sichtweite. (Mechanismus: W3P-Weltblock Local\War3World_ (die Runtime pusht alle 50 ms, seqlock)) - `last_seen(owner: 'str | int' = 'enemy', max_age: 'float | None' = None) -> 'list'` [verified] [Gepushter Snapshot] Zuletzt gesehene Einheiten des Gegners (oder 'creep' für Creeps, oder einer Spielernummer): [(Zustand der Einheit damals, Spieluhr damals, vergangene Sekunden)], neueste zuerst. Sieht man eine Einheit sterben, fliegt sie aus der Liste. Fair-Modus und normaler Modus erfassen beide nach "was wir gerade sehen" – das ist die Karte im Kopf eines Spielers: aufgeklärte Truppenstärke, wo der gegnerische Held zuletzt war, wann der Gegner seine Expansion gebaut hat. max_age: nur Einträge aus so vielen zurückliegenden Spielsekunden. (Mechanismus: visibleTo des Push-Snapshots (bei jeder Aktualisierung des Snapshots werden sichtbare gegnerische Einheiten/Creeps festgehalten)) - `map()` [verified] [Gepushter Snapshot] Geländetabelle dieser Partie, MapInfo: .walkable(x,y) .buildable(x,y) .at(x,y) .bounds (spielbarer Bereich) .starts (Startpositionen) .cells (bit0 nicht begehbar, bit1 nicht bebaubar). Nach Partiebeginn dauert die Berechnung einige Sekunden; bis dahin None. Bäume sind nicht enthalten (dafür trees()). (Mechanismus: W3P-Kartenblock Local\War3Map_ (die Runtime berechnet ihn nach Partiebeginn in Etappen, IsTerrainPathable Laufen/Bauen)) - `me() -> 'int | None'` [verified] [Gepushter Snapshot] Welche Spielernummer ich habe (0~11). (Mechanismus: Header des Weltblocks) - `resources(player: 'int | None' = None) -> 'dict | None'` [verified] [Gepushter Snapshot] {'gold','lumber','food_used','food_cap','gold_gathered','lumber_gathered'}; player ist standardmäßig der eigene Spieler, jeder Spieler ist lesbar. Nicht lesbar = None, nicht als 0 behandeln. (Mechanismus: Weltblock players[16]) - `players() -> 'list'` [verified] [Gepushter Snapshot] Alle 16 Spieler-Slots: Player(id, gold, lumber, food_used, food_cap, gold_gathered, lumber_gathered, race, known). (Mechanismus: Weltblock players[16]) - `units(owner: 'str | int' = 'all', types=None, alive: 'bool' = True) -> 'list'` [verified] [Gepushter Snapshot] Einheiten nach Besitzer/Typ filtern. owner: 'me' / 'enemy' / 'creep' / 'all' / Spielernummer. types: Menge von Vier-Zeichen-Codes. (Mechanismus: Weltblock units[]) - `unit(handle) -> 'object | None'` [verified] [Gepushter Snapshot] Einheit über das Handle-Paar (lo, hi) finden (Order-Ziele, Aufgabenziele und Events liefern alle Handle-Paare). (Mechanismus: Weltblock by_handle) - `is_building(u) -> 'bool'` [verified] [Gepushter Snapshot] Ob es ein Gebäude ist (inkl. Türme). Entschieden wird über Bewegungsgeschwindigkeit 0 laut Einheitentabelle; das Hauptgebäude der Untoten hat eine Grundfläche von 0, also nicht über die Grundfläche entscheiden. (Mechanismus: Snapshot + units.json (spd==0 = Gebäude)) - `my_workers() -> 'list'` [verified] [Gepushter Snapshot] Eigene Arbeiter (Bauer/Peon/Akolyth/Irrlicht). (Mechanismus: Push-Snapshot) - `idle_workers() -> 'list'` [verified] [Gepushter Snapshot] Arbeiter ohne Beschäftigung: keine Order und keine Aufgabe (wem du in diesem Tick gerade etwas zugewiesen hast, zählt nicht). ⚠ Gibst du einem Arbeiter mit Aufgabe erneut einen Sammelbefehl, unterbricht das den Sammelzyklus (Einkommen fällt auf null). (Mechanismus: Push-Snapshot (Order-Slot + Aufgaben-Slot)) - `my_heroes() -> 'list'` [verified] [Gepushter Snapshot] Eigene lebende Helden (tote stehen in der Wiederbelebungsliste des Altars, siehe revive). (Mechanismus: Push-Snapshot) - `my_army() -> 'list'` [verified] [Gepushter Snapshot] Eigene Kampfeinheiten: keine Arbeiter, keine Gebäude. (Mechanismus: Push-Snapshot + units.json) - `my_buildings(types=None) -> 'list'` [verified] [Gepushter Snapshot] Eigene Gebäude (inkl. Türme und Baustellen); über types lassen sich bestimmte Arten auswählen, z. B. {'hbar'}. (Mechanismus: Push-Snapshot) - `is_constructing(worker) -> 'bool'` [verified] [Gepushter Snapshot] Ob dieser Arbeiter gerade baut (oder zum Bauplatz läuft / beim Reparieren hilft; inkl. gerade in diesem Tick zugewiesener). Bei der Wahl eines Bauarbeiters überspringen, sonst steht die vorige Baustelle still. (Mechanismus: Push-Snapshot (Order = Vier-Zeichen-Code eines Gebäudes, oder Bau-/Reparaturbefehl)) - `under_construction(building) -> 'bool'` [verified] [Gepushter Snapshot] Dieses Gebäude ist noch nicht fertig (TP nicht voll). ⚠ Beschädigte Gebäude haben ebenfalls keine vollen TP – für die Eröffnung reicht das, sobald gekämpft wird, zusätzlich die Zeit berücksichtigen. (Mechanismus: Push-Snapshot (die TP einer Baustelle steigen von sehr niedrig bis voll)) - `gold_mines() -> 'list'` [verified] [Gepushter Snapshot] Goldminen auf der Karte. ⚠ Die Entangled Gold Mine der Nachtelfen und die neutrale Goldmine sind zwei Einheiten an derselben Koordinate; zum Sammeln die eigene zuweisen. (Mechanismus: Push-Snapshot (ngol/egol/ugol)) - `enemies(fighters_only: 'bool' = False) -> 'list'` [verified] [Gepushter Snapshot] Einheiten gegnerischer Spieler (ohne Creeps). fighters_only: Arbeiter und Gebäude weglassen. (Mechanismus: Push-Snapshot) - `creeps() -> 'list'` [verified] [Gepushter Snapshot] Creeps (neutral feindselig). ⚠ Nachts sinkt die Sichtweite; liegen entfernte Lager im Kriegsnebel, werden Zielbefehle auf sie abgelehnt (Grundcode 1001). (Mechanismus: Push-Snapshot (owner 12 = neutral feindselig)) - `life_mana(u) -> 'dict | None'` [verified] [Gepushter Snapshot] {'hp','hp_max','mana','mana_max'} (Float, Rohwerte der Engine). Für u reicht die Einheit aus dem Snapshot (sie wird durch die neueste Kopie ersetzt). (Mechanismus: Einheit im Weltblock: hp/hpMax/mana/manaMax) - `hero_info(hero) -> 'dict | None'` [verified] [Gepushter Snapshot] {'level','xp','skill_points'}. (Mechanismus: Einheit im Weltblock: level/xp/skillPoints) - `abilities(u) -> 'list'` [verified] [Gepushter Snapshot] [{code, level, cooldown, flags}]; Buffs liefert buffs(u). Nur für Einheiten "mit Details" (Helden > Spielereinheiten > Creeps, bis zu 256). (Mechanismus: Details im Weltblock: Fähigkeiten (Code/Stufe/Flags/verbleibende Abklingzeit)) - `buffs(u) -> 'list'` [verified] [Gepushter Snapshot] Buff-Codes auf der Einheit (z. B. 'BHds' Gottesschild, 'Bslo' Verlangsamen). Welcher Effekt zu welchem Code gehört, steht in data/game/buffs.json. (Mechanismus: Details im Weltblock: Fähigkeitsobjekte, die mit B beginnen) - `cooldown(u, ability: 'str') -> 'float | None'` [verified] [Gepushter Snapshot] Wie viele Sekunden (Spielsekunden) diese Fähigkeit noch abklingen muss; 0 = einsatzbereit; Fähigkeit nicht vorhanden (oder Einheit ohne Details) = None. (Mechanismus: Details im Weltblock: verbleibende Abklingzeit der Fähigkeit (Fähigkeits-Timer)) - `inventory(hero) -> 'list | None'` [verified] [Gepushter Snapshot] Vier-Zeichen-Codes der 6 Gegenstandsplätze (leere Plätze = None); ohne Inventar None. (Mechanismus: Details im Weltblock: 6 Inventarplätze) - `current_order(u) -> 'dict | None'` [verified] [Gepushter Snapshot] {'order','target','x','y'}: die aktuelle Order der Einheit (order ist 0x000D00xx oder der Vier-Zeichen-Code eines Gebäudes, 0 = untätig). target ist ein Handle-Paar; mit g.unit(target) bekommst du die Einheit. (Mechanismus: Einheit im Weltblock: order / Order-Ziel / Order-Zielpunkt) - `current_target(u)` [verified] [Gepushter Snapshot] Die Einheit, die diese Einheit **tatsächlich angreift/verfolgt** (sonst None). ⚠ Nach einem Angriffsbefehl wird der Order-Slot schnell leer, der Angriff hängt an der Aufgabe – um zu prüfen, "wen sie angreift", diese Funktion nehmen, nicht current_order. (Mechanismus: Aufgabenziel der Einheit im Weltblock) - `clock() -> 'float | None'` [verified] [Gepushter Snapshot] Spieluhr der Engine (Spielsekunden, 0 während des Ladens). Bei erhöhter Spielgeschwindigkeit läuft sie schneller als die Echtzeit. (Mechanismus: Header des Weltblocks: clockMs (Spieluhr der Engine)) - `production(building)` [verified] [Gepushter Snapshot] Was dieses Gebäude gerade tut: Production(kind, queue, duration, elapsed, blocked, progress, remaining…); tut es nichts, None. kind 'queue' (Training/Forschung/Held, queue höchstens 7 Plätze, [0] ist das aktuelle) / 'construction' (im Bau) / 'upgrade' (Hauptgebäude/Turm wird aufgewertet); blocked = eingereiht, aber nicht gestartet (meist zu wenig Nahrung – Zeit für einen Bauernhof); progress 0..1. Auch gegnerische Gebäude sind lesbar (im Fair-Modus nur sichtbare Gebäude). (Mechanismus: Produktionstabelle im Weltblock (Fähigkeitsobjekte Aque/ABnP/AUnP + von der Runtime verfolgte vergangene Zeit; gemessener Fehler < 0.2 Spielsekunden)) - `queue(building) -> 'list'` [verified] [Gepushter Snapshot] Vier-Zeichen-Codes in der Trainings-/Forschungswarteschlange ([0] ist in Arbeit); untätig oder kein Produktionsgebäude = []. (Mechanismus: Produktionstabelle im Weltblock) - `all_production(owner: 'str | int' = 'me') -> 'list'` [verified] [Gepushter Snapshot] Alle laufenden Produktionen [(Gebäude, Production)]. owner wie bei units(): 'me' / 'enemy' / Spielernummer / 'all'. Profi-Nutzung: sehen, welche Einheiten der Gegner trainiert, welche Technologien er erforscht und wann er seinen Tier-up macht (sobald seine Gebäude aufgeklärt sind). (Mechanismus: Produktionstabelle im Weltblock) - `path_distance(a, b) -> 'float | None'` [verified] [Gepushter Snapshot] Laufweg einer Bodeneinheit von a nach b (a, b als Einheit oder (x,y)); unerreichbar = None. Auf Inselkarten damit prüfen, "ob man zu diesem Creep-Lager/dieser Expansion auf dem Landweg kommt" – zuverlässiger als die Luftlinie (um Wälder, Klippen und Gebäude herum). Genauigkeit eine Zelle (128); Lücken schmaler als eine Zelle gelten als unpassierbar. (Mechanismus: Kartenblock (Engine IsTerrainPathable) + Baumblock + Grundfläche der Gebäude, A* auf SDK-Seite (128 pro Zelle)) - `reachable(a, b) -> 'bool | None'` [verified] [Gepushter Snapshot] Ob das Ziel am Boden erreichbar ist (Kartenblock noch nicht berechnet = None). (Mechanismus: Wie oben) - `walk_path(a, b) -> 'list | None'` [verified] [Gepushter Snapshot] Knickpunkte des Wegs [(x,y)...] (der letzte Punkt ist b); zusammen mit path(units, Punktliste) folgt die Armee diesem Weg (Türme umgehen, Nebenwege nehmen). (Mechanismus: Wie oben) - `upkeep(player: 'int | None' = None) -> 'dict | None'` [inferred] [Gepushter Snapshot] Unterhaltsstufe: {'level': 'none'/'low'/'high', 'income': 1.0/0.7/0.4, 'next_at': Nahrung der nächsten Stufe (keine = None)}. Profi-Wissen: Beim Tier-up auf Stufe 3 und bei Angriffs-/Rüstungs-Upgrades bei 50 Nahrung bleiben, erst vor der Entscheidungsschlacht auf 80 gehen. (Mechanismus: Feste Regel in 1.27: 0~50 Nahrung kein Unterhalt, 51~80 Einkommen ×0.7, 81~100 ×0.4) - `xp_to_next(hero) -> 'int | None'` [verified] [Gepushter Snapshot] Wie viele EP dem Helden bis zur nächsten Stufe fehlen (Stufe 10 = 0). (Mechanismus: Weltblock level/xp + Formel NeedHeroXP aus MiscGame) - `creep_camps(link: 'float' = 600.0) -> 'list'` [verified] [Gepushter Snapshot] Fasst die (sichtbaren) Creeps auf der Karte zu Lagern zusammen: [{'x','y','units','level','hp','max_level'}], sortiert nach Entfernung zu unserer Hauptbasis, nah zuerst. level = Gesamtstufe des Lagers (das übliche Maß für die Schwierigkeit beim Creepen), hp = Gesamt-TP. Zusammen mit time_to_kill / path_distance ein Lager auswählen. (Mechanismus: Push-Snapshot (Creeps im Abstand von 600 zu einer Gruppe verbunden) + Stufen aus units.json) - `buff_info(code: 'str') -> 'dict | None'` [verified] [Lokal berechnet] Was ein Buff-Code ist: {'ability','effect','dur','hero_dur','targets'} (z. B. 'Bslo' -> Verlangsamen). Bei mehreren Zeilen pro Code wird die erste geliefert. (Mechanismus: data/game/buffs.json (BuffID aus AbilityData.slk -> Fähigkeit/Effekt/Dauer)) - `stats(u, player: 'int | None' = None)` [verified] [Gepushter Snapshot] Kampfwerte der Einheit, combat.UnitStats: max. TP/Mana, Rüstung (inkl. Angriffs-/Rüstungs-Upgrades, Beweglichkeit des Helden), Rüstungstyp, Laufgeschwindigkeit, Sichtweite bei Tag/Nacht, Waffen (was sie treffen können, Reichweite, Angriffsintervall, Schadensbereich, Angriffstyp, Flächenschaden). u als Einheit (nutzt automatisch Tech und Heldenstufe ihres Besitzers) oder als Vier-Zeichen-Code (player standardmäßig der eigene). Dazu .dps_vs(gegner) / .hits_to_kill(gegner) / combat.time_to_kill(gruppe, gegner). ⚠ Ohne Gegenstände, Auren und Buffs. (Mechanismus: Datentabellen (UnitBalance/UnitWeapons/UpgradeData/MiscGame) + aktuelle Tech-Stufen + Heldenstufe) - `time_to_kill(attackers, target) -> 'float | None'` [verified] [Gepushter Snapshot] Wie viele Spielsekunden diese Einheiten gemeinsam brauchen, um target zu töten (mit den aktuellen TP von target; berücksichtigt Konter, Rüstung, Angriffs-/Rüstungs-Upgrades; nicht Stellungsspiel, Flächenschaden, Heilung). Profi-Nutzung: Beim Fokusfeuer zuerst das Ziel, das "am schnellsten stirbt" (kleinstes time_to_kill), nicht das nächstgelegene. Nicht angreifbar = None. (Mechanismus: stats() + aktuelle TP) - `time_of_day() -> 'float | None'` [verified] [Gepushter Snapshot] Tageszeit im Spiel (Stunden, 0~24). Die Partie beginnt um 8 Uhr morgens; ein ganzer Tag = 480 Spielsekunden (je 240 Sekunden Tag und Nacht, skaliert mit der Tag-/Nacht-Geschwindigkeit). Nicht lesbar (alte Runtime / nicht in einer Partie) = None. (Mechanismus: Erweiterungsbereich im Weltblock: GetFloatGameState(GAME_STATE_TIME_OF_DAY)) - `is_night() -> 'bool | None'` [verified] [Gepushter Snapshot] Ob gerade Nacht ist (18:00~6:00). Profi-Spielweise: Nachts schlafen Creeps (beim Creepen triffst du zuerst, ohne umzingelt zu werden), alle Einheiten sehen weniger weit (gute Zeit für Überfälle), Schildwachen/Einheiten der Nachtelfen sind nachts neben Bäumen unsichtbar. Nicht lesbar = None. (Mechanismus: Erweiterungsbereich im Weltblock (6~18 Uhr Tag)) - `seconds_until(hour: 'float') -> 'float | None'` [verified] [Gepushter Snapshot] Wie viele Spielsekunden es noch bis hour Uhr Spielzeit sind (z. B. seconds_until(18) = wie lange bis zum Einbruch der Nacht, zum Planen des nächtlichen Creepens). (Mechanismus: Erweiterungsbereich im Weltblock + 480 Sekunden pro Tag (gemessen 20 Spielsekunden/Stunde)) - `items_on_ground() -> 'list'` [verified] [Gepushter Snapshot] Gegenstände am Boden [Item(addr, handle_lo, handle_hi, type, x, y, life)]. Aufheben/Verbrauchen löst das Event item.removed aus. (Mechanismus: Weltblock items[] (nur Gegenstände am Boden: Handle des Besitzers komplett FF)) - `trees(x: 'float | None' = None, y: 'float | None' = None, limit: 'int' = 60) -> 'list'` [verified] [Gepushter Snapshot] Lebende Bäume (in DestructableData mit tree in targType); mit (x,y) nach Entfernung sortiert, nah zuerst, höchstens limit Stück. Jeder ist ein Tree(addr, handle_lo, handle_hi, type, x, y, life) und kann direkt an gather zum Holzfällen übergeben werden. (Mechanismus: Baumblock Local\War3Trees_ (alle 2 Sekunden aktualisiert)) - `events() -> 'list'` [verified] [Gepushter Snapshot] Was seit dem letzten Aufruf passiert ist: unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started (aus dem Vergleich der Veröffentlichungen, Genauigkeit = Veröffentlichungsperiode 50 ms), dazu damage / killed auf Engine-Ebene (die Runtime zeichnet sie sofort im Spiel-Thread auf, daher gibt es einen für **jeden einzelnen Treffer**): damage: handle = der Getroffene, .source_addr = der Angreifer (mit snapshot().unit_by_addr zur Einheit), .value = tatsächlicher TP-Verlust, .raw_damage = Schaden vor Rüstung, .attack_type (normal/pierce/siege/magic/chaos/hero/spell), .damage_type killed: dieser Treffer hat die Einheit getötet, .source_addr = Killer dazu production.done, abgeleitet aus der von der Runtime verfolgten Produktionstabelle (Genauigkeit = Veröffentlichungsperiode): Einheit = Gebäude, .done_code = Vier-Zeichen-Code des Fertiggestellten, .done_kind = 'training' (Einheit/Held/Wiederbelebung) / 'research' / 'construction' (Gebäude fertig) / 'upgrade' (Tier-up/Turm-Upgrade), .value = benötigte Spielsekunden Seit 09-25 ergänzt: spell.cast: Einheit = Zaubernder, .spell Vier-Zeichen-Code der Fähigkeit, b Stufe, value Abklingzeit in Sekunden, x,y Zielpunkt (erkannt, wenn die Abklingzeit startet, Genauigkeit = Veröffentlichungsperiode) player.left: .player = Nummer des Spielers, der gegangen ist / als besiegt entfernt wurde; game.ended: Partie verlassen selection.changed: Die Auswahl des lokalen Spielers hat sich geändert (Einheiten mit g.selection() holen) message: eine Zeile in einem Nachrichtenfeld auf dem Bildschirm (Spielhinweis, Chat, System): .text voller Text, .frame Nummer des Nachrichtenfelds, .chat = {'channel', 'sender', 'text'} (bei Chat-Nachrichten; was der Spieler ins Chatfeld tippt, liest du hier) ui.click / ui.hover / hotkey / mouse.world: Oberfläche & Eingabe (g.ui), .key ist der Canvas-key / die Hotkey-Schreibweise Jedes ist ein Event(seq, kind, clock, addr, handle, type, owner, a, b, x, y, value, extra). Im Fair-Modus (fair=True) nur: Events eigener Einheiten, Events von Einheiten, die gerade sichtbar sind (oder vor bis zu 1 Sekunde noch sichtbar waren), Schaden gegen uns/durch uns, sowie die lokalen Oberflächen-, Nachrichten- und Partie-Events. (Mechanismus: Event-Ring Local\War3Events_ (Veröffentlichungsvergleich + von der Runtime erfasste Schadens-Events)) - `selection() -> 'list'` [verified] [Gepushter Snapshot] Die Einheiten, die der lokale Spieler gerade ausgewählt hat (Haupteinheit zuerst; höchstens 12). Ändert sich die Auswahl, kommt ein selection.changed-Event. (Mechanismus: W3P-Weltblock, Erweiterungsbereich selAddrs (die Runtime liefert bei jeder Veröffentlichung die Auswahl des lokalen Spielers mit)) - `messages() -> 'list'` [verified] [Gepushter Snapshot] Neue Nachrichten in den Nachrichtenfeldern auf dem Bildschirm seit dem letzten Aufruf: [{'text', 'frame', 'repeat', 'seq', 'game_ms'}]. Spielhinweise (etwa, dass mehr Farmen nötig sind oder dass dort nicht gebaut werden kann), Chat und Systemnachrichten stehen alle hier; frame unterscheidet, welches Nachrichtenfeld. Dieselben Nachrichten wie die message-Events im Event-Stream (jeweils mit eigenem Cursor). (Mechanismus: Shared Memory Local\War3Msgs_ (von der Runtime erfasste Bildschirmnachrichten)) - `tech(code: 'str', player: 'int | None' = None) -> 'int | None'` [verified] [Schnellspur] Forschungsstufe / Anzahl fertiger Gebäude (Upgrade-Ketten zählen mit: ein Schloss zählt auch als htow). player standardmäßig der eigene, jeder Spieler ist abfragbar. (Mechanismus: W3P-Abfrage q_tech (Tech-Zähler des Spielers in der Engine)) - `can_do(u, code: 'str') -> 'int | None'` [verified] [Schnellspur] Machbarkeitsurteil der Engine: 0/220 möglich; 3 Nahrung, 8 Gold fehlt, 9 Holz fehlt, 32 Warteschlange voll, 183 Voraussetzung fehlt, 185 Altar belebt gerade wieder, 221 nicht vorhanden/im Bau. ⚠ Für Arbeiter, die ein Gebäude bauen, immer 221 – taugt nicht zur Prüfung des Bauplatzes (dafür build_near). (Mechanismus: W3P-Abfrage q_feasible (Machbarkeitsprüfung der Engine)) - `can_do_many(pairs) -> 'list'` [verified] [Schnellspur] Viele can_do auf einmal: pairs = [(Einheit, Vier-Zeichen-Code), ...], liefert die Urteilscodes in derselben Reihenfolge (nicht abfragbar = None). Wenn du planst, was in einem Tick gebaut/trainiert werden soll, frag zuerst alles gesammelt ab – N-mal schneller als einzelne can_do (Referenz-Brain 09-23: Bauplanung 76 -> 25 ms). (Mechanismus: W3P-Abfrage q_feasible × N, als ein Batch eingereicht) - `tech_many(codes, player: 'int | None' = None) -> 'dict'` [verified] [Schnellspur] Viele Tech-/Gebäudezähler auf einmal: {Vier-Zeichen-Code: Anzahl oder None}. (Mechanismus: W3P-Abfrage q_tech × N, als ein Batch eingereicht) - `visible(x: 'float', y: 'float') -> 'bool | None'` [verified] [Schnellspur] Ob wir diesen Punkt gerade sehen (nicht im Kriegsnebel/in der schwarzen Maske). Ein Bot im Fair-Modus sollte nur sichtbare Gegner verwenden. (Mechanismus: W3P-Abfrage q_visible (sichtbar / Kriegsnebel / schwarze Maske)) - `gold_left(mine) -> 'int | None'` [inferred] [Schnellspur] Wie viel Gold noch in der Goldmine ist. (Mechanismus: W3P-Abfrage q_mine_gold (Restgold der Mine in der Engine)) - `enemy_ai_plan(enemy_unit) -> 'dict | None'` [verified] [Schnellspur] Captain der Computer-KI: wohin er seine Truppen führt (schon vor dem Abmarsch weißt du, wo er dich angreifen will). Nur gegen Computergegner; folgt die Einheit keinem Captain, None. (Mechanismus: W3P-Abfrage q_captain (Computer-Captain, dem die gegnerische Einheit folgt)) - `order_of(u) -> 'int | None'` [verified] [Gepushter Snapshot] Aktuelle Order der Einheit, **inklusive der Befehle, die du in diesem Tick gerade erteilt hast** (solange der Snapshot noch nicht aufgeholt hat, gilt die neue Order aus der Quittung). ⚠ 09-23 im Live-Spiel: hello_bot hatte gerade einen Bauern zum Bau eines Bauernhofs geschickt, im selben Tick sah rush_bot ihn im Snapshot als "untätig" und schickte ihn zur Kaserne – der Bauernhof blieb immer wieder halb fertig liegen. Wenn du "untätige/nicht bauende" Einheiten auswählst, nimm diese Funktion statt u.order. (Mechanismus: Order aus dem Snapshot + gerade angenommene Befehle dieses Prozesses (Quittungen)) - `can_afford(code: 'str') -> 'bool'` [verified] [Gepushter Snapshot] Ob das aktuelle Gold/Holz für code reicht (Einheiten, Gebäude; nach den Preisen in units.json). Was nicht in der Preistabelle steht, gilt immer als bezahlbar. ⚠ Die Vier-Zeichen-Codes für Tier-ups haben in der Tabelle kumulierte Preise, hier fällt das Ergebnis also eher vorsichtig aus; maßgeblich ist letztlich die Quittung der Engine. (Mechanismus: Eigene Ressourcen aus dem Push-Snapshot + Preise aus units.json) - `map_data()` [verified] [Lokal berechnet] Daten der gerade gespielten Karte (openwar3.mapdata.MapData): name_of('HC07') liefert Namen eigener Einheiten/Gegenstände/Fähigkeiten, dazu hero_names, tooltip. Einheiten auf RPG-Karten sind meist von der Karte selbst definiert und fehlen in der eingebauten Namenstabelle; wurde das Spiel nicht vom Launcher gestartet (Kartendatei nicht auffindbar), kommt None zurück. (Mechanismus: Kartendatei (Pfad aus --map des Launchers): w3u/w3t/w3a + wts, bei geschützten Karten die TXT-Dateien in der Karte) ## Befehle Lässt Einheiten handeln. Greift in etwa einem Frame, jeder Befehl mit Quittung. - `batch() -> 'Batch'` [verified] [Schnellspur] Fasst die Befehle eines Ticks zu einem Batch zusammen: with g.batch() as b: g.attack(archers, target) # liefert Pending, wird erst nach dem Block zur Quittung g.move(wounded, *home) g.cast(hero, "thunderclap") print(b.sent, b.wait_ms, [r.reason for r in b.receipts]) Jeder einzeln gesendete Befehl wartet einmal auf den Spiel-Thread (ca. 10 ms); ein Batch wartet nur einmal – damit kam das Referenz-Brain am 09-23 pro Runde von 48 -> 26 ms. * Die Claim-Prüfung läuft weiterhin pro Befehl (gehaltene Einheiten bekommen sofort eine held-Quittung und kommen nicht in den Batch); * Befehle im Block liefern Pending: .ok vor dem Ende des Blocks zu lesen löst eine Exception aus (die Quittung existiert noch nicht), danach wie eine Receipt verwenden; * Exception im Block = der ganze Batch verfällt (status 97 cancelled), gehaltene Einheiten werden zurückgegeben; * Abfragen (can_do / tech / visible …) sowie build_near und buy kommen nicht in den Batch, sondern werden wie bisher sofort gestellt – ihre Ergebnisse werden sofort gebraucht; viele auf einmal abfragen mit can_do_many / tech_many; * verschachtelte with g.batch() gehen im äußersten Batch auf; über 16 Befehle teilt die Runtime automatisch in mehrere Abschnitte (je Abschnitt ein Warten). (Mechanismus: Befehle im Block werden zu einem Batch gesammelt und am Ende des Blocks auf einmal eingereicht (im selben Frame ausgeführt, nur einmal auf den Spiel-Thread warten)) - `move(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Schnellspur] Nach (x,y) gehen, unterwegs nicht angreifen (für Rückzüge). Nimmt eine Einheit oder eine Liste (Befehl im selben Frame). queue='after': erst die aktuelle Aufgabe erledigen, dann gehen (hinter die aktuelle Order eingereiht). Quittung values[0] = wie viele Orders die Einheit nach dem Befehl in der Warteschlange hat (inkl. der aktuellen). (Mechanismus: W3P point: move (extra-Bit = Art des Anstellens)) - `attack_move(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Schnellspur] Angriffsbewegung (A auf den Boden): unterwegs angreifen, was auftaucht. queue wie bei move. (Mechanismus: W3P point: attack auf Punkt) - `attack(units, target, force: 'bool' = False, queue: 'str | None' = None)` [verified] [Schnellspur] target angreifen. Standardmäßig per Rechtsklick (auf Gegner = genau diesen angreifen; am 09-23 gemessen: Order-Ziel und Aufgabenziel sind beide diese Einheit). ⚠ Das Ziel muss in Sicht sein, Unsichtbares wird abgelehnt (Grundcode 1001). force=True nutzt den Angriffsbefehl 0x0F (nötig für eigene Einheiten/neutrale Kleintiere) – gemessen: Er setzt nur die Angriffs-Order und merkt sich das Ziel nicht, die Einheit greift dann andere Gegner in der Nähe an. Für ein bestimmtes Ziel nicht verwenden. (Mechanismus: W3P target: Zielbefehl (Rechtsklick smart)) - `stop(units)` [verified] [Schnellspur] Alles anhalten (Order-ID 0x000D0004), auch eingereihte Orders werden gelöscht. (Mechanismus: W3P immediate: stop) - `hold(units, queue: 'str | None' = None)` [verified] [Schnellspur] Position halten (nicht hinterherlaufen, nur Ziele in Reichweite angreifen). (Mechanismus: W3P immediate: holdposition) - `patrol(units, x: 'float', y: 'float', queue: 'str | None' = None)` [inferred] [Schnellspur] Zwischen aktueller Position und (x,y) patrouillieren. (Mechanismus: W3P point: patrol) - `attack_ground(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Schnellspur] Boden angreifen: Artillerie feuert auf eine Fläche (unsichtbare Einheiten und Gegner hinter Bäumen treffen, einen Engpass sperren). Nur Einheiten, die den Boden angreifen können, nehmen den Befehl an. (Mechanismus: W3P point: attackground (Belagerungseinheiten / Mörsertrupp / Katapult)) - `cancel(building)` [verified] [Schnellspur] Abbrechen: letzter Platz der Trainings-/Forschungswarteschlange (Geld zurück), Gebäude im Bau (75 % zurück), Hauptgebäude im Upgrade. (Mechanismus: W3P immediate: cancel) - `path(units, points, attack: 'bool' = False)` [verified] [Schnellspur] Eine Reihe von Punkten nacheinander abgehen (Shift-Wegpunkte: Wegpunkte, Türme umgehen, Aufklärungsrouten). attack=True: jeder Abschnitt ist eine Angriffsbewegung. Einmal eingereicht; eine Quittung pro Punkt (in der Reihenfolge von points). (Mechanismus: Ein Batch: der erste Abschnitt wird sofort ausgeführt, der Rest in umgekehrter Reihenfolge mit queue='after' eingefügt (die Engine kann nur hinter der aktuellen Order einfügen)) - `gather(workers, target, queue: 'str | None' = None)` [verified] [Schnellspur] Gold sammeln/Holz fällen (target ist eine Goldmine oder ein Baum aus trees()). ⚠ Nur untätigen Arbeitern zuweisen (idle_workers): Gibst du einem Arbeiter mit Aufgabe den Befehl erneut, unterbricht das den Sammelzyklus. Profi-Nutzung: nach dem Bauen zurück zur Mine = nach build(...) gather(worker, mine, queue='after'). (Mechanismus: W3P target: harvest (Goldmine oder Baum)) - `repair(workers, building, queue: 'str | None' = None)` [verified] [Schnellspur] Reparieren / beim Bau helfen (Baustellen der Menschen und Orcs stehen still, wenn niemand daran baut). (Mechanismus: W3P target: repair) - `build(worker, code: 'str', x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Schnellspur] Lässt den Arbeiter code bei (x,y) bauen (Koordinaten auf 32 ausgerichtet). Quittung angenommen = die Order des Arbeiters ist bereits dieses Gebäude (oder der Baubeginn-Befehl); mit queue='after' = in die Order-Warteschlange des Arbeiters eingereiht (Quittung values[0] = Anzahl in der Warteschlange). ⚠ Angenommen ≠ baubar: Auch einen Punkt im Wald nimmt die Engine sofort an, der Arbeiter scheitert erst, wenn er dort ankommt (am 09-23 gemessen); wird das Geld anderswo ausgegeben, erscheint die Baustelle ebenfalls nicht. Weißt du nicht, wo Platz ist, nimm build_near (verfolgt das Ergebnis, sperrt gescheiterte Punkte). Mehrere hintereinander: build_queue. (Mechanismus: W3P build: Bau-Order, im selben Frame wird die Order des Arbeiters zur Bestätigung zurückgelesen) - `build_queue(worker, plan)` [verified] [Schnellspur] Ein Arbeiter baut mehrere Gebäude nacheinander (Shift-Bauen): plan = [(Vier-Zeichen-Code, x, y), ...]. Einmal eingereicht; Quittungen in der Reihenfolge von plan. ⚠ Das Geld wird erst bei Baubeginn abgezogen (nicht beim Einreihen) – sind 3 Gebäude eingereiht, reicht das Geld aber nur für 1, scheitern die beiden anderen, wenn der Arbeiter dort ankommt. (Mechanismus: Ein Batch: das erste Gebäude sofort, der Rest in umgekehrter Reihenfolge mit queue='after') - `build_near(worker, code: 'str', x: 'float', y: 'float', min_r: 'float' = 450, max_r: 'float' = 1500, max_tries: 'int' = 24)` [verified] [Schnellspur] Sucht um (x,y) von nah nach fern einen freien Platz und baut dort code. **Blockiert nicht**, kann in jedem Tick aufgerufen werden: * Für diesen Gebäudetyp läuft noch ein Versuch (Arbeiter unterwegs) -> liefert diesen Punkt, ohne den Befehl zu wiederholen; * der letzte Versuch hat geklappt (Baustelle erschienen) -> sucht bei Bedarf einen neuen Punkt; * der letzte Versuch ist gescheitert (Arbeiter merkt erst vor Ort, dass kein Platz ist, die Engine zieht die Order zurück, keine Baustelle) -> dieser Punkt wird 45 Sekunden gesperrt, weiter mit dem nächsten; * nicht genug Geld -> liefert direkt None (kein Versuch, keine Sperre); alle Punkte ausprobiert -> None. ⚠ Warum verfolgen: am 09-23 im Live-Spiel nahm die Engine Punkte im Wald **sofort an**, der Arbeiter scheiterte erst vor Ort (die Quittung im selben Frame kann das nicht erkennen); und die Bauplatzprüfung der Engine liefert für Arbeiter, die ein Gebäude bauen, immer 221, man kann also nicht erst "prüfen" und dann bauen. Nur offensichtlich belegte Punkte (mitten im Hauptgebäude) werden sofort abgelehnt. (Mechanismus: build Punkt für Punkt + Verfolgung (Baustelle erscheint = Erfolg; Arbeiter gibt die Order auf und es gibt keine Baustelle = dieser Punkt wird gesperrt)) - `train(building, code: 'str')` [verified] [Schnellspur] Einheit trainieren / Technologie erforschen / Hauptgebäude aufwerten (Tier-up = dem Hauptgebäude selbst den Vier-Zeichen-Code des Zielgebäudes geben, z. B. 'hkee'). Bei Ablehnung sagt reason in der Quittung, warum (nicht genug Nahrung, Gold fehlt, Holz fehlt, Warteschlange voll, Voraussetzung fehlt …). (Mechanismus: W3P immediate: Vier-Zeichen-Code, bei Ablehnung mit Grundcode der Machbarkeitsprüfung) - `learn(hero, ability: 'str')` [verified] [Schnellspur] Held lernt eine Fähigkeit (Vier-Zeichen-Code, z. B. 'AHbz' Blizzard). (Mechanismus: W3P learn: gilt erst als gelernt, wenn die Fertigkeitspunkte sinken) - `cast(u, spell, target=None, x: 'float | None' = None, y: 'float | None' = None)` [verified] [Schnellspur] Zauber wirken. spell ist ein Order-String ('thunderbolt' Sturmblitz, 'blizzard', 'holybolt' Heiliges Licht …, siehe data/order-ids.txt) oder eine Order-ID. Mit target = auf eine Einheit; mit x,y = auf den Boden; ohne beides = ohne Ziel (Donnerknall, Gottesschild, Wasserelementar beschwören). Eine angenommene Quittung heißt nur, dass die Engine akzeptiert hat; ob der Zauber gewirkt wurde, zeigt cooldown() (Abklingzeit läuft) bzw. buffs() (Buff erscheint). (Mechanismus: W3P target / point / immediate (je nach Parametern)) - `rally(building, x: 'float | None' = None, y: 'float | None' = None, target=None)` [verified] [Schnellspur] Sammelpunkt setzen (auf einen Punkt oder auf eine Einheit/Goldmine). (Mechanismus: W3P rally) - `revive(altar, hero=None)` [verified] [Schnellspur] Toten Helden am Altar wiederbeleben (ohne hero wird der erste der Liste wiederbelebt). Häufige Ablehnungsgründe (stehen in reason der Quittung): nicht genug Nahrung (auch Helden kosten Nahrung), nicht genug Geld, gerade erst gestorben (erst etwa 3 Spielsekunden nach dem Tod möglich), Wiederbelebung läuft bereits (beim Annehmen räumt die Engine diesen Slot sofort). (Mechanismus: W3P revive: Liste toter Helden -> der Altar wirkt Wiederbeleben auf den toten Helden) - `pick_up(hero, item)` [verified] [Schnellspur] Held hebt einen Gegenstand vom Boden auf (item aus items_on_ground). Danach erscheint er im Inventar, und am Boden wird das Event item.removed ausgelöst. (Mechanismus: W3P target: Rechtsklick auf Gegenstand) - `use_item(hero, slot: 'int', target=None, x: 'float | None' = None, y: 'float | None' = None)` [verified] [Schnellspur] Gegenstand in Inventarplatz slot (0~5) benutzen; optional mit Zieleinheit oder Zielpunkt. ⚠ Bei Gegenständen auf einen Punkt (z. B. Elfenbeinturm) liefert die Engine auch bei Erfolg 0, die Quittung gilt immer als angenommen – prüf, ob der Inventarplatz leer geworden ist. (Mechanismus: W3P use_item (nach Platznummer)) - `drop_item(hero, slot: 'int', x: 'float', y: 'float')` [verified] [Schnellspur] Den Gegenstand aus Inventarplatz slot bei (x,y) ablegen (der Held geht hin und legt ihn ab). (Mechanismus: W3P item_drop (nach JASS UnitDropItemPoint: dropitem 0xD0021 auf Punkt + Gegenstand als unmittelbares Ziel)) - `give_item(hero, slot: 'int', to)` [verified] [Schnellspur] Den Gegenstand aus Inventarplatz slot an to übergeben (anderer Held / Einheit; der Held geht hin und übergibt ihn). An einen Laden = verkaufen (siehe sell_item). (Mechanismus: W3P item_drop (nach JASS UnitDropItemTarget: dropitem auf Einheit)) - `sell_item(hero, slot: 'int', shop)` [verified] [Schnellspur] Den Gegenstand aus Inventarplatz slot an einen Laden verkaufen (der Held muss neben dem Laden stehen; nur verkaufbare Gegenstände werden angenommen, zum halben Preis). (Mechanismus: Wie give_item, Ziel ist ein Laden (gemessen: Staff of Sanctuary für 125 Gold verkauft)) - `move_item(hero, slot: 'int', to_slot: 'int')` [verified] [Schnellspur] Inventarplatz wechseln (Platz slot auf Platz to_slot verschieben; sind beide belegt, werden sie getauscht). Zum Sortieren der Hotkey-Belegung. (Mechanismus: W3P target: Order 0xD0022+Platznummer, Ziel = Gegenstand (nach JASS UnitDropItemSlot)) - `buy(shop, item_code: 'str')` [inferred] [Schnellspur] Gegenstand im Laden kaufen (für den Helden, der neben dem Laden steht). Fehlt eine Tech-Voraussetzung, liefert die Engine 0 und zieht kein Geld ab. (Mechanismus: W3P buy: der Laden verkauft an einen Helden daneben) - `call_to_arms(hall, on: 'bool' = True)` [verified] [Schnellspur] Zu den Waffen (Menschen): Bauern werden zur Miliz (das Rathaus auf Stufe 1 hat diese Fähigkeit nicht, nur Bergfried/Schloss). (Mechanismus: W3P immediate: townbellon/off) ## Spielsteuerung Spielgeschwindigkeit, Pause, Veröffentlichungsintervall, Sprechblasen, Canvas, Oberfläche & Eingabe, Nachrichten. - `ui()` [verified] [Direktes Schreiben] Oberfläche & Eingabe (openwar3.ui.UI): klickbare Buttons und Auswahlkarten, Hotkeys, Position per Klick auf den Boden wählen, lesen, worauf die Maus zeigt. Der Klick auf einen Button kommt beim Spiel nicht an; rein lokale Eingabe + lokales Zeichnen, auch im Multiplayer sicher. (Mechanismus: W3P 74 input_enable + Shared Memory Local\War3Input_ (die Runtime empfängt die Fenstereingaben)) - `set_speed(percent: 'int') -> 'bool'` [verified] [Steuerkanal] Spielgeschwindigkeit (100 = normale Geschwindigkeit). (Mechanismus: Aktion 47 (25~800%)) - `pause(on: 'bool' = True)` [verified] [Schnellspur] Spiel pausieren / fortsetzen. In der Pause steht die Engine-Uhr still, über die Schnellspur lassen sich weiterhin Befehle erteilen (der Event-Dispatch läuft weiter). (Mechanismus: W3P pause) - `set_publish_period(ms: 'int') -> 'None'` [verified] [Gepushter Snapshot] Veröffentlichungsperiode des Weltzustands (16~1000 Millisekunden, Standard 50). Eine Erfassung kostet etwa 0.5 ms, auch 33 ms sind kein Problem; der Wert gilt für den ganzen Rechner, wer zuletzt schreibt, gewinnt. (Mechanismus: Weltblock requestedPeriodMs) - `say(u, text: 'str', seconds: 'float' = 4.0) -> 'bool'` [verified] [Steuerkanal] Über der Einheit erscheint eine Chat-Sprechblase (für Streams/Debugging, beeinflusst das Spiel nicht). Gibt False zurück, wenn keine Sprechblase erschienen ist; der Grund steht in g.last_say_error. (Mechanismus: Aktion 56) - `message(text: 'str') -> 'bool'` [inferred] [Steuerkanal] Schreibt eine Zeile in den Nachrichtenbereich unten links im Spiel (nur lokal sichtbar). Funktioniert erst, nachdem das Spiel selbst einmal einen Hinweis angezeigt hat (die DLL greift das Nachrichtenfeld bei dieser Gelegenheit ab). (Mechanismus: Aktion 45) - `end_game() -> 'bool'` [verified] [Steuerkanal] Beendet diesen Spielprozess (farm.py --keep startet anhand von next_game.json automatisch die nächste Partie). (Mechanismus: Aktion 22) - `canvas()` [verified] [Direktes Schreiben] Canvas: zeichnet Textfelder, Panels, Fortschrittsbalken, Bilder sowie Kreise und Routen auf dem Boden ins Spielbild (openwar3.canvas.Canvas). Die Runtime zeichnet selbst, legt keine Spiel-Handles an und ändert den Spielzustand nicht – auch im Multiplayer sicher; Stil frei wählbar (chinesische Schrift, abgerundete Ecken, Transparenz). (Mechanismus: W3P 73 canvas_enable + Shared Memory Local\War3Canvas_ (die Runtime zeichnet in jedem Frame, bevor das Spiel den Mauszeiger zeichnet; der Mauszeiger liegt darüber)) - `press_to_continue() -> 'bool'` [verified] [Direktes Schreiben] Drückt auf dem Ladebildschirm „Beliebige Taste drücken, um fortzufahren“ einmal die Leertaste. Viele RPG-/Story-Karten starten nach dem Laden erst auf Tastendruck (09-24 mit WarChasers gemessen: ohne Tastendruck bleibt das Spiel im Ladebildschirm, Spieluhr 0, die Schnellspur wird nicht geleert). openwar3.run drückt beim Warten auf den Spielbeginn selbst, manuell ist das meist nicht nötig. (Mechanismus: PostMessage WM_KEYDOWN/UP Leertaste an das Spielfenster (ohne den Fokus zu stehlen)) ## Sandbox JASS-Kanal: Einheiten erzeugen, Bündnisse setzen, umbenennen, Text anzeigen … für RPG-Hilfen und Begleiter; die Welt verändern kann er nur im Einzelspieler und in lokalen Tools. - `jass()` [verified] [Schnellspur] Beliebige JASS-Natives per Name aufrufen: g.jass.CreateUnit(g.jass.Player(1), "Hpal", x, y, 270.0). Parameter I/R/B/S/H werden automatisch konvertiert (Einheiten-/Gegenstandsobjekte direkt übergeben); im Multiplayer sind nur lesende Natives erlaubt. Details in openwar3/jass.py und docs/COMPANION_ZH.md. (Mechanismus: W3P 70 jass (die Runtime sucht die Native per Name in der Native-Tabelle, 1291 Stück)) - `player_slots() -> 'list[dict]'` [verified] [Schnellspur] Die 16 Spielerslots: controller (user = echter Spieler / computer / neutral …), state (empty / playing / left), human, me, ally (ob mit mir verbündet). Auf RPG-Karten damit einen freien Slot für den Begleiter finden oder prüfen, ob es ein Einzelspielerspiel ist. (Mechanismus: JASS GetPlayerController / GetPlayerSlotState / IsPlayerAlly) - `spawn(code: 'str', x: 'float', y: 'float', player: 'int | None' = None, facing: 'float' = 270.0)` [verified] [Schnellspur] Erzeugt eine Einheit bei (x,y) (player standardmäßig der lokale Spieler) und liefert die Einheit aus dem Snapshot (nach der nächsten Weltveröffentlichung, ca. 50 ms); klappt es nicht, None. Die gelieferte Einheit hat zusätzlich das Attribut jass_handle. ⚠ Nur im Einzelspieler nutzbar (im Multiplayer Desync). (Mechanismus: JASS CreateUnit + W3P 72 Handle -> Einheit) - `set_alliance(a: 'int', b: 'int', allied: 'bool' = True, vision: 'bool' = True, control: 'bool' = False, xp: 'bool' = False, both: 'bool' = True) -> 'None'` [verified] [Schnellspur] Bündnis von Spieler a gegenüber b setzen: allied = greifen sich nicht an + rufen sich gegenseitig zu Hilfe; vision teilt die Sicht; control teilt die Einheitensteuerung (b kann die Einheiten von a befehligen); xp teilt Erfahrung. both=True setzt beide Richtungen (control nur a -> b). (Mechanismus: JASS SetPlayerAlliance) - `set_player_name(player: 'int', name: 'str') -> 'None'` [verified] [Schnellspur] Ändert den Spielernamen (den in Punktetafel, Chat und Verbündeten-Panel angezeigten). Zum Benennen des Begleiters. (Mechanismus: JASS SetPlayerName) - `show_text(text: 'str', seconds: 'float' = 6.0, player: 'int | None' = None) -> 'None'` [verified] [Schnellspur] Zeigt unten links auf dem Bildschirm eine Textzeile an (wie sie Karten-Trigger verwenden), standardmäßig für den lokalen Spieler. Unterstützt |cffRRGGBB-Farbcodes. (Mechanismus: JASS DisplayTimedTextToPlayer) ## Verbindung & Tools Verbindungsstatus und reine Rechenhilfen. - `status() -> 'dict'` [verified] [Lokal berechnet] Verbindungsstatus: pid, Weltveröffentlichung (Periode, Erfassungsdauer), Zähler der Schnellspur. (Mechanismus: Weltblock + Schnellspur + Claim-Tabelle) - `nearest(candidates, to)` [verified] [Lokal berechnet] Der Kandidat, der to (Einheit oder (x,y)) am nächsten ist; ohne Kandidaten None. (Mechanismus: Reine Berechnung)