feat(api): implement Diablo-style item affix system

Add procedural item generation with affix naming system:
- Items with RARE/EPIC/LEGENDARY rarity get dynamic names
- Prefixes (e.g., "Flaming") add elemental damage, material bonuses
- Suffixes (e.g., "of Strength") add stat bonuses
- Affix count scales with rarity: RARE=1, EPIC=2, LEGENDARY=3

New files:
- models/affixes.py: Affix and BaseItemTemplate dataclasses
- services/affix_loader.py: YAML-based affix pool loading
- services/base_item_loader.py: Base item template loading
- services/item_generator.py: Main procedural generation service
- data/affixes/prefixes.yaml: 14 prefix definitions
- data/affixes/suffixes.yaml: 15 suffix definitions
- data/base_items/weapons.yaml: 12 weapon templates
- data/base_items/armor.yaml: 12 armor templates
- tests/test_item_generator.py: 34 comprehensive tests

Modified:
- enums.py: Added AffixType and AffixTier enums
- items.py: Added affix tracking fields (applied_affixes, generated_name)

Example output: "Frozen Dagger of the Bear" (EPIC with ice damage + STR/CON)
This commit is contained in:
2025-11-26 17:57:34 -06:00
parent f3ac0c8647
commit 185be7fee0
11 changed files with 2658 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
# Item Prefix Affixes
# Prefixes appear before the item name: "Flaming Dagger"
#
# Affix Structure:
# affix_id: Unique identifier
# name: Display name (what appears in the item name)
# affix_type: "prefix"
# tier: "minor" (RARE), "major" (EPIC), "legendary" (LEGENDARY only)
# description: Flavor text describing the effect
# stat_bonuses: Dict of stat_name -> bonus value
# defense_bonus: Direct defense bonus
# resistance_bonus: Direct resistance bonus
# damage_bonus: Flat damage bonus (weapons)
# damage_type: Elemental damage type
# elemental_ratio: Portion converted to elemental (0.0-1.0)
# crit_chance_bonus: Added to crit chance
# crit_multiplier_bonus: Added to crit multiplier
# allowed_item_types: [] = all types, or ["weapon", "armor"]
# required_rarity: null = any, or "legendary"
prefixes:
# ==================== ELEMENTAL PREFIXES (FIRE) ====================
flaming:
affix_id: "flaming"
name: "Flaming"
affix_type: "prefix"
tier: "minor"
description: "Imbued with fire magic, dealing bonus fire damage"
damage_type: "fire"
elemental_ratio: 0.25
damage_bonus: 3
allowed_item_types: ["weapon"]
blazing:
affix_id: "blazing"
name: "Blazing"
affix_type: "prefix"
tier: "major"
description: "Wreathed in intense flames"
damage_type: "fire"
elemental_ratio: 0.35
damage_bonus: 6
allowed_item_types: ["weapon"]
# ==================== ELEMENTAL PREFIXES (ICE) ====================
frozen:
affix_id: "frozen"
name: "Frozen"
affix_type: "prefix"
tier: "minor"
description: "Enchanted with frost magic"
damage_type: "ice"
elemental_ratio: 0.25
damage_bonus: 3
allowed_item_types: ["weapon"]
glacial:
affix_id: "glacial"
name: "Glacial"
affix_type: "prefix"
tier: "major"
description: "Encased in eternal ice"
damage_type: "ice"
elemental_ratio: 0.35
damage_bonus: 6
allowed_item_types: ["weapon"]
# ==================== ELEMENTAL PREFIXES (LIGHTNING) ====================
shocking:
affix_id: "shocking"
name: "Shocking"
affix_type: "prefix"
tier: "minor"
description: "Crackles with electrical energy"
damage_type: "lightning"
elemental_ratio: 0.25
damage_bonus: 3
allowed_item_types: ["weapon"]
thundering:
affix_id: "thundering"
name: "Thundering"
affix_type: "prefix"
tier: "major"
description: "Charged with the power of storms"
damage_type: "lightning"
elemental_ratio: 0.35
damage_bonus: 6
allowed_item_types: ["weapon"]
# ==================== MATERIAL PREFIXES ====================
iron:
affix_id: "iron"
name: "Iron"
affix_type: "prefix"
tier: "minor"
description: "Reinforced with sturdy iron"
stat_bonuses:
constitution: 1
defense_bonus: 2
steel:
affix_id: "steel"
name: "Steel"
affix_type: "prefix"
tier: "major"
description: "Forged from fine steel"
stat_bonuses:
constitution: 2
strength: 1
defense_bonus: 4
# ==================== QUALITY PREFIXES ====================
sharp:
affix_id: "sharp"
name: "Sharp"
affix_type: "prefix"
tier: "minor"
description: "Honed to a fine edge"
damage_bonus: 3
crit_chance_bonus: 0.02
allowed_item_types: ["weapon"]
keen:
affix_id: "keen"
name: "Keen"
affix_type: "prefix"
tier: "major"
description: "Razor-sharp edge that finds weak points"
damage_bonus: 5
crit_chance_bonus: 0.04
allowed_item_types: ["weapon"]
# ==================== DEFENSIVE PREFIXES ====================
sturdy:
affix_id: "sturdy"
name: "Sturdy"
affix_type: "prefix"
tier: "minor"
description: "Built to withstand punishment"
defense_bonus: 3
allowed_item_types: ["armor"]
reinforced:
affix_id: "reinforced"
name: "Reinforced"
affix_type: "prefix"
tier: "major"
description: "Heavily reinforced for maximum protection"
defense_bonus: 5
resistance_bonus: 2
allowed_item_types: ["armor"]
# ==================== LEGENDARY PREFIXES ====================
infernal:
affix_id: "infernal"
name: "Infernal"
affix_type: "prefix"
tier: "legendary"
description: "Burns with hellfire"
damage_type: "fire"
elemental_ratio: 0.45
damage_bonus: 12
allowed_item_types: ["weapon"]
required_rarity: "legendary"
vorpal:
affix_id: "vorpal"
name: "Vorpal"
affix_type: "prefix"
tier: "legendary"
description: "Cuts through anything with supernatural precision"
damage_bonus: 10
crit_chance_bonus: 0.08
crit_multiplier_bonus: 0.5
allowed_item_types: ["weapon"]
required_rarity: "legendary"

