forked from Rockachopa/Timmy-time-dashboard
267 lines
8.4 KiB
Python
267 lines
8.4 KiB
Python
"""Matrix configuration loader utility.
|
|
|
|
Provides a typed dataclass for Matrix world configuration and a loader
|
|
that fetches settings from YAML with sensible defaults.
|
|
"""
|
|
|
|
import logging
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import yaml
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class PointLight:
|
|
"""A single point light in the Matrix world."""
|
|
|
|
color: str = "#FFFFFF"
|
|
intensity: float = 1.0
|
|
position: dict[str, float] = field(default_factory=lambda: {"x": 0, "y": 0, "z": 0})
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "PointLight":
|
|
"""Create a PointLight from a dictionary with defaults."""
|
|
return cls(
|
|
color=data.get("color", "#FFFFFF"),
|
|
intensity=data.get("intensity", 1.0),
|
|
position=data.get("position", {"x": 0, "y": 0, "z": 0}),
|
|
)
|
|
|
|
|
|
def _default_point_lights_factory() -> list[PointLight]:
|
|
"""Factory function for default point lights."""
|
|
return [
|
|
PointLight(
|
|
color="#FFAA55", # Warm amber (Workshop)
|
|
intensity=1.2,
|
|
position={"x": 0, "y": 5, "z": 0},
|
|
),
|
|
PointLight(
|
|
color="#3B82F6", # Cool blue (Matrix)
|
|
intensity=0.8,
|
|
position={"x": -5, "y": 3, "z": -5},
|
|
),
|
|
PointLight(
|
|
color="#A855F7", # Purple accent
|
|
intensity=0.6,
|
|
position={"x": 5, "y": 3, "z": 5},
|
|
),
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class LightingConfig:
|
|
"""Lighting configuration for the Matrix world."""
|
|
|
|
ambient_color: str = "#FFAA55" # Warm amber (Workshop warmth)
|
|
ambient_intensity: float = 0.5
|
|
point_lights: list[PointLight] = field(default_factory=_default_point_lights_factory)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any] | None) -> "LightingConfig":
|
|
"""Create a LightingConfig from a dictionary with defaults."""
|
|
if data is None:
|
|
data = {}
|
|
|
|
point_lights_data = data.get("point_lights", [])
|
|
point_lights = (
|
|
[PointLight.from_dict(pl) for pl in point_lights_data]
|
|
if point_lights_data
|
|
else _default_point_lights_factory()
|
|
)
|
|
|
|
return cls(
|
|
ambient_color=data.get("ambient_color", "#FFAA55"),
|
|
ambient_intensity=data.get("ambient_intensity", 0.5),
|
|
point_lights=point_lights,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class EnvironmentConfig:
|
|
"""Environment settings for the Matrix world."""
|
|
|
|
rain_enabled: bool = False
|
|
starfield_enabled: bool = True
|
|
fog_color: str = "#0f0f23"
|
|
fog_density: float = 0.02
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any] | None) -> "EnvironmentConfig":
|
|
"""Create an EnvironmentConfig from a dictionary with defaults."""
|
|
if data is None:
|
|
data = {}
|
|
return cls(
|
|
rain_enabled=data.get("rain_enabled", False),
|
|
starfield_enabled=data.get("starfield_enabled", True),
|
|
fog_color=data.get("fog_color", "#0f0f23"),
|
|
fog_density=data.get("fog_density", 0.02),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class FeaturesConfig:
|
|
"""Feature toggles for the Matrix world."""
|
|
|
|
chat_enabled: bool = True
|
|
visitor_avatars: bool = True
|
|
pip_familiar: bool = True
|
|
workshop_portal: bool = True
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any] | None) -> "FeaturesConfig":
|
|
"""Create a FeaturesConfig from a dictionary with defaults."""
|
|
if data is None:
|
|
data = {}
|
|
return cls(
|
|
chat_enabled=data.get("chat_enabled", True),
|
|
visitor_avatars=data.get("visitor_avatars", True),
|
|
pip_familiar=data.get("pip_familiar", True),
|
|
workshop_portal=data.get("workshop_portal", True),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class AgentConfig:
|
|
"""Configuration for a single Matrix agent."""
|
|
|
|
name: str = ""
|
|
role: str = ""
|
|
enabled: bool = True
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "AgentConfig":
|
|
"""Create an AgentConfig from a dictionary with defaults."""
|
|
return cls(
|
|
name=data.get("name", ""),
|
|
role=data.get("role", ""),
|
|
enabled=data.get("enabled", True),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class AgentsConfig:
|
|
"""Agent registry configuration."""
|
|
|
|
default_count: int = 5
|
|
max_count: int = 20
|
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any] | None) -> "AgentsConfig":
|
|
"""Create an AgentsConfig from a dictionary with defaults."""
|
|
if data is None:
|
|
data = {}
|
|
|
|
agents_data = data.get("agents", [])
|
|
agents = [AgentConfig.from_dict(a) for a in agents_data] if agents_data else []
|
|
|
|
return cls(
|
|
default_count=data.get("default_count", 5),
|
|
max_count=data.get("max_count", 20),
|
|
agents=agents,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class MatrixConfig:
|
|
"""Complete Matrix world configuration.
|
|
|
|
Combines lighting, environment, features, and agent settings
|
|
into a single configuration object.
|
|
"""
|
|
|
|
lighting: LightingConfig = field(default_factory=LightingConfig)
|
|
environment: EnvironmentConfig = field(default_factory=EnvironmentConfig)
|
|
features: FeaturesConfig = field(default_factory=FeaturesConfig)
|
|
agents: AgentsConfig = field(default_factory=AgentsConfig)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any] | None) -> "MatrixConfig":
|
|
"""Create a MatrixConfig from a dictionary with defaults for missing sections."""
|
|
if data is None:
|
|
data = {}
|
|
return cls(
|
|
lighting=LightingConfig.from_dict(data.get("lighting")),
|
|
environment=EnvironmentConfig.from_dict(data.get("environment")),
|
|
features=FeaturesConfig.from_dict(data.get("features")),
|
|
agents=AgentsConfig.from_dict(data.get("agents")),
|
|
)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert the configuration to a plain dictionary."""
|
|
return {
|
|
"lighting": {
|
|
"ambient_color": self.lighting.ambient_color,
|
|
"ambient_intensity": self.lighting.ambient_intensity,
|
|
"point_lights": [
|
|
{
|
|
"color": pl.color,
|
|
"intensity": pl.intensity,
|
|
"position": pl.position,
|
|
}
|
|
for pl in self.lighting.point_lights
|
|
],
|
|
},
|
|
"environment": {
|
|
"rain_enabled": self.environment.rain_enabled,
|
|
"starfield_enabled": self.environment.starfield_enabled,
|
|
"fog_color": self.environment.fog_color,
|
|
"fog_density": self.environment.fog_density,
|
|
},
|
|
"features": {
|
|
"chat_enabled": self.features.chat_enabled,
|
|
"visitor_avatars": self.features.visitor_avatars,
|
|
"pip_familiar": self.features.pip_familiar,
|
|
"workshop_portal": self.features.workshop_portal,
|
|
},
|
|
"agents": {
|
|
"default_count": self.agents.default_count,
|
|
"max_count": self.agents.max_count,
|
|
"agents": [
|
|
{"name": a.name, "role": a.role, "enabled": a.enabled}
|
|
for a in self.agents.agents
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def load_from_yaml(path: str | Path) -> MatrixConfig:
|
|
"""Load Matrix configuration from a YAML file.
|
|
|
|
Missing keys are filled with sensible defaults. If the file
|
|
cannot be read or parsed, returns a fully default configuration.
|
|
|
|
Args:
|
|
path: Path to the YAML configuration file.
|
|
|
|
Returns:
|
|
A MatrixConfig instance with loaded or default values.
|
|
"""
|
|
path = Path(path)
|
|
|
|
if not path.exists():
|
|
logger.warning("Matrix config file not found: %s, using defaults", path)
|
|
return MatrixConfig()
|
|
|
|
try:
|
|
with open(path, encoding="utf-8") as f:
|
|
raw_data = yaml.safe_load(f)
|
|
|
|
if not isinstance(raw_data, dict):
|
|
logger.warning("Matrix config invalid format, using defaults")
|
|
return MatrixConfig()
|
|
|
|
return MatrixConfig.from_dict(raw_data)
|
|
|
|
except yaml.YAMLError as exc:
|
|
logger.warning("Matrix config YAML parse error: %s, using defaults", exc)
|
|
return MatrixConfig()
|
|
except OSError as exc:
|
|
logger.warning("Matrix config read error: %s, using defaults", exc)
|
|
return MatrixConfig()
|