import { z } from 'zod'; import type { GameConnector,Bot,Decision,RuntimeConfig } from '../types'; const deathSignal=z.object({ id:z.string().min(1).max(160), at:z.string().min(1).max(80), cause:z.string().min(1).max(300), killer:z.string().max(120).optional(), zone:z.string().max(120).optional(), verified:z.boolean() }).strict(); const observation=z.object({ zone:z.string().max(120), level:z.number().int().min(1).max(100), summary:z.string().max(2000), availableActions:z.array(z.enum(['explore','quest','socialize','trade','rest'])), alive:z.boolean().default(true), death:deathSignal.optional() }); export class SimulationConnector implements GameConnector { id='simulation'; async health(){return {ok:true,detail:'Simulation ready. No AzerothCore connection.'};} async observe(bot:Bot){return {zone:bot.zone,level:bot.level,availableActions:bot.personality.activities,summary:'Simulated world observation. Game mechanics are not executed.',alive:bot.status!=='dead'};} async lifecycle(){ /* Acknowledged only inside the simulation. */ } async decide(bot:Bot,d:Decision){return {summary:`${bot.name} chose to ${d.action}: ${d.goal}`,zone:bot.zone};} } export class AzerothCoreConnector implements GameConnector { id='azerothcore'; constructor(private config:RuntimeConfig){} private async request(path:string,body?:unknown){ if(!this.config.BRIDGE_URL||!this.config.BRIDGE_TOKEN)throw new Error('Configure BRIDGE_URL and BRIDGE_TOKEN on the server first.'); const url=new URL(this.config.BRIDGE_URL); if(!['http:','https:'].includes(url.protocol))throw new Error('Invalid bridge URL'); const response=await fetch(`${url.href.replace(/\/$/,'')}/v1${path}`,{method:body?'POST':'GET',headers:{Authorization:`Bearer ${this.config.BRIDGE_TOKEN}`,'Content-Type':'application/json'},...(body?{body:JSON.stringify(body)}:{}),signal:AbortSignal.timeout(12000),redirect:'error'}); if(!response.ok)throw new Error(`Game bridge returned HTTP ${response.status}`); return response.json(); } async health(){const r=z.object({ok:z.boolean(),detail:z.string().max(300)}).parse(await this.request('/health'));return r;} async observe(bot:Bot){return observation.parse(await this.request(`/bots/${encodeURIComponent(bot.id)}/observation`));} async lifecycle(bot:Bot,action:string,requestId:string){ const r=z.object({accepted:z.literal(true)}).parse(await this.request(`/bots/${encodeURIComponent(bot.id)}/lifecycle`,{action,identity:{id:bot.id,name:bot.name,race:bot.race,role:bot.role,tier:bot.tier,mortal:bot.mortal,plotArmorHpFloor:bot.plotArmorHpFloor},requestId})); void r; } async decide(bot:Bot,decision:Decision,requestId:string){return z.object({accepted:z.literal(true),summary:z.string().max(1000),zone:z.string().max(120).optional()}).parse(await this.request(`/bots/${encodeURIComponent(bot.id)}/intent`,{decision,requestId}));} } // Add a connector factory here; personality and memory code remains game-independent. export const connectors:RecordGameConnector>={simulation:()=>new SimulationConnector(),azerothcore:c=>new AzerothCoreConnector(c)};