finished out skills per level, added skill trees to professions templates
This commit is contained in:
@@ -37,7 +37,7 @@ def _name(rng: random.Random, theme: Dict) -> str:
|
||||
|
||||
def _damage_power(tier: int, rng: random.Random) -> float:
|
||||
# Linear-with-jitter curve—easy to reason about and scale
|
||||
base = 50 + 7 * tier
|
||||
base = 10 * tier
|
||||
jitter = 1.0 + rng.uniform(-0.08, 0.08)
|
||||
return round(base * jitter, 2)
|
||||
|
||||
|
||||
@@ -11,27 +11,23 @@ from app.game.systems.leveling import set_level
|
||||
|
||||
dice = Dice()
|
||||
|
||||
# tuning knobs
|
||||
level_growth = 1.25
|
||||
progression = DEFAULT_LEVEL_PROGRESSION
|
||||
|
||||
|
||||
|
||||
|
||||
def build_char(name:str, origin_story:str, race_id:str, profession_id:str, fame:int=0, level:int=1) -> Entity:
|
||||
def build_char(name:str, origin_story:str, race_id:str, profession_id:str,ability_pathway:str,level:int=1) -> Entity:
|
||||
|
||||
races = RaceRepository()
|
||||
professions = ProfessionRepository()
|
||||
progression = DEFAULT_LEVEL_PROGRESSION
|
||||
|
||||
race = races.get(race_id)
|
||||
profession = professions.get(profession_id)
|
||||
profession.ability_paths
|
||||
|
||||
e = Entity(
|
||||
uuid = str(uuid.uuid4()),
|
||||
name = name,
|
||||
origin_story = origin_story,
|
||||
fame = fame,
|
||||
race =race,
|
||||
ability_pathway=ability_pathway,
|
||||
profession = profession
|
||||
)
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class Entity:
|
||||
origin_story:str = ""
|
||||
race: Race = field(default_factory=Race)
|
||||
profession: Profession = field(default_factory=Profession)
|
||||
ability_pathway: str = ""
|
||||
|
||||
level: int = 0
|
||||
xp: int = 0
|
||||
|
||||
@@ -16,6 +16,7 @@ class Profession:
|
||||
physical_defense_per_level: float
|
||||
magic_attack_per_level: float
|
||||
magic_defense_per_level: float
|
||||
ability_paths: list[str] = field(default_factory=list)
|
||||
|
||||
tags: list[str] = field(default_factory=list) # e.g., {"playable"}, {"leader","elite"}
|
||||
enemy: Optional[EnemyProfile] = None # ⬅ optional enemy-only tuning
|
||||
@@ -88,6 +89,7 @@ class Profession:
|
||||
physical_defense_per_level=as_float(data["physical_defense_per_level"], "physical_defense_per_level"),
|
||||
magic_attack_per_level=as_float(data["magic_attack_per_level"], "magic_attack_per_level"),
|
||||
magic_defense_per_level=as_float(data["magic_defense_per_level"], "magic_defense_per_level"),
|
||||
ability_paths=(data.get("ability_paths",[])),
|
||||
tags=tags,
|
||||
enemy=enemy,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# 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.
|
||||
@@ -25,12 +30,15 @@ def set_level(entity:Entity, target_level: int, prog: LevelProgression) -> None:
|
||||
# set next level xp as xp needed for next level
|
||||
entity.xp_to_next_level = prog.xp_for_level(target_level + 1)
|
||||
|
||||
spells_list = newly_unlocked_abilities(class_name=entity.profession.name,
|
||||
path="Hellknight",
|
||||
# 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=1,
|
||||
per_tier=skills_per_level,
|
||||
primary=entity.profession.primary_stat)
|
||||
_add_abilities(entity,spells_list)
|
||||
_add_abilities(entity,skills)
|
||||
|
||||
_recalc(entity)
|
||||
|
||||
@@ -46,12 +54,12 @@ def grant_xp(entity:Entity, amount: int, prog: LevelProgression) -> Tuple[int, i
|
||||
|
||||
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",
|
||||
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,spells_list)
|
||||
_add_abilities(entity,skills)
|
||||
|
||||
if new_level > old_level:
|
||||
entity.level = new_level
|
||||
@@ -70,6 +78,30 @@ def grant_xp(entity:Entity, amount: int, prog: LevelProgression) -> Tuple[int, i
|
||||
|
||||
# ---------- 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 (0–1) 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)
|
||||
|
||||
@@ -10,32 +10,38 @@ Frostbinder:
|
||||
nouns: ["Spike", "Lance", "Shard", "Burst", "Ray"]
|
||||
of_things: ["Frost", "Winter", "Stillness", "Silence", "Cold"]
|
||||
|
||||
Bloodborn:
|
||||
Khaosfire:
|
||||
elements: ["curse", "shadow"]
|
||||
adjectives: ["Tainted", "Cursed", "Corrupted", "Polluted", "Toxic", "Deadly"]
|
||||
nouns: ["Poison", "Infection", "Wound", "Bane", "Plague", "Ravage", "Scourge"]
|
||||
of_things: ["Infectious", "Contagion", "Disease", "Fungus", "Spore", "Seeds"]
|
||||
|
||||
Assassin:
|
||||
Exsanguin:
|
||||
elements: ["wind", "shadow"]
|
||||
adjectives: ["Stealthy", "Deadly", "Ruthless", "Silent", "Lethal", "Fearsome"]
|
||||
nouns: ["Dagger", "Knife", "Poison", "Bolt", "Arrows", "Scimitar", "Twinblade"]
|
||||
of_things: ["Hunter", "Stalker", "Predator", "Ghost", "Shadow", "Silhouette"]
|
||||
|
||||
Cleric:
|
||||
Sanctifier:
|
||||
elements: ["fire", "light"]
|
||||
adjectives: ["Holy", "Divine", "Pure", "Blessed", "Sacred", "Radiant"]
|
||||
nouns: ["Flame", "Lightning", "Bolt", "Sword", "Shield", "Vocation", "Call"]
|
||||
of_things: ["Healing", "Protection", "Blessing", "Salvation", "Redemption", "Mercy"]
|
||||
|
||||
Hexist:
|
||||
Necromantia:
|
||||
elements: ["curse", "shadow"]
|
||||
adjectives: ["Dark", "Malefic", "Sinister", "Maledicta", "Witchlike", "Pestilential"]
|
||||
nouns: ["Spell", "Hex", "Curse", "Influence", "Taint", "Bane", "Malice"]
|
||||
of_things: ["Poison", "Deceit", "Demonic", "Abomination", "Evil", "Sinister"]
|
||||
|
||||
Ranger:
|
||||
Huntsman:
|
||||
elements: ["air", "wind"]
|
||||
adjectives: ["Wild", "Free", "Wanderer", "Survivor", "Huntress", "Skilled"]
|
||||
nouns: ["Arrows", "Rifle", "Bow", "Arrowhead", "Shotgun", "Crossbow", "Pistol"]
|
||||
of_things: ["Wolves", "Forest", "Wilderness", "Hunter", "Tracking", "Ambush"]
|
||||
|
||||
Kratosphere:
|
||||
elements: ["stone", "earth"]
|
||||
adjectives: ["Rocky", "Solid", "Unyielding", "Resilient", "Firm"]
|
||||
nouns: ["Shield", "Spike", "Breach", "Barrier", "Fortress"]
|
||||
of_things: ["Stonework", "Rockfall", "Stonefall", "Earthquake", "Bedrock"]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.1
|
||||
physical_defense_per_level: 1.1
|
||||
magic_attack_per_level: 1.7
|
||||
magic_defense_per_level: 1.6
|
||||
ability_paths: ['Frostbinder']
|
||||
tags: [playable, mage]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.8
|
||||
physical_defense_per_level: 1.4
|
||||
magic_attack_per_level: 1.2
|
||||
magic_defense_per_level: 1.1
|
||||
ability_paths: ['Exsanguin']
|
||||
tags: [playable, flex]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.8
|
||||
physical_defense_per_level: 1.4
|
||||
magic_attack_per_level: 1.2
|
||||
magic_defense_per_level: 1.1
|
||||
ability_paths: ['Khaosfire']
|
||||
tags: [playable, martial]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.1
|
||||
physical_defense_per_level: 1.1
|
||||
magic_attack_per_level: 1.7
|
||||
magic_defense_per_level: 1.6
|
||||
ability_paths: ['Sanctifier']
|
||||
tags: [playable, mage]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.8
|
||||
physical_defense_per_level: 1.4
|
||||
magic_attack_per_level: 1.2
|
||||
magic_defense_per_level: 1.1
|
||||
ability_paths: ['Kratosphere']
|
||||
tags: [playable, martial]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.1
|
||||
physical_defense_per_level: 1.1
|
||||
magic_attack_per_level: 1.7
|
||||
magic_defense_per_level: 1.6
|
||||
ability_paths: ['Necromantia']
|
||||
tags: [playable, mage]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.8
|
||||
physical_defense_per_level: 1.4
|
||||
magic_attack_per_level: 1.2
|
||||
magic_defense_per_level: 1.1
|
||||
ability_paths: ['Huntsman']
|
||||
tags: [playable, flex]
|
||||
@@ -8,4 +8,5 @@ physical_attack_per_level: 1.1
|
||||
physical_defense_per_level: 1.1
|
||||
magic_attack_per_level: 1.7
|
||||
magic_defense_per_level: 1.6
|
||||
ability_paths: ['Hellknight']
|
||||
tags: [playable, mage]
|
||||
Reference in New Issue
Block a user