# Companheiro de RPG

> Dê ao jogador um companheiro de IA em mapas RPG e personalizados: ele segue você, ajuda a matar monstros, cura você quando sua vida está baixa e conversa com você. Quatro modos; herde uma classe, mude algumas propriedades e você tem o seu próprio companheiro.

Fonte: https://war3ai.com/pt/docs/companion/

Não é só para partidas de confronto. Em mapas RPG e personalizados, você pode ter um **companheiro de IA**: ele anda com você, ajuda a matar monstros, cura você quando sua vida está baixa e, quando não há nada acontecendo, puxa conversa — as falas podem vir de um LLM local.

**Como usar é decisão sua.** Isso se divide em três camadas de API, de baixo para cima, e qualquer uma delas pode ser usada diretamente:

| Camada | O que é | Para quem |
|---|---|---|
| **Canal JASS** `g.jass` | As 1291 funções JASS que autores de mapas podem usar, chamadas direto pelo nome (criar unidades, definir aliados, dar itens, mudar nomes, exibir texto, reviver heróis…) | Quem quer criar a própria jogabilidade |
| **APIs de conveniência** | `g.spawn`, `g.set_alliance`, `g.player_slots`, `g.show_text`, `g.map_data`: as tarefas mais comuns, já empacotadas | Quem quer escrever os próprios scripts auxiliares |
| **Framework de companheiro** | `openwar3.companion.Companion` + `openwar3.talk.Talk`: herde, mude algumas propriedades e você tem um parceiro que segue, ajuda na luta, cura e conversa | Quem quer um companheiro |

> **Atenção**
>
> Só para partidas **offline, em rede local ou criadas por você**. Criar unidades e definir aliados são mudanças no mundo que esta máquina faz por conta própria: numa partida solo (contra o computador), tudo bem; numa partida multijogador, os outros jogadores perdem a sincronia. Por isso, em partidas multijogador o canal JASS só libera funções de leitura, e o companheiro recua automaticamente para “só falar”.

## O jeito mais rápido: ativar com um clique no Farsight

1. **Escolha o mapa**: no Farsight, página “Instâncias e partidas” → “Próxima partida” → Mapa, escolha um mapa RPG (tudo o que estiver em `Scenario` e `Download`, dentro de `Maps` na pasta do jogo, aparece na lista, por exemplo `(4)WarChasers`).
2. **Escolha o esquema**: no menu “Esquema de IA” do card da instância, escolha **Exemplo de companheiro (buddy)** → “Selecionar”.
3. **Inicie o teste**: quando o jogo abrir, **jogue você mesmo na janela do jogo**. O companheiro — um paladino chamado “Luz” — aparece ao seu lado.

Também dá para usar a linha de comando:

```bash
python tools/play.py --bot brains/examples/buddy.py --inst 20 --rpg --map "<pasta do jogo>\Maps\Scenario\(4)WarChasers.w3m"
```

`--rpg` (no manifesto do esquema, `"judge": false`) significa não decidir vitória e derrota pelas regras de confronto: em RPG, heróis mortos podem reviver, e não existe “perdeu todas as construções, perdeu a partida”. Muitos mapas RPG param em “Pressione qualquer tecla para continuar” depois de carregar; quando o SDK percebe que “está numa partida, mas o relógio do jogo continua em 0”, ele mesmo aperta espaço (`g.press_to_continue()`, que só envia uma mensagem de tecla para a janela do jogo, sem roubar o foco).

## Escreva o seu próprio companheiro

```python
from openwar3.companion import Companion
from openwar3.talk import Talk

class MyBuddy(Companion):
    mode = "ally"                        # modo, veja a tabela abaixo
    unit = "Hpal"                        # o que criar: qualquer código de quatro caracteres, inclusive os definidos pelo mapa
    nickname = "Luz"
    heal = ("holybolt", "AHhb", 0.55)    # (ordem de feitiço, habilidade a aprender, cura quando a vida do mestre ficar abaixo disto); None = não cura
    follow_distance = 350
    talk = Talk(persona="Um paladino alegre e animado, que adora torcer pelo seu mestre")
```

### Quatro modos

