Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions args/bosses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, nargs = "?", const = "random", type = str,
help = "Oops, all <boss>! Replace all bosses with the specified boss enemy ID or name, or \"random\".")

def process(args):
if args.mix_bosses_dragons:
Expand All @@ -47,6 +49,48 @@ 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!

# 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 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 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())

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]
elif normalized_input == "random":
args.oops = "random"
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."
)
Comment on lines +63 to +92

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of using a try-except block to handle control flow and checking the exception string to decide whether to re-raise, you can check if the input is numeric using str.isdigit(). This makes the logic cleaner, more readable, and avoids relying on exception message parsing.

        if args.oops.isdigit():
            oops_id = int(args.oops)
            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
        else:
            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]
            elif normalized_input == "random":
                args.oops = "random"
            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 = ""

Expand All @@ -73,6 +117,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

Expand All @@ -91,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"),
Expand All @@ -100,6 +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", oops, "oops"),
]

def menu(args):
Expand Down
58 changes: 58 additions & 0 deletions data/enemy_formations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

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

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
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
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:
Comment on lines +218 to +225

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This nested loop can be simplified and made more Pythonic by using the built-in any() function along with the existing formation.enemies() helper method, which already filters and returns the active enemy IDs for the formation.

Suggested change
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:
if any(enemy_id in boss_enemy_ids for enemy_id in formation.enemies()):

# 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:
Expand Down