""" Enemy data models for combat encounters. This module defines the EnemyTemplate dataclass representing enemies/monsters that can be encountered in combat. Enemy definitions are loaded from YAML files for data-driven game design. """ from dataclasses import dataclass, field, asdict from typing import Dict, Any, List, Optional, Tuple from enum import Enum from app.models.stats import Stats class EnemyDifficulty(Enum): """Enemy difficulty levels for scaling and encounter building.""" EASY = "easy" MEDIUM = "medium" HARD = "hard" BOSS = "boss" @dataclass class LootEntry: """ Single entry in an enemy's loot table. Attributes: item_id: Reference to item definition drop_chance: Probability of dropping (0.0 to 1.0) quantity_min: Minimum quantity if dropped quantity_max: Maximum quantity if dropped """ item_id: str drop_chance: float = 0.1 quantity_min: int = 1 quantity_max: int = 1 def to_dict(self) -> Dict[str, Any]: """Serialize loot entry to dictionary.""" return asdict(self) @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'LootEntry': """Deserialize loot entry from dictionary.""" return cls( item_id=data["item_id"], drop_chance=data.get("drop_chance", 0.1), quantity_min=data.get("quantity_min", 1), quantity_max=data.get("quantity_max", 1), ) @dataclass class EnemyTemplate: """ Template definition for an enemy type. EnemyTemplates define the base characteristics of enemy types. When combat starts, instances are created from templates with randomized variations. Attributes: enemy_id: Unique identifier (e.g., "goblin", "dire_wolf") name: Display name (e.g., "Goblin Scout") description: Flavor text about the enemy base_stats: Base stat block for this enemy abilities: List of ability_ids this enemy can use loot_table: Potential drops on defeat experience_reward: Base XP granted on defeat gold_reward_min: Minimum gold dropped gold_reward_max: Maximum gold dropped difficulty: Difficulty classification for encounter building tags: Classification tags (e.g., ["humanoid", "goblinoid"]) image_url: Optional image reference for UI Combat-specific attributes: base_damage: Base damage for basic attack (no weapon) crit_chance: Critical hit chance (0.0 to 1.0) flee_chance: Chance to successfully flee from this enemy """ enemy_id: str name: str description: str base_stats: Stats abilities: List[str] = field(default_factory=list) loot_table: List[LootEntry] = field(default_factory=list) experience_reward: int = 10 gold_reward_min: int = 1 gold_reward_max: int = 5 difficulty: EnemyDifficulty = EnemyDifficulty.EASY tags: List[str] = field(default_factory=list) image_url: Optional[str] = None # Combat attributes base_damage: int = 5 crit_chance: float = 0.05 flee_chance: float = 0.5 def get_gold_reward(self) -> int: """ Roll random gold reward within range. Returns: Random gold amount between min and max """ import random return random.randint(self.gold_reward_min, self.gold_reward_max) def roll_loot(self) -> List[Dict[str, Any]]: """ Roll for loot drops based on loot table. Returns: List of dropped items with quantities """ import random drops = [] for entry in self.loot_table: if random.random() < entry.drop_chance: quantity = random.randint(entry.quantity_min, entry.quantity_max) drops.append({ "item_id": entry.item_id, "quantity": quantity, }) return drops def is_boss(self) -> bool: """Check if this enemy is a boss.""" return self.difficulty == EnemyDifficulty.BOSS def has_tag(self, tag: str) -> bool: """Check if enemy has a specific tag.""" return tag.lower() in [t.lower() for t in self.tags] def to_dict(self) -> Dict[str, Any]: """ Serialize enemy template to dictionary. Returns: Dictionary containing all enemy data """ return { "enemy_id": self.enemy_id, "name": self.name, "description": self.description, "base_stats": self.base_stats.to_dict(), "abilities": self.abilities, "loot_table": [entry.to_dict() for entry in self.loot_table], "experience_reward": self.experience_reward, "gold_reward_min": self.gold_reward_min, "gold_reward_max": self.gold_reward_max, "difficulty": self.difficulty.value, "tags": self.tags, "image_url": self.image_url, "base_damage": self.base_damage, "crit_chance": self.crit_chance, "flee_chance": self.flee_chance, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'EnemyTemplate': """ Deserialize enemy template from dictionary. Args: data: Dictionary containing enemy data (from YAML or JSON) Returns: EnemyTemplate instance """ # Parse base stats stats_data = data.get("base_stats", {}) base_stats = Stats.from_dict(stats_data) # Parse loot table loot_table = [ LootEntry.from_dict(entry) for entry in data.get("loot_table", []) ] # Parse difficulty difficulty_value = data.get("difficulty", "easy") if isinstance(difficulty_value, str): difficulty = EnemyDifficulty(difficulty_value) else: difficulty = difficulty_value return cls( enemy_id=data["enemy_id"], name=data["name"], description=data.get("description", ""), base_stats=base_stats, abilities=data.get("abilities", []), loot_table=loot_table, experience_reward=data.get("experience_reward", 10), gold_reward_min=data.get("gold_reward_min", 1), gold_reward_max=data.get("gold_reward_max", 5), difficulty=difficulty, tags=data.get("tags", []), image_url=data.get("image_url"), base_damage=data.get("base_damage", 5), crit_chance=data.get("crit_chance", 0.05), flee_chance=data.get("flee_chance", 0.5), ) def __repr__(self) -> str: """String representation of the enemy template.""" return ( f"EnemyTemplate({self.enemy_id}, {self.name}, " f"difficulty={self.difficulty.value}, " f"xp={self.experience_reward})" )