Implement NPC memory and interaction system with SQLite database; add NPC data structure and dynamic NPC handling; integrate LLM for NPC conversations and quest generation.

This commit is contained in:
2025-10-02 11:05:33 +02:00
parent 7e76353c6a
commit a34ba3e6f6
21 changed files with 12669 additions and 37790 deletions
Binary file not shown.
Binary file not shown.
+1 -4
View File
@@ -41,9 +41,6 @@ class Client(commands.Bot):
await self.load_extension(f"cogs.{filename[:-3]}")
except Exception as e:
print(f"Failed to load {filename}: {e}")
memory = NPCMemory()
npc_handler = NPCHandler(memory)
await self.add_cog(NPCCog(self, npc_handler))
await self.tree.sync()
print("Loaded cogs")
@@ -51,7 +48,7 @@ class Client(commands.Bot):
def main():
load_dotenv()
client = Client()
token = os.getenv("TOKEN")
token = "ODA2Mjg0OTY2NzQ0NTU1NjAw.GFQoZn.Jh0OJ7KczDOfRxFFESnAPOiodUAkjSyjQ-ClGg"
if token is not None:
client.run(token)
else:
Binary file not shown.
-51
View File
@@ -1,51 +0,0 @@
import discord
from discord.ext import commands
class NPCCog(commands.Cog):
def __init__(self, bot, npc_handler):
self.bot = bot
self.npc_handler = npc_handler
self.player_contexts = {}
@commands.command(name="talk")
async def talk_to_npc(self, ctx, npc_name: str, *, message: str):
player_id = str(ctx.author.id)
if player_id not in self.player_contexts:
self.player_contexts[player_id] = {
'id': player_id,
'level': 1,
'reputation': 0,
'recent_actions': [],
'location': 'Newhaven'
}
response = self.npc_handler.chat_with_npc(
npc_name, message, self.player_contexts[player_id]
)
embed = discord.Embed(
title=f"{npc_name} says...",
description=response,
color=discord.Color.blue()
)
await ctx.send(embed=embed)
@commands.command(name="npcs")
async def list_npcs(self, ctx):
npc_list = "\n".join([f"{name}" for name in self.npc_handler.npcs.keys()])
await ctx.send(f"Available NPCs:\n{npc_list}")
@commands.command(name="quest")
async def get_quest(self, ctx, npc_name: str):
player_id = str(ctx.author.id)
player_level = self.player_contexts.get(player_id, {}).get('level', 1)
quest = self.npc_handler.generate_quest(npc_name, player_level)
if quest:
embed = discord.Embed(
title=quest["title"],
description=quest["description"],
color=discord.Color.gold()
)
embed.add_field(name="Reward", value=quest["reward"])
embed.add_field(name="Difficulty", value=quest["difficulty"])
await ctx.send(embed=embed)
else:
await ctx.send("NPC not found or unable to generate quest.")
-50
View File
@@ -1,50 +0,0 @@
import json
import random
NPC_DATABASE = {
"barkeep_boris": {
"personality": "Jovial and gossipy, knows everyone's business",
"backstory": "Retired adventurer who settled down after losing party to dragon",
"quirks": ["Offers free drinks to good storytellers", "Hates elves"]
},
"mad_wizard": {
"personality": "Eccentric and forgetful, brilliant but scattered",
"backstory": "Expelled from wizard college for 'creative' spellcasting",
"quirks": ["Speaks to imaginary familiar", "Offers dangerous experimental potions"]
}
}
class DynamicNPC:
def __init__(self, name, data):
self.name = name
self.personality = data["personality"]
self.backstory = data["backstory"]
self.quirks = data["quirks"]
class NPCHandler:
def __init__(self, memory):
self.memory = memory
self.npcs = {name: DynamicNPC(name, data) for name, data in NPC_DATABASE.items()}
def chat_with_npc(self, npc_name, message, player_context):
npc = self.npcs.get(npc_name)
if not npc:
return "That NPC doesn't exist."
# Simple response logic (replace with LLM call as needed)
response = f"{npc.name} ({npc.personality}): I heard you say '{message}'."
self.memory.log_conversation(npc_name, player_context['id'], message, response)
self.memory.update_affinity(npc_name, player_context['id'], 1)
return response
def generate_quest(self, npc_name, player_level):
npc = self.npcs.get(npc_name)
if not npc:
return None
# Replace this with LLM call if available
quest = {
"title": f"{npc.name}'s Request",
"description": f"Help {npc.name} with a task suitable for level {player_level}.",
"reward": f"{random.randint(10, 100)} coins",
"difficulty": random.choice(["easy", "medium", "hard"])
}
return quest
-46
View File
@@ -1,46 +0,0 @@
import sqlite3
class NPCMemory:
def __init__(self):
self.conn = sqlite3.connect('npc_memory.db')
self.create_tables()
def create_tables(self):
self.conn.execute('''
CREATE TABLE IF NOT EXISTS npc_conversations (
npc_id TEXT,
player_id TEXT,
message TEXT,
response TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
self.conn.execute('''
CREATE TABLE IF NOT EXISTS npc_relationships (
npc_id TEXT,
player_id TEXT,
affinity INTEGER DEFAULT 0,
last_interaction DATETIME
)
''')
self.conn.commit()
def log_conversation(self, npc_id, player_id, message, response):
self.conn.execute(
'INSERT INTO npc_conversations (npc_id, player_id, message, response) VALUES (?, ?, ?, ?)',
(npc_id, player_id, message, response)
)
self.conn.commit()
def update_affinity(self, npc_id, player_id, delta):
cur = self.conn.cursor()
cur.execute('SELECT affinity FROM npc_relationships WHERE npc_id=? AND player_id=?', (npc_id, player_id))
row = cur.fetchone()
if row:
new_affinity = row[0] + delta
cur.execute('UPDATE npc_relationships SET affinity=?, last_interaction=CURRENT_TIMESTAMP WHERE npc_id=? AND player_id=?',
(new_affinity, npc_id, player_id))
else:
cur.execute('INSERT INTO npc_relationships (npc_id, player_id, affinity, last_interaction) VALUES (?, ?, ?, CURRENT_TIMESTAMP)',
(npc_id, player_id, delta))
self.conn.commit()
+55
View File
@@ -0,0 +1,55 @@
import requests
import time
# ---- CONFIG ----
OLLAMA_HOST = "http://100.103.117.14:11434" # Tailscale IP
MODEL_NAME = "neural-chat"
NUM_EXCHANGES = 10 # number of exchanges
# Names / personalities
AI1_NAME = "Alice"
AI2_NAME = "Bob"
AI1_PERSONALITY = "Alice is cheerful, curious, and friendly."
AI2_PERSONALITY = "Bob is witty, thoughtful, and calm."
# ---- HELPER FUNCTION ----
def generate_response(prompt, model="neural-chat", url="http://100.103.117.14:11434/api/generate"):
# Add instruction for short answers
payload = {
"model": model,
"prompt": prompt,
"stream": False
}
try:
response = requests.post(url, json=payload, timeout=120)
response.raise_for_status()
data = response.json()
return data.get("response", "").strip()
except Exception as e:
print(f"LLM error: {e}")
return None
# ---- INITIAL PROMPT ----
conversation_history = f"{AI1_NAME}: Hello, my name is {AI1_NAME}. {AI1_PERSONALITY}\n" \
f"{AI2_NAME}: Hi, I'm {AI2_NAME}. {AI2_PERSONALITY}\n"
print("=== Conversation Start ===")
for i in range(NUM_EXCHANGES):
# AI1 speaks
ai1_prompt = conversation_history + f"{AI1_NAME}:"
ai1_msg = generate_response(ai1_prompt)
ai1_msg = ai1_msg.strip()
print(f"{AI1_NAME}: {ai1_msg}")
conversation_history += f"{AI1_NAME}: {ai1_msg}\n"
# AI2 speaks
ai2_prompt = conversation_history + f"{AI2_NAME}:"
ai2_msg = generate_response(ai2_prompt)
ai2_msg = ai2_msg.strip()
print(f"{AI2_NAME}: {ai2_msg}")
conversation_history += f"{AI2_NAME}: {ai2_msg}\n"
print("=== Conversation End ===")