197 lines
6.3 KiB
Python
197 lines
6.3 KiB
Python
"""
|
|
Item system for equipment, consumables, and quest items.
|
|
|
|
This module defines the Item dataclass representing all types of items in the game,
|
|
including weapons, armor, consumables, and quest items.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field, asdict
|
|
from typing import Dict, Any, List, Optional
|
|
|
|
from app.models.enums import ItemType, DamageType
|
|
from app.models.effects import Effect
|
|
|
|
|
|
@dataclass
|
|
class Item:
|
|
"""
|
|
Represents an item in the game (weapon, armor, consumable, or quest item).
|
|
|
|
Items can provide passive stat bonuses when equipped, have weapon/armor stats,
|
|
or provide effects when consumed.
|
|
|
|
Attributes:
|
|
item_id: Unique identifier
|
|
name: Display name
|
|
item_type: Category (weapon, armor, consumable, quest_item)
|
|
description: Item lore and information
|
|
value: Gold value for buying/selling
|
|
is_tradeable: Whether item can be sold on marketplace
|
|
stat_bonuses: Passive bonuses to stats when equipped
|
|
Example: {"strength": 5, "constitution": 3}
|
|
effects_on_use: Effects applied when consumed (consumables only)
|
|
|
|
Weapon-specific attributes:
|
|
damage: Base weapon damage
|
|
damage_type: Type of damage (physical, fire, etc.)
|
|
crit_chance: Probability of critical hit (0.0 to 1.0)
|
|
crit_multiplier: Damage multiplier on critical hit
|
|
|
|
Armor-specific attributes:
|
|
defense: Physical defense bonus
|
|
resistance: Magical resistance bonus
|
|
|
|
Requirements (future):
|
|
required_level: Minimum character level to use
|
|
required_class: Class restriction (if any)
|
|
"""
|
|
|
|
item_id: str
|
|
name: str
|
|
item_type: ItemType
|
|
description: str
|
|
value: int = 0
|
|
is_tradeable: bool = True
|
|
|
|
# Passive bonuses (equipment)
|
|
stat_bonuses: Dict[str, int] = field(default_factory=dict)
|
|
|
|
# Active effects (consumables)
|
|
effects_on_use: List[Effect] = field(default_factory=list)
|
|
|
|
# Weapon-specific
|
|
damage: int = 0
|
|
damage_type: Optional[DamageType] = None
|
|
crit_chance: float = 0.05 # 5% default critical hit chance
|
|
crit_multiplier: float = 2.0 # 2x damage on critical hit
|
|
|
|
# Armor-specific
|
|
defense: int = 0
|
|
resistance: int = 0
|
|
|
|
# Requirements (future expansion)
|
|
required_level: int = 1
|
|
required_class: Optional[str] = None
|
|
|
|
def is_weapon(self) -> bool:
|
|
"""Check if this item is a weapon."""
|
|
return self.item_type == ItemType.WEAPON
|
|
|
|
def is_armor(self) -> bool:
|
|
"""Check if this item is armor."""
|
|
return self.item_type == ItemType.ARMOR
|
|
|
|
def is_consumable(self) -> bool:
|
|
"""Check if this item is a consumable."""
|
|
return self.item_type == ItemType.CONSUMABLE
|
|
|
|
def is_quest_item(self) -> bool:
|
|
"""Check if this item is a quest item."""
|
|
return self.item_type == ItemType.QUEST_ITEM
|
|
|
|
def can_equip(self, character_level: int, character_class: Optional[str] = None) -> bool:
|
|
"""
|
|
Check if a character can equip this item.
|
|
|
|
Args:
|
|
character_level: Character's current level
|
|
character_class: Character's class (if class restrictions exist)
|
|
|
|
Returns:
|
|
True if item can be equipped, False otherwise
|
|
"""
|
|
# Check level requirement
|
|
if character_level < self.required_level:
|
|
return False
|
|
|
|
# Check class requirement
|
|
if self.required_class and character_class != self.required_class:
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_total_stat_bonus(self, stat_name: str) -> int:
|
|
"""
|
|
Get the total bonus for a specific stat from this item.
|
|
|
|
Args:
|
|
stat_name: Name of the stat (e.g., "strength", "intelligence")
|
|
|
|
Returns:
|
|
Bonus value for that stat (0 if not present)
|
|
"""
|
|
return self.stat_bonuses.get(stat_name, 0)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""
|
|
Serialize item to a dictionary.
|
|
|
|
Returns:
|
|
Dictionary containing all item data
|
|
"""
|
|
data = asdict(self)
|
|
data["item_type"] = self.item_type.value
|
|
if self.damage_type:
|
|
data["damage_type"] = self.damage_type.value
|
|
data["effects_on_use"] = [effect.to_dict() for effect in self.effects_on_use]
|
|
return data
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'Item':
|
|
"""
|
|
Deserialize item from a dictionary.
|
|
|
|
Args:
|
|
data: Dictionary containing item data
|
|
|
|
Returns:
|
|
Item instance
|
|
"""
|
|
# Convert string values back to enums
|
|
item_type = ItemType(data["item_type"])
|
|
damage_type = DamageType(data["damage_type"]) if data.get("damage_type") else None
|
|
|
|
# Deserialize effects
|
|
effects = []
|
|
if "effects_on_use" in data and data["effects_on_use"]:
|
|
effects = [Effect.from_dict(e) for e in data["effects_on_use"]]
|
|
|
|
return cls(
|
|
item_id=data["item_id"],
|
|
name=data["name"],
|
|
item_type=item_type,
|
|
description=data["description"],
|
|
value=data.get("value", 0),
|
|
is_tradeable=data.get("is_tradeable", True),
|
|
stat_bonuses=data.get("stat_bonuses", {}),
|
|
effects_on_use=effects,
|
|
damage=data.get("damage", 0),
|
|
damage_type=damage_type,
|
|
crit_chance=data.get("crit_chance", 0.05),
|
|
crit_multiplier=data.get("crit_multiplier", 2.0),
|
|
defense=data.get("defense", 0),
|
|
resistance=data.get("resistance", 0),
|
|
required_level=data.get("required_level", 1),
|
|
required_class=data.get("required_class"),
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
"""String representation of the item."""
|
|
if self.is_weapon():
|
|
return (
|
|
f"Item({self.name}, weapon, dmg={self.damage}, "
|
|
f"crit={self.crit_chance*100:.0f}%, value={self.value}g)"
|
|
)
|
|
elif self.is_armor():
|
|
return (
|
|
f"Item({self.name}, armor, def={self.defense}, "
|
|
f"res={self.resistance}, value={self.value}g)"
|
|
)
|
|
elif self.is_consumable():
|
|
return (
|
|
f"Item({self.name}, consumable, "
|
|
f"effects={len(self.effects_on_use)}, value={self.value}g)"
|
|
)
|
|
else:
|
|
return f"Item({self.name}, quest_item)"
|