- Implement Combat Service - Implement Damage Calculator - Implement Effect Processor - Implement Combat Actions - Created Combat API Endpoints
218 lines
6.8 KiB
Python
218 lines
6.8 KiB
Python
"""
|
|
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})"
|
|
)
|