Senza categoria

Come ho implementato Orione, l’NPC IA di Minecraft con ChatGPT e OpenAI

Negli ultimi giorni ho realizzato Orione, un NPC che entra realmente nel mio server Minecraft e si comporta come un personaggio semi-autonomo. L’obiettivo non era creare un semplice bot da chat, ma un’entita’ capace di muoversi nel mondo, raccogliere risorse, interagire con una cassa condivisa, difendere una base e rispondere ai miei comandi in gioco.

Architettura del progetto

L’infrastruttura gira su Proxmox ed e’ stata separata in due container distinti: uno dedicato al server Minecraft e uno dedicato all’NPC IA. Nel primo gira il server Java con avvio automatico; nel secondo gira un servizio Node.js che connette Orione al mondo di gioco tramite Mineflayer, con supporto al pathfinding, gestione inventario, automazioni e logica personalizzata.

Per garantire continuita’ dopo i riavvii ho aggiunto uno stato persistente su file. In questo modo Orione ricorda la cassa assegnata, il giocatore autorizzato ai comandi, eventuali timer di sospensione e altre informazioni utili al riavvio del processo.

Ruolo di ChatGPT e OpenAI

ChatGPT e’ stato usato come supporto di progettazione e sviluppo: struttura del codice, raffinamento dei loop di controllo, diagnostica dei problemi, definizione dei comandi naturali e revisione incrementale del comportamento del bot. Le API OpenAI sono state integrate per dare una base a una futura componente conversazionale contestuale, capace di usare il mondo Minecraft come ambiente operativo reale e non solo come sfondo per una chat.

Il punto chiave del progetto e’ l’unione tra linguaggio e azione: un prompt o un comando non produce solo una risposta testuale, ma puo’ tradursi in movimento, raccolta risorse, crafting, difesa o interazione con blocchi e contenitori.

Il ruolo concreto delle API OpenAI

Le API OpenAI non servono a far muovere direttamente il personaggio nel mondo Minecraft. Il loro ruolo corretto e’ piu’ alto livello: interpretare richieste umane, trasformarle in intenzioni comprensibili dal codice del bot, generare risposte piu’ naturali, spiegare cosa Orione sta facendo e supportare la pianificazione di attivita’ complesse.

In pratica il flusso corretto e’ questo: il giocatore scrive un comando o una richiesta, il livello applicativo decide se la richiesta va gestita con regole fisse oppure se conviene passarla al modello OpenAI, il modello restituisce una risposta o una struttura logica, e solo a quel punto il codice locale decide quali azioni consentite eseguire davvero in gioco.

Questo significa che l’API non dovrebbe mai avere accesso diretto e incontrollato a funzioni pericolose. La parte IA deve restare separata da quella esecutiva. Ad esempio il modello puo’ dire che l’intento del giocatore e’ raccogliere legna e mettere al sicuro le risorse, ma poi tocca al codice Node.js scegliere le routine abilitate: trovare alberi naturali, usare la cassa assegnata, evitare case costruite dai giocatori, interrompersi se il pathfinding fallisce.

In una architettura piu’ evoluta, OpenAI puo’ essere usato anche per:

  • interpretare frasi meno rigide dei semplici comandi testuali;
  • riassumere lo stato corrente del bot in linguaggio naturale;
  • produrre spiegazioni in chat su cosa sta facendo Orione e perche’;
  • scegliere tra task ad alto livello, lasciando l’esecuzione a funzioni locali sicure;
  • mantenere un comportamento piu’ credibile e meno meccanico nelle interazioni con i giocatori.

Dal punto di vista pratico, le API OpenAI entrano quindi come livello di orchestrazione semantica. Mineflayer, pathfinder, inventario, crafting, combattimento e gestione dei blocchi restano responsabilita’ del bot locale.

Un esempio minimo di chiamata puo’ essere questo:

async function askOpenAI(prompt) {
  const response = await openai.responses.create({
    model: process.env.OPENAI_MODEL || 'gpt-5.5',
    input: prompt
  })

  return response.output_text
}

