feat: implement core D&D helpers logic and system architecture
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Persistence module for D&D Helpers.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from src.llm.models import CharacterStateUpdate
|
||||
|
||||
DATA_CHARS_DIR = Path("data/chars")
|
||||
|
||||
|
||||
def ensure_chars_dir():
|
||||
"""Ensures the character data directory exists."""
|
||||
DATA_CHARS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def get_character_state(character_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Reads character state from a JSON file.
|
||||
If the character doesn't exist, returns a default state.
|
||||
"""
|
||||
ensure_chars_dir()
|
||||
file_path = DATA_CHARS_DIR / f"{character_name}.json"
|
||||
|
||||
if not file_path.exists():
|
||||
return {
|
||||
"character_name": character_name,
|
||||
"stats": {"hp": 0, "max_hp": 0, "ac": 0},
|
||||
"status_effects": [],
|
||||
"inventory": [],
|
||||
}
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def update_character_state(update: CharacterStateUpdate):
|
||||
"""
|
||||
Updates character state based on a CharacterStateUpdate model.
|
||||
Reads the current state, applies changes, and writes it back.
|
||||
"""
|
||||
ensure_chars_dir()
|
||||
|
||||
state = get_character_state(update.character_name)
|
||||
|
||||
# Update HP
|
||||
if update.hp_change is not None:
|
||||
current_hp = state.get("stats", {}).get("hp", 0)
|
||||
state["stats"]["hp"] = current_hp + update.hp_change
|
||||
|
||||
# Update Status Effects
|
||||
status_effects = set(state.get("status_effects", []))
|
||||
for effect in update.status_effects_added:
|
||||
status_effects.add(effect)
|
||||
for effect in update.status_effects_removed:
|
||||
status_effects.discard(effect)
|
||||
state["status_effects"] = list(status_effects)
|
||||
|
||||
# Update Inventory
|
||||
inventory = state.get("inventory", [])
|
||||
for change in update.inventory_changes:
|
||||
# Find if item already exists
|
||||
item_exists = False
|
||||
for item in inventory:
|
||||
if item["item"] == change.item:
|
||||
if change.action == "added":
|
||||
item["quantity"] = item.get("quantity", 1) + change.quantity
|
||||
elif change.action == "removed":
|
||||
item["quantity"] = max(0, item.get("quantity", 1) - change.quantity)
|
||||
item_exists = True
|
||||
break
|
||||
|
||||
if not item_exists:
|
||||
if change.action == "added":
|
||||
inventory.append(
|
||||
{
|
||||
"item": change.item,
|
||||
"quantity": change.quantity,
|
||||
"weight": 0, # Default weight if not provided
|
||||
}
|
||||
)
|
||||
elif change.action == "removed":
|
||||
# Item not in inventory, do nothing or log
|
||||
pass
|
||||
|
||||
state["inventory"] = inventory
|
||||
|
||||
file_path = DATA_CHARS_DIR / f"{update.character_name}.json"
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=4)
|
||||
|
||||
return str(file_path)
|
||||
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from src.llm.models import LoreUpdate
|
||||
|
||||
DATA_LORE_DIR = Path("data/lore")
|
||||
|
||||
CATEGORY_MAP = {
|
||||
"NPC": "NPCs",
|
||||
"Location": "Locations",
|
||||
"WorldBuilding": "World",
|
||||
"Plot": "Timeline",
|
||||
}
|
||||
|
||||
|
||||
def ensure_lore_dir():
|
||||
"""Ensures the lore data directory and subdirectories exist."""
|
||||
DATA_LORE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
for folder in CATEGORY_MAP.values():
|
||||
if folder != "Timeline":
|
||||
(DATA_LORE_DIR / folder).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def update_lore(update: LoreUpdate):
|
||||
"""
|
||||
Updates lore based on a LoreUpdate model.
|
||||
- For NPC/Location/WorldBuilding: Updates the specific entity's file.
|
||||
- For Plot: Appends to Timeline.md.
|
||||
"""
|
||||
ensure_lore_dir()
|
||||
|
||||
category = update.category
|
||||
folder = CATEGORY_MAP.get(category, "Other")
|
||||
|
||||
if category == "Plot":
|
||||
file_path = DATA_LORE_DIR / "Timeline.md"
|
||||
content_to_append = f"- {update.content}\n"
|
||||
elif update.entity_name:
|
||||
category_folder = CATEGORY_MAP.get(category, "Other")
|
||||
file_path = DATA_LORE_DIR / category_folder / f"{update.entity_name}.md"
|
||||
# For entity-specific files, we can append the content as a list item.
|
||||
# In a more complex system, we might check for existing headers.
|
||||
content_to_append = f"- {update.content}\n"
|
||||
else:
|
||||
# Fallback if no entity name is provided for a category that expects one.
|
||||
# We'll put it in a general file for that category.
|
||||
category_folder = CATEGORY_MAP.get(category, "Other")
|
||||
file_path = DATA_LORE_DIR / category_folder / "General.md"
|
||||
content_to_append = f"- {update.content}\n"
|
||||
|
||||
with open(file_path, "a", encoding="utf-8") as f:
|
||||
f.write(content_to_append)
|
||||
|
||||
return str(file_path)
|
||||
Reference in New Issue
Block a user