+ CURRENT WORLD RUMORS/EVENTS:
+ {world_state}
+
+ {target_context}
+ React appropriately based on your personality, alignment, and current strategy rules.
+
+ CRITICAL ENGINE RULES:
+ Respond ONLY in valid JSON. You MUST use exactly these keys: "thought", "speech", "emotion", "action", "action_target", and "target_speech".
+
+ ACTION RULE:
+ Your "action" key MUST be exactly one of the following words:
+ {action_macros}
+
+ - Use CONVERSE if you want to initiate a back-and-forth dialogue with a standard, unintelligent NPC. You will write their response for them in "target_speech".
+
+ YOUR RESPONSE MUST BE A SINGLE, VALID JSON OBJECT. YOU MUST USE THIS EXACT TEMPLATE:
+ {{
+ "thought": "Your internal reasoning here.",
+ "speech": "What YOU say out loud.",
+ "emotion": "MACRO WORD",
+ "action": "MACRO WORD",
+ "action_target": "Target name",
+ "target_speech": "If action is CONVERSE, write what the target NPC replies back to you here. Otherwise, leave blank."
+ }}
+ """
+
+ if session_id not in chat_memory:
+ chat_memory[session_id] = [{"role": "system", "content": dynamic_system_prompt}]
+ else:
+ chat_memory[session_id][0] = {"role": "system", "content": dynamic_system_prompt}
+
+ chat_memory[session_id].append({"role": "user", "content": f"{player_name} says: {message}"})
+
+ # =====================================================================
+ # MEMORY EXTRACTION TRIGGER
+ # =====================================================================
+ if len(chat_memory[session_id]) > 10:
+ messages_to_summarize = chat_memory[session_id][1:6]
+ chat_log_str = "\n".join([m['content'] for m in messages_to_summarize])
+
+ await memory_queue.put({
+ 'session_id': session_id,
+ 'player_name': player_name,
+ 'npc_tag': npc_tag,
+ 'chat_log': chat_log_str
+ })
+
+ chat_memory[session_id] = [chat_memory[session_id][0]] + chat_memory[session_id][-5:]
+
+ # =====================================================================
+ # LLM INFERENCE
+ # =====================================================================
+ async with semaphore:
+ print(f"[THINKING] Processing reply for {player_name} (Strategy {llm_strategy})...")
+ async with session.post('http://localhost:11434/api/chat', json={
+ "model": "llama3",
+ "messages": chat_memory[session_id],
+ "format": "json",
+ "stream": False,
+ "options": {
+ "temperature": 0.2
+ }
+ }, timeout=45) as response:
+
+ response.raise_for_status()
+ result = await response.json()
+ raw_reply_text = result['message']['content']
+
+ # =====================================================================
+ # JSON SANITIZATION
+ # =====================================================================
+ try:
+ agent_brain = json.loads(raw_reply_text)
+ agent_brain = {k.lower(): v for k, v in agent_brain.items()}
+
+ if "thought" not in agent_brain: agent_brain["thought"] = ""
+ if "speech" not in agent_brain: agent_brain["speech"] = ""
+ if "emotion" not in agent_brain: agent_brain["emotion"] = "NEUTRAL"
+ if "action" not in agent_brain: agent_brain["action"] = "WANDER"
+ if "action_target" not in agent_brain: agent_brain["action_target"] = ""
+ if "target_speech" not in agent_brain: agent_brain["target_speech"] = ""
+
+ if not agent_brain["speech"].strip():
+ agent_brain["speech"] = "*grunts quietly*"
+
+ agent_brain["action_target"] = agent_brain["action_target"].replace("?", "").replace(".", "").strip()
+
+ clean_reply_text = json.dumps(agent_brain)
+
+ except json.JSONDecodeError:
+ print(f"[WARNING] AI Hallucinated! Overriding with safe defaults.")
+ clean_reply_text = json.dumps({
+ "thought": "I lost my train of thought.",
+ "speech": "*grunts quietly*",
+ "emotion": "NEUTRAL",
+ "action": "WANDER",
+ "action_target": "",
+ "target_speech": ""
+ })
+
+ print(f"[REPLY] from {npc_tag} to {player_name}: {clean_reply_text}")
+ chat_memory[session_id].append({"role": "assistant", "content": clean_reply_text})