La parte importante non e’ la chiamata in se’, ma il contratto attorno alla chiamata. Conviene definire un insieme ristretto di azioni possibili, ad esempio followPlayer, collectWood, guardChest, sleepAtBed, e tradurre l’output del modello solo in questi comportamenti noti. In questo modo il modello aiuta a decidere o spiegare, ma non puo’ produrre effetti arbitrari nel server.

Questa separazione e’ fondamentale anche per costi, debugging e affidabilita’. Se il modello non risponde, il bot deve continuare a funzionare almeno nelle attivita’ base. Se il modello propone qualcosa di incoerente, il codice puo’ scartarlo. E se il server cambia stato, e’ il bot locale che ha sempre l’ultima parola sulle azioni realmente possibili.

Stack tecnico utilizzato

  • Proxmox VE per ospitare i container separati.
  • Debian 12 come base per il server Minecraft e per il container dell’NPC.
  • Java Temurin/OpenJDK per il server Minecraft.
  • Node.js per il processo del bot.
  • Mineflayer per collegare Orione al server come se fosse un giocatore.
  • mineflayer-pathfinder per movimento, navigation e goal.
  • mineflayer-collectblock per facilitare alcune raccolte automatiche.
  • OpenAI API per l’estensione della parte IA.
  • systemd per rendere sia il server sia il bot persistenti al boot.
  • ViaProxy per gestire la compatibilita’ protocollo tra bot e server quando necessario.

Documentazione completa delle implementazioni fatte

Nel corso dello sviluppo Orione e’ stato esteso con una serie di implementazioni progressive, pensate per farlo sembrare sempre meno un bot rigido e sempre piu’ un personaggio reale presente nel server. Qui sotto riassumo tutte le funzionalita’ introdotte.

1. Modalita’ servo e controllo autorizzato

  • Comando orione servo per attivare la modalita’ in cui Orione risponde a un giocatore specifico.
  • Memorizzazione persistente del giocatore autorizzato ai comandi.
  • Blocco di molti comandi quando la modalita’ servo non e’ attiva.
  • Separazione tra comportamento autonomo e comportamento guidato dal giocatore.

2. Guida e comandi informativi

  • Comando orione guida per far elencare a Orione i comandi base disponibili.
  • Comando orione coordinate per conoscere la posizione attuale del bot.
  • Comando orione dove sono per ottenere la posizione del giocatore che parla.
  • Comando orione inventario per far riportare cosa ha addosso o nello zaino.
  • Comando orione controlla cassa per ispezionare il contenuto della cassa di riferimento.

3. Richiamo, movimento e teleport

  • Comando orione seguimi per fare seguire il giocatore.
  • Comando orione resta per fermare il follow.
  • Comando orione vieni qui o orione torna da me per richiamarlo vicino al giocatore.
  • Comando orione teleportati da me per usare il teleport via comandi server quando necessario.
  • Miglioramento del richiamo in modo che il bot sia gestibile anche quando e’ lontano o incastrato.

4. Cassa condivisa e persistenza

  • Comando orione usa questa cassa per associare una chest come deposito o punto logistico.
  • Salvataggio persistente della posizione della cassa su file JSON.
  • Ripristino automatico della cassa assegnata al reboot del servizio.
  • Uso della cassa come fonte di strumenti, torce, cibo, carbone e materiali.

5. Gestione risorse e prelievo oggetti

  • Comando orione prendi torce dalla cassa.
  • Comando orione prendi legna.
  • Comando orione prendi carbone.
  • Comando orione prendi cibo.
  • Comando orione prendi armatura.
  • Comando orione prendi ascia legno con tentativo di prelievo dalla cassa e crafting solo come fallback.
  • Controllo esplicito per fare verificare se l’ascia fosse gia’ presente nella cassa prima di craftarla.