| mode | Quem é o companheiro | Descrição |
|---|---|---|
| `ally` (padrão) | Ocupa um slot de jogador vazio como seu **aliado** | Tem cor e nome próprios (o placar e o painel de aliados mostram o `nickname`); você não consegue selecioná-lo, ele age sozinho. O framework configura aliança + visão compartilhada automaticamente |
| `own` | Criado **em seu nome** | Você pode comandá-lo manualmente a qualquer momento; quando você não está cuidando dele, a IA o controla por você |
| `adopt` | Assume uma unidade que **já existe** no mapa | Sobrescreva `adopt(g)` para retornar essa unidade (o mascote ou o seguidor que o mapa dá a você) |
| `voice` | Não cria unidade, **só fala** | Faz companhia e dá avisos; não altera o mundo, funciona até em partidas multijogador |

Sem slot vazio, `ally` recua automaticamente para `own`; em partidas multijogador, ou se não for possível criar a unidade, recua para `voice`.

> **Nota**
>
> No modo `ally`, o companheiro se liga à “melhor unidade daquele slot no momento” (heróis primeiro), e não a uma unidade fixa. Nos testes, um mapa tratou o companheiro como um jogador de verdade: removeu o paladino e entregou um herói do mapa — o companheiro assumiu esse herói na hora e aprendeu as habilidades que o mapa definiu para ele. Quando o herói morre, a prioridade é revivê-lo ali mesmo; se o próprio mapa o reviver, ele continua usando esse herói.

### O que ele faz a cada tick

Verifica em ordem e faz a primeira coisa que se aplicar:

| Ordem | Comportamento | Condição | Ajuste |
|---|---|---|---|
| 1 | Recuar | A própria vida abaixo de 25% e inimigos por perto: recua para trás do mestre | `retreat_at` |
| 2 | Curar | Vida do mestre abaixo do valor definido, habilidade fora de recarga, distância de até 900 | `heal` (None desliga) |
| 3 | Ajudar na luta | Inimigos perto do mestre: **quem está atacando o mestre > quem o mestre está atacando > o mais próximo** | `assist_radius`, ou sobrescreva `pick_target` |
| 4 | Seguir | Se estiver longe demais do mestre, vai atrás; se estiver muito longe, volta correndo sem ficar lutando | `follow_distance`, `leash` |
| 5 | Conversar | Sem inimigos por perto, solta uma frase a cada 1 ~ 2.5 minutos | Tabela de falas |

“Inimigo” segue as relações de aliança do próprio jogo (atualizadas a cada 20 segundos). Mapas RPG costumam ter vários aliados, então não dá para simplesmente tratar “todos os jogadores menos eu” como inimigos.

Hooks que podem ser sobrescritos: `find_master` (quem é o mestre; por padrão, o herói de nível mais alto do jogador local), `adopt`, `pick_target`, `on_poke` (o mestre clicou com o botão direito no companheiro), além de `on_start` / `on_tick` / `on_event` / `on_end` do Bot. Curas, ajudas na luta, abates, seguimentos, recuos, falas e revividas são contados em `self.stats` e impressos no fim.

### Como chamá-lo