View File

@@ -0,0 +1,155 @@
# Item Suffix Affixes
# Suffixes appear after the item name: "Dagger of Strength"
#
# Suffix naming convention:
# - Minor tier: "of [Stat]" (e.g., "of Strength")
# - Major tier: "of the [Animal/Element]" (e.g., "of the Bear")
# - Legendary tier: "of the [Mythical]" (e.g., "of the Titan")
suffixes:
# ==================== STAT SUFFIXES (MINOR) ====================
of_strength:
affix_id: "of_strength"
name: "of Strength"
affix_type: "suffix"
tier: "minor"
description: "Grants physical power"
stat_bonuses:
strength: 2
of_dexterity:
affix_id: "of_dexterity"
name: "of Dexterity"
affix_type: "suffix"
tier: "minor"
description: "Grants agility and precision"
stat_bonuses:
dexterity: 2
of_constitution:
affix_id: "of_constitution"
name: "of Fortitude"
affix_type: "suffix"
tier: "minor"
description: "Grants endurance"
stat_bonuses:
constitution: 2
of_intelligence:
affix_id: "of_intelligence"
name: "of Intelligence"
affix_type: "suffix"
tier: "minor"
description: "Grants magical aptitude"
stat_bonuses:
intelligence: 2
of_wisdom:
affix_id: "of_wisdom"
name: "of Wisdom"
affix_type: "suffix"
tier: "minor"
description: "Grants insight and perception"
stat_bonuses:
wisdom: 2
of_charisma:
affix_id: "of_charisma"
name: "of Charm"
affix_type: "suffix"
tier: "minor"
description: "Grants social influence"
stat_bonuses:
charisma: 2
of_luck:
affix_id: "of_luck"
name: "of Fortune"
affix_type: "suffix"
tier: "minor"
description: "Grants favor from fate"
stat_bonuses:
luck: 2
# ==================== ENHANCED STAT SUFFIXES (MAJOR) ====================
of_the_bear:
affix_id: "of_the_bear"
name: "of the Bear"
affix_type: "suffix"
tier: "major"
description: "Grants the might and endurance of a bear"
stat_bonuses:
strength: 4
constitution: 2
of_the_fox:
affix_id: "of_the_fox"
name: "of the Fox"
affix_type: "suffix"
tier: "major"
description: "Grants the cunning and agility of a fox"
stat_bonuses:
dexterity: 4
luck: 2
of_the_owl:
affix_id: "of_the_owl"
name: "of the Owl"
affix_type: "suffix"
tier: "major"
description: "Grants the wisdom and insight of an owl"
stat_bonuses:
intelligence: 3
wisdom: 3
# ==================== DEFENSIVE SUFFIXES ====================
of_protection:
affix_id: "of_protection"
name: "of Protection"
affix_type: "suffix"
tier: "minor"
description: "Offers physical protection"
defense_bonus: 3
of_warding:
affix_id: "of_warding"
name: "of Warding"
affix_type: "suffix"
tier: "major"
description: "Wards against physical and magical harm"
defense_bonus: 5
resistance_bonus: 3
# ==================== LEGENDARY SUFFIXES ====================
of_the_titan:
affix_id: "of_the_titan"
name: "of the Titan"
affix_type: "suffix"
tier: "legendary"
description: "Grants titanic strength and endurance"
stat_bonuses:
strength: 8
constitution: 4
required_rarity: "legendary"
of_the_wind:
affix_id: "of_the_wind"
name: "of the Wind"
affix_type: "suffix"
tier: "legendary"
description: "Swift as the wind itself"
stat_bonuses:
dexterity: 8
luck: 4
crit_chance_bonus: 0.05
required_rarity: "legendary"
of_invincibility:
affix_id: "of_invincibility"
name: "of Invincibility"
affix_type: "suffix"
tier: "legendary"
description: "Grants supreme protection"
defense_bonus: 10
resistance_bonus: 8
required_rarity: "legendary"