6. Raccolta della legna migliorata

  • Prima versione della raccolta della legna.
  • Miglioramento della logica per evitare comportamenti inefficienti o distruttivi.
  • Filtro per considerare come legna solo tronchi appartenenti ad alberi naturali.
  • Esclusione delle costruzioni del giocatore dalla raccolta automatica.

7. Raccolta del carbone e sicurezza del mining

  • Abilitazione della ricerca di carbone come risorsa utile in modalita’ libera.
  • Restrizione della ricerca ai blocchi esposti e piu’ sicuri.
  • Riduzione della tendenza a scavare verso il basso in modo incontrollato.
  • Controlli per evitare che Orione si comporti come un miner cieco invece che come un NPC sensato.

8. Modalita’ libera e autonomia

  • Comando orione libero per attivare una modalita’ autonoma.
  • Priorita’ su raccolta di uova, carne, legna e carbone.
  • Loop di lavoro periodici per mantenere il bot attivo anche senza comandi continui.
  • Transizione dal comportamento puramente reattivo a una forma di attivita’ persistente.

9. Casa iniziale e costruzione

  • Richiesta iniziale per fare costruire una casa al boot.
  • Comando orione costruisci casa per creare una struttura semplice.
  • Gestione di supporti e blocchi minimi necessari alla costruzione.
  • Primi tentativi di comportamento da survival builder, non solo raccoglitore.

10. Notte, letti e riposo

  • Ricerca del primo letto disponibile quando non ci sono giocatori online.
  • Uso del letto come punto di attesa e sicurezza.
  • Se trova pecore in modalita’ libera, tentativo di ottenere lana e farsi un letto.
  • Migliorie per fare sembrare il comportamento notturno piu’ naturale.

11. Comando dormi con disconnessione e riconnessione

  • Comando orione dormi.
  • Disconnessione volontaria del bot per 30 secondi.
  • Persistenza del timer di sleep su file.
  • Ritardo di bootstrap al riavvio per aspettare la fine del periodo di sonno prima di riconnettersi.

12. Difesa della cassa e combattimento

  • Comportamento difensivo verso zombie vicini alla cassa.
  • Modalita’ di guardia notturna quando il bot si trova in zona letto o in area base.
  • Interruzione del riposo in presenza di ostili.
  • Primo passaggio da NPC passivo a NPC che difende una zona precisa.

13. Armi, armatura e crafting base

  • Costruzione o recupero di armi base.
  • Tentativo di craftare una spada, preferendo opzioni migliori quando disponibili.
  • Equip automatico di armatura e strumenti trovati o presi dalla cassa.
  • Introduzione di una logica minima di preparazione al combattimento.

14. Coordinate, presenza e realismo

  • Aggiunta di comandi per sapere dove si trova Orione.
  • Aggiunta della possibilita’ di sapere dove si trova il giocatore.
  • Migliorie generali per farlo apparire piu’ simile a un giocatore reale e meno a un processo invisibile.

15. Integrazione della logica di servizio e obbedienza selettiva

  • Obiettivo esplicito: Orione deve rispondere ai comandi solo se gli viene detto orione servo.
  • Riduzione dei comportamenti da “sempre in ascolto”.
  • Maggiore coerenza tra ruolo del personaggio e interazione col proprietario.

16. Gestione problemi osservati durante i test

  • Correzioni legate al fatto che Orione non guardava correttamente nella cassa prima di craftare un oggetto.
  • Correzioni legate alla perdita del riferimento della cassa dopo i riavvii.
  • Migliorie sulla resilienza del bot quando il pathfinding si bloccava o il task falliva.
  • Logging e verifiche incrementali basate sul comportamento reale osservato nel server.

17. Compatibilita’ tecnica del bot

  • Separazione del server Minecraft e del bot in container diversi.
  • Uso di ViaProxy per gestire differenze di protocollo tra bot e server.
  • Integrazione del tutto come servizi di sistema con restart automatico.
  • Preparazione del bot per un funzionamento stabile nel tempo, non solo per una demo temporanea.