- **Comandos de chat**: digite no chat `-follow` para ele seguir você, `-stay` para ficar de guarda onde está, `-heal` para curar na hora, `-hi` para cumprimentar. Para mudar a lista de comandos, altere `commands`; para mudar as reações, sobrescreva `on_command`.
- **Clique com o botão direito no companheiro**: dispara `on_poke`. No exemplo, a reação é: se o mestre não estiver com a vida cheia, ele cura um pouco; senão, diz alguma coisa.
- **Diálogo com retrato**: o cumprimento, a queda do mestre, a subida de nível do mestre e a volta do companheiro são ditos pelo diálogo com retrato do próprio jogo (o retrato na parte de baixo passa a ser o do companheiro e aparece uma legenda na tela); as outras falas saem em balões sobre a cabeça.
- **Painel de status**: um painel no lado esquerdo da tela mostra a barra de vida do companheiro, o que ele está fazendo, o humor (feliz / empolgado / tenso / com medo / triste), os abates e o número de curas. Ele é desenhado com o [canvas](https://war3ai.com/pt/docs/canvas/) e é seguro em partidas multijogador.

### Falas e LLM local

`Talk` escolhe falas conforme o evento e as mostra num balão sobre a cabeça; no modo `voice`, ou quando não dá para mostrar o balão, a fala aparece no canto inferior esquerdo da tela. Cada frase também vai para o log do esquema, para você conferir depois o que ele disse.

| Evento | Quando | Evento | Quando |
|---|---|---|---|
| `hello` | Acabou de chegar | `master_low` | Mestre com pouca vida |
| `poke` | O mestre clicou nele com o botão direito | `master_levelup` | Mestre subiu de nível |
| `fight` | Começou a luta | `master_died` / `master_back` | Mestre caiu / reviveu |
| `kill` | Matou um monstro (diz o nome dele) | `buddy_low` / `buddy_died` / `buddy_back` | O próprio companheiro com pouca vida / caiu / voltou |
| `healed` | Curou o mestre | `idle` / `item` | Conversa à toa / pegou um item |

As falas podem usar os placeholders `{master}`, `{me}`, `{map}`, `{enemy}`, `{level}` e `{item}`; para mudar as falas, altere `talk.lines`; a recarga fica em `talk.cooldown`.

**Conectar um LLM local**: `Talk(llm=LocalLLM(url, model))`; qualquer API compatível com OpenAI serve (LM Studio, Ollama…). O modelo responde numa thread em segundo plano, e a fala só sai quando a resposta chega; se ele não estiver ligado, estourar o tempo ou der erro, o companheiro usa as falas fixas, sem travar o jogo. As requisições vão só para o endereço local que você informar, e o conteúdo é o que acontece no jogo (como o mestre se chama, que monstros ele matou).

## Nomes de unidades em mapas personalizados

A maioria das unidades, itens e heróis de mapas RPG é criada pelo próprio mapa (códigos de quatro caracteres como `HC07` e `I00A`) e não está na tabela de nomes embutida. `g.map_data` lê direto o arquivo do mapa da partida atual:

```python
md = g.map_data
md.name_of("HC07")        # 'Optimus Primo' — nomes alterados pelo mapa têm prioridade
md.hero_names("HC07")     # lista de títulos
md.hero_skills("OC10")    # habilidades que o mapa definiu para este herói
md.tooltip("I00A")        # texto de descrição
```

Mapas protegidos ou otimizados (muitos RPGs populares) não trazem os arquivos padrão de dados de objetos; nesse caso, os nomes são lidos dos dados de texto do mapa. Nos testes, todos os 38 mapas RPG / personalizados desta máquina foram lidos com sucesso, e 37 deles forneceram nomes de unidades.

## Compartilhe como esquema

O companheiro é só uma subclasse de `openwar3.Bot`, então pode virar um [esquema de IA](https://war3ai.com/pt/docs/schemes/) e ser compartilhado. No manifesto, acrescente dois campos:

```json
{"id": "my-buddy", "name": "Meu companheiro", "entry": "my_buddy.py", "fair": false, "judge": false}
```

`"fair": false`: para usar o canal JASS (criar unidades, definir aliados); `"judge": false`: não decidir vitória e derrota pelas regras de confronto.

## Registro de testes

2026-09-24, instância de teste, mapa WarChasers, velocidade 2×:

- Canal JASS: todas as 18 verificações passaram — slots de jogador, conversão de ida e volta entre unidades e handles, valores de retorno reais, parâmetros de string, criar unidade num slot vazio, definir aliados, mudar nome, remover unidade; chamadas pela via do jogador e com o número errado de parâmetros foram corretamente rejeitadas.
- Companheiro: passou sozinho pelo “Pressione qualquer tecla para continuar” → apareceu ao lado do mestre e cumprimentou → foi atrás dele até o círculo de poder da escolha de herói, recebeu um herói do mapa e o assumiu → seguiu (a 200 ~ 400 do mestre) → lutou contra monstros e, ao matar um, disse “Mandou bem!” → recuou com pouca vida → morreu, foi revivido pelo mapa e continuou seguindo.

## O que ainda falta

1. **Não dá para ler qualquer texto que o jogador digita no chat.** Os comandos de chat fixos já funcionam; para o companheiro conversar livremente com você, ainda é preciso obter o texto em si.
2. **O companheiro não entende a jogabilidade de um mapa específico** (missões, lojas, história). Ele faz o genérico: seguir, ajudar na luta, curar. Para entender um mapa, escreva isso na subclasse, conforme aquele mapa — `g.map_data` consulta nomes, `g.jass` chama qualquer função. Essa é justamente a parte que fica para você decidir.