View File

@@ -0,0 +1,152 @@
# Base Armor Templates for Procedural Generation
#
# These templates define the foundation that affixes attach to.
# Example: "Leather Vest" + "Sturdy" prefix = "Sturdy Leather Vest"
#
# Armor categories:
# - Cloth: Low defense, high resistance (mages)
# - Leather: Balanced defense/resistance (rogues)
# - Chain: Medium defense, low resistance (versatile)
# - Plate: High defense, low resistance (warriors)
armor:
# ==================== CLOTH (MAGE ARMOR) ====================
cloth_robe:
template_id: "cloth_robe"
name: "Cloth Robe"
item_type: "armor"
description: "Simple cloth robes favored by spellcasters"
base_defense: 2
base_resistance: 5
base_value: 15
required_level: 1
drop_weight: 1.3
silk_robe:
template_id: "silk_robe"
name: "Silk Robe"
item_type: "armor"
description: "Fine silk robes that channel magical energy"
base_defense: 3
base_resistance: 8
base_value: 40
required_level: 3
drop_weight: 0.9
arcane_vestments:
template_id: "arcane_vestments"
name: "Arcane Vestments"
item_type: "armor"
description: "Robes woven with magical threads"
base_defense: 5
base_resistance: 12
base_value: 80
required_level: 5
drop_weight: 0.6
min_rarity: "uncommon"
# ==================== LEATHER (ROGUE ARMOR) ====================
leather_vest:
template_id: "leather_vest"
name: "Leather Vest"
item_type: "armor"
description: "Basic leather protection for agile fighters"
base_defense: 5
base_resistance: 2
base_value: 20
required_level: 1
drop_weight: 1.3
studded_leather:
template_id: "studded_leather"
name: "Studded Leather"
item_type: "armor"
description: "Leather armor reinforced with metal studs"
base_defense: 8
base_resistance: 3
base_value: 45
required_level: 3
drop_weight: 1.0
hardened_leather:
template_id: "hardened_leather"
name: "Hardened Leather"
item_type: "armor"
description: "Boiled and hardened leather for superior protection"
base_defense: 12
base_resistance: 5
base_value: 75
required_level: 5
drop_weight: 0.7
min_rarity: "uncommon"
# ==================== CHAIN (VERSATILE) ====================
chain_shirt:
template_id: "chain_shirt"
name: "Chain Shirt"
item_type: "armor"
description: "A shirt of interlocking metal rings"
base_defense: 7
base_resistance: 2
base_value: 35
required_level: 2
drop_weight: 1.0
chainmail:
template_id: "chainmail"
name: "Chainmail"
item_type: "armor"
description: "Full chainmail armor covering torso and arms"
base_defense: 10
base_resistance: 3
base_value: 50
required_level: 3
drop_weight: 1.0
heavy_chainmail:
template_id: "heavy_chainmail"
name: "Heavy Chainmail"
item_type: "armor"
description: "Thick chainmail with reinforced rings"
base_defense: 14
base_resistance: 4
base_value: 85
required_level: 5
drop_weight: 0.7
min_rarity: "uncommon"
# ==================== PLATE (WARRIOR ARMOR) ====================
scale_mail:
template_id: "scale_mail"
name: "Scale Mail"
item_type: "armor"
description: "Overlapping metal scales on leather backing"
base_defense: 12
base_resistance: 2
base_value: 60
required_level: 4
drop_weight: 0.8
half_plate:
template_id: "half_plate"
name: "Half Plate"
item_type: "armor"
description: "Plate armor protecting vital areas"
base_defense: 16
base_resistance: 2
base_value: 120
required_level: 6
drop_weight: 0.5
min_rarity: "rare"
plate_armor:
template_id: "plate_armor"
name: "Plate Armor"
item_type: "armor"
description: "Full metal plate protection"
base_defense: 22
base_resistance: 3
base_value: 200
required_level: 7
drop_weight: 0.4
min_rarity: "rare"

