# SPDX-License-Identifier: GPL-3.0
# Copyright (c) 2014-2023 William Edwards <shadowapex@gmail.com>, Benjamin Bean <superman2k5@gmail.com>
from __future__ import annotations
import datetime as dt
import logging
import random
from typing import TYPE_CHECKING, Sequence, Tuple
if TYPE_CHECKING:
from tuxemon.db import MonsterModel
from tuxemon.element import Element
from tuxemon.monster import Monster
from tuxemon.technique.technique import Technique
logger = logging.getLogger(__name__)
[docs]def simple_damage_multiplier(
attack_types: Sequence[Element],
target_types: Sequence[Element],
) -> float:
"""
Calculates damage multiplier based on strengths and weaknesses.
Parameters:
attack_types: The types of the technique.
target_types: The types of the target.
Returns:
The attack multiplier.
"""
m = 1.0
for attack_type in attack_types:
for target_type in target_types:
if target_type:
if (
attack_type.slug == "aether"
or target_type.slug == "aether"
):
continue
m = attack_type.lookup_multiplier(target_type.slug)
m = min(4, m)
m = max(0.25, m)
return m
[docs]def simple_damage_calculate(
technique: Technique,
user: Monster,
target: Monster,
) -> Tuple[int, float]:
"""
Calculates the damage of a technique based on stats and multiplier.
Parameters:
technique: The technique to calculate for.
user: The user of the technique.
target: The one the technique is being used on.
Returns:
A tuple (damage, multiplier).
"""
if technique.range == "melee":
user_strength = user.melee * (7 + user.level)
target_resist = target.armour
elif technique.range == "touch":
user_strength = user.melee * (7 + user.level)
target_resist = target.dodge
elif technique.range == "ranged":
user_strength = user.ranged * (7 + user.level)
target_resist = target.dodge
elif technique.range == "reach":
user_strength = user.ranged * (7 + user.level)
target_resist = target.armour
elif technique.range == "reliable":
user_strength = 7 + user.level
target_resist = 1
else:
raise RuntimeError(
"unhandled damage category %s",
technique.range,
)
mult = simple_damage_multiplier(
(technique.types),
(target.types),
)
move_strength = technique.power * mult
damage = int(user_strength * move_strength / target_resist)
return damage, mult
[docs]def damage_full_hp(target: Monster, value: int) -> int:
"""
Damage based on target's full hp.
Parameters:
target: The one the technique is being used on.
value: Numerical value (target.hp // value).
Returns:
Inflicted damage.
"""
damage = target.hp // value
return damage
[docs]def simple_recover(target: Monster) -> int:
"""
Simple recover based on target's full hp.
Parameters:
technique: The technique causing recover.
target: The one being healed.
Returns:
Recovered health.
"""
heal = min(target.hp // 16, target.hp - target.current_hp)
return heal
[docs]def simple_lifeleech(user: Monster, target: Monster) -> int:
"""
Simple lifeleech based on a few factors.
Parameters:
technique: The technique causing lifeleech.
user: The user of the technique.
target: The one the technique is being used on.
Returns:
Inflicted damage.
"""
if user.current_hp <= 0:
damage = 0
else:
damage = min(
target.hp // 16, target.current_hp, user.hp - user.current_hp
)
return damage
[docs]def simple_grabbed(monster: Monster) -> None:
for move in monster.moves:
if move.range.ranged:
move.potency = move.default_potency * 0.5
move.power = move.default_power * 0.5
elif move.range.reach:
move.potency = move.default_potency * 0.5
move.power = move.default_power * 0.5
[docs]def simple_stuck(monster: Monster) -> None:
for move in monster.moves:
if move.range.melee:
move.potency = move.default_potency * 0.5
move.power = move.default_power * 0.5
elif move.range.touch:
move.potency = move.default_potency * 0.5
move.power = move.default_power * 0.5
[docs]def escape(level_user: int, level_target: int, attempts: int) -> bool:
escape = 0.4 + (0.15 * (attempts + level_user - level_target))
if random.random() <= escape:
return True
else:
return False
[docs]def check_taste(monster: Monster, stat: str) -> int:
"""
It checks the taste and return the value
"""
positive = 0
negative = 0
if stat == "speed":
if monster.taste_cold == "mild":
negative = (monster.speed) * 10 // 100
if monster.taste_warm == "peppy":
positive = (monster.speed) * 10 // 100
value = positive - negative
return value
elif stat == "melee":
if monster.taste_cold == "sweet":
negative = (monster.melee) * 10 // 100
if monster.taste_warm == "salty":
positive = (monster.melee) * 10 // 100
value = positive - negative
return value
elif stat == "armour":
if monster.taste_cold == "soft":
negative = (monster.armour) * 10 // 100
if monster.taste_warm == "hearty":
positive = (monster.armour) * 10 // 100
value = positive - negative
return value
elif stat == "ranged":
if monster.taste_cold == "flakey":
negative = (monster.ranged) * 10 // 100
if monster.taste_warm == "zesty":
positive = (monster.ranged) * 10 // 100
value = positive - negative
return value
elif stat == "dodge":
if monster.taste_cold == "dry":
negative = (monster.dodge) * 10 // 100
if monster.taste_warm == "refined":
positive = (monster.dodge) * 10 // 100
value = positive - negative
return value
else:
value = positive
return value
[docs]def today_ordinal() -> int:
"""
It gives today's proleptic Gregorian ordinal.
"""
today = dt.date.today().toordinal()
return today
[docs]def set_weight(kg: float) -> float:
"""
It generates a personalized weight,
random number: between +/- 10%.
Eg 100 kg +/- 10 kg
"""
if kg == 0:
weight = kg
else:
minor = kg - (kg * 0.1)
major = (kg * 0.1) + kg
weight = round(random.uniform(minor, major), 2)
return weight
[docs]def set_height(cm: float) -> float:
"""
It generates a personalized height,
random number: between +/- 10%.
Eg 100 cm +/- 10 cm
"""
if cm == 0:
height = cm
else:
minor = cm - (cm * 0.1)
major = (cm * 0.1) + cm
height = round(random.uniform(minor, major), 2)
return height
[docs]def convert_lbs(kg: float) -> float:
"""
It converts kilograms into pounds.
"""
pounds = round(kg * 2.2046, 2)
return pounds
[docs]def convert_ft(cm: float) -> float:
"""
It converts centimeters into feet.
"""
foot = round(cm * 0.032808399, 2)
return foot
[docs]def convert_km(steps: float) -> float:
"""
It converts steps into kilometers.
One tile: 1 meter
"""
m = steps * 1
km = round(m / 1000, 2)
return km
[docs]def convert_mi(steps: float) -> float:
"""
It converts steps into miles.
"""
km = convert_km(steps)
mi = round(km * 0.6213711922, 2)
return mi
[docs]def weight_height_diff(
monster: Monster, db: MonsterModel
) -> Tuple[float, float]:
weight = round(((monster.weight - db.weight) / db.weight) * 100, 1)
height = round(((monster.height - db.height) / db.height) * 100, 1)
return weight, height