diff --git a/args/arguments.py b/args/arguments.py index 155e985a..a6128617 100644 --- a/args/arguments.py +++ b/args/arguments.py @@ -5,7 +5,7 @@ def __init__(self): "settings", "objectives", "starting_party", "characters", "swdtechs", "blitzes", "lores", "rages", "dances", "steal", "commands", - "xpmpgp", "scaling", "bosses", "encounters", "boss_ai", + "xpmpgp", "scaling", "bosses", "encounters", "boss_ai", "enemy_stats", "espers", "natural_magic", "starting_gold_items", "items", "shops", "chests", "graphics", diff --git a/args/enemy_stats.py b/args/enemy_stats.py new file mode 100644 index 00000000..e42c4d2d --- /dev/null +++ b/args/enemy_stats.py @@ -0,0 +1,102 @@ +class EnemyStatArg: + def __init__(self, name, short_arg, arg_min, arg_max, value_min, value_max, percent=True): + # Name of the attribute -- should match the attr in data/enemy.py + self.name = name + + # Short argument name + self.short_arg = short_arg + + # Min/Max percent/delta for the argument + self.arg_min = arg_min + self.arg_max = arg_max + + # The hard min/max for the stat + self.value_min = value_min + self.value_max = value_max + + # Whether the arg is a percent. If false, arg is treated as a value delta + self.percent = percent + + def get_default(self): + # Default is just a function of whether it's a percent or delta + return 100 if self.percent else 0 + + def _get_arg_value_type(self): + return "percent" if self.percent else "delta" + + def get_dest_str(self): + return f"enemy_{self.name}_random_{self._get_arg_value_type()}" + + def get_dest_attr(self, args): + return getattr(args, self.get_dest_str()) + + # helper method to get the long argument name associated with the arg_attr + def get_long_arg(self): + return self.get_dest_str().replace("_", "-") + + # helper methods to get the min/max attributes set by arguments._process_min_max + def get_min_attr(self, args): + return getattr(args, f"{self.get_dest_str()}_min") + def get_max_attr(self, args): + return getattr(args, f"{self.get_dest_str()}_max") + + def get_pretty_name(self): + import string + return string.capwords(self.name.replace("_", " ")) + + def get_description(self): + return f"Each enemy {self.get_pretty_name()} set to random {self._get_arg_value_type()} of original (or normalized/distorted) within given range." + + +#Enemy stat arguments +ENEMY_SPEED_ARG = EnemyStatArg("speed", "-esrp", 25, 201, 1, 128) +ENEMY_VIGOR_ARG = EnemyStatArg("vigor", "-evrp", 25, 401, 1, 255) +ENEMY_MPWR_ARG = EnemyStatArg("magic", "-emrp", 25, 201, 1, 128) +ENEMY_MDEF_ARG = EnemyStatArg("magic_defense", "-emdrp", 0, 151, 0, 255) +ENEMY_DEF_STAT_ARG = EnemyStatArg("defense", "-edrp", 0, 151, 0, 255) +ENEMY_ACC_STAT_ARG = EnemyStatArg("accuracy", "-eard", -100, 101, 1, 200, percent=False) +ENEMY_EVADE_STAT_ARG = EnemyStatArg("evasion", "-eerd", -250, 101, 0, 250, percent=False) +ENEMY_MEVADE_STAT_ARG = EnemyStatArg("magic_evasion", "-emerd", -250, 101, 0, 250, percent=False) +ENEMY_STAT_ARGS = [ENEMY_SPEED_ARG, ENEMY_VIGOR_ARG, ENEMY_MPWR_ARG, ENEMY_MDEF_ARG, ENEMY_DEF_STAT_ARG, ENEMY_ACC_STAT_ARG, ENEMY_EVADE_STAT_ARG, ENEMY_MEVADE_STAT_ARG] + +def name(): + return "Enemy Stats" + +def parse(parser): + enemy_stats = parser.add_argument_group("Enemy Stats") + + for stat_arg in ENEMY_STAT_ARGS: + enemy_stats.add_argument(stat_arg.short_arg, f"--{stat_arg.get_long_arg()}", default = [stat_arg.get_default(), stat_arg.get_default()], type = int, + nargs = 2, metavar = ("MIN", "MAX"), choices = range(stat_arg.arg_min, stat_arg.arg_max), + help = stat_arg.get_description()) + +def process(args): + for stat_arg in ENEMY_STAT_ARGS: + args._process_min_max(stat_arg.get_dest_str()) + +def flags(args): + flags = "" + + for stat_arg in ENEMY_STAT_ARGS: + if stat_arg.get_min_attr(args) != stat_arg.get_default() or stat_arg.get_max_attr(args) != stat_arg.get_default(): + flags += f" {stat_arg.short_arg} {stat_arg.get_min_attr(args)} {stat_arg.get_max_attr(args)}" + + return flags + +def options(args): + options = [] + for stat_arg in ENEMY_STAT_ARGS: + options.append( + (f"Enemy {stat_arg.get_pretty_name()}", f"{stat_arg.get_min_attr(args)}-{stat_arg.get_max_attr(args)}") + ) + return options + +def log(args): + from log import format_option + log = [name()] + + entries = options(args) + for entry in entries: + log.append(format_option(*entry)) + + return log diff --git a/data/enemies.py b/data/enemies.py index ff5de70c..6a9dbcb4 100644 --- a/data/enemies.py +++ b/data/enemies.py @@ -5,6 +5,7 @@ from data.enemy_packs import EnemyPacks from data.enemy_zones import EnemyZones from data.enemy_scripts import EnemyScripts +from args.enemy_stats import ENEMY_STAT_ARGS import data.bosses as bosses class Enemies(): @@ -144,6 +145,21 @@ def boss_experience(self): for enemy_id, exp in custom_exp.items(): self.enemies[enemy_id].exp = exp * self.enemies[enemy_id].level + def stats_random(self, enemy_stat_arg): + import random + for enemy in self.enemies: + stat_value = getattr(enemy, enemy_stat_arg.name) + if enemy_stat_arg.percent: + # Percent + stat_percent = random.randint(enemy_stat_arg.get_min_attr(self.args), enemy_stat_arg.get_max_attr(self.args)) / 100.0 + value = int(stat_value * stat_percent) + else: + # Delta + stat_delta = random.randint(enemy_stat_arg.get_min_attr(self.args), enemy_stat_arg.get_max_attr(self.args)) + value = int(stat_value + stat_delta) + value = max(min(value, enemy_stat_arg.value_max), enemy_stat_arg.value_min) + setattr(enemy, enemy_stat_arg.name, value) + def boss_normalize_distort_stats(self): import random @@ -310,6 +326,10 @@ def mod(self, maps): if self.args.boss_normalize_distort_stats: self.boss_normalize_distort_stats() + for stat_arg in ENEMY_STAT_ARGS: + if stat_arg.get_dest_attr(self.args): + self.stats_random(stat_arg) + if self.args.permadeath: self.remove_fenix_downs()