# 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.

Quelle: https://war3ai.com/de/docs/companion/

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 "<Spielverzeichnis>\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.
