complete regen of hero classes, spells, races, etc

This commit is contained in:
2025-11-02 18:16:00 -06:00
parent 7bf81109b3
commit fd572076e0
34 changed files with 882 additions and 489 deletions

View File

@@ -1,72 +1,105 @@
from app.utils.logging import get_logger
from app.game.utils.common import Dice
# utils/leveling.py
from typing import Dict, Any, Callable, Optional, List, Tuple
from app.game.generators.level_progression import LevelProgression
from app.game.models.entities import Entity
from app.game.generators.abilities_factory import newly_unlocked_abilities
logger = get_logger(__file__)
dice = Dice()
def calculate_xp_and_level(current_xp: int, xp_gain: int, base_xp: int = 100, growth_rate: float = 1.2):
def set_level(entity:Entity, target_level: int, prog: LevelProgression) -> None:
"""
Calculates new XP and level after gaining experience.
Args:
current_xp (int): The player's current total XP.
xp_gain (int): The amount of XP gained.
base_xp (int): XP required for level 1 → 2.
growth_rate (float): Multiplier for each levels XP requirement.
Returns:
dict: {
'new_xp': int,
'new_level': int,
'xp_to_next_level': int,
'remaining_xp_to_next': int
}
Snap an entity to a *specific* level. Sets XP to that level's floor.
Optionally applies all level-up rewards from current_level+1 .. target_level (idempotent if you recompute stats from level).
"""
# Calculate current level based on XP
level = 1
xp_needed = base_xp
total_xp_for_next = xp_needed
# ensures we never try to go above the max level in the game
target_level = max(1, min(target_level, prog.max_level))
current = entity.level
# Determine current level from current_xp
while current_xp >= total_xp_for_next:
level += 1
xp_needed = int(base_xp * (growth_rate ** (level - 1)))
total_xp_for_next += xp_needed
# if not changing levels, just set the xp and move along
if current == target_level:
entity.xp = prog.xp_for_level(target_level)
_recalc(entity)
# Add new XP
new_xp = current_xp + xp_gain
# Set final level + floor XP
entity.level = target_level
entity.xp = prog.xp_for_level(target_level)
# Check if level up(s) occurred
new_level = level
while new_xp >= total_xp_for_next:
new_level += 1
xp_needed = int(base_xp * (growth_rate ** (new_level - 1)))
total_xp_for_next += xp_needed
# set next level xp as xp needed for next level
entity.xp_to_next_level = prog.xp_for_level(target_level + 1)
# XP required for next level
xp_to_next_level = xp_needed
levels_gained = new_level - level
spells_list = newly_unlocked_abilities(class_name=entity.profession.name,
path="Hellknight",
level=target_level,
per_tier=1,
primary=entity.profession.primary_stat)
_add_abilities(entity,spells_list)
return {
"new_xp": new_xp,
"new_level": new_level,
"xp_to_next_level": xp_to_next_level,
"levels_gained": levels_gained
}
_recalc(entity)
def grant_xp(entity:Entity, amount: int, prog: LevelProgression) -> Tuple[int, int]:
"""
Add XP and auto-level if thresholds crossed.
Returns (old_level, new_level).
"""
old_level = entity.level or 1
entity.xp = entity.xp + int(amount)
new_level = prog.level_for_xp(entity.xp)
if new_level > old_level:
for L in range(old_level + 1, new_level + 1):
spells_list = newly_unlocked_abilities(class_name=entity.profession.name,
path="Hellknight",
level=new_level,
per_tier=1,
primary=entity.profession.primary_stat)
_add_abilities(entity,spells_list)
if new_level > old_level:
entity.level = new_level
_recalc(entity)
# --- compute XP to next level ---
if new_level >= prog.max_level:
# Maxed out
entity.xp_to_next_level = 0
else:
next_floor = prog.xp_for_level(new_level + 1)
entity.xp_to_next_level = max(0, next_floor - entity.xp)
return old_level, new_level
# ---------- reward + recalc helpers ----------
def _add_abilities(entity:Entity, abilities_list:list) -> None:
for ability in abilities_list:
entity.abilities.append(ability)
def _recalc(entity:Entity) -> None:
"""
Recompute derived stats from entity.level + profession.
Replace with your actual formulas.
"""
L = entity.level
prof = entity.profession
# scale attack/defense by per-level gains
if prof:
base_pa = entity.profession.physical_attack_per_level or 0
base_pd = entity.profession.physical_defense_per_level or 0
base_ma = entity.profession.magic_attack_per_level or 0
base_md = entity.profession.magic_defense_per_level or 0
entity.physical_attack = round(base_pa + prof.physical_attack_per_level * (L - 1),2)
entity.physical_defense = round(base_pd + prof.physical_defense_per_level * (L - 1),2)
entity.magic_attack = round(base_ma + prof.magic_attack_per_level * (L - 1),2)
entity.magic_defense = round(base_md + prof.magic_defense_per_level * (L - 1),2)
# HP/MP growth from profession base
if prof:
entity.status.max_hp = entity.status.max_hp + int(prof.base_hp) + int((L * prof.base_hp) * 0.5)
entity.status.max_mp = entity.status.max_mp + int(prof.base_mp) + int((L * prof.base_mp) * 0.5)
# set current to max if you keep current_hp/mp
entity.status.current_hp = entity.status.max_hp
entity.status.current_mp = entity.status.max_mp
def hp_and_mp_gain(level: int, hit_die: str, rule: str) -> int:
rule = (rule or "avg").lower()
if level == 1:
# Common RPG convention: level 1 gets max die
return dice.max_of_die(hit_die)
if rule == "max":
return dice.max_of_die(hit_die)
if rule == "avg":
return dice.roll_dice(hit_die)
# default fallback
return dice.roll_dice(hit_die)