diff --git a/BaseClasses.py b/BaseClasses.py index 3b67ffcc..37af2399 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -133,6 +133,8 @@ class World(object): set_player_attr('crystals_needed_for_gt', 7) set_player_attr('crystals_ganon_orig', {}) set_player_attr('crystals_gt_orig', {}) + set_player_attr('ganon_item', 'default') + set_player_attr('ganon_item_orig', 'default') set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) @@ -338,6 +340,17 @@ class World(object): ret.prog_items['Master Sword', item.player] += 1 elif self.difficulty_requirements[item.player].progressive_sword_limit >= 1: ret.prog_items['Fighter Sword', item.player] += 1 + elif 'Bombs' in item.name: + if ret.has('L5 Bombs', item.player): + pass + elif ret.has('L4 Bombs', item.player): + ret.prog_items['L5 Bombs', item.player] += 1 + elif ret.has('L3 Bombs', item.player): + ret.prog_items['L4 Bombs', item.player] += 1 + elif ret.has('L2 Bombs', item.player): + ret.prog_items['L3 Bombs', item.player] += 1 + else: + ret.prog_items['L2 Bombs', item.player] += 1 elif 'Glove' in item.name: if ret.has('Titans Mitts', item.player): pass @@ -720,12 +733,12 @@ class CollectionState(object): return basemagic >= smallmagic def can_kill_most_things(self, player, enemies=5): - return (self.has_blunt_weapon(player) + return (self.bomb_mode_check(player, 1) and + (self.has_blunt_weapon(player) or self.has_bomb_level(player, 1) or self.has('Cane of Somaria', player) or (self.has('Cane of Byrna', player) and (enemies < 6 or self.can_extend_magic(player))) or self.can_shoot_arrows(player) - or self.has('Fire Rod', player) - ) + or self.has('Fire Rod', player))) # In the future, this can be used to check if the player starts without bombs def can_use_bombs(self, player): @@ -743,7 +756,7 @@ class CollectionState(object): or self.has('Ice Rod', player) or self.has('Cane of Somaria', player) or self.has('Cane of Byrna', player)) - + def can_hit_crystal_through_barrier(self, player): return (self.can_use_bombs(player) or self.can_shoot_arrows(player) @@ -764,7 +777,7 @@ class CollectionState(object): return ( self.has_bottle(player) and self.has('Bug Catching Net', player) and - (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and + (self.has_Boots(player) or (self.can_use_medallions(player) and self.has('Quake', player))) and cave.can_reach(self) and self.is_not_bunny(cave, player) ) @@ -775,6 +788,65 @@ class CollectionState(object): def has_beam_sword(self, player): return self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) + def has_bomb_level(self, player, level): + if self.world.swords[player] != 'bombs': + return False + if level == 5: + return self.has('L5 Bombs', player) + elif level == 4: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) + elif level == 3: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) + elif level == 2: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) + return True + + def bomb_mode_check(self, player, level): + return self.world.swords[player] != 'bombs' or self.has_bomb_level(player, level) + + def can_hit_stunned_ganon(self, player): + ganon_item = self.world.ganon_item[player] + if ganon_item == 'default': + if self.world.swords[player] == 'bombs': + ganon_item = 'bomb' + else: + ganon_item = 'arrow' + if ganon_item == 'arrow': + return self.has('Silver Arrows', player) and self.can_shoot_arrows(player) + elif ganon_item == 'boomerang': + return self.has('Blue Boomerang', player) or self.has('Red Boomerang', player) + elif ganon_item == 'hookshot': + return self.has('Hookshot', player) + elif ganon_item == 'bomb': + return self.can_use_bombs(player) + elif ganon_item == 'powder': + return self.has('Magic Powder', player) + elif ganon_item == 'fire_rod': + return self.has('Fire Rod', player) + elif ganon_item == 'ice_rod': + return self.has('Fire Rod', player) + elif ganon_item == 'bombos': + return self.has('Bombos', player) and self.can_use_medallions(player) + elif ganon_item == 'ether': + return self.has('Ether', player) and self.can_use_medallions(player) + elif ganon_item == 'quake': + return self.has('Quake', player) and self.can_use_medallions(player) + elif ganon_item == 'hammer': + return self.has('Hammer', player) + elif ganon_item == 'bee': + return (self.has_bottle(player) and + ((self.has('Bug Catching Net', player) and self.can_get_good_bee(player)) + or self.can_buy_unlimited('Bee', player))) + elif ganon_item == 'somaria': + return self.has('Cane of Somaria', player) + elif ganon_item == 'byrna': + return self.has('Cane of Byrna', player) + else: + return False + + def can_use_medallions(self, player): + return self.has_sword(player) or self.world.swords[player] == 'bombs' + def has_blunt_weapon(self, player): return self.has_sword(player) or self.has('Hammer', player) @@ -795,7 +867,7 @@ class CollectionState(object): return self.has('Ocarina', player) and lw.can_reach(self) and self.is_not_bunny(lw, player) def can_melt_things(self, player): - return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) + return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.can_use_medallions(player)) def can_avoid_lasers(self, player): return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player) @@ -867,6 +939,21 @@ class CollectionState(object): elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1: self.prog_items['Fighter Sword', item.player] += 1 changed = True + elif 'Bombs' in item.name: + if self.has('L5 Bombs', item.player): + pass + elif self.has('L4 Bombs', item.player): + self.prog_items['L5 Bombs', item.player] += 1 + changed = True + elif self.has('L3 Bombs', item.player): + self.prog_items['L4 Bombs', item.player] += 1 + changed = True + elif self.has('L2 Bombs', item.player): + self.prog_items['L3 Bombs', item.player] += 1 + changed = True + else: + self.prog_items['L2 Bombs', item.player] += 1 + changed = True elif 'Glove' in item.name: if self.has('Titans Mitts', item.player): pass @@ -939,6 +1026,17 @@ class CollectionState(object): to_remove = 'Fighter Sword' else: to_remove = None + elif 'Bomb' in to_remove: + if self.has('L5 Bombs', item.player): + to_remove = 'L5 Bombs' + elif self.has('L4 Bombs', item.player): + to_remove = 'L4 Bombs' + elif self.has('L3 Bombs', item.player): + to_remove = 'L3 Bombs' + elif self.has('L2 Bombs', item.player): + to_remove = 'L2 Bombs' + else: + to_remove = None elif 'Glove' in item.name: if self.has('Titans Mitts', item.player): to_remove = 'Titans Mitts' @@ -2167,6 +2265,7 @@ class Spoiler(object): 'item_functionality': self.world.difficulty_adjustments, 'gt_crystals': self.world.crystals_needed_for_gt, 'ganon_crystals': self.world.crystals_needed_for_ganon, + 'ganon_vulnerability_item': self.world.ganon_item, 'open_pyramid': self.world.open_pyramid, 'accessibility': self.world.accessibility, 'hints': self.world.hints, @@ -2184,8 +2283,8 @@ class Spoiler(object): 'experimental': self.world.experimental, 'keydropshuffle': self.world.keydropshuffle, 'shopsanity': self.world.shopsanity, - 'triforcegoal': self.world.treasure_hunt_count, - 'triforcepool': self.world.treasure_hunt_total, + 'triforcegoal': self.world.treasure_hunt_count, + 'triforcepool': self.world.treasure_hunt_total, 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} } @@ -2250,6 +2349,8 @@ class Spoiler(object): outfile.write('Crystals required for GT:'.ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player]) + addition)) addition = ' (Random)' if self.world.crystals_ganon_orig[player] == 'random' else '' outfile.write('Crystals required for Ganon:'.ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player]) + addition)) + addition = ' (Random)' if self.world.ganon_item_orig[player] == 'random' else '' + outfile.write('Ganon Vulnerability Item:'.ljust(line_width) + '%s\n' % (str(self.metadata['ganon_vulnerability_item'][player]) + addition)) outfile.write('Pyramid hole pre-opened:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No')) outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player]) outfile.write('Map shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No')) @@ -2410,7 +2511,7 @@ er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, # byte 1: LLLW WSSR (logic, mode, sword, retro) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} world_mode = {"open": 0, "standard": 1, "inverted": 2} -sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} +sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3, "bombs": 2} # fix this, kara # byte 2: GGGD DFFH (goal, diff, item_func, hints) goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4} diff --git a/Bosses.py b/Bosses.py index ebf8b4dd..0fb60798 100644 --- a/Bosses.py +++ b/Bosses.py @@ -1,5 +1,5 @@ import logging -import random +import RaceRandom as random from BaseClasses import Boss from Fill import FillError @@ -16,38 +16,47 @@ def BossFactory(boss, player): def ArmosKnightsDefeatRule(state, player): # Magic amounts are probably a bit overkill - return ( - state.has_blunt_weapon(player) or + return (state.bomb_mode_check(player, 1) and + (state.has_blunt_weapon(player) or state.can_shoot_arrows(player) or (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 10)) or (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or (state.has('Ice Rod', player) and state.can_extend_magic(player, 32)) or (state.has('Fire Rod', player) and state.can_extend_magic(player, 32)) or state.has('Blue Boomerang', player) or - state.has('Red Boomerang', player)) + state.has('Red Boomerang', player) or + state.has_bomb_level(player, 1))) def LanmolasDefeatRule(state, player): - return ( - state.has_blunt_weapon(player) or + return (state.bomb_mode_check(player, 1) and + (state.has_blunt_weapon(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) or - state.can_shoot_arrows(player)) + state.can_shoot_arrows(player) or + state.has_bomb_level(player, 1))) def MoldormDefeatRule(state, player): - return state.has_blunt_weapon(player) + return (state.bomb_mode_check(player, 1) and + (state.has_blunt_weapon(player) or state.has_bomb_level(player, 1))) def HelmasaurKingDefeatRule(state, player): - return (state.has('Hammer', player) or state.can_use_bombs(player)) and (state.has_sword(player) or state.can_shoot_arrows(player)) + return (state.bomb_mode_check(player, 2) and + (state.has('Hammer', player) or state.can_use_bombs(player)) and + (state.has_sword(player) or state.can_shoot_arrows(player) or state.has_bomb_level(player, 2))) def ArrghusDefeatRule(state, player): if not state.has('Hookshot', player): return False + + if not state.bomb_mode_check(player, 2): + return False + # TODO: ideally we would have a check for bow and silvers, which combined with the # hookshot is enough. This is not coded yet because the silvers that only work in pyramid feature # makes this complicated - if state.has_blunt_weapon(player): + if state.has_blunt_weapon(player) or state.has_bomb_level(player, 2): return True return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot @@ -55,31 +64,33 @@ def ArrghusDefeatRule(state, player): def MothulaDefeatRule(state, player): - return ( - state.has_blunt_weapon(player) or + return (state.bomb_mode_check(player, 1) and + (state.has_blunt_weapon(player) or (state.has('Fire Rod', player) and state.can_extend_magic(player, 10)) or # TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply # to non-vanilla locations, so are harder to test, so sticking with what VT has for now: (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or - state.can_get_good_bee(player) - ) + state.can_get_good_bee(player) or + state.has_bomb_level(player, 1))) def BlindDefeatRule(state, player): - return state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) + return (state.bomb_mode_check(player, 1) and + (state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or + state.has('Cane of Byrna', player) or state.has_bomb_level(player, 1))) def KholdstareDefeatRule(state, player): - return ( + return (state.bomb_mode_check(player, 2) and ( state.has('Fire Rod', player) or ( state.has('Bombos', player) and # FIXME: the following only actually works for the vanilla location for swordless - (state.has_sword(player) or state.world.swords[player] == 'swordless') + (state.can_use_medallions(player) or state.world.swords[player] == 'swordless') ) ) and ( - state.has_blunt_weapon(player) or + state.has_bomb_level(player, 2) or state.has_blunt_weapon(player) or (state.has('Fire Rod', player) and state.can_extend_magic(player, 20)) or # FIXME: this actually only works for the vanilla location for swordless ( @@ -88,20 +99,26 @@ def KholdstareDefeatRule(state, player): state.world.swords[player] == 'swordless' and state.can_extend_magic(player, 16) ) - ) - ) + )) def VitreousDefeatRule(state, player): - return state.can_shoot_arrows(player) or state.has_blunt_weapon(player) + return (state.bomb_mode_check(player, 2) and + (state.can_shoot_arrows(player) or state.has_blunt_weapon(player) or + state.has_bomb_level(player, 2))) def TrinexxDefeatRule(state, player): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): return False + if not state.bomb_mode_check(player, 2): + return False return (state.has('Hammer', player) or state.has('Golden Sword', player) or state.has('Tempered Sword', player) or - (state.has('Master Sword', player) and state.can_extend_magic(player, 16)) or - (state.has_sword(player) and state.can_extend_magic(player, 32))) + state.has_bomb_level(player, 4) or + ((state.has('Master Sword', player) or state.has_bomb_level(player, 3)) + and state.can_extend_magic(player, 16)) or + ((state.has_sword(player) or state.has_bomb_level(player, 2)) + and state.can_extend_magic(player, 32))) def AgahnimDefeatRule(state, player): return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player) @@ -126,20 +143,20 @@ def can_place_boss(world, player, boss, dungeon_name, level=None): return False if dungeon_name == 'Ganons Tower' and level == 'top': - if boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: + if boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: return False if dungeon_name == 'Ganons Tower' and level == 'middle': if boss in ["Blind"]: return False - if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: + if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: return False if dungeon_name == 'Skull Woods' and boss in ["Trinexx"]: return False - if boss in ["Agahnim", "Agahnim2", "Ganon"]: + if boss in ["Agahnim", "Agahnim2", "Ganon"]: return False return True diff --git a/CLI.py b/CLI.py index 3e13f790..164c1c64 100644 --- a/CLI.py +++ b/CLI.py @@ -93,9 +93,9 @@ def parse_cli(argv, no_defaults=False): for player in range(1, multiargs.multi + 1): playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', - 'ow_shuffle', 'ow_swap', 'ow_keepsimilar', 'ow_fluteshuffle', - 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', + 'ow_swap', 'ow_keepsimilar', 'ow_fluteshuffle', 'shuffle', 'door_shuffle', + 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', @@ -131,6 +131,7 @@ def parse_settings(): "goal": "ganon", "crystals_gt": "7", "crystals_ganon": "7", + "ganon_item": "default", "swords": "random", "difficulty": "normal", "item_functionality": "normal", @@ -169,7 +170,7 @@ def parse_settings(): "dungeon_counters": "default", "mixed_travel": "prevent", "standardize_palettes": "standardize", - + "triforce_pool": 30, "triforce_goal": 20, "triforce_pool_min": 0, diff --git a/DoorShuffle.py b/DoorShuffle.py index 3478d027..0acd618b 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random from collections import defaultdict, deque import logging import operator as op diff --git a/DungeonGenerator.py b/DungeonGenerator.py index c8599748..1c49cba0 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random import collections import itertools from collections import defaultdict, deque diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index b1c02c81..d24e81d6 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -3,7 +3,7 @@ import argparse import copy import os import logging -import random +import RaceRandom as random import textwrap import shlex import sys diff --git a/Dungeons.py b/Dungeons.py index 0acf7852..30ec3955 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random from BaseClasses import Dungeon from Bosses import BossFactory diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ef0d7711..89b57e9a 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. from collections import defaultdict diff --git a/Fill.py b/Fill.py index 9a9ebd0f..003c5284 100644 --- a/Fill.py +++ b/Fill.py @@ -1,4 +1,4 @@ -import random +import RaceRandom as random import logging from BaseClasses import CollectionState diff --git a/ItemList.py b/ItemList.py index e37162d6..d5413b75 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1,7 +1,7 @@ from collections import namedtuple import logging import math -import random +import RaceRandom as random from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState from Bosses import place_bosses @@ -35,7 +35,7 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', - 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', + 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'bombs_only', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', 'retro', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', @@ -55,6 +55,7 @@ difficulties = { progressivearmor = ['Progressive Armor'] * 2, basicarmor = ['Blue Mail', 'Red Mail'], swordless = ['Rupees (20)'] * 4, + bombs_only = ['Progressive Bombs'] * 4, progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'], basicbow = ['Bow', 'Silver Arrows'], @@ -80,6 +81,7 @@ difficulties = { progressivearmor = ['Progressive Armor'] * 2, basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, + bombs_only = ['Progressive Bombs'] * 4, progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword'], basicbow = ['Bow'] * 2, @@ -105,6 +107,7 @@ difficulties = { progressivearmor = ['Progressive Armor'] * 2, # neither will count basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, + bombs_only = ['Progressive Bombs'] * 4, progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Fighter Sword', 'Master Sword', 'Master Sword'], basicbow = ['Bow'] * 2, @@ -269,7 +272,8 @@ def generate_itempool(world, player): for item in precollected_items: world.push_precollected(ItemFactory(item, player)) - if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player): + if (world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player) + and not world.state.has_bomb_level(player, 1)): if "Link's Uncle" not in placed_items: found_sword = False found_bow = False @@ -734,7 +738,7 @@ def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_ precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.extend(['Rupees (20)']) - + if owSwap in ['mixed', 'crossed'] and owShuffle == 'vanilla': precollected_items.append('Bombs (3)') precollected_items.append('Rupees (5)') @@ -791,6 +795,8 @@ def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_ if swords == 'swordless': pool.extend(diff.swordless) + elif swords == 'bombs': + pool.extend(diff.bombs_only) elif swords == 'vanilla': swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy() random.shuffle(swords_to_use) @@ -855,6 +861,12 @@ def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_ pool.extend(['Small Key (Universal)']) else: pool.extend(['Small Key (Universal)']) + if swords == 'bombs': + pool = [item.replace('Single Bomb', 'Small Heart') for item in pool] + pool = [item.replace('Bombs (3)', 'Small Heart') for item in pool] + pool = [item.replace('Bombs (10)', 'Small Heart') for item in pool] + pool = [item.replace('Bomb Upgrade (+5)', 'Small Heart') for item in pool] + pool = [item.replace('Bomb Upgrade (+10)', 'Small Heart') for item in pool] return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray): @@ -893,7 +905,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s itemtotal = itemtotal + customitemarray["generickeys"] customitems = [ - "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce" + "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "L2 Bombs", "L3 Bombs", "L4 Bombs", "L5 Bombs", "Progressive Bombs", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce" ] for customitem in customitems: pool.extend([customitem] * customitemarray[get_custom_array_key(customitem)]) @@ -968,7 +980,7 @@ def test(): for goal in ['ganon', 'triforcehunt', 'pedestal']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: for mode in ['open', 'standard', 'inverted', 'retro']: - for swords in ['random', 'assured', 'swordless', 'vanilla']: + for swords in ['random', 'assured', 'swordless', 'vanilla', 'bombs']: for progressive in ['on', 'off']: for shuffle in ['full', 'insanity_legacy']: for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: diff --git a/Items.py b/Items.py index 808a0740..be566bc8 100644 --- a/Items.py +++ b/Items.py @@ -22,7 +22,7 @@ def ItemFactory(items, player): return ret -# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) +# Format: Name: (Advancement, Priority, Type, ItemCode, Cost, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'), 'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), @@ -166,6 +166,11 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'), 'Nothing': (False, False, None, 0x5A, 1, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'), 'Bee Trap': (False, False, None, 0xB0, 50, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'friendship'), + 'L2 Bombs': (True, False, 'SwordBomb', 0xB1, 100, 'Some decent\nexplosives\nrest here!', 'the decent grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes againsword boy fights again', 'fancy bombs'), + 'L3 Bombs': (True, False, 'SwordBomb', 0xB2, 150, 'Some good\nexplosives\nrest here!', 'the good grenades sword', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes againsword boy fights again', 'fancy bombs'), + 'L4 Bombs': (True, False, 'SwordBomb', 0xB3, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes againsword boy fights again', 'fancy bombs'), + 'L5 Bombs': (True, False, 'SwordBomb', 0xB4, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms sale', 'blend fungus into bombs', '\'splosion boy explodes againsword boy fights again', 'fancy bombs'), + 'Progressive Bombs': (True, False, 'SwordBomb', 0xB5, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), 'Red Potion': (False, False, None, 0x2E, 150, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'), 'Green Potion': (False, False, None, 0x2F, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'), 'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'), diff --git a/Main.py b/Main.py index f7dee19f..a3e7fce8 100644 --- a/Main.py +++ b/Main.py @@ -4,7 +4,7 @@ from itertools import zip_longest import json import logging import os -import random +import RaceRandom as random import time import zlib @@ -35,6 +35,31 @@ class EnemizerError(RuntimeError): pass +def get_random_ganon_item(swordmode): + options = [ + "default", + "arrow", + "boomerang", + "hookshot", + "bomb", + "powder", + "fire_rod", + "ice_rod", + "bombos", + "ether", + "quake", + "hammer", + "bee", + "somaria", + "byrna", + ] + if swordmode == "swordless": + options.remove("bombos") + options.remove("ether") + options.remove("quake") + return random.choice(options) + + def main(args, seed=None, fish=None): if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) @@ -42,8 +67,8 @@ def main(args, seed=None, fish=None): start = time.perf_counter() - # if args.securerandom: - # random.use_secure() + if args.securerandom: + random.use_secure() # initialize the world if args.code: @@ -73,6 +98,8 @@ def main(args, seed=None, fish=None): 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.crystals_ganon_orig = args.crystals_ganon.copy() world.crystals_gt_orig = args.crystals_gt.copy() + world.ganon_item = {player: get_random_ganon_item(args.swords) if args.ganon_item[player] == 'random' else args.ganon_item[player] for player in range(1, world.players + 1)} + world.ganon_item_orig = args.ganon_item.copy() world.owKeepSimilar = args.ow_keepsimilar.copy() world.owFluteShuffle = args.ow_fluteshuffle.copy() world.open_pyramid = args.openpyramid.copy() @@ -384,6 +411,8 @@ def copy_world(world): ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() + ret.ganon_item = world.ganon_item.copy() + ret.ganon_item_orig = world.ganon_item_orig.copy() ret.owKeepSimilar = world.owKeepSimilar.copy() ret.owFluteShuffle = world.owFluteShuffle.copy() ret.open_pyramid = world.open_pyramid.copy() diff --git a/Mystery.py b/Mystery.py index 9fb6155a..4435385c 100644 --- a/Mystery.py +++ b/Mystery.py @@ -1,6 +1,6 @@ import argparse import logging -import random +import RaceRandom as random import urllib.request import urllib.parse import yaml @@ -167,7 +167,10 @@ def roll_settings(weights): ret.crystals_gt = get_choice('tower_open') ret.crystals_ganon = get_choice('ganon_open') - + + ganon_item = get_choice('ganon_item') + ret.ganon_item = ganon_item if ganon_item != 'none' else 'default' + if ret.goal == 'triforcehunt': goal_min = get_choice_default('triforce_goal_min', default=20) goal_max = get_choice_default('triforce_goal_max', default=20) @@ -187,7 +190,8 @@ def roll_settings(weights): ret.swords = {'randomized': 'random', 'assured': 'assured', 'vanilla': 'vanilla', - 'swordless': 'swordless' + 'swordless': 'swordless', + 'bombs': 'bombs' }[get_choice('weapons')] ret.difficulty = get_choice('item_pool') diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 33571f16..a4de07df 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1,4 +1,5 @@ -import random, logging, copy +import RaceRandom as random +import logging, copy from sortedcontainers import SortedList from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot from OWEdges import OWTileGroups, OWEdgeGroups, OpenStd, parallel_links, IsParallel diff --git a/Plando.py b/Plando.py index 254a92b6..2a4b911d 100755 --- a/Plando.py +++ b/Plando.py @@ -3,7 +3,7 @@ import argparse import hashlib import logging import os -import random +import RaceRandom as random import time import sys diff --git a/PotShuffle.py b/PotShuffle.py index e8e6d3ce..d62f52eb 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -275,7 +275,7 @@ vanilla_pots = { def shuffle_pots(world, player): - import random + import RaceRandom as random new_pot_contents = {} diff --git a/README.md b/README.md index 53c6ce11..4c0c4f23 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,21 @@ Flute spots remain unchanged. New flute spots are chosen at random. You can also cancel out of the flute menu by pressing X. +## Ganon Vulnerability Item (--ganon_item) + +### Default + +Ganon will remain vulnerable to the default item (silver arrows except in bomb-only mode) in his final phase. + +### Random + +Ganon will be vulnerable to a random item in his final phase instead of being vulnerable to silver. Ganon will tell you what his weakness is as he enters his final phase, and in swordless mode a medallion will never be picked. + +### ** + +Ganon will be vulnerable to the specified item in his final phase instead of being vulnerable to silver. + + # Command Line Options ``` @@ -127,3 +142,9 @@ This keeps similar edge transitions paired together with other pairs of transiti ``` For randomizing the flute spots around the overworld + +``` +--ganon_item +``` + +For specifying what item ganon will be vulnerable to while stunned in his final phase. (default: default) diff --git a/Rom.py b/Rom.py index 0211a5e3..37740d4f 100644 --- a/Rom.py +++ b/Rom.py @@ -5,7 +5,7 @@ import json import hashlib import logging import os -import random +import RaceRandom as random import struct import sys import subprocess @@ -28,7 +28,7 @@ from OverworldShuffle import default_flute_connections, flute_data JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '6e44346357f8a9471a8499ab787635c1' +RANDOMIZERBASEHASH = '35f262588baff34e2e2bf523a45a2703' class JsonRom(object): @@ -613,14 +613,14 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): owMode = 1 elif world.owShuffle[player] == 'full': owMode = 2 - + if world.owSwap[player] == 'mixed': owMode |= 0x100 world.fix_fake_world[player] = True elif world.owSwap[player] == 'crossed': owMode |= 0x200 world.fix_fake_world[player] = True - + write_int16(rom, 0x150002, owMode) owFlags = 0 @@ -638,12 +638,12 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # set world flag rom.write_byte(0x153A00 + b, 0x00 if b >= 0x40 else 0x40) - + for edge in world.owedges: if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player: write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc) write_int16(rom, edge.getAddress() + 0x0e, edge.getTarget()) - + # patch flute spots if world.owFluteShuffle[player] == 'vanilla': flute_spots = default_flute_connections @@ -655,7 +655,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): offset = 0 if (world.mode[player] == 'inverted') != (owid in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): offset = 0x40 - + data = flute_data[owid] write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid write_int16(rom, snes_to_pc(0x02E86B + (o * 2)), data[1]) #vram @@ -673,8 +673,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(snes_to_pc(0x0AB78B + o), data[11] // 0x100) # X high byte rom.write_byte(snes_to_pc(0x0AB793 + o), data[10] & 0xff) # Y low byte rom.write_byte(snes_to_pc(0x0AB79B + o), data[10] // 0x100) # Y high byte - - + + # patch entrance/exits/holes for region in world.regions: for exit in region.exits: @@ -690,7 +690,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): write_int16(rom, 0x15C79 + 2 * offset, scroll_y) write_int16(rom, 0x15D17 + 2 * offset, scroll_x) - # for positioning fixups we abuse the roomid as a way of identifying which exit data we are appling + # for positioning fixups we abuse the roomid as a way of identifying which exit data we are applying # Thanks to Zarby89 for originally finding these values # todo fix screen scrolling @@ -1094,10 +1094,18 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] if world.retro[player]: - prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee - 0xE2: 0xDB} #10 Arrows -> Red Rupee + prize_replacements = {0xE1: 0xDA, # 5 Arrows -> Blue Rupee + 0xE2: 0xDB} # 10 Arrows -> Red Rupee prizes = [prize_replacements.get(prize, prize) for prize in prizes] dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] + + if world.swords[player] == "bombs": + prize_replacements = {0xDC: 0xD9, # 1 Bomb -> Green Rupee + 0xDD: 0xDA, # 3 Bombs -> Blue Rupee + 0xDE: 0xDB} # 10 Bombs -> Red Rupee + prizes = [prize_replacements.get(prize, prize) for prize in prizes] + dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] + rom.write_bytes(0x180100, dig_prizes) # write tree pull prizes @@ -1176,11 +1184,28 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180029, 0x01) # Smithy quick item give # set swordless mode settings - rom.write_byte(0x18003F, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer can harm ganon - rom.write_byte(0x180040, 0x01 if world.swords[player] == 'swordless' else 0x00) # open curtains - rom.write_byte(0x180041, 0x01 if world.swords[player] == 'swordless' else 0x00) # swordless medallions - rom.write_byte(0x180043, 0xFF if world.swords[player] == 'swordless' else 0x00) # starting sword for link - rom.write_byte(0x180044, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer activates tablets + if world.swords[player] == 'swordless': + rom.write_byte(0x18003F, 0x01) # hammer can harm ganon + rom.write_byte(0x180040, 0x01) # open curtains + rom.write_byte(0x180041, 0x01) # swordless medallions (on pads) + rom.write_byte(0x180043, 0xFF) # starting sword for link + rom.write_byte(0x180044, 0x01) # hammer activates tablets + elif world.swords[player] == 'bombs': + rom.write_byte(0x18002F, 0x03) # special bombs + rom.write_byte(0x180040, 0x01) # open curtains + rom.write_byte(0x180041, 0x02) # swordless medallions (always) + rom.write_byte(0x180043, 0xFF) # starting sword for link + rom.write_byte(0x180044, 0x01) # hammer activates tablets + # since we have infinite bombs, let's get rid of bomb drops + rom.write_byte(0x030051, 0xDB) # fish bottle merchant + rom.write_byte(0x0301F8, 0xD9) # replace Pot bombs with green rupees + rom.write_byte(0x0301FD, 0xD9) + rom.write_byte(0x030224, 0x04) # adjust width of offset for replaced pot bomb + rom.write_byte(0x030229, 0x04) + rom.write_byte(0x00EDA7, 0x35) # DW chest game (bomb -> blue rupee) + # thiefs and pikits shouldn't steal bombs + rom.write_bytes(0x0ECB54, [0xA9, 0x00, 0xEA, 0xEA]) # thief + rom.write_bytes(0x0F0D80, [0xA9, 0x00, 0xEA, 0xEA]) # pikit # set up clocks for timed modes if world.shuffle[player] == 'vanilla': @@ -1425,7 +1450,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x180080, [50, 50, 70, 70]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) - rom.write_byte(0x18004D, ((0x01 if 'arrows' in world.escape_assist[player] else 0x00) | + rom.write_byte(0x18004D, ((0x22 if world.swords[player] == 'bombs' else 0x00) | + (0x01 if 'arrows' in world.escape_assist[player] else 0x00) | (0x02 if 'bombs' in world.escape_assist[player] else 0x00) | (0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist @@ -1441,6 +1467,26 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18005E, world.crystals_needed_for_gt[player]) rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) + ganon_item_byte = { + "default": 0x00, + "arrow": 0x01, + "boomerang": 0x02, + "hookshot": 0x03, + "bomb": 0x04, + "powder": 0x05, + "fire_rod": 0x06, + "ice_rod": 0x07, + "bombos": 0x08, + "ether": 0x09, + "quake": 0x0A, + "hammer": 0x0C, + "bee": 0x10, + "somaria": 0x11, + "byrna": 0x12, + } + if world.ganon_item[player] in ganon_item_byte: + rom.write_byte(0x18002E, ganon_item_byte[world.ganon_item[player]]) + # block HC upstairs doors in rain state in standard mode prevent_rain = world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' rom.write_byte(0x18008A, 0x01 if prevent_rain else 0x00) @@ -2134,25 +2180,61 @@ def write_strings(rom, world, player, team): # We still need the older hints of course. Those are done here. + ganon_item = world.ganon_item[player] + if ganon_item == 'default': + if world.swords[player] == 'bombs': + ganon_item = 'bomb' + else: + ganon_item = 'arrow' - silverarrows = world.find_items('Silver Arrows', player) - random.shuffle(silverarrows) - silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' - tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint - tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint - - prog_bow_locs = world.find_items('Progressive Bow', player) - distinguished_prog_bow_loc = next((location for location in prog_bow_locs if location.item.code == 0x65), None) - progressive_silvers = world.difficulty_requirements[player].progressive_bow_limit >= 2 or world.swords[player] == 'swordless' - if distinguished_prog_bow_loc: - prog_bow_locs.remove(distinguished_prog_bow_loc) - silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' + if ganon_item == 'arrow': + silverarrows = world.find_items('Silver Arrows', player) + random.shuffle(silverarrows) + silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint - - if any(prog_bow_locs): - silverarrow_hint = (' %s?' % hint_text(random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint + prog_bow_locs = world.find_items('Progressive Bow', player) + distinguished_prog_bow_loc = next((location for location in prog_bow_locs if location.item.code == 0x65), None) + progressive_silvers = world.difficulty_requirements[player].progressive_bow_limit >= 2 or world.swords[player] == 'swordless' + if distinguished_prog_bow_loc: + prog_bow_locs.remove(distinguished_prog_bow_loc) + silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' + tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint + + if any(prog_bow_locs): + silverarrow_hint = (' %s?' % hint_text(random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' + tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint + elif ganon_item == 'bomb': + tt['ganon_phase_3_no_bow'] = 'You can\'t best\nme without\nexplosives!' + tt['ganon_phase_3_silvers'] = 'Explosives!\nMy one true\nweakness!' + elif ganon_item == 'bee': + tt['ganon_phase_3_no_bow'] = 'You can\'t best\nme without\na bee!' + tt['ganon_phase_3_silvers'] = 'Oh no! A bee!\nMy one true\nweakness!' + else: + name_table = { + 'boomerang': ['a boomerang', 'a boomerang', 'Red Boomerang'], + 'hookshot': ['a hookshot', 'a hookshot', 'Hookshot'], + 'powder': ['the powder', 'powder', 'Magic Powder'], + 'fire_rod': ['the fire rod', 'the fire rod', 'Fire Rod'], + 'ice_rod': ['the ice rod', 'the ice rod', 'Ice Rod'], + 'bombos': ['bombos', 'bombos', 'Bombos'], + 'ether': ['ether', 'ether', 'Ether'], + 'quake': ['quake', 'quake', 'Quake'], + 'hammer': ['a hammer', 'a hammer', 'Hammer'], + 'somaria': ['somaria', 'somaria', 'Cane of Somaria'], + 'byrna': ['byrna', 'byrna', 'Cane of Byrna'], + } + locations = world.find_items(name_table[ganon_item][2], player) + random.shuffle(locations) + location_hint = (' %s?' % hint_text(locations[0]).replace('Ganon\'s', 'my')) if locations else '?\nI think not!' + have_text = name_table[ganon_item][1] + none_text = name_table[ganon_item][0]; + if len(have_text) <= 6: + have_text = 'Oh no! %s' % have_text; + tt['ganon_phase_3_no_bow'] = 'Did you find %s%s' % (none_text, location_hint) + tt['ganon_phase_3_silvers'] = '%s!\nMy one true\nweakness!' % have_text; + crystal5 = world.find_items('Crystal 5', player)[0] crystal6 = world.find_items('Crystal 6', player)[0] tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5.hint_text, crystal6.hint_text) diff --git a/Rules.py b/Rules.py index ef1cd58d..22825700 100644 --- a/Rules.py +++ b/Rules.py @@ -347,7 +347,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Mire Lobby Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Post-Gap Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Falling Bridge WN', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) # this is due to the fact the the door opposite is blocked - set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ... + set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.bomb_mode_check(player, 1) and (state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player) or state.has_bomb_level(1))) # need to defeat wizzrobes, bombs don't work ... set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player)) @@ -595,9 +595,21 @@ def global_rules(world, player): add_key_logic_rules(world, player) # End of door rando rules. - 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_location('Ganon', player), + lambda state: (state.has_beam_sword(player) or state.has_bomb_level(player, 3)) + 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.can_hit_stunned_ganon(player) or + state.has_beam_sword(player) and + (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) or state.has_bomb_level(player, 3)) + # need to damage ganon to get tiles to drop def bomb_rules(world, player): bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended. @@ -669,14 +681,14 @@ def bomb_rules(world, player): ] for location,bombable in enemy_kill_drops: if bombable: - add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.can_kill_most_things(player)) + add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.can_kill_most_things(player)) else: add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player)) - add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player)) bombable_floors = ['PoD Pit Room Bomb Hole', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', 'GT Bob\'s Room Hole'] for entrance in bombable_floors: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) if world.doorShuffle[player] == 'vanilla': add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. @@ -717,13 +729,13 @@ def default_rules(world, player): set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player)) set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling - + # Item Access set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player)) set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player)) set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player)) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) - + # Entrance Access set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player)) @@ -738,10 +750,10 @@ def default_rules(world, player): set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword required to cast magic (!) set_rule(world.get_entrance('Dark World Hammer Peg Cave', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Bonk Fairy (Dark)', player), lambda state: state.has_Boots(player)) - set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) + set_rule(world.get_entrance('Misery Mire', player), lambda state: state.can_use_medallions(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: state.can_lift_rocks(player)) - # Region Access + # Region Access set_rule(world.get_entrance('DM Hammer Bridge (West)', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('DM Hammer Bridge (East)', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('DM Broken Bridge (West)', player), lambda state: state.has('Hookshot', player)) @@ -837,9 +849,11 @@ def default_rules(world, player): set_rule(world.get_entrance('Shopping Mall SW', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) - + if world.swords[player] == 'swordless': swordless_rules(world, player) + if world.swords[player] == 'bombs': + bomb_mode_rules(world, player) def ow_rules(world, player): @@ -857,23 +871,23 @@ def ow_rules(world, player): set_rule(world.get_entrance('Skull Woods Portal Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Skull Woods Forgotten (Middle) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Skull Woods Front Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x02 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Lumberjack Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Lumberjack Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('GT Entry Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) set_rule(world.get_entrance('GT Entry Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'crossed', 'insanity')) - + set_rule(world.get_entrance('West Death Mountain (Top) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Spectacle Rock Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('West Dark Death Mountain (Top) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bubble Boy Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('West Dark Death Mountain (Bottom) Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x05 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('East Death Mountain (Top West) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('East Death Mountain (Top East) Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -895,7 +909,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('East Dark Death Mountain (Bottom) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Death Mountain Teleporter (East)', player), lambda state: state.can_lift_heavy_rocks(player)) - + if (world.mode[player] == 'inverted') == (0x07 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('TR Pegs Area Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('TR Pegs Teleporter', player), lambda state: state.has('Hammer', player)) @@ -903,7 +917,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Turtle Rock Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Turtle Rock Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Turtle Rock Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) - + if (world.mode[player] == 'inverted') == (0x0a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Mountain Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Mountain Entry Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -912,12 +926,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Bumper Cave Area Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bumper Cave Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x0f in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Zora Waterfall Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Catfish Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x10 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Lost Woods Pass West Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lost Woods Pass East Top Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -930,24 +944,24 @@ def ow_rules(world, player): set_rule(world.get_entrance('Skull Woods Pass East Bottom Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('West Dark World Teleporter (Hammer)', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) set_rule(world.get_entrance('West Dark World Teleporter (Rock)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) # bunny cannot lift bushes - + if (world.mode[player] == 'inverted') == (0x11 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Kakariko Fortune Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Outcast Fortune Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x12 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Kakariko Pond Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Outcast Pond Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x13 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Sanctuary Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bonk Rock Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Chapel Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Chapel Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x14 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Graveyard Ledge Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) set_rule(world.get_entrance('Kings Grave Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) @@ -957,28 +971,28 @@ def ow_rules(world, player): set_rule(world.get_entrance('Dark Graveyard Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Graveyard Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Graveyard Grave Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x15 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('River Bend Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('River Bend East Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Qirn Jump Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Qirn Jump East Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x16 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Potion Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Potion Shop Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Witch Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Witch Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x17 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Zora Approach Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Zora Approach Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Catfish Approach Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Catfish Approach Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x18 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Kakariko Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Kakariko Grass Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -986,19 +1000,19 @@ def ow_rules(world, player): set_rule(world.get_entrance('Village of Outcasts Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Village of Outcasts Southwest Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer House Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x1a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Forgotton Forest Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Forgotton Forest Fence Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Shield Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False) 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('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle - + set_rule(world.get_entrance('HC Area Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Courtyard Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1011,7 +1025,7 @@ def ow_rules(world, player): add_rule(world.get_entrance('Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Pyramid Entrance', player), lambda state: False) set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) - + set_rule(world.get_entrance('Pyramid Exit Ledge Drop', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Pyramid Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Pyramid Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1020,7 +1034,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Pyramid From Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Pyramid Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Post Aga Inverted Teleporter', player), lambda state: state.has('Beat Agahnim 1', player)) - + if (world.mode[player] == 'inverted') == (0x1d in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Wooden Bridge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Wooden Bridge Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1029,12 +1043,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Broken Bridge West Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Broken Bridge East Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Broken Bridge Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x1e in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Eastern Palace Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Palace of Darkness Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x22 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Blacksmith Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Blacksmith Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1042,19 +1056,19 @@ def ow_rules(world, player): else: set_rule(world.get_entrance('Hammer Pegs Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer Pegs Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x25 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Sand Dunes Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Dunes Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x28 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Maze Race Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Maze Race Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dig Game Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dig Game Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x29 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Kakariko Suburb Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Kakariko Suburb South Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1062,24 +1076,24 @@ def ow_rules(world, player): set_rule(world.get_entrance('Frog Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Frog Prison Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Archery Game Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x2a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Flute Boy Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Flute Boy Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Stumpy Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Stumpy Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x2b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Central Bonk Rocks Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Bonk Rocks Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x2c in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Links House Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Big Bomb Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x2d in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Stone Bridge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Stone Bridge South Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1088,19 +1102,19 @@ def ow_rules(world, player): set_rule(world.get_entrance('Hammer Bridge North Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer Bridge South Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Hobo Mirror Spot', player), lambda state: state.has_Mirror(player) and state.has_Pearl(player) and state.has('Flippers', player)) - + if (world.mode[player] == 'inverted') == (0x2e in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Tree Line Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Tree Line Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x2f in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Eastern Nook Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer else: set_rule(world.get_entrance('Darkness Nook Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('East Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) - + if (world.mode[player] == 'inverted') == (0x30 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Checkerboard Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Desert Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1115,14 +1129,14 @@ def ow_rules(world, player): set_rule(world.get_entrance('Misery Mire Blocked Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Misery Mire Main Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Misery Mire Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - + if (world.mode[player] == 'inverted') == (0x32 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Cave 45 Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Flute Boy Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Stumpy Approach Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Stumpy Bush Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x33 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('C Whirlpool Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('C Whirlpool Outer Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1133,12 +1147,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Dark C Whirlpool Outer Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('South Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) set_rule(world.get_entrance('South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) - + if (world.mode[player] == 'inverted') == (0x34 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Statues Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Hype Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x35 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Lake Hylia Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lake Hylia Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1155,12 +1169,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Ice Lake Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Ice Palace Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Ice Palace Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - + if (world.mode[player] == 'inverted') == (0x37 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Ice Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Shopping Mall Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x3a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Desert Pass Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Desert Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1168,17 +1182,17 @@ def ow_rules(world, player): set_rule(world.get_entrance('Swamp Nook Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Swamp Nook Southeast Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Swamp Nook Pegs Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x3b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Dam Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Swamp Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x3c in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('South Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark South Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) - + if (world.mode[player] == 'inverted') == (0x3f in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): set_rule(world.get_entrance('Octoballoon Mirror Spot', player), lambda state: state.has_Mirror(player)) else: @@ -1218,7 +1232,7 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Hype Cave', player), player) # bomb required add_bunny_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), player) # bomb required add_bunny_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), player) - + add_bunny_rule(world.get_entrance('Lost Woods Bush (West)', player), player) add_bunny_rule(world.get_entrance('Lost Woods Bush (East)', player), player) add_bunny_rule(world.get_entrance('DM Hammer Bridge (West)', player), player) @@ -1487,7 +1501,21 @@ def swordless_rules(world, player): set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', 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': + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle + + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion for opening in swordless (!) + add_bunny_rule(world.get_entrance('Turtle Rock', player), player) + add_bunny_rule(world.get_entrance('Misery Mire', player), player) + +def bomb_mode_rules(world, player): + set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) + set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) + + 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('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) + if world.mode[player] != 'inverted': set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle @@ -1508,7 +1536,7 @@ std_kill_rooms = { 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. 'PoD Turtle Party': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], # Lots of turtles. - 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. + 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. @@ -1560,7 +1588,7 @@ def standard_rules(world, player): return loc.item and loc.item.name == 'Bombs (10)' def standard_escape_rule(state): - return state.can_kill_most_things(player) or bomb_escape_rule() + return state.can_kill_most_things(player) or bomb_escape_rule() add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule) @@ -1854,7 +1882,7 @@ def set_big_bomb_rules(world, player): # same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which # means you need an escape route of either Flippers or Flute add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player))) - + #TODO: Fix red bomb rules, artifically adding a bunch of rules to help reduce unbeatable seeds in OW shuffle add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) #add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Pyramid Area', 'Region', player)) @@ -2051,7 +2079,7 @@ def set_inverted_big_bomb_rules(world, player): set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) else: raise Exception('No logic found for routing from %s to the pyramid.' % bombshop_entrance.name) - + if world.owShuffle[player] != 'vanilla' or world.owSwap[player] != 'vanilla': add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) #temp disable progression until routing to Pyramid get be guaranteed diff --git a/Utils.py b/Utils.py index 39b7d46f..01d4f8bf 100644 --- a/Utils.py +++ b/Utils.py @@ -668,8 +668,8 @@ class bidict(dict): if __name__ == '__main__': - # make_new_base2current() + print(make_new_base2current()) # read_entrance_data(old_rom=sys.argv[1]) # room_palette_data(old_rom=sys.argv[1]) # extract_data_from_us_rom(sys.argv[1]) - extract_data_from_jp_rom(sys.argv[1]) + # extract_data_from_jp_rom(sys.argv[1]) diff --git a/data/base2current.bps b/data/base2current.bps index d788ef02..86ffec0e 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index ebb0a9ba..c9f097f4 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -34,6 +34,7 @@ "random", "assured", "swordless", + "bombs", "vanilla" ] }, @@ -202,6 +203,26 @@ "random" ] }, + "ganon_item": { + "choices": [ + "default", + "random", + "arrow", + "boomerang", + "hookshot", + "bomb", + "powder", + "fire_rod", + "ice_rod", + "bombos", + "ether", + "quake", + "hammer", + "bee", + "somaria", + "byrna" + ] + }, "beemizer": { "choices": [ "4", "3", "2", "1", "0" @@ -421,4 +442,4 @@ }, "outputname": {}, "code": {} -} \ No newline at end of file +} diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 4fb6f25f..9ca56f15 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -91,6 +91,11 @@ " Ether and Bombos Tablet can be activated with Hammer", " (and Book). Bombos pads have been added in Ice", " Palace, to allow for an alternative to firerod.", + "Bombs: Similar to swordless, but only bombs deal damage", + " to enemies. Bombs deal sword-class damage and can be", + " upgraded. Special enemies such as red eyegores still", + " require the traditional item to kill. Medallions can", + " be used despite not having a sword.", "Vanilla: Swords are in vanilla locations." ], "goal": [ @@ -249,6 +254,13 @@ "Random: Picks a random value between 0 and 7 (inclusive).", "0-7: Number of crystals needed" ], + "ganon_item": [ + "What item Ganon is vulnerable to while stunned in his final phase.", + "Default: The usual item (silver arrows except in bomb-only mode) will", + " damage stunned Ganon.", + "Random: Picks a random damaging item (but not a medallion if swordless)", + ": The specified item will damage stunned Ganon." + ], "openpyramid": [ "Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. (default: %(default)s)" ], "rom": [ "Path to an ALttP JP (1.0) rom to use as a base." , diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index f0cab9b1..5082813e 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -124,7 +124,7 @@ "randomizer.overworld.overworldflute": "Flute Shuffle", "randomizer.overworld.overworldflute.vanilla": "Vanilla", "randomizer.overworld.overworldflute.random": "Random", - + "randomizer.entrance.openpyramid": "Pre-open Pyramid Hole", "randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool", "randomizer.entrance.shufflelinks": "Include Link's House in the shuffle pool", @@ -244,10 +244,29 @@ "randomizer.item.crystals_ganon.7": "7", "randomizer.item.crystals_ganon.random": "Random", + "randomizer.item.ganon_item": "Item to harm stunned Ganon", + "randomizer.item.ganon_item.default": "Default", + "randomizer.item.ganon_item.random": "Random", + "randomizer.item.ganon_item.arrow": "Silver Arrows", + "randomizer.item.ganon_item.boomerang": "Boomerang", + "randomizer.item.ganon_item.hookshot": "Hookshot", + "randomizer.item.ganon_item.bomb": "Bombs", + "randomizer.item.ganon_item.powder": "Powder", + "randomizer.item.ganon_item.fire_rod": "Fire Rod", + "randomizer.item.ganon_item.ice_rod": "Ice Rod", + "randomizer.item.ganon_item.bombos": "Bombos", + "randomizer.item.ganon_item.ether": "Ether", + "randomizer.item.ganon_item.quake": "Quake", + "randomizer.item.ganon_item.hammer": "Hammer", + "randomizer.item.ganon_item.bee": "Bee", + "randomizer.item.ganon_item.somaria": "Cane of Somaria", + "randomizer.item.ganon_item.byrna": "Cane of Byrna", + "randomizer.item.weapons": "Weapons", "randomizer.item.weapons.random": "Randomized", "randomizer.item.weapons.assured": "Assured", "randomizer.item.weapons.swordless": "Swordless", + "randomizer.item.weapons.bombs": "Bomb-Only", "randomizer.item.weapons.vanilla": "Vanilla", "randomizer.item.beemizer": "Beemizer", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 1871dcaf..f9cb8226 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -50,20 +50,36 @@ "random" ] }, + "ganon_item": { + "type": "selectbox", + "options": [ + "default", + "random", + "arrow", + "boomerang", + "hookshot", + "bomb", + "powder", + "fire_rod", + "ice_rod", + "bombos", + "ether", + "quake", + "hammer", + "bee", + "somaria", + "byrna" + ] + }, "weapons": { "type": "selectbox", "options": [ "random", "assured", "swordless", + "bombs", "vanilla" ] - }, - "beemizer": { - "type": "selectbox", - "options": [ - "0", "1", "2", "3", "4" - ] } }, "rightItemFrame": { @@ -122,6 +138,12 @@ "vt26", "balanced" ] + }, + "beemizer": { + "type": "selectbox", + "options": [ + "0", "1", "2", "3", "4" + ] } } } diff --git a/source/classes/constants.py b/source/classes/constants.py index d345c637..99ddf8aa 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -63,6 +63,7 @@ SETTINGSTOPROCESS = { "goal": "goal", "crystals_gt": "crystals_gt", "crystals_ganon": "crystals_ganon", + "ganon_item": "ganon_item", "weapons": "swords", "itempool": "difficulty", "itemfunction": "item_functionality",