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

Quelle: https://war3ai.com/de/docs/ui-input/

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_<pid>` 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.
