Bosshunt mode

This commit is contained in:
2026-01-21 08:20:59 -06:00
parent 3255c75828
commit 67307a872c
8 changed files with 104 additions and 23 deletions

View File

@@ -74,6 +74,8 @@ class World(object):
self.dark_rooms = {}
self.damage_challenge = {}
self.shuffle_damage_table = {}
self.bosses_ganon = {}
self.bosshunt_include_agas = {}
self.ganon_item = {}
self.ganon_item_orig = {}
self.custom = custom
@@ -168,6 +170,8 @@ class World(object):
set_player_attr('escape_assist', [])
set_player_attr('crystals_needed_for_ganon', 7)
set_player_attr('crystals_needed_for_gt', 7)
set_player_attr('bosses_ganon', 8)
set_player_attr('bosshunt_include_agas', False)
set_player_attr('ganon_item', 'silver')
set_player_attr('crystals_ganon_orig', {})
set_player_attr('crystals_gt_orig', {})
@@ -356,7 +360,7 @@ class World(object):
else:
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']:
return False
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']:
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt', 'bosshunt']:
return True
else:
return False
@@ -3118,6 +3122,8 @@ class Spoiler(object):
'beemizer': self.world.beemizer,
'gt_crystals': self.world.crystals_needed_for_gt,
'ganon_crystals': self.world.crystals_needed_for_ganon,
'ganon_bosses': self.world.bosses_ganon,
'bosshunt_include_agas': self.world.bosshunt_include_agas,
'ganon_item': self.world.ganon_item,
'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility,
@@ -3340,6 +3346,10 @@ class Spoiler(object):
if custom['ganongoal'] and 'requirements' in custom['ganongoal']:
outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['ganongoal']['goaltext'])
elif self.metadata['goal'][player] == 'bosshunt':
outfile.write('Ganon Requirement:'.ljust(line_width) + '%s bosses%s\n' %
(str(self.world.bosses_ganon[player]),
' (including both Agahnims)' if self.world.bosshunt_include_agas[player] else ''))
else:
outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player]))
if custom['pedgoal'] and 'requirements' in custom['pedgoal']:
@@ -3749,8 +3759,9 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2}
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5,
'ganonhunt': 6, 'completionist': 7, 'sanctuary': 1}
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3,
'crystals': 4, 'trinity': 5, 'ganonhunt': 6, 'completionist': 7,
'sanctuary': 1, 'bosshunt': 6}
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
func_mode = {"normal": 0, "hard": 1, "expert": 2}

4
CLI.py
View File

@@ -134,7 +134,7 @@ def parse_cli(argv, no_defaults=False):
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle',
'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'bosses_ganon', 'bosshunt_include_agas', 'ganon_item', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap', 'startinventory',
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
@@ -178,6 +178,8 @@ def parse_settings():
"goal": "ganon",
"crystals_gt": "7",
"crystals_ganon": "7",
"bosses_ganon": "8",
"bosshunt_include_agas": False,
"ganon_item": "silver",
"swords": "random",
"flute_mode": "normal",

View File

@@ -218,12 +218,14 @@ def get_custom_array_key(item):
def generate_itempool(world, player):
if (world.difficulty[player] not in ['normal', 'hard', 'expert']
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals',
'ganonhunt', 'completionist', 'sanctuary']
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons',
'triforcehunt', 'trinity', 'crystals',
'ganonhunt', 'completionist', 'sanctuary',
'bosshunt']
or world.mode[player] not in ['open', 'standard', 'inverted']
or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']
or world.progressive not in ['on', 'off', 'random']):
raise NotImplementedError('Not supported yet')
raise NotImplementedError('Not supported yet')
if world.timer in ['ohko', 'timed-ohko']:
world.can_take_damage[player] = False

20
Main.py
View File

@@ -80,7 +80,7 @@ def random_ganon_item(sword_mode):
def main(args, seed=None, fish=None):
check_python_version()
if args.print_template_yaml:
return export_yaml(args, fish)
@@ -124,14 +124,14 @@ def main(args, seed=None, fish=None):
from OverworldShuffle import __version__ as ORVersion
logger.info(
world.fish.translate("cli","cli","app.title") + "\n",
ORVersion,
"%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed,
Settings.make_code(world, 1) if world.players == 1 else ''
world.fish.translate("cli","cli","app.title") + "\n",
ORVersion,
"%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed,
Settings.make_code(world, 1) if world.players == 1 else ''
)
for k,v in {"DR":__version__,"OR":ORVersion}.items():
logger.info((k + ' Version:').ljust(16) + '%s' % v)
logger.info((k + ' Version:').ljust(16) + '%s' % v)
parsed_names = parse_player_names(args.names, world.players, args.teams)
world.teams = len(parsed_names)
@@ -478,6 +478,8 @@ def init_world(args, fish):
world.crystals_ganon_orig = args.crystals_ganon.copy()
world.crystals_gt_orig = args.crystals_gt.copy()
world.ganon_item_orig = args.ganon_item.copy()
world.bosses_ganon = {player: int(args.bosses_ganon[player]) for player in range(1, world.players + 1)}
world.bosshunt_include_agas = args.bosshunt_include_agas.copy()
world.owTerrain = args.ow_terrain.copy()
world.owKeepSimilar = args.ow_keepsimilar.copy()
world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
@@ -525,7 +527,7 @@ def init_world(args, fish):
world.money_balance = args.money_balance.copy()
# custom settings - these haven't been promoted to full settings yet
in_progress_settings = ['force_enemy', 'free_lamp_cone']
in_progress_settings = ['force_enemy']
for player in range(1, world.players + 1):
for setting in in_progress_settings:
if world.customizer and world.customizer.has_setting(player, setting):
@@ -795,6 +797,8 @@ def copy_world(world):
ret.free_lamp_cone = world.free_lamp_cone.copy()
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
ret.bosses_ganon = world.bosses_ganon.copy()
ret.bosshunt_include_agas = world.bosshunt_include_agas.copy()
ret.ganon_item = world.ganon_item.copy()
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy()
@@ -1024,6 +1028,8 @@ def copy_world_premature(world, player, create_flute_exits=True):
ret.free_lamp_cone = world.free_lamp_cone.copy()
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
ret.bosses_ganon = world.bosses_ganon.copy()
ret.bosshunt_include_agas = world.bosshunt_include_agas.copy()
ret.ganon_item = world.ganon_item.copy()
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy()

