diff --git a/BaseClasses.py b/BaseClasses.py index 0490eab8..d021d18f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -76,9 +76,9 @@ class World(object): self.boss_shuffle = boss_shuffle self.escape_assist = {player: [] for player in range(1, players + 1)} self.hints = hints - self.crystals_needed_for_ganon = 7 - self.crystals_needed_for_gt = 7 - self.open_pyramid = False + self.crystals_needed_for_ganon = {} + self.crystals_needed_for_gt = {} + self.open_pyramid = {player: False for player in range(1, players + 1)} self.dynamic_regions = [] self.dynamic_locations = [] self.spoiler = Spoiler(self) @@ -1049,6 +1049,9 @@ class Spoiler(object): 'shuffle': self.world.shuffle, 'item_pool': self.world.difficulty, 'item_functionality': self.world.difficulty_adjustments, + 'gt_crystals': self.world.crystals_needed_for_gt, + 'ganon_crystals': self.world.crystals_needed_for_ganon, + 'open_pyramid': self.world.open_pyramid, 'accessibility': self.world.accessibility, 'hints': self.world.hints, 'mapshuffle': self.world.mapshuffle, @@ -1085,6 +1088,9 @@ class Spoiler(object): outfile.write('Difficulty: %s\n' % self.metadata['item_pool']) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality']) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle']) + outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals']) + outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals']) + outfile.write('Pyramid hole pre-opened: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['open_pyramid'].items()}) outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) outfile.write('Accessibility: %s\n' % self.metadata['accessibility']) outfile.write('L\\R Quickswap enabled: %s\n' % ('Yes' if self.world.quickswap else 'No')) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index c586c4d9..e97ddb59 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -278,7 +278,7 @@ def parse_arguments(argv, no_defaults=False): for player in range(1, multiargs.multi + 1): playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True) - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle']: + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) diff --git a/Main.py b/Main.py index d8961050..f54ca5f3 100644 --- a/Main.py +++ b/Main.py @@ -47,9 +47,9 @@ def main(args, seed=None): elif any([world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle]): mcsb_name = '-%s%s%s%sshuffle' % ('M' if world.mapshuffle else '', 'C' if world.compassshuffle else '', 'S' if world.keyshuffle else '', 'B' if world.bigkeyshuffle else '') - world.crystals_needed_for_ganon = random.randint(0, 7) if args.crystals_ganon == 'random' else int(args.crystals_ganon) - world.crystals_needed_for_gt = random.randint(0, 7) if args.crystals_gt == 'random' else int(args.crystals_gt) - world.open_pyramid = args.openpyramid + world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} + world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} + world.open_pyramid = args.openpyramid.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -239,8 +239,9 @@ def copy_world(world): ret.compassshuffle = world.compassshuffle ret.keyshuffle = world.keyshuffle ret.bigkeyshuffle = world.bigkeyshuffle - ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon - ret.crystals_needed_for_gt = world.crystals_needed_for_gt + ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() + ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() + ret.open_pyramid = world.open_pyramid.copy() for player in range(1, world.players + 1): if world.mode[player] != 'inverted': diff --git a/Rom.py b/Rom.py index aa6d1621..e8918aa6 100644 --- a/Rom.py +++ b/Rom.py @@ -902,8 +902,8 @@ def patch_rom(world, player, rom, enemized): rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest - rom.write_byte(0x18008B, 0x01 if world.open_pyramid else 0x00) # pre-open Pyramid Hole - rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt == 0 else 0x00) # GT pre-opened if crystal requirement is 0 + rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player] else 0x00) # pre-open Pyramid Hole + rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0 rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness @@ -952,8 +952,8 @@ def patch_rom(world, player, rom, enemized): else: rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected - rom.write_byte(0x18005E, world.crystals_needed_for_gt) - rom.write_byte(0x18005F, world.crystals_needed_for_ganon) + rom.write_byte(0x18005E, world.crystals_needed_for_gt[player]) + rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" else 0x00) # block HC upstairs doors in rain state in standard mode rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle else 0x00) @@ -1549,8 +1549,8 @@ def write_strings(rom, world, player): greenpendant = world.find_items('Green Pendant', player)[0] tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text - tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt - tt['sign_ganon'] = ('You need %d crystal to beat Ganon.' if world.crystals_needed_for_ganon == 1 else 'You need %d crystals to beat Ganon.') % world.crystals_needed_for_ganon + tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player] + tt['sign_ganon'] = ('You need %d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else 'You need %d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player] if world.goal[player] in ['dungeons']: tt['sign_ganon'] = 'You need to complete all the dungeons.' diff --git a/Rules.py b/Rules.py index 576494fb..99810d96 100644 --- a/Rules.py +++ b/Rules.py @@ -339,7 +339,7 @@ def global_rules(world, player): 'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']: 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) + 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 @@ -455,7 +455,7 @@ def default_rules(world, player): set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) - set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid) + set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) set_rule(world.get_entrance('Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. if world.swords[player] == 'swordless': @@ -463,7 +463,7 @@ def default_rules(world, player): set_trock_key_rules(world, player) - set_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt, player)) + set_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) def inverted_rules(world, player): # s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached @@ -610,7 +610,7 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.can_flute(player)) - set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid) + set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. if world.swords[player] == 'swordless': @@ -618,7 +618,7 @@ def inverted_rules(world, player): set_trock_key_rules(world, player) - set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt, player)) + set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) def no_glitches_rules(world, player): if world.mode[player] != 'inverted': @@ -706,7 +706,7 @@ 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_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_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)) + 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 if world.mode[player] != 'inverted':