Struttura logica del bot

Il codice del bot e’ stato organizzato attorno ad alcuni blocchi fondamentali:

  • bootstrap e caricamento configurazione da .env;
  • lettura e scrittura dello stato persistente su file JSON;
  • inizializzazione della connessione Mineflayer;
  • registrazione dei listener sugli eventi principali (spawn, chat, end, error);
  • parser dei comandi testuali;
  • loop periodici per raccolta, difesa, riposo, crafting e gestione della cassa;
  • funzioni di utilita’ per ricerca blocchi, movimento, withdraw/deposit e verifica delle risorse.

Una semplificazione della struttura iniziale in JavaScript e’ questa:

import mineflayer from 'mineflayer'
import { pathfinder } from 'mineflayer-pathfinder'
import OpenAI from 'openai'
import fs from 'fs'
import dotenv from 'dotenv'

dotenv.config()

const bot = mineflayer.createBot({
  host: process.env.MC_HOST,
  port: Number(process.env.MC_PORT || 25565),
  username: process.env.MC_USERNAME,
  version: process.env.MC_VERSION
})

bot.loadPlugin(pathfinder)

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const stateFile = '/opt/minecraft-ai-bot/state.json'

function loadState() {
  try {
    return JSON.parse(fs.readFileSync(stateFile, 'utf8'))
  } catch {
    return {}
  }
}

function saveState(state) {
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2))
}

Configurazione minima per ricrearlo

Per replicare il progetto servono almeno due ambienti separati: uno con Minecraft server e uno con Node.js. Nel container del bot bastano pochi file iniziali:

mkdir -p /opt/minecraft-ai-bot
cd /opt/minecraft-ai-bot
npm init -y
npm install mineflayer mineflayer-pathfinder mineflayer-collectblock openai dotenv vec3

Un esempio di file .env e’ il seguente:

MC_HOST=172.16.4.10
MC_PORT=25565
MC_USERNAME=OrioneAI
MC_VERSION=1.21.4
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-5.5

Per l’avvio automatico del bot ho usato un servizio systemd simile a questo:

[Unit]
Description=Minecraft AI Bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/minecraft-ai-bot
ExecStart=/usr/bin/node /opt/minecraft-ai-bot/index.js
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Gestione dei comandi in gioco

Una parte importante del progetto e’ il parser dei comandi chat. La logica non si limita a confrontare stringhe, ma applica anche regole di autorizzazione: ad esempio Orione deve rispondere davvero solo quando entra in modalita’ servo, oppure quando il comando proviene dal giocatore autorizzato.

bot.on('chat', async (username, message) => {
  if (username === bot.username) return

  const text = message.trim().toLowerCase()

  if (text === 'orione servo') {
    state.servantLeader = username
    saveState(state)
    bot.chat(`Servo attivo per ${username}`)
    return
  }

  if (text === 'orione coordinate') {
    const p = bot.entity.position
    bot.chat(`Sono a ${Math.floor(p.x)} ${Math.floor(p.y)} ${Math.floor(p.z)}`)
    return
  }
})

Raccolta risorse e cassa condivisa

Per far sembrare Orione utile e credibile non bastava farlo camminare. Ho aggiunto un sistema di lavoro basato su priorita’: ricerca di legna da alberi naturali, recupero di carbone esposto, uso di una cassa assegnata come deposito e prelievo automatico di strumenti, torce, cibo e materiali. Il bot controlla il contenuto della cassa, valuta il proprio inventario e poi decide se prelevare, craftare o cercare nel mondo.

Questa parte richiede attenzione per evitare effetti collaterali indesiderati: ad esempio considerare come legna solo i tronchi realmente associati ad alberi, cosi’ da non smontare case o strutture dei giocatori.

Difesa, notte e comportamenti autonomi