View File

@@ -0,0 +1,182 @@
# Base Weapon Templates for Procedural Generation
#
# These templates define the foundation that affixes attach to.
# Example: "Dagger" + "Flaming" prefix = "Flaming Dagger"
#
# Template Structure:
# template_id: Unique identifier
# name: Base item name
# item_type: "weapon"
# description: Flavor text
# base_damage: Weapon damage
# base_value: Gold value
# damage_type: "physical" (default)
# crit_chance: Critical hit chance (0.0-1.0)
# crit_multiplier: Crit damage multiplier
# required_level: Min level to use/drop
# drop_weight: Higher = more common (1.0 = standard)
# min_rarity: Minimum rarity for this template
weapons:
# ==================== ONE-HANDED SWORDS ====================
dagger:
template_id: "dagger"
name: "Dagger"
item_type: "weapon"
description: "A small, quick blade for close combat"
base_damage: 6
base_value: 15
damage_type: "physical"
crit_chance: 0.08
crit_multiplier: 2.0
required_level: 1
drop_weight: 1.5
short_sword:
template_id: "short_sword"
name: "Short Sword"
item_type: "weapon"
description: "A versatile one-handed blade"
base_damage: 10
base_value: 30
damage_type: "physical"
crit_chance: 0.06
crit_multiplier: 2.0
required_level: 1
drop_weight: 1.3
longsword:
template_id: "longsword"
name: "Longsword"
item_type: "weapon"
description: "A standard warrior's blade"
base_damage: 14
base_value: 50
damage_type: "physical"
crit_chance: 0.05
crit_multiplier: 2.0
required_level: 3
drop_weight: 1.0
# ==================== TWO-HANDED WEAPONS ====================
greatsword:
template_id: "greatsword"
name: "Greatsword"
item_type: "weapon"
description: "A massive two-handed blade"
base_damage: 22
base_value: 100
damage_type: "physical"
crit_chance: 0.04
crit_multiplier: 2.5
required_level: 5
drop_weight: 0.7
min_rarity: "uncommon"
# ==================== AXES ====================
hatchet:
template_id: "hatchet"
name: "Hatchet"
item_type: "weapon"
description: "A small throwing axe"
base_damage: 8
base_value: 20
damage_type: "physical"
crit_chance: 0.06
crit_multiplier: 2.2
required_level: 1
drop_weight: 1.2
battle_axe:
template_id: "battle_axe"
name: "Battle Axe"
item_type: "weapon"
description: "A heavy axe designed for combat"
base_damage: 16
base_value: 60
damage_type: "physical"
crit_chance: 0.05
crit_multiplier: 2.3
required_level: 4
drop_weight: 0.9
# ==================== BLUNT WEAPONS ====================
club:
template_id: "club"
name: "Club"
item_type: "weapon"
description: "A simple wooden club"
base_damage: 7
base_value: 10
damage_type: "physical"
crit_chance: 0.04
crit_multiplier: 2.0
required_level: 1
drop_weight: 1.5
mace:
template_id: "mace"
name: "Mace"
item_type: "weapon"
description: "A flanged mace for crushing armor"
base_damage: 12
base_value: 40
damage_type: "physical"
crit_chance: 0.05
crit_multiplier: 2.0
required_level: 2
drop_weight: 1.0
# ==================== STAVES ====================
quarterstaff:
template_id: "quarterstaff"
name: "Quarterstaff"
item_type: "weapon"
description: "A simple wooden staff"
base_damage: 6
base_value: 10
damage_type: "physical"
crit_chance: 0.05
crit_multiplier: 2.0
required_level: 1
drop_weight: 1.2
wizard_staff:
template_id: "wizard_staff"
name: "Wizard Staff"
item_type: "weapon"
description: "A staff attuned to magical energy"
base_damage: 8
base_value: 45
damage_type: "physical"
crit_chance: 0.05
crit_multiplier: 2.0
required_level: 3
drop_weight: 0.8
# ==================== RANGED ====================
shortbow:
template_id: "shortbow"
name: "Shortbow"
item_type: "weapon"
description: "A compact bow for quick shots"
base_damage: 8
base_value: 25
damage_type: "physical"
crit_chance: 0.07
crit_multiplier: 2.0
required_level: 1
drop_weight: 1.1
longbow:
template_id: "longbow"
name: "Longbow"
item_type: "weapon"
description: "A powerful bow with excellent range"
base_damage: 14
base_value: 55
damage_type: "physical"
crit_chance: 0.08
crit_multiplier: 2.2
required_level: 4
drop_weight: 0.9