from __future__ import annotations
import enum
from typing import Union
from pydantic import BaseModel, ConfigDict, Field
[docs]class BaseStatType(enum.Enum):
STR = "STR"
LUK = "LUK"
INT = "INT"
DEX = "DEX"
[docs]class AttackType(enum.Enum):
attack_power = "attack_power"
magic_attack = "magic_attack"
[docs]class StatProps(enum.Enum):
STR = "STR"
LUK = "LUK"
INT = "INT"
DEX = "DEX"
STR_multiplier = "STR_multiplier"
LUK_multiplier = "LUK_multiplier"
INT_multiplier = "INT_multiplier"
DEX_multiplier = "DEX_multiplier"
STR_static = "STR_static"
LUK_static = "LUK_static"
INT_static = "INT_static"
DEX_static = "DEX_static"
attack_power = "attack_power"
magic_attack = "magic_attack"
attack_power_multiplier = "attack_power_multiplier"
magic_attack_multiplier = "magic_attack_multiplier"
critical_rate = "critical_rate"
critical_damage = "critical_damage"
boss_damage_multiplier = "boss_damage_multiplier"
damage_multiplier = "damage_multiplier"
final_damage_multiplier = "final_damage_multiplier"
ignored_defence = "ignored_defence"
MHP = "MHP"
MMP = "MMP"
MHP_multiplier = "MHP_multiplier"
MMP_multiplier = "MMP_multiplier"
[docs] @classmethod
def multiplier(cls, base_type: BaseStatType):
return StatProps(base_type.value + "_multiplier")
[docs] @classmethod
def static(cls, base_type: BaseStatType):
return StatProps(base_type.value + "_static")
[docs]class Stat(BaseModel):
STR: float = 0.0
LUK: float = 0.0
INT: float = 0.0
DEX: float = 0.0
STR_multiplier: float = 0.0
LUK_multiplier: float = 0.0
INT_multiplier: float = 0.0
DEX_multiplier: float = 0.0
STR_static: float = 0.0
LUK_static: float = 0.0
INT_static: float = 0.0
DEX_static: float = 0.0
attack_power: float = 0.0
magic_attack: float = 0.0
attack_power_multiplier: float = 0.0
magic_attack_multiplier: float = 0.0
critical_rate: float = 0.0
critical_damage: float = 0.0
boss_damage_multiplier: float = 0.0
damage_multiplier: float = 0.0
final_damage_multiplier: float = 0.0
ignored_defence: float = 0.0
MHP: float = 0.0
MMP: float = 0.0
MHP_multiplier: float = 0.0
MMP_multiplier: float = 0.0
elemental_resistance: float = 0.0
[docs] @classmethod
def all_stat(cls, v) -> Stat:
return Stat(STR=v, LUK=v, INT=v, DEX=v)
[docs] @classmethod
def all_stat_multiplier(cls, v) -> Stat:
return Stat(
STR_multiplier=v, LUK_multiplier=v, INT_multiplier=v, DEX_multiplier=v
)
def __add__(self, arg: Stat) -> Stat:
return Stat(
STR=self.STR + arg.STR,
LUK=self.LUK + arg.LUK,
INT=self.INT + arg.INT,
DEX=self.DEX + arg.DEX,
STR_multiplier=self.STR_multiplier + arg.STR_multiplier,
LUK_multiplier=self.LUK_multiplier + arg.LUK_multiplier,
INT_multiplier=self.INT_multiplier + arg.INT_multiplier,
DEX_multiplier=self.DEX_multiplier + arg.DEX_multiplier,
STR_static=self.STR_static + arg.STR_static,
LUK_static=self.LUK_static + arg.LUK_static,
INT_static=self.INT_static + arg.INT_static,
DEX_static=self.DEX_static + arg.DEX_static,
attack_power=self.attack_power + arg.attack_power,
magic_attack=self.magic_attack + arg.magic_attack,
attack_power_multiplier=self.attack_power_multiplier
+ arg.attack_power_multiplier,
magic_attack_multiplier=self.magic_attack_multiplier
+ arg.magic_attack_multiplier,
critical_rate=self.critical_rate + arg.critical_rate,
critical_damage=self.critical_damage + arg.critical_damage,
boss_damage_multiplier=self.boss_damage_multiplier
+ arg.boss_damage_multiplier,
damage_multiplier=self.damage_multiplier + arg.damage_multiplier,
MHP=self.MHP + arg.MHP,
MMP=self.MMP + arg.MMP,
MHP_multiplier=self.MHP_multiplier + arg.MHP_multiplier,
MMP_multiplier=self.MMP_multiplier + arg.MMP_multiplier,
final_damage_multiplier=self.final_damage_multiplier
+ arg.final_damage_multiplier
+ 0.01 * self.final_damage_multiplier * arg.final_damage_multiplier,
ignored_defence=100
- 0.01 * ((100 - self.ignored_defence) * (100 - arg.ignored_defence)),
elemental_resistance=self.elemental_resistance + arg.elemental_resistance,
)
def __iadd__(self, arg: Stat):
self.STR += arg.STR
self.LUK += arg.LUK
self.INT += arg.INT
self.DEX += arg.DEX
self.STR_multiplier += arg.STR_multiplier
self.LUK_multiplier += arg.LUK_multiplier
self.INT_multiplier += arg.INT_multiplier
self.DEX_multiplier += arg.DEX_multiplier
self.STR_static += arg.STR_static
self.LUK_static += arg.LUK_static
self.INT_static += arg.INT_static
self.DEX_static += arg.DEX_static
self.attack_power += arg.attack_power
self.magic_attack += arg.magic_attack
self.attack_power_multiplier += arg.attack_power_multiplier
self.magic_attack_multiplier += arg.magic_attack_multiplier
self.critical_rate += arg.critical_rate
self.critical_damage += arg.critical_damage
self.boss_damage_multiplier += arg.boss_damage_multiplier
self.damage_multiplier += arg.damage_multiplier
self.MHP += arg.MHP
self.MMP += arg.MMP
self.MHP_multiplier += arg.MHP_multiplier
self.MMP_multiplier += arg.MMP_multiplier
self.final_damage_multiplier += (
arg.final_damage_multiplier
+ 0.01 * self.final_damage_multiplier * arg.final_damage_multiplier
)
self.ignored_defence = 100 - 0.01 * (
(100 - self.ignored_defence) * (100 - arg.ignored_defence)
)
self.elemental_resistance += arg.elemental_resistance
return self
[docs] def stack(self, stack: int) -> Stat:
"""
StackableBuffSkill을 위한 메소드로, armor_ignore 및 final_damage_multiplier를 단순 곱셈한 값을 반환합니다.
"""
return Stat(
STR=self.STR * stack,
LUK=self.LUK * stack,
INT=self.INT * stack,
DEX=self.DEX * stack,
STR_multiplier=self.STR_multiplier * stack,
LUK_multiplier=self.LUK_multiplier * stack,
INT_multiplier=self.INT_multiplier * stack,
DEX_multiplier=self.DEX_multiplier * stack,
STR_static=self.STR_static * stack,
LUK_static=self.LUK_static * stack,
INT_static=self.INT_static * stack,
DEX_static=self.DEX_static * stack,
attack_power=self.attack_power * stack,
magic_attack=self.magic_attack * stack,
attack_power_multiplier=self.attack_power_multiplier * stack,
magic_attack_multiplier=self.magic_attack_multiplier * stack,
critical_rate=self.critical_rate * stack,
critical_damage=self.critical_damage * stack,
boss_damage_multiplier=self.boss_damage_multiplier * stack,
damage_multiplier=self.damage_multiplier * stack,
MHP=self.MHP * stack,
MMP=self.MMP * stack,
MHP_multiplier=self.MHP_multiplier * stack,
MMP_multiplier=self.MMP_multiplier * stack,
final_damage_multiplier=self.final_damage_multiplier * stack,
ignored_defence=self.ignored_defence * stack,
elemental_resistance=self.elemental_resistance * stack,
)
[docs] def get(self, prop: StatProps):
return getattr(self, prop.value)
[docs] def get_base_stat_coefficient(self, base_stat_type: BaseStatType) -> float:
if base_stat_type == BaseStatType.STR:
return self.STR * (self.STR_multiplier * 0.01 + 1) + self.STR_static
if base_stat_type == BaseStatType.DEX:
return self.DEX * (self.DEX_multiplier * 0.01 + 1) + self.DEX_static
if base_stat_type == BaseStatType.INT:
return self.INT * (self.INT_multiplier * 0.01 + 1) + self.INT_static
if base_stat_type == BaseStatType.LUK:
return self.LUK * (self.LUK_multiplier * 0.01 + 1) + self.LUK_static
raise ValueError
[docs] def get_attack_coefficient(self, attack_type: AttackType) -> float:
if attack_type == AttackType.attack_power:
return self.attack_power * (1 + 0.01 * self.attack_power_multiplier)
if attack_type == AttackType.magic_attack:
return self.magic_attack * (1 + 0.01 * self.magic_attack_multiplier)
raise ValueError
[docs] def show(self) -> str:
output = f"""
===================================
Basis Stats
STR: {self.get_base_stat_coefficient(BaseStatType.STR):8.2f} | Basis {self.STR:7.1f} | {self.STR_multiplier:5.1f} % | static {self.STR_static:7.1f} |
DEX: {self.get_base_stat_coefficient(BaseStatType.DEX):8.2f} | Basis {self.DEX:7.1f} | {self.DEX_multiplier:5.1f} % | static {self.DEX_static:7.1f} |
INT: {self.get_base_stat_coefficient(BaseStatType.INT):8.2f} | Basis {self.INT:7.1f} | {self.INT_multiplier:5.1f} % | static {self.INT_static:7.1f} |
LUK: {self.get_base_stat_coefficient(BaseStatType.LUK):8.2f} | Basis {self.LUK:7.1f} | {self.LUK_multiplier:5.1f} % | static {self.LUK_static:7.1f} |
MaxHP: Basis {self.MHP:10.2f} | {self.MHP_multiplier:5.1f} %
MaxMP: Basis {self.MMP:10.2f} | {self.MMP_multiplier:5.1f} %
ATT: {self.attack_power:6.1f} | {self.attack_power_multiplier:5.1f} % |
MAT: {self.magic_attack:6.1f} | {self.magic_attack_multiplier:5.1f} % |
===================================
critical_rate : {self.critical_rate:7.2f}
critical_damage : {self.critical_damage:7.2f}
boss_damage_multiplier : {self.boss_damage_multiplier:7.2f}
damage_multiplier : {self.damage_multiplier:7.2f}
final_damage_multiplier: {self.final_damage_multiplier:7.2f}
ignored_defence : {self.ignored_defence:7.2f}
elemental_resistance : { self.elemental_resistance:7.2f}
"""
return output
[docs] def short_dict(self) -> dict[str, float]:
long_dict = self.model_dump()
return {k: v for k, v in long_dict.items() if v != 0}
[docs] @classmethod
def sum(cls, stats: list[Stat]) -> Stat:
final_damage_multiplier: float = 1
for stat in stats:
final_damage_multiplier += (
stat.final_damage_multiplier * final_damage_multiplier * 0.01
)
final_damage_multiplier -= 1
final_damage_multiplier *= 100
defence: float = 1
for stat in stats:
defence -= defence * 0.01 * stat.ignored_defence
ignored_defence = 100 * (1 - defence)
return Stat(
STR=sum([v.STR for v in stats]),
LUK=sum([v.LUK for v in stats]),
INT=sum([v.INT for v in stats]),
DEX=sum([v.DEX for v in stats]),
STR_multiplier=sum([v.STR_multiplier for v in stats]),
LUK_multiplier=sum([v.LUK_multiplier for v in stats]),
INT_multiplier=sum([v.INT_multiplier for v in stats]),
DEX_multiplier=sum([v.DEX_multiplier for v in stats]),
STR_static=sum([v.STR_static for v in stats]),
LUK_static=sum([v.LUK_static for v in stats]),
INT_static=sum([v.INT_static for v in stats]),
DEX_static=sum([v.DEX_static for v in stats]),
attack_power=sum([v.attack_power for v in stats]),
magic_attack=sum([v.magic_attack for v in stats]),
attack_power_multiplier=sum([v.attack_power_multiplier for v in stats]),
magic_attack_multiplier=sum([v.magic_attack_multiplier for v in stats]),
critical_rate=sum([v.critical_rate for v in stats]),
critical_damage=sum([v.critical_damage for v in stats]),
boss_damage_multiplier=sum([v.boss_damage_multiplier for v in stats]),
damage_multiplier=sum([v.damage_multiplier for v in stats]),
MHP=sum([v.MHP for v in stats]),
MMP=sum([v.MMP for v in stats]),
MHP_multiplier=sum([v.MHP_multiplier for v in stats]),
MMP_multiplier=sum([v.MMP_multiplier for v in stats]),
final_damage_multiplier=final_damage_multiplier,
ignored_defence=ignored_defence,
elemental_resistance=sum([v.elemental_resistance for v in stats]),
)
[docs]class ActionStat(BaseModel):
cooltime_reduce: float = 0.0
summon_duration: float = 0.0
buff_duration: float = 0.0
cooltime_reduce_rate: float = 0.0
def __add__(self, arg: ActionStat) -> ActionStat:
return ActionStat(
cooltime_reduce=self.cooltime_reduce + arg.cooltime_reduce,
summon_duration=self.summon_duration + arg.summon_duration,
buff_duration=self.buff_duration + arg.buff_duration,
cooltime_reduce_rate=self.cooltime_reduce_rate + arg.cooltime_reduce_rate,
)
def __iadd__(self, arg: ActionStat) -> ActionStat:
self.cooltime_reduce += arg.cooltime_reduce
self.summon_duration += arg.summon_duration
self.buff_duration += arg.buff_duration
self.cooltime_reduce_rate += arg.cooltime_reduce_rate
return self
[docs] def calculate_cooldown(self, original_cooldown):
# TODO - need to apply correct logic
return (
original_cooldown * (1 - self.cooltime_reduce_rate * 0.01)
- self.cooltime_reduce
)
[docs] def calculate_buff_duration(self, original_duration):
return original_duration * (1 + 0.01 * self.buff_duration)
[docs]class LevelStat(BaseModel):
STR: float = 0.0
LUK: float = 0.0
INT: float = 0.0
DEX: float = 0.0
attack_power: float = 0.0
magic_attack: float = 0.0
[docs] def get_stat(self, level: int) -> Stat:
multiplier = level // 10
return Stat(
STR=self.STR * multiplier,
LUK=self.LUK * multiplier,
INT=self.INT * multiplier,
DEX=self.DEX * multiplier,
attack_power=self.attack_power * multiplier,
magic_attack=self.magic_attack * multiplier,
)
def __add__(self, arg: LevelStat):
return LevelStat(
STR=self.STR + arg.STR,
LUK=self.LUK + arg.LUK,
INT=self.INT + arg.INT,
DEX=self.DEX + arg.DEX,
attack_power=self.attack_power + arg.attack_power,
magic_attack=self.magic_attack + arg.magic_attack,
)
AnyStat = Union[Stat, ActionStat, LevelStat]
[docs]class ExtendedStat(BaseModel):
stat: Stat = Field(default_factory=Stat)
action_stat: ActionStat = Field(default_factory=ActionStat)
level_stat: LevelStat = Field(default_factory=LevelStat)
model_config = ConfigDict(extra="forbid")
def __add__(self, arg: ExtendedStat) -> ExtendedStat:
return ExtendedStat(
stat=self.stat + arg.stat,
action_stat=self.action_stat + arg.action_stat,
level_stat=self.level_stat + arg.level_stat,
)
[docs] def compute_by_level(self, level) -> Stat:
return self.stat + self.level_stat.get_stat(level)