47
Rom.py
View File

@@ -44,7 +44,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '015d196d606fe1870a37df38ec335835'
RANDOMIZERBASEHASH = '0a1b516a3ecde44603b6b2dca2b93616'
class JsonRom(object):
@@ -1211,7 +1211,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
rom.write_byte(0x180194, 1) # Must turn in triforce pieces (instant win not enabled)
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
@@ -1285,6 +1285,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# 08: Goal items collected (ie. Triforce Pieces)
# 09: Max collection rate
# 0A: Custom goal
# 0B: Reserved for Bingo
# 0C: All bosses (prize bosses + aga1 + aga2)
def get_goal_bytes(type):
goal_bytes = []
@@ -1339,6 +1341,11 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
ganon_goal += [0x02, world.crystals_needed_for_ganon[player]]
elif world.goal[player] in ['ganonhunt']:
ganon_goal += [0x88] # triforce pieces
elif world.goal[player] in ['bosshunt']:
if world.bosshunt_include_agas[player]:
ganon_goal += [0x0C, world.bosses_ganon[player]] # total bosses
else:
ganon_goal += [0x05, world.bosses_ganon[player]] # prize bosses
elif world.goal[player] in ['completionist']:
ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate
else:
@@ -1482,7 +1489,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
rom.write_byte(loot_icons + crystal_id, crystal_category)
pendant_ids = [0x37, 0x38, 0x39]
if world.goal[player] in ['pedestal', 'dungeons']:
if world.goal[player] in ['pedestal', 'dungeons', 'trinity']:
pendant_category = 0x0C
else:
pendant_category = 0x06
@@ -1635,12 +1642,28 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# b - Big Key
# a - Small Key
#
dungeon_items_menu = 0x00
if world.doorShuffle[player] not in ['vanilla', 'basic']:
dungeon_items_menu |= 0x0F
if world.keyshuffle[player] not in ['none', 'universal']:
dungeon_items_menu |= 0x01
if world.bigkeyshuffle[player] != 'none':
dungeon_items_menu |= 0x02
enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby']
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00)
| (0x02 if world.bigkeyshuffle[player] != 'none' else 0x00)
| (0x04 if world.mapshuffle[player] != 'none' or enable_menu_map_check else 0x00)
| (0x08 if world.compassshuffle[player] != 'none' else 0x00) # free roaming items in menu
| (0x10 if world.logic[player] == 'nologic' else 0))) # boss icon
if world.mapshuffle[player] != 'none' or enable_menu_map_check:
dungeon_items_menu |= 0x04
if world.compassshuffle[player] != 'none':
dungeon_items_menu |= 0x08
if world.logic[player] == 'nologic' or world.goal[player] == 'bosshunt':
dungeon_items_menu |= 0x10
rom.write_byte(0x180045, dungeon_items_menu)
def get_reveal_bytes(itemName):
if world.prizeshuffle[player] != 'wild':
@@ -2746,12 +2769,18 @@ def write_strings(rom, world, player, team):
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! You can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
elif world.goal[player] == 'ganonhunt':
tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon'
elif world.goal[player] == 'bosshunt':
bosshunt_count = '%d guardian%s of %sdungeons' % \
(world.bosses_ganon[player],
'' if world.bosses_ganon[player] == 1 else 's',
'' if world.bosshunt_include_agas[player] else 'prize ')
tt['sign_ganon'] = 'To beat Ganon you must defeat %s.' % bosshunt_count
elif world.goal[player] == 'completionist':
tt['sign_ganon'] = 'Ganon only respects those who have done everything'
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
def get_custom_goal_text(type):
goal_text = world.custom_goals[player][type]['goaltext']
placeholder_count = goal_text.count('%d')

View File

@@ -80,6 +80,15 @@ def set_rules(world, player):
add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player))
elif world.goal[player] == 'ganonhunt':
add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
elif world.goal[player] == 'bosshunt':
if world.bosshunt_include_agas[player]:
add_rule(world.get_location('Ganon', player), lambda state:
state.item_count('Beat Agahnim 1', player) +
state.item_count('Beat Agahnim 2', player) +
state.item_count('Beat Boss', player) >= world.bosses_ganon[player])
else:
add_rule(world.get_location('Ganon', player), lambda state:
state.item_count('Beat Boss', player) >= world.bosses_ganon[player])
elif world.goal[player] == 'completionist':
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))

Binary file not shown.

View File

@@ -72,6 +72,7 @@
"trinity",
"crystals",
"ganonhunt",
"bosshunt",
"completionist",
"sanctuary"
]
@@ -299,6 +300,27 @@
"random"
]
},
"bosses_ganon": {
"choices": [
"12",
"11",
"10",
"9",
"8",
"7",
"6",
"5",
"4",
"3",
"2",
"1",
"0"
]
},
"bosshunt_include_agas": {
"action": "store_true",
"type": "bool"
},
"crystals_gt": {
"choices": [
"7",