diff --git a/BaseClasses.py b/BaseClasses.py index b8554ed8..2b23197f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -365,6 +365,19 @@ class World(object): ret.prog_items['L2 Bombs', item.player] += 1 else: ret.prog_items['L1 Bombs', item.player] += 1 + elif 'Cane' in item.name: + if ret.has('L5 Cane', item.player): + pass + elif ret.has('L4 Cane', item.player): + ret.prog_items['L5 Cane', item.player] += 1 + elif ret.has('L3 Cane', item.player): + ret.prog_items['L4 Cane', item.player] += 1 + elif ret.has('L2 Cane', item.player): + ret.prog_items['L3 Cane', item.player] += 1 + elif ret.has('L1 Cane', item.player): + ret.prog_items['L2 Cane', item.player] += 1 + else: + ret.prog_items['L1 Cane', item.player] += 1 elif 'Glove' in item.name: if ret.has('Titans Mitts', item.player): pass @@ -393,6 +406,11 @@ class World(object): ret.prog_items[item.name, item.player] += 1 elif item.advancement or item.smallkey or item.bigkey: ret.prog_items[item.name, item.player] += 1 + if item.name.endswith(' Cane'): + if ret.world.swords[item.player] == 'byrna' and not ret.has('Cane of Byrna', item.player): + ret.prog_items['Cane of Byrna', item.player] += 1 + if ret.world.swords[item.player] == 'somaria' and not ret.has('Cane of Somaria', item.player): + ret.prog_items['Cane of Somaria', item.player] += 1 for item in self.itempool: soft_collect(item) @@ -1239,8 +1257,8 @@ class CollectionState(object): return basemagic >= smallmagic def can_kill_most_things(self, player, enemies=5): - return (self.bomb_mode_check(player, 1) and - (self.has_blunt_weapon(player) or self.has_bomb_level(player, 1) + return (self.special_weapon_check(player, 1) and + (self.has_blunt_weapon(player) or self.has_special_weapon_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) @@ -1259,7 +1277,7 @@ class CollectionState(object): # In the future, this can be used to check if the player starts without bombs def can_use_bombs(self, player): if self.world.swords[player] == 'bombs': - return self.has_bomb_level(player, 1) + return self.has_special_weapon_level(player, 1) return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player)) and ((hasattr(self.world, "override_bomb_check") and self.world.override_bomb_check) or self.can_farm_bombs(player)) def can_hit_crystal(self, player): @@ -1316,23 +1334,41 @@ class CollectionState(object): return False; return self.has_sword(player, level) - def has_bomb_level(self, player, level): - if self.world.swords[player] != 'bombs': + def has_special_weapon_level(self, player, level): + if self.world.swords[player] == 'bombs': + 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) + elif level == 1: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) or self.has('L1 Bombs', player) + return True + elif self.world.swords[player] in ['byrna', 'somaria', 'cane']: + if self.world.swords[player] == 'cane' and not self.has('Cane of Somaria', player) and not self.has('Cane of Byrna', player): + return False + if level == 5: + return self.has('L5 Cane', player) + elif level == 4: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) + elif level == 3: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) or self.has('L3 Cane', player) + elif level == 2: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) or self.has('L3 Cane', player) or self.has('L2 Cane', player) + elif level == 1: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) or self.has('L3 Cane', player) or self.has('L2 Cane', player) or self.has('L1 Cane', player) + return True + else: 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) - elif level == 1: - return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) or self.has('L1 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 special_weapon_check(self, player, level): + if self.world.swords[player] in ['bombs', 'byrna', 'somaria', 'cane']: + return self.has_special_weapon_level(player, level) + else: + return True def can_hit_stunned_ganon(self, player): ganon_item = self.world.ganon_item[player] @@ -1375,7 +1411,7 @@ class CollectionState(object): return False def can_use_medallions(self, player): - return self.has_sword(player) or self.world.swords[player] == 'bombs' + return self.has_sword(player) or self.world.swords[player] in ['bombs', 'byrna', 'somaria', 'cane'] def has_blunt_weapon(self, player): return self.has_real_sword(player) or self.has('Hammer', player) @@ -1491,6 +1527,24 @@ class CollectionState(object): else: self.prog_items['L1 Bombs', item.player] += 1 changed = True + elif 'Cane' in item.name: + if self.has('L5 Cane', item.player): + pass + elif self.has('L4 Cane', item.player): + self.prog_items['L5 Cane', item.player] += 1 + changed = True + elif self.has('L3 Cane', item.player): + self.prog_items['L4 Cane', item.player] += 1 + changed = True + elif self.has('L2 Cane', item.player): + self.prog_items['L3 Cane', item.player] += 1 + changed = True + elif self.has('L1 Cane', item.player): + self.prog_items['L2 Cane', item.player] += 1 + changed = True + else: + self.prog_items['L1 Cane', item.player] += 1 + changed = True elif 'Glove' in item.name: if self.has('Titans Mitts', item.player): pass @@ -1541,6 +1595,13 @@ class CollectionState(object): elif event or item.advancement: self.prog_items[item.name, item.player] += 1 changed = True + if item.name.endswith(' Cane'): + if self.world.swords[item.player] == 'byrna' and not self.has('Cane of Byrna', item.player): + self.prog_items['Cane of Byrna', item.player] += 1 + changed = True + if self.world.swords[item.player] == 'somaria' and not self.has('Cane of Somaria', item.player): + self.prog_items['Cane of Somaria', item.player] += 1 + changed = True self.stale[item.player] = True @@ -1576,6 +1637,19 @@ class CollectionState(object): to_remove = 'L1 Bombs' else: to_remove = None + elif 'Cane' in to_remove: + if self.has('L5 Cane', item.player): + to_remove = 'L5 Cane' + elif self.has('L4 Cane', item.player): + to_remove = 'L4 Cane' + elif self.has('L3 Cane', item.player): + to_remove = 'L3 Cane' + elif self.has('L2 Cane', item.player): + to_remove = 'L2 Cane' + elif self.has('L1 Cane', item.player): + to_remove = 'L1 Cane' + else: + to_remove = None elif 'Glove' in item.name: if self.has('Titans Mitts', item.player): to_remove = 'Titans Mitts' @@ -1601,7 +1675,15 @@ class CollectionState(object): to_remove = None if to_remove is not None: - + if to_remove.endswith(' Cane') and not self.has('L5 Cane', item.player) and not self.has('L4 Cane', item.player) and not self.has('L3 Cane', item.player) and not self.has('L2 Cane', item.player) and not self.has('L1 Cane', item.player): + if self.world.swords[item.player] == 'byrna': + self.prog_items['Cane of Byrna', item.player] -= 1 + if self.prog_items['Cane of Byrna', item.player] < 1: + del (self.prog_items['Cane of Byrna', item.player]) + if self.world.swords[item.player] == 'somaria': + self.prog_items['Cane of Somaria', item.player] -= 1 + if self.prog_items['Cane of Somaria', item.player] < 1: + del (self.prog_items['Cane of Somaria', item.player]) self.prog_items[to_remove, item.player] -= 1 if self.prog_items[to_remove, item.player] < 1: del (self.prog_items[to_remove, item.player]) @@ -2940,8 +3022,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)} } @@ -3320,7 +3402,7 @@ er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "lite": 4, "le # byte 1: LLLW WSSS (logic, mode, sword) 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, "bombs": 4, "pseudo": 5, "assured_pseudo": 6} +sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3, "bombs": 4, "pseudo": 5, "assured_pseudo": 5, "byrna": 6, "somaria": 6, "cane": 6} # 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 96128591..d266531f 100644 --- a/Bosses.py +++ b/Bosses.py @@ -16,7 +16,7 @@ def BossFactory(boss, player): def ArmosKnightsDefeatRule(state, player): # Magic amounts are probably a bit overkill - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_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 @@ -25,38 +25,38 @@ def ArmosKnightsDefeatRule(state, player): (state.has('Fire Rod', player) and state.can_extend_magic(player, 32)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or - state.has_bomb_level(player, 1))) + state.has_special_weapon_level(player, 1))) def LanmolasDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_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) or - state.has_bomb_level(player, 1))) + state.has_special_weapon_level(player, 1))) def MoldormDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and - (state.has_blunt_weapon(player) or state.has_bomb_level(player, 1))) + return (state.special_weapon_check(player, 1) and + (state.has_blunt_weapon(player) or state.has_special_weapon_level(player, 1))) def HelmasaurKingDefeatRule(state, player): - return (state.bomb_mode_check(player, 2) and + return (state.special_weapon_check(player, 2) and (state.has('Hammer', player) or state.can_use_bombs(player)) and - (state.has_real_sword(player) or state.can_shoot_arrows(player) or state.has_bomb_level(player, 2))) + (state.has_real_sword(player) or state.can_shoot_arrows(player) or state.has_special_weapon_level(player, 2))) def ArrghusDefeatRule(state, player): if not state.has('Hookshot', player): return False - if not state.bomb_mode_check(player, 2): + if not state.special_weapon_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) or state.has_bomb_level(player, 2): + if state.has_blunt_weapon(player) or state.has_special_weapon_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 @@ -64,7 +64,7 @@ def ArrghusDefeatRule(state, player): def MothulaDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_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 @@ -72,15 +72,15 @@ def MothulaDefeatRule(state, player): (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) or - state.has_bomb_level(player, 1))) + state.has_special_weapon_level(player, 1))) def BlindDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_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))) + state.has('Cane of Byrna', player) or state.has_special_weapon_level(player, 1))) def KholdstareDefeatRule(state, player): - return (state.bomb_mode_check(player, 2) and + return (state.special_weapon_check(player, 2) and ( state.has('Fire Rod', player) or ( @@ -90,7 +90,7 @@ def KholdstareDefeatRule(state, player): ) ) and ( - state.has_bomb_level(player, 2) or state.has_blunt_weapon(player) or + state.has_special_weapon_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 ( @@ -102,21 +102,21 @@ def KholdstareDefeatRule(state, player): )) def VitreousDefeatRule(state, player): - return (state.bomb_mode_check(player, 2) and + return (state.special_weapon_check(player, 2) and (state.can_shoot_arrows(player) or state.has_blunt_weapon(player) or - state.has_bomb_level(player, 2))) + state.has_special_weapon_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): + if not state.special_weapon_check(player, 2): return False return (state.has('Hammer', player) or state.has_real_sword(player, 3) or - state.has_bomb_level(player, 4) or - ((state.has_real_sword(player, 2) or state.has_bomb_level(player, 3)) + state.has_special_weapon_level(player, 4) or + ((state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) and state.can_extend_magic(player, 16)) or - ((state.has_real_sword(player) or state.has_bomb_level(player, 2)) + ((state.has_real_sword(player) or state.has_special_weapon_level(player, 2)) and state.can_extend_magic(player, 32))) def AgahnimDefeatRule(state, player): diff --git a/ItemList.py b/ItemList.py index 3301dc6b..74ecab77 100644 --- a/ItemList.py +++ b/ItemList.py @@ -34,7 +34,7 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', - 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'bombs_only', + 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'bombs_only', 'cane_only', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', 'retro', 'bombbag', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', @@ -55,6 +55,7 @@ difficulties = { basicarmor = ['Blue Mail', 'Red Mail'], swordless = ['Rupees (20)'] * 4, bombs_only = ['Progressive Bombs'] * 4, + cane_only = ['Progressive Cane'] * 3 + ['Rupees (20)'], progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'], basicbow = ['Bow', 'Silver Arrows'], @@ -82,6 +83,7 @@ difficulties = { basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, bombs_only = ['Progressive Bombs'] * 4, + cane_only = ['Progressive Cane'] * 3 + ['Rupees (20)'], progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword'], basicbow = ['Bow'] * 2, @@ -109,6 +111,7 @@ difficulties = { basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, bombs_only = ['Progressive Bombs'] * 4, + cane_only = ['Progressive Cane'] * 3 + ['Rupees (20)'], progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Fighter Sword', 'Master Sword', 'Master Sword'], basicbow = ['Bow'] * 2, @@ -283,8 +286,7 @@ 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_bomb_level(player, 1) if world.swords[player] else world.state.has_blunt_weapon(player)) - and not world.state.has_bomb_level(player, 1)): + if world.mode[player] == 'standard' and not (world.state.has_special_weapon_level(player, 1) if world.swords[player] in ['bombs', 'byrna', 'somaria', 'cane'] else world.state.has_blunt_weapon(player)): if world.swords[player] == 'bombs' and "Link's Uncle" not in placed_items: possible_weapons = [] for item in pool: @@ -293,6 +295,22 @@ def generate_itempool(world, player): starting_weapon = random.choice(possible_weapons) placed_items["Link's Uncle"] = starting_weapon pool.remove(starting_weapon) + elif world.swords[player] in ['byrna', 'somaria'] and "Link's Uncle" not in placed_items: + possible_weapons = [] + for item in pool: + if item in ['Progressive Cane', 'L1 Cane', 'L2 Cane', 'L3 Cane', 'L4 Cane', 'L5 Cane']: + possible_weapons.append(item) + starting_weapon = random.choice(possible_weapons) + placed_items["Link's Uncle"] = starting_weapon + pool.remove(starting_weapon) + elif world.swords[player] in ['cane'] and "Link's Uncle" not in placed_items: + possible_weapons = [] + for item in pool: + if item in ['Cane of Byrna', 'Cane of Somaria']: + possible_weapons.append(item) + starting_weapon = random.choice(possible_weapons) + placed_items["Link's Uncle"] = starting_weapon + pool.remove(starting_weapon) elif "Link's Uncle" not in placed_items: found_sword = False found_bow = False @@ -866,6 +884,14 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(diff.swordless) elif swords == 'bombs': pool.extend(diff.bombs_only) + elif swords == 'byrna': + pool = [item.replace('Cane of Byrna', 'Progressive Cane') for item in pool] + pool.extend(diff.cane_only) + elif swords == 'somaria': + pool = [item.replace('Cane of Somaria', 'Progressive Cane') for item in pool] + pool.extend(diff.cane_only) + elif swords == 'cane': + pool.extend(diff.cane_only) elif swords == 'vanilla': swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy() random.shuffle(swords_to_use) @@ -974,7 +1000,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", "L1 Bombs", "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" + "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", "L1 Bombs", "L2 Bombs", "L3 Bombs", "L4 Bombs", "L5 Bombs", "Progressive Bombs", "L1 Cane", "L2 Cane", "L3 Cane", "L4 Cane", "L5 Cane", "Progressive Cane", "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)]) @@ -1049,7 +1075,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', 'bombs']: + for swords in ['random', 'assured', 'swordless', 'vanilla', 'bombs', 'byrna', 'somaria', 'cane']: for progressive in ['on', 'off']: for shuffle in ['vanilla', 'full', 'crossed', 'insanity']: for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: diff --git a/Items.py b/Items.py index 9f732523..f10d2aca 100644 --- a/Items.py +++ b/Items.py @@ -168,10 +168,16 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche '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'), 'L1 Bombs': (True, False, 'SwordBomb', 0xB1, 50, 'Some basic\nexplosives\nrest here!', 'the basic grenades', 'the bomb-holding kid', 'booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'basic bombs'), 'L2 Bombs': (True, False, 'SwordBomb', 0xB2, 100, 'Some decent\nexplosives\nrest here!', 'the decent grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'L3 Bombs': (True, False, 'SwordBomb', 0xB3, 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 again', 'fancy bombs'), + 'L3 Bombs': (True, False, 'SwordBomb', 0xB3, 150, 'Some good\nexplosives\nrest here!', 'the good grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), 'L4 Bombs': (True, False, 'SwordBomb', 0xB4, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'L5 Bombs': (True, False, 'SwordBomb', 0xB5, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'Progressive Bombs': (True, False, 'SwordBomb', 0xB6, 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'), + 'L5 Bombs': (True, False, 'SwordBomb', 0xB5, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'Progressive Bombs': (True, False, 'SwordBomb', 0xB6, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'L1 Cane': (True, False, 'SwordCane', 0xB7, 50, 'A basic\nstick\nrests here!', 'the basic stick', 'the stick-holding kid', 'stick for sale', 'fungus into stick', 'cane boy improves again', 'basic cane'), + 'L2 Cane': (True, False, 'SwordCane', 0xB8, 100, 'A decent\nstick\nrests here!', 'the decent stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'L3 Cane': (True, False, 'SwordCane', 0xB9, 150, 'A good\nstick\nrests here!', 'the good stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'L4 Cane': (True, False, 'SwordCane', 0xBA, 200, 'A golden\nstick\nrests here!', 'the golden stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'L5 Cane': (True, False, 'SwordCane', 0xBB, 200, 'A golden\nstick\nrests here!', 'the golden stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'Progressive Cane': (True, False, 'SwordCane', 0xBC, 200, 'a better\nstick\nrests here!', 'the unknown stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), '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/Rom.py b/Rom.py index 657b1176..f70c9d3e 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '7c1873254dcd5fb8b18934d806cd1949' +RANDOMIZERBASEHASH = 'b61fd3ea2d9c4c0465317052fa30a721' class JsonRom(object): @@ -1006,7 +1006,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): TRIFORCE_PIECE = ItemFactory('Triforce Piece', player).code GREEN_CLOCK = ItemFactory('Green Clock', player).code - rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on + if world.swords[player] in ['byrna', 'cane']: + rom.write_byte(0x18004F, 0x00) # Byrna Invulnerability: off + else: + rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on # handle difficulty_adjustments if world.difficulty_adjustments[player] == 'hard': @@ -1060,8 +1063,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180085, 0x80) # full #Cape magic cost rom.write_bytes(0x3ADA7, [0x04, 0x08, 0x10]) - # Byrna Invulnerability: on - rom.write_byte(0x18004F, 0x01) #Enable catching fairies rom.write_byte(0x34FD6, 0xF0) # Rupoor negative value @@ -1267,6 +1268,64 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x118188, credits_string_bot("TEMPERED BOMBS")) rom.write_bytes(0x1181A6, credits_string_top("GOLD BOMBS")) rom.write_bytes(0x1181C4, credits_string_bot("GOLD BOMBS")) + elif world.swords[player] in ['byrna', 'somaria', 'cane']: + rom.write_byte(0x180040, 0x01) # open curtains + rom.write_byte(0x180041, 0x02) # swordless medallions (always) + rom.write_byte(0x180034, 0x00) # starting max bombs = 0 + + # remove magic cost of cane(s) + if world.swords[player] in ['byrna', 'cane']: + rom.write_bytes(0x045C42, [0x00, 0x00, 0x00]) + rom.write_bytes(0x045CC7, [0xEA, 0xEA]) + rom.write_byte(0x045CCD, 0x81) + rom.write_bytes(0x03B088, [0x00, 0x00, 0x00]) + rom.write_bytes(0x03B0A8, [0xEA, 0xEA]) + rom.write_byte(0x03B0AE, 0x81) + rom.write_bytes(0x18016B, [0x00, 0x00, 0x00]) + if world.swords[player] in ['somaria', 'cane']: + rom.write_bytes(0x03B07C, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + rom.write_bytes(0x03B0A8, [0xEA, 0xEA]) + rom.write_byte(0x03B0AE, 0x81) + + if world.swords[player] == 'byrna': + rom.write_byte(0x18002F, 0x03) + colr = 0x2C + elif world.swords[player] == 'somaria': + rom.write_byte(0x18002F, 0x04) + colr = 0x24 + elif world.swords[player] == 'cane': + rom.write_byte(0x18002F, 0x05) + colr = 0x28 + + spritedata = [ + 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x17, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x18, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x19, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1A, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1B, colr, + 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x17, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x18, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x19, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1A, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1B, colr, + ]; + rom.write_bytes(0x06FC51, spritedata) + + # update sword references in credits to cane + rom.write_bytes(0x11807A, credits_string_top("FIRST CANE ")) + rom.write_bytes(0x118098, credits_string_bot("FIRST CANE ")) + rom.write_bytes(0x1180B6, credits_string_top("CANELESS ")) + rom.write_bytes(0x1180D4, credits_string_bot("CANELESS ")) + rom.write_bytes(0x1180F2, credits_string_top("FIGHTER'S CANE ")) + rom.write_bytes(0x118110, credits_string_bot("FIGHTER'S CANE ")) + rom.write_bytes(0x11812E, credits_string_top("MASTER CANE ")) + rom.write_bytes(0x11814C, credits_string_bot("MASTER CANE ")) + rom.write_bytes(0x11816A, credits_string_top("TEMPERED CANE ")) + rom.write_bytes(0x118188, credits_string_bot("TEMPERED CANE ")) + rom.write_bytes(0x1181A6, credits_string_top("GOLD CANE ")) + rom.write_bytes(0x1181C4, credits_string_bot("GOLD CANE ")) # set up clocks for timed modes if world.shuffle[player] == 'vanilla': @@ -1387,15 +1446,15 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif startingstate.has('Fighter Sword', player): equip[0x359] = 1 - if startingstate.has('L5 Bombs', player): + if startingstate.has('L5 Bombs', player) or startingstate.has('L5 Cane', player): equip[0x38F] = 5 - elif startingstate.has('L4 Bombs', player): + elif startingstate.has('L4 Bombs', player) or startingstate.has('L4 Cane', player): equip[0x38F] = 4 - elif startingstate.has('L3 Bombs', player): + elif startingstate.has('L3 Bombs', player) or startingstate.has('L3 Cane', player): equip[0x38F] = 3 - elif startingstate.has('L2 Bombs', player): + elif startingstate.has('L2 Bombs', player) or startingstate.has('L2 Cane', player): equip[0x38F] = 2 - elif startingstate.has('L1 Bombs', player): + elif startingstate.has('L1 Bombs', player) or startingstate.has('L1 Cane', player) or world.swords[player] == 'cane': equip[0x38F] = 1 if startingstate.has('Mirror Shield', player): @@ -1425,6 +1484,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): 'Titans Mitts', 'Power Glove', 'Progressive Glove', 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'L5 Bombs', 'L4 Bombs', 'L3 Bombs', 'L2 Bombs', 'L1 Bombs', 'Progressive Bombs', + 'L5 Cane', 'L4 Cane', 'L3 Cane', 'L2 Cane', 'L1 Cane', 'Progressive Cane', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: diff --git a/Rules.py b/Rules.py index 135f8230..33455a0b 100644 --- a/Rules.py +++ b/Rules.py @@ -337,11 +337,11 @@ 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.bomb_mode_check(player, 1) and + set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.special_weapon_check(player, 1) and (state.has_real_sword(player) or (state.has('Fire Rod', player) and (state.can_use_bombs(player) or state.can_extend_magic(player, 9))) or # 9 fr shots or 8 with some bombs (state.has('Ice Rod', player) and state.can_use_bombs(player)) or # freeze popo and throw, bomb to finish - state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player) or state.has_bomb_level(player, 1))) # need to defeat wizzrobes, bombs don't work ... + state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player) or state.has_special_weapon_level(player, 1))) # need to defeat wizzrobes, bombs don't work ... # byrna could work with sufficient magic 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)) @@ -593,7 +593,7 @@ def global_rules(world, player): set_rule( world.get_location('Ganon', player), - lambda state: (state.has_real_sword(player, 2) or state.has_bomb_level(player, 3)) + lambda state: (state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player) and (state.has_real_sword(player, 3) or @@ -604,7 +604,7 @@ def global_rules(world, player): set_rule( world.get_entrance('Ganon Drop', player), - lambda state: state.has_real_sword(player, 2) or state.has_bomb_level(player, 3)) + lambda state: state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) # need to damage ganon to get tiles to drop def bomb_rules(world, player): @@ -860,6 +860,8 @@ def default_rules(world, player): swordless_rules(world, player) if world.swords[player] == 'bombs': bomb_mode_rules(world, player) + if world.swords[player] in ['byrna', 'somaria', 'cane']: + cane_mode_rules(world, player) if world.swords[player] in ['pseudo', 'assured_pseudo']: pseudo_sword_mode_rules(world, player) @@ -1531,11 +1533,25 @@ 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_bomb_level(player, 2)) - set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_bomb_level(player, 2)) + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) if world.mode[player] != 'inverted': - set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_bomb_level(player, 2) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_special_weapon_level(player, 2) 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 in bomb-only + add_bunny_rule(world.get_entrance('Turtle Rock', player), player) + add_bunny_rule(world.get_entrance('Misery Mire', player), player) + +def cane_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_special_weapon_level(player, 2)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) + + if world.mode[player] != 'inverted': + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_special_weapon_level(player, 2) 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 in bomb-only add_bunny_rule(world.get_entrance('Turtle Rock', player), player) diff --git a/data/base2current.bps b/data/base2current.bps index e136579b..efd73101 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 91dc20d2..92825856 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -37,6 +37,9 @@ "pseudo", "assured_pseudo", "bombs", + "byrna", + "somaria", + "cane", "vanilla" ] },