Una parte interessante del progetto e’ stata l’aggiunta di comportamenti da pseudo-giocatore: difesa della zona della cassa, ricerca di un letto quando non ci sono utenti online, crafting di strumenti base, equipaggiamento minimo e modalita’ libera per raccolta di risorse. Ho introdotto anche un comando di sospensione temporanea, in cui Orione si disconnette e si ricollega dopo un intervallo prestabilito.

Per la notte e per i momenti di inattivita’ la logica puo’ essere schematizzata cosi’: se non ci sono giocatori online, il bot cerca il primo letto disponibile; se trova ostili nei pressi della zona protetta, interrompe il riposo e passa in modalita’ difesa.

Integrazione con OpenAI

L’integrazione con OpenAI e’ stata pensata come uno strato sopra la logica operativa. In pratica il modello puo’ essere usato per interpretare intenzioni, spiegare cosa sta facendo il bot, generare risposte piu’ naturali o decidere azioni ad alto livello. La parte critica e’ non lasciare che il modello esegua direttamente qualsiasi cosa: serve sempre una mappatura tra intenzione e set di azioni consentite dal codice.

async function askOpenAI(prompt) {
  const response = await openai.responses.create({
    model: process.env.OPENAI_MODEL || 'gpt-5.5',
    input: prompt
  })

  return response.output_text
}

Nella pratica conviene usare il modello per analisi e pianificazione locale, mentre la parte esecutiva resta in funzioni deterministiche del bot.

Passaggi pratici di sviluppo

  1. Creare il server Minecraft e verificare che un client umano possa entrare correttamente.
  2. Creare un container separato per il bot con Node.js, cosi’ da isolare dipendenze, log e restart.
  3. Far connettere inizialmente un bot minimale, senza IA, solo con login ed evento spawn.
  4. Aggiungere il pathfinding e verificare movimenti semplici verso coordinate o giocatori.
  5. Implementare un parser di comandi ridotto, per esempio orione servo e orione coordinate.
  6. Aggiungere gestione dell’inventario e interazione con una cassa vicina.
  7. Inserire persistenza dello stato per non perdere riferimenti importanti al riavvio.
  8. Solo dopo questa base, integrare OpenAI per risposte, pianificazione o comportamento contestuale.
  9. Infine consolidare con systemd, logging e test ripetuti nel mondo reale.

Cosa serve davvero per ricrearlo

Se qualcuno volesse rifare il progetto da zero, questi sono i punti essenziali:

  1. un server Minecraft Java accessibile in rete;
  2. un bot Mineflayer che riesca a connettersi stabilmente al server;
  3. un sistema di persistenza locale per ricordare stato e coordinate importanti;
  4. un parser dei comandi semplice ma robusto;
  5. funzioni di movimento affidabili e protette da timeout o fallback;
  6. una gestione sicura di casse, crafting e inventario;
  7. una separazione chiara tra IA testuale e azioni consentite in gioco;
  8. servizi systemd per restart automatico e resilienza al boot.

Le difficolta’ principali

La parte piu’ complessa non e’ stata la chat, ma il comportamento fisico nel mondo: pathfinding, ostacoli, blocchi irregolari, inventario, gestione delle casse e distinzione tra risorse naturali e strutture costruite dal giocatore. Un bot che sembra davvero presente deve saper fallire bene, riprovare, evitare di scavare a caso e non distruggere la base.

Per questo ho introdotto controlli aggiuntivi sulla raccolta della legna, limiti sulla ricerca del carbone in profondita’, persistenza della cassa, fallback sui task bloccati e log di debug per capire quando il percorso o l’interazione con un blocco fallivano.

Prossimi sviluppi

La base adesso e’ solida: il prossimo passo e’ rendere Orione ancora piu’ credibile, con routine migliori, priorita’ dinamiche, uso piu’ intelligente delle risorse nella cassa, e una componente conversazionale sempre piu’ integrata con lo stato reale del mondo di gioco.

L’idea finale e’ semplice: non un plugin che simula un NPC, ma un personaggio che vive davvero nel server e collabora con i giocatori come se fosse uno di loro.