Add ganon triforce hunt (#117)

* Add ganon triforce hunt

* Add self to license

* Correction of help message for Local Ganon Triforce Hunt.

* if 'triforcehunt in world.goal[player]:
This commit is contained in:
CaitSith2 2020-06-26 07:18:53 -07:00 committed by GitHub
parent 46038830c3
commit 545bb8023c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 44 additions and 20 deletions

View File

@ -518,6 +518,9 @@ class CollectionState(object):
def item_count(self, item, player: int) -> int: def item_count(self, item, player: int) -> int:
return self.prog_items[item, player] return self.prog_items[item, player]
def has_triforce_pieces(self, count: int, player: int) -> bool:
return self.item_count('Triforce Piece', player) + self.item_count('Power Star', player) >= count
def has_crystals(self, count: int, player: int) -> bool: def has_crystals(self, count: int, player: int) -> bool:
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'] crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count

View File

@ -67,7 +67,7 @@ def parse_arguments(argv, no_defaults=False):
Vanilla: Swords are in vanilla locations. Vanilla: Swords are in vanilla locations.
''') ''')
parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?', parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?',
choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'crystals'], choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals'],
help='''\ help='''\
Select completion goal. (default: %(default)s) Select completion goal. (default: %(default)s)
Ganon: Collect all crystals, beat Agahnim 2 then Ganon: Collect all crystals, beat Agahnim 2 then
@ -79,7 +79,11 @@ def parse_arguments(argv, no_defaults=False):
Triforce Hunt: Places 30 Triforce Pieces in the world, collect Triforce Hunt: Places 30 Triforce Pieces in the world, collect
20 of them to beat the game. 20 of them to beat the game.
Local Triforce Hunt: Places 30 Triforce Pieces in your world, collect Local Triforce Hunt: Places 30 Triforce Pieces in your world, collect
20 of them to beat the game. 20 of them to beat the game.
Ganon Triforce Hunt: Places 30 Triforce Pieces in the world, collect
20 of them, then defeat Ganon.
Local Ganon Triforce Hunt: Places 30 Triforce Pieces in your world,
collect 20 of them, then defeat Ganon.
''') ''')
parser.add_argument('--triforce_pieces_available', default=defval(30), parser.add_argument('--triforce_pieces_available', default=defval(30),
type=lambda value: min(max(int(value), 1), 90), type=lambda value: min(max(int(value), 1), 90),

View File

@ -244,8 +244,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
continue continue
gftower_trash_count = ( gftower_trash_count = (
random.randint(15, 50) if world.goal[player] in {'triforcehunt', 'localtriforcehunt'} else random.randint(0, random.randint(15, 50) if 'triforcehunt' in world.goal[player]
15)) else random.randint(0, 15))
gtower_locations = [location for location in fill_locations if gtower_locations = [location for location in fill_locations if
'Ganons Tower' in location.name and location.player == player] 'Ganons Tower' in location.name and location.player == player]

2
Gui.py
View File

@ -240,7 +240,7 @@ def guiMain(args=None):
goalVar = StringVar() goalVar = StringVar()
goalVar.set('ganon') goalVar.set('ganon')
goalOptionMenu = OptionMenu(goalFrame, goalVar, 'ganon', 'pedestal', 'dungeons', 'triforcehunt', goalOptionMenu = OptionMenu(goalFrame, goalVar, 'ganon', 'pedestal', 'dungeons', 'triforcehunt',
'localtriforcehunt', 'crystals') 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals')
goalOptionMenu.pack(side=RIGHT) goalOptionMenu.pack(side=RIGHT)
goalLabel = Label(goalFrame, text='Game goal') goalLabel = Label(goalFrame, text='Game goal')
goalLabel.pack(side=LEFT) goalLabel.pack(side=LEFT)

View File

@ -124,7 +124,7 @@ difficulties = {
def generate_itempool(world, player): def generate_itempool(world, player):
if world.difficulty[player] not in ['normal', 'hard', 'expert']: if world.difficulty[player] not in ['normal', 'hard', 'expert']:
raise NotImplementedError(f"Diffulty {world.difficulty[player]}") raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'crystals'}: if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals'}:
raise NotImplementedError(f"Goal {world.goal[player]}") raise NotImplementedError(f"Goal {world.goal[player]}")
if world.mode[player] not in {'open', 'standard', 'inverted'}: if world.mode[player] not in {'open', 'standard', 'inverted'}:
raise NotImplementedError(f"Mode {world.mode[player]}") raise NotImplementedError(f"Mode {world.mode[player]}")
@ -142,9 +142,8 @@ def generate_itempool(world, player):
region = world.get_region('Light World', player) region = world.get_region('Light World', player)
loc = Location(player, "Murahdahla", parent=region) loc = Location(player, "Murahdahla", parent=region)
loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player)
player) >= \
state.world.treasure_hunt_count[player]
region.locations.append(loc) region.locations.append(loc)
world.dynamic_locations.append(loc) world.dynamic_locations.append(loc)
@ -261,7 +260,7 @@ def generate_itempool(world, player):
random.shuffle(nonprogressionitems) random.shuffle(nonprogressionitems)
triforce_pieces = world.triforce_pieces_available[player] triforce_pieces = world.triforce_pieces_available[player]
if world.goal[player] in {'triforcehunt', 'localtriforcehunt'} and triforce_pieces > 30: if 'triforcehunt' in world.goal[player] and triforce_pieces > 30:
progressionitems += [ItemFactory("Triforce Piece", player)] * (triforce_pieces - 30) progressionitems += [ItemFactory("Triforce Piece", player)] * (triforce_pieces - 30)
nonprogressionitems = nonprogressionitems[(triforce_pieces-30):] nonprogressionitems = nonprogressionitems[(triforce_pieces-30):]
@ -510,7 +509,7 @@ def get_pool_core(world, player: int):
pool.extend(diff.timedohko) pool.extend(diff.timedohko)
extraitems -= len(diff.timedohko) extraitems -= len(diff.timedohko)
clock_mode = 'countdown-ohko' clock_mode = 'countdown-ohko'
if goal in {'triforcehunt', 'localtriforcehunt'}: if 'triforcehunt' in goal:
while len(diff.triforcehunt) > world.triforce_pieces_available[player]: while len(diff.triforcehunt) > world.triforce_pieces_available[player]:
diff.triforcehunt.pop() diff.triforcehunt.pop()
pool.extend(diff.triforcehunt) pool.extend(diff.triforcehunt)
@ -643,8 +642,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99. treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99.
treasure_hunt_icon = 'Triforce Piece' treasure_hunt_icon = 'Triforce Piece'
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling. # Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
if (customitemarray[66] < treasure_hunt_count) and (goal in {'triforcehunt', 'localtriforcehunt'}) and ( if (customitemarray[66] < treasure_hunt_count) and ('triforcehunt' in goal) and (customitemarray[68] == 0):
customitemarray[68] == 0):
extrapieces = treasure_hunt_count - customitemarray[66] extrapieces = treasure_hunt_count - customitemarray[66]
pool.extend(['Triforce Piece'] * extrapieces) pool.extend(['Triforce Piece'] * extrapieces)
itemtotal = itemtotal + extrapieces itemtotal = itemtotal + extrapieces

View File

@ -2,6 +2,7 @@ MIT License
Copyright (c) 2017 LLCoolDave Copyright (c) 2017 LLCoolDave
Copyright (c) 2020 Berserker66 Copyright (c) 2020 Berserker66
Copyright (c) 2020 CaitSith2
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -288,9 +288,11 @@ def roll_settings(weights):
'pedestal': 'pedestal', 'pedestal': 'pedestal',
'triforce_hunt': 'triforcehunt', 'triforce_hunt': 'triforcehunt',
'triforce-hunt': 'triforcehunt', # deprecated, moving all goals to `_` 'triforce-hunt': 'triforcehunt', # deprecated, moving all goals to `_`
'local_triforce_hunt': 'localtriforcehunt' 'local_triforce_hunt': 'localtriforcehunt',
'ganon_triforce_hunt': 'ganontriforcehunt',
'local_ganon_triforce_hunt': 'localganontriforcehunt'
}[goal] }[goal]
ret.openpyramid = goal == 'fast_ganon' ret.openpyramid = goal in ['fast_ganon', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt']
ret.crystals_gt = get_choice('tower_open', weights) ret.crystals_gt = get_choice('tower_open', weights)

8
Rom.py
View File

@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'e7eee92d3a89283f591fdf7ac66a4ab7' RANDOMIZERBASEHASH = 'a567da86e8bd499256da4bba2209a3fd'
class LocalRom(object): class LocalRom(object):
@ -1084,6 +1084,8 @@ def patch_rom(world, rom, player, team, enemized):
if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt']: if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt']:
rom.write_byte(0x18003E, 0x01) # make ganon invincible rom.write_byte(0x18003E, 0x01) # make ganon invincible
elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
rom.write_byte(0x18003E, 0x05) # make ganon invincible until 20 triforce pieces are collected
elif world.goal[player] in ['dungeons']: elif world.goal[player] in ['dungeons']:
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
elif world.goal[player] in ['crystals']: elif world.goal[player] in ['crystals']:
@ -1778,6 +1780,10 @@ def write_strings(rom, world, player, team):
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)] 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_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!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
if world.goal[player] == 'ganontriforcehunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce pieces with your friends to defeat Ganon.' % world.treasure_hunt_count[player]
elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
tt['sign_ganon'] = 'You need to find %d Triforce pieces to defeat Ganon.' % world.treasure_hunt_count[player]
tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)] tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)]

