From e06ae789f68a854b48a8afc7b36869db40fec6a8 Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Mon, 18 May 2026 09:00:03 -0400 Subject: [PATCH 1/4] Add 'Oops All Mode --- args/bosses.py | 36 +++++++++++++++++++++++++ data/enemy_formations.py | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/args/bosses.py b/args/bosses.py index 2854fc1c..d44dee35 100644 --- a/args/bosses.py +++ b/args/bosses.py @@ -35,6 +35,8 @@ def parse(parser): help = "Undead status removed from bosses") bosses.add_argument("-bmkl", "--boss-marshal-keep-lobos", action = "store_true", help = "Don't replace the Marshal's Lobos with randomized enemies") + bosses.add_argument("-oops", default = None, type = str, + help = "Oops, all ! Replace all bosses with the specified boss enemy ID or name.") def process(args): if args.mix_bosses_dragons: @@ -47,6 +49,37 @@ def process(args): if vanilla_locations and args.statue_boss_location == BossLocations.MIX: args.statue_boss_location = BossLocations.SHUFFLE + if args.oops is not None: + import data.bosses as bosses + excluded_final_battle_ids = set(bosses.final_battle_enemy_name.keys()) + excluded_final_battle_ids.remove(298) # Keep Kefka (Final) as valid! + + try: + # Try to parse as integer ID first + oops_id = int(args.oops) + if not (0 <= oops_id <= 383) or oops_id in excluded_final_battle_ids: + raise ValueError() + args.oops = oops_id + except ValueError: + # If not a valid integer ID, try to parse as normalized name + def normalize(name): + return "".join(c.lower() for c in name if c.isalnum()) + + name_to_id = {} + for enemy_dict in [bosses.normal_enemy_name, bosses.dragon_enemy_name, bosses.statue_enemy_name, bosses.final_battle_enemy_name]: + for eid, name in enemy_dict.items(): + if eid not in excluded_final_battle_ids: + name_to_id[normalize(name)] = eid + + normalized_input = normalize(args.oops) + if normalized_input in name_to_id: + args.oops = name_to_id[normalized_input] + else: + raise ValueError( + f"Invalid boss ID or name: '{args.oops}'. " + f"Please check the enemy maps in data/bosses.py for correct names and IDs." + ) + def flags(args): flags = "" @@ -73,6 +106,8 @@ def flags(args): flags += " -bnu" if args.boss_marshal_keep_lobos: flags += " -bmkl" + if args.oops is not None: + flags += f" -oops {args.oops}" return flags @@ -100,6 +135,7 @@ def options(args): ("Boss Experience", args.boss_experience, "boss_experience"), ("No Undead", args.boss_no_undead, "boss_no_undead"), ("Marshal Keep Lobos", args.boss_marshal_keep_lobos, "boss_marshal_keep_lobos"), + ("Oops All Boss ID", args.oops, "oops"), ] def menu(args): diff --git a/data/enemy_formations.py b/data/enemy_formations.py index 8c672e8b..43bc1917 100644 --- a/data/enemy_formations.py +++ b/data/enemy_formations.py @@ -179,6 +179,64 @@ def mod(self): if self.args.random_encounters_chupon: self.add_chupon() + if self.args.oops is not None: + self.oops_mod() + + def oops_mod(self): + boss_enemy_ids = set() + import data.bosses as bosses + boss_enemy_ids.update(bosses.normal_enemy_name.keys()) + boss_enemy_ids.update(bosses.dragon_enemy_name.keys()) + boss_enemy_ids.update(bosses.statue_enemy_name.keys()) + boss_enemy_ids.update(bosses.final_battle_enemy_name.keys()) + + # The user-selected target boss ID + target_boss_id = self.args.oops + + # Find the original formation ID of the chosen boss to get its native mold + original_formation_id = None + boss_name = None + for enemy_dict in [bosses.normal_enemy_name, bosses.dragon_enemy_name, bosses.statue_enemy_name, bosses.final_battle_enemy_name]: + if target_boss_id in enemy_dict: + boss_name = enemy_dict[target_boss_id] + break + + if boss_name is not None: + for formation_dict in [bosses.normal_formation_name, bosses.dragon_formation_name, bosses.statue_formation_name, bosses.final_battle_formation_name]: + for fid, name in formation_dict.items(): + if name == boss_name: + original_formation_id = fid + break + if original_formation_id is not None: + break + + target_mold = None + if original_formation_id is not None: + target_mold = self.formations[original_formation_id].mold + + for formation in self.formations: + has_boss = False + for enemy_index in range(formation.ENEMY_CAPACITY): + if formation.enemy_slots & (1 << enemy_index): + if formation.enemy_ids[enemy_index] in boss_enemy_ids: + has_boss = True + break + + if has_boss: + # Set only the first slot (slot 0) as active to prevent multi-boss VRAM overlays + formation.enemy_slots = 1 + + # Set slot 0's enemy ID to the target boss ID + formation.enemy_ids[0] = target_boss_id + + # Center the single boss perfectly on screen (y = 5, x = 6) + formation.enemy_y_positions[0] = 5 + formation.enemy_x_positions[0] = 6 + + # Apply the native mold for correct VRAM layout & rendering + if target_mold is not None: + formation.mold = target_mold + def print_scripts(self): for formation_index, formation in enumerate(self.formations): if formation.enable_event_script: From 6ba54edaedb43a185eda0986db04dfa4533a35c9 Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Wed, 3 Jun 2026 08:59:59 -0400 Subject: [PATCH 2/4] Add random option to Oops All Bosses flag --- args/bosses.py | 4 +++- data/enemy_formations.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/args/bosses.py b/args/bosses.py index d44dee35..c968ca1a 100644 --- a/args/bosses.py +++ b/args/bosses.py @@ -36,7 +36,7 @@ def parse(parser): bosses.add_argument("-bmkl", "--boss-marshal-keep-lobos", action = "store_true", help = "Don't replace the Marshal's Lobos with randomized enemies") bosses.add_argument("-oops", default = None, type = str, - help = "Oops, all ! Replace all bosses with the specified boss enemy ID or name.") + help = "Oops, all ! Replace all bosses with the specified boss enemy ID or name, or \"random\".") def process(args): if args.mix_bosses_dragons: @@ -74,6 +74,8 @@ def normalize(name): normalized_input = normalize(args.oops) if normalized_input in name_to_id: args.oops = name_to_id[normalized_input] + elif normalized_input == "random": + args.oops = "random" else: raise ValueError( f"Invalid boss ID or name: '{args.oops}'. " diff --git a/data/enemy_formations.py b/data/enemy_formations.py index 43bc1917..5a1c9b3a 100644 --- a/data/enemy_formations.py +++ b/data/enemy_formations.py @@ -193,6 +193,14 @@ def oops_mod(self): # The user-selected target boss ID target_boss_id = self.args.oops + if target_boss_id == "random": + import random + excluded_final_battle_ids = set(bosses.final_battle_enemy_name.keys()) + excluded_final_battle_ids.remove(298) # Keep Kefka (Final) as valid! + valid_boss_ids = [eid for eid in bosses.enemy_name.keys() if eid not in excluded_final_battle_ids and eid not in bosses.removed_enemy_name] + target_boss_id = random.choice(valid_boss_ids) + self.args.oops = target_boss_id + # Find the original formation ID of the chosen boss to get its native mold original_formation_id = None boss_name = None @@ -210,6 +218,10 @@ def oops_mod(self): if original_formation_id is not None: break + if self.args.spoiler_log: + from log import section + section("Oops All Bosses", [f" Boss: {boss_name} (ID {target_boss_id})"], []) + target_mold = None if original_formation_id is not None: target_mold = self.formations[original_formation_id].mold From c0bca1b4fea3f64b5327840cd6468cda00f1fccf Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Thu, 4 Jun 2026 16:34:52 -0400 Subject: [PATCH 3/4] Refactor boss ID validation and native mold retrieval logic - Improved validation of boss IDs to ensure only valid IDs are accepted. - Simplified the process of finding a boss's native mold by directly checking formations. - Added more descriptive error messages for invalid boss IDs. --- args/bosses.py | 15 ++++++++++++--- data/enemy_formations.py | 24 ++++++------------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/args/bosses.py b/args/bosses.py index c968ca1a..bbda56b0 100644 --- a/args/bosses.py +++ b/args/bosses.py @@ -54,13 +54,22 @@ def process(args): excluded_final_battle_ids = set(bosses.final_battle_enemy_name.keys()) excluded_final_battle_ids.remove(298) # Keep Kefka (Final) as valid! + # Build the set of valid boss IDs + valid_boss_ids = set() + for enemy_dict in [bosses.normal_enemy_name, bosses.dragon_enemy_name, bosses.statue_enemy_name, bosses.final_battle_enemy_name]: + valid_boss_ids.update(enemy_dict.keys()) + valid_boss_ids.difference_update(excluded_final_battle_ids) + try: # Try to parse as integer ID first oops_id = int(args.oops) - if not (0 <= oops_id <= 383) or oops_id in excluded_final_battle_ids: - raise ValueError() + if oops_id not in valid_boss_ids: + raise ValueError(f"Invalid boss ID: {oops_id}. Must be a valid boss enemy ID.") args.oops = oops_id - except ValueError: + except ValueError as e: + if "Invalid boss ID" in str(e): + raise e + # If not a valid integer ID, try to parse as normalized name def normalize(name): return "".join(c.lower() for c in name if c.isalnum()) diff --git a/data/enemy_formations.py b/data/enemy_formations.py index 5a1c9b3a..5bcd0587 100644 --- a/data/enemy_formations.py +++ b/data/enemy_formations.py @@ -201,30 +201,18 @@ def oops_mod(self): target_boss_id = random.choice(valid_boss_ids) self.args.oops = target_boss_id - # Find the original formation ID of the chosen boss to get its native mold - original_formation_id = None - boss_name = None - for enemy_dict in [bosses.normal_enemy_name, bosses.dragon_enemy_name, bosses.statue_enemy_name, bosses.final_battle_enemy_name]: - if target_boss_id in enemy_dict: - boss_name = enemy_dict[target_boss_id] - break - - if boss_name is not None: - for formation_dict in [bosses.normal_formation_name, bosses.dragon_formation_name, bosses.statue_formation_name, bosses.final_battle_formation_name]: - for fid, name in formation_dict.items(): - if name == boss_name: - original_formation_id = fid - break - if original_formation_id is not None: - break + boss_name = bosses.enemy_name.get(target_boss_id) if self.args.spoiler_log: from log import section section("Oops All Bosses", [f" Boss: {boss_name} (ID {target_boss_id})"], []) + # Find the native mold of the chosen boss from any formation containing it target_mold = None - if original_formation_id is not None: - target_mold = self.formations[original_formation_id].mold + for formation in self.formations: + if target_boss_id in formation.enemies(): + target_mold = formation.mold + break for formation in self.formations: has_boss = False From faeead847ab6d793e92b01337990a02f453f0809 Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Fri, 5 Jun 2026 06:02:41 -0400 Subject: [PATCH 4/4] fix: oops: no arg results in random choice --- args/bosses.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/args/bosses.py b/args/bosses.py index bbda56b0..1bb0583d 100644 --- a/args/bosses.py +++ b/args/bosses.py @@ -35,7 +35,7 @@ def parse(parser): help = "Undead status removed from bosses") bosses.add_argument("-bmkl", "--boss-marshal-keep-lobos", action = "store_true", help = "Don't replace the Marshal's Lobos with randomized enemies") - bosses.add_argument("-oops", default = None, type = str, + bosses.add_argument("-oops", default = None, nargs = "?", const = "random", type = str, help = "Oops, all ! Replace all bosses with the specified boss enemy ID or name, or \"random\".") def process(args): @@ -137,6 +137,10 @@ def options(args): if args.statue_boss_location: statue_battles = args.statue_boss_location.capitalize() + oops = args.oops + if oops == "random": + oops = "Random" + return [ ("Boss Battles", boss_battles, "boss_battles"), ("Dragons", dragon_battles, "dragon_battles"), @@ -146,7 +150,7 @@ def options(args): ("Boss Experience", args.boss_experience, "boss_experience"), ("No Undead", args.boss_no_undead, "boss_no_undead"), ("Marshal Keep Lobos", args.boss_marshal_keep_lobos, "boss_marshal_keep_lobos"), - ("Oops All Boss ID", args.oops, "oops"), + ("Oops All Boss ID", oops, "oops"), ] def menu(args):