What a mess
This commit is contained in:
290
api/world_loader.py
Normal file
290
api/world_loader.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""
|
||||
Standalone world loader for the API.
|
||||
Loads game data from JSON files without bot dependencies.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Outcome:
|
||||
"""Represents an outcome of an action"""
|
||||
text: str
|
||||
items_reward: Dict[str, int] = field(default_factory=dict)
|
||||
damage_taken: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Action:
|
||||
"""Represents an action that can be performed on an interactable"""
|
||||
id: str
|
||||
label: str
|
||||
stamina_cost: int = 2
|
||||
outcomes: Dict[str, Outcome] = field(default_factory=dict)
|
||||
|
||||
def add_outcome(self, outcome_type: str, outcome: Outcome):
|
||||
self.outcomes[outcome_type] = outcome
|
||||
|
||||
|
||||
@dataclass
|
||||
class Interactable:
|
||||
"""Represents an interactable object"""
|
||||
id: str
|
||||
name: str
|
||||
image_path: str = ""
|
||||
actions: List[Action] = field(default_factory=list)
|
||||
|
||||
def add_action(self, action: Action):
|
||||
self.actions.append(action)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Exit:
|
||||
"""Represents an exit from a location"""
|
||||
direction: str
|
||||
destination: str
|
||||
description: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Location:
|
||||
"""Represents a location in the game world"""
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
image_path: str = ""
|
||||
exits: Dict[str, str] = field(default_factory=dict) # direction -> destination_id
|
||||
exit_stamina: Dict[str, int] = field(default_factory=dict) # direction -> stamina_cost
|
||||
interactables: List[Interactable] = field(default_factory=list)
|
||||
npcs: List[str] = field(default_factory=list)
|
||||
tags: List[str] = field(default_factory=list) # Location tags like 'workbench', 'safe_zone'
|
||||
x: float = 0.0 # X coordinate for distance calculations
|
||||
y: float = 0.0 # Y coordinate for distance calculations
|
||||
danger_level: int = 0 # Danger level (0-5)
|
||||
|
||||
def add_exit(self, direction: str, destination: str, stamina_cost: int = 5):
|
||||
self.exits[direction] = destination
|
||||
self.exit_stamina[direction] = stamina_cost
|
||||
|
||||
def add_interactable(self, interactable: Interactable):
|
||||
self.interactables.append(interactable)
|
||||
|
||||
|
||||
@dataclass
|
||||
class World:
|
||||
"""Represents the entire game world"""
|
||||
locations: Dict[str, Location] = field(default_factory=dict)
|
||||
|
||||
def add_location(self, location: Location):
|
||||
self.locations[location.id] = location
|
||||
|
||||
|
||||
class WorldLoader:
|
||||
"""Loads world data from JSON files"""
|
||||
|
||||
def __init__(self, gamedata_path: str = "./gamedata"):
|
||||
self.gamedata_path = Path(gamedata_path)
|
||||
self.interactable_templates = {}
|
||||
|
||||
def load_interactable_templates(self) -> Dict[str, Any]:
|
||||
"""Load interactable templates from interactables.json"""
|
||||
json_path = self.gamedata_path / 'interactables.json'
|
||||
|
||||
try:
|
||||
with open(json_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
self.interactable_templates = data.get('interactables', {})
|
||||
print(f"📦 Loaded {len(self.interactable_templates)} interactable templates")
|
||||
except FileNotFoundError:
|
||||
print("⚠️ interactables.json not found")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error loading interactables.json: {e}")
|
||||
|
||||
return self.interactable_templates
|
||||
|
||||
def create_interactable_from_template(
|
||||
self,
|
||||
template_id: str,
|
||||
template_data: Dict[str, Any],
|
||||
instance_data: Dict[str, Any]
|
||||
) -> Interactable:
|
||||
"""Create an Interactable object from template and instance data"""
|
||||
interactable = Interactable(
|
||||
id=template_id,
|
||||
name=template_data.get('name', 'Unknown'),
|
||||
image_path=template_data.get('image_path', '')
|
||||
)
|
||||
|
||||
# Get actions from template
|
||||
template_actions = template_data.get('actions', {})
|
||||
|
||||
# Get outcomes from instance
|
||||
instance_outcomes = instance_data.get('outcomes', {})
|
||||
|
||||
# Build actions by merging template actions with instance outcomes
|
||||
for action_id, action_template in template_actions.items():
|
||||
action = Action(
|
||||
id=action_template['id'],
|
||||
label=action_template['label'],
|
||||
stamina_cost=action_template.get('stamina_cost', 2)
|
||||
)
|
||||
|
||||
# Get instance-specific outcome data for this action
|
||||
if action_id in instance_outcomes:
|
||||
outcome_data = instance_outcomes[action_id]
|
||||
|
||||
# Build outcomes from the instance data
|
||||
text_dict = outcome_data.get('text', {})
|
||||
rewards = outcome_data.get('rewards', {})
|
||||
|
||||
# Add success outcome
|
||||
if text_dict.get('success'):
|
||||
items_reward = {}
|
||||
if 'items' in rewards:
|
||||
for item in rewards['items']:
|
||||
items_reward[item['item_id']] = item.get('quantity', 1)
|
||||
|
||||
outcome = Outcome(
|
||||
text=text_dict['success'],
|
||||
items_reward=items_reward,
|
||||
damage_taken=rewards.get('damage', 0)
|
||||
)
|
||||
action.add_outcome('success', outcome)
|
||||
|
||||
# Add failure outcome
|
||||
if text_dict.get('failure'):
|
||||
outcome = Outcome(
|
||||
text=text_dict['failure'],
|
||||
items_reward={},
|
||||
damage_taken=0
|
||||
)
|
||||
action.add_outcome('failure', outcome)
|
||||
|
||||
# Add critical failure outcome
|
||||
if text_dict.get('crit_failure'):
|
||||
outcome = Outcome(
|
||||
text=text_dict['crit_failure'],
|
||||
items_reward={},
|
||||
damage_taken=rewards.get('crit_damage', 0)
|
||||
)
|
||||
action.add_outcome('critical_failure', outcome)
|
||||
|
||||
interactable.add_action(action)
|
||||
|
||||
return interactable
|
||||
|
||||
def load_locations(self) -> Dict[str, Location]:
|
||||
"""Load all locations from locations.json"""
|
||||
json_path = self.gamedata_path / 'locations.json'
|
||||
locations = {}
|
||||
|
||||
try:
|
||||
with open(json_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Get danger config
|
||||
danger_config = data.get('danger_config', {})
|
||||
|
||||
# First pass: create all locations
|
||||
locations_data = data.get('locations', [])
|
||||
if isinstance(locations_data, dict):
|
||||
# Old format: dict of locations
|
||||
locations_iter = locations_data.items()
|
||||
else:
|
||||
# New format: list of locations
|
||||
locations_iter = [(loc['id'], loc) for loc in locations_data]
|
||||
|
||||
for loc_id, loc_data in locations_iter:
|
||||
# Get danger level from danger_config
|
||||
danger_level = 0
|
||||
if loc_id in danger_config:
|
||||
danger_level = danger_config[loc_id].get('danger_level', 0)
|
||||
|
||||
location = Location(
|
||||
id=loc_id,
|
||||
name=loc_data.get('name', 'Unknown Location'),
|
||||
description=loc_data.get('description', ''),
|
||||
image_path=loc_data.get('image_path', ''),
|
||||
x=float(loc_data.get('x', 0.0)),
|
||||
y=float(loc_data.get('y', 0.0)),
|
||||
danger_level=danger_level,
|
||||
tags=loc_data.get('tags', []),
|
||||
npcs=loc_data.get('npcs', [])
|
||||
)
|
||||
|
||||
# Add exits
|
||||
for direction, destination in loc_data.get('exits', {}).items():
|
||||
location.add_exit(direction, destination)
|
||||
|
||||
# Add NPCs
|
||||
location.npcs = loc_data.get('npcs', [])
|
||||
|
||||
# Add interactables
|
||||
interactables_data = loc_data.get('interactables', {})
|
||||
if isinstance(interactables_data, dict):
|
||||
# New format: dict of interactables
|
||||
interactables_list = [
|
||||
{**data, 'instance_id': inst_id, 'id': data.get('template_id', inst_id)}
|
||||
for inst_id, data in interactables_data.items()
|
||||
]
|
||||
else:
|
||||
# Old format: list of interactables
|
||||
interactables_list = interactables_data
|
||||
|
||||
for interactable_data in interactables_list:
|
||||
template_id = interactable_data.get('id')
|
||||
instance_id = interactable_data.get('instance_id', template_id)
|
||||
|
||||
if template_id in self.interactable_templates:
|
||||
template = self.interactable_templates[template_id]
|
||||
interactable = self.create_interactable_from_template(
|
||||
instance_id,
|
||||
template,
|
||||
interactable_data
|
||||
)
|
||||
location.add_interactable(interactable)
|
||||
|
||||
locations[loc_id] = location
|
||||
|
||||
# Second pass: add connections from the connections array
|
||||
connections = data.get('connections', [])
|
||||
for conn in connections:
|
||||
from_id = conn.get('from')
|
||||
to_id = conn.get('to')
|
||||
direction = conn.get('direction')
|
||||
stamina_cost = conn.get('stamina_cost', 5) # Default 5 if not specified
|
||||
|
||||
if from_id in locations and direction:
|
||||
locations[from_id].add_exit(direction, to_id, stamina_cost)
|
||||
|
||||
print(f"🗺️ Loaded {len(locations)} locations with {len(connections)} connections")
|
||||
except FileNotFoundError:
|
||||
print("⚠️ locations.json not found")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error loading locations.json: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
return locations
|
||||
|
||||
def load_world(self) -> World:
|
||||
"""Load the entire world"""
|
||||
world = World()
|
||||
|
||||
# Load interactable templates first
|
||||
self.load_interactable_templates()
|
||||
|
||||
# Load locations
|
||||
locations = self.load_locations()
|
||||
for location in locations.values():
|
||||
world.add_location(location)
|
||||
|
||||
return world
|
||||
|
||||
|
||||
def load_world() -> World:
|
||||
"""Convenience function to load the world"""
|
||||
loader = WorldLoader()
|
||||
return loader.load_world()
|
||||
Reference in New Issue
Block a user