View File

@ -143,7 +143,7 @@ def item_name(state, location, player):
def locality_rules(world, player): def locality_rules(world, player):
if world.goal[player] == "localtriforcehunt": if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
world.local_items[player].add('Triforce Piece') world.local_items[player].add('Triforce Piece')
if world.local_items[player]: if world.local_items[player]:
for location in world.get_locations(): for location in world.get_locations():
@ -418,8 +418,13 @@ def global_rules(world, player):
'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']: 'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']:
forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player) forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player)
set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)
and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_triforce_pieces(world.treasure_hunt_count[player], player)
and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times
else:
set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)
and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop
@ -817,7 +822,10 @@ def swordless_rules(world, player):
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain
set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace
set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)) if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_triforce_pieces(world.treasure_hunt_count[player], player))
else:
set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player))
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':

Binary file not shown.

View File

@ -80,6 +80,8 @@ goals:
pedestal: 0 # Pull the Triforce from the Master Sword pedestal pedestal: 0 # Pull the Triforce from the Master Sword pedestal
triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
triforce_pieces_available: # set to how many triforces pieces are available to collect in the world. 30 is the default. Max is 112, Min is 1. triforce_pieces_available: # set to how many triforces pieces are available to collect in the world. 30 is the default. Max is 112, Min is 1.
# format "pieces: chance" # format "pieces: chance"
25: 0 25: 0