Files
COC_API/app/game/systems/leveling.py

138 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# utils/leveling.py
import math
import random
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
GLOBAL_CHANCE_PER_LEVEL = 0.3
def set_level(entity:Entity, target_level: int, prog: LevelProgression) -> None:
"""
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).
"""
# 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
# 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)
# Set final level + floor XP
entity.level = target_level
entity.xp = prog.xp_for_level(target_level)
# set next level xp as xp needed for next level
entity.xp_to_next_level = prog.xp_for_level(target_level + 1)
# entity get's a random number of spells based on the number of levels gained
skills_per_level = calculate_skills_gained(current,target_level)
skills = newly_unlocked_abilities(class_name=entity.profession.name,
path=entity.ability_pathway,
level=target_level,
per_tier=skills_per_level,
primary=entity.profession.primary_stat)
_add_abilities(entity,skills)
_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):
skills = newly_unlocked_abilities(class_name=entity.profession.name,
path=entity.ability_pathway,
level=new_level,
per_tier=1,
primary=entity.profession.primary_stat)
_add_abilities(entity,skills)
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 calculate_skills_gained(current_level: int, target_level: int, chance_per_level: float = GLOBAL_CHANCE_PER_LEVEL) -> int:
"""
Returns the number of new skills a player might gain
when leveling from current_level to target_level.
chance_per_level: probability (01) of gaining a skill per level.
Guarantees at least 2 skills for small level gains.
"""
levels_gained = max(0, target_level - current_level)
if levels_gained == 0:
return 0
# Simulate random gain per level
gained = sum(1 for _ in range(levels_gained) if random.random() < chance_per_level)
# Guarantee at least 2 skills if the player barely leveled
if levels_gained < 3:
gained = max(gained, 2)
# Smooth scaling: for huge jumps, don't explode linearly
# (cap roughly at 40% of total levels gained)
cap = math.ceil(levels_gained * 0.4)
return min(gained, cap)
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