From dd6d8e508ff04c0f878f87117a0b86a7a0c0b7f1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 11 Oct 2022 07:12:52 -0500 Subject: [PATCH] Replaced bomb/rupee logic with pseudo locations/items --- BaseClasses.py | 157 ++++++------------------------------------------- ItemList.py | 105 +++++++++++++++++++++++++++++++++ Items.py | 2 + Main.py | 9 +-- Rules.py | 3 +- 5 files changed, 131 insertions(+), 145 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 81999759..b03e48a5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1087,150 +1087,14 @@ class CollectionState(object): return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player)) def can_farm_rupees(self, player): - tree_pulls = ['Lost Woods East Area', - 'Snitch Lady (East)', - 'Turtle Rock Area', - 'Pyramid Area', - 'Hype Cave Area', - 'Dark South Pass Area', - 'Bumper Cave Area'] - pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] - post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] + return self.has('Farmable Rupees', player) - rupee_farms = ['Archery Game', '50 Rupee Cave', '20 Rupee Cave'] - - bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] - pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] - rock_crabs = ['Desert Pass Area'] - - def can_reach_non_bunny(regionname): - region = self.world.get_region(regionname, player) - return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has_Pearl(player)) - - for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']: - if can_reach_non_bunny(region): - return True - - # tree pulls - if self.can_kill_most_things(player) and any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']): - for region in tree_pulls: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - else: - for region in post_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - - # bush crabs (final item isn't considered) - if self.world.enemy_shuffle[player] == 'none': - if self.world.prizes[player]['crab'][0] in [0xda, 0xdb]: - for region in bush_crabs: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_bush_crabs: - if can_reach_non_bunny(region): - return True - if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xda, 0xdb]: - for region in rock_crabs: - if can_reach_non_bunny(region): - return True - - return False - def can_farm_bombs(self, player): if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player): return True - - bush_bombs = ['Flute Boy Approach Area', - 'Kakariko Area', - 'Village of Outcasts Area', - 'Forgotten Forest Area', - 'Bat Cave Ledge', - 'East Dark Death Mountain (Bottom)'] - rock_bombs = ['Links House Area', - 'Dark Chapel Area', - 'Wooden Bridge Area', - 'Ice Cave Area', - 'Eastern Nook Area', - 'West Death Mountain (Bottom)', - 'Kakariko Fortune Area', - 'Skull Woods Forest', - 'Catfish Area', - 'Dark Fortune Area', - 'Qirn Jump Area', - 'Shield Shop Area', - 'Palace of Darkness Nook Area', - 'Swamp Nook Area', - 'Dark South Pass Area'] - bonk_bombs = ['Kakariko Fortune Area', 'Dark Graveyard Area'] #TODO: Flute Boy Approach Area and Bonk Rock Ledge are available post-Aga - bomb_caves = ['Graveyard Cave', 'Light World Bomb Hut'] - tree_pulls = ['Lost Woods East Area', - 'Snitch Lady (East)', - 'Turtle Rock Area', - 'Pyramid Area', - 'Hype Cave Area', - 'Dark South Pass Area', - 'Bumper Cave Area'] - pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] - post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] - - bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] - pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] - rock_crabs = ['Desert Pass Area'] - - def can_reach_non_bunny(regionname): - region = self.world.get_region(regionname, player) - return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has_Pearl(player)) - - # bomb pickups - for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []): - if can_reach_non_bunny(region): - return True - - if self.can_lift_rocks(player): - for region in rock_bombs: - if can_reach_non_bunny(region): - return True - - if not self.world.shuffle_bonk_drops[player] and self.can_collect_bonkdrops(player): - for region in bonk_bombs: - if can_reach_non_bunny(region): - return True - - # tree pulls - if self.can_kill_most_things(player) and any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']): - for region in tree_pulls: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - else: - for region in post_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - - # bush crabs (final item isn't considered) - if self.world.enemy_shuffle[player] == 'none': - if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]: - for region in bush_crabs: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_bush_crabs: - if can_reach_non_bunny(region): - return True - if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]: - for region in rock_crabs: - if can_reach_non_bunny(region): - return True + if self.has('Farmable Bombs', player): + return True # stun prize if self.can_stun_enemies(player) and self.world.prizes[player]['stun'] in [0xdc, 0xdd, 0xde]: @@ -1239,6 +1103,7 @@ class CollectionState(object): # bomb purchases if self.can_farm_rupees(player) and (self.can_buy_unlimited('Bombs (10)', player) or self.can_reach('Big Bomb Shop', None, player)): return True + return False def item_count(self, item, player): @@ -1391,7 +1256,7 @@ class CollectionState(object): if self.has_Pearl(player): return True - return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world + return not region.can_cause_bunny(player) def can_reach_light_world(self, player): if True in [i.is_light_world for i in self.reachable_regions[player]]: @@ -1635,6 +1500,12 @@ class Region(object): return True + def can_cause_bunny(self, player): + if 'Moon Pearl' in list(map(str, [i for i in self.world.precollected_items if i.player == player])): + return False + + return self.is_dark_world if self.world.mode[player] != 'inverted' else self.is_light_world + def __str__(self): return str(self.__unicode__()) @@ -1830,6 +1701,9 @@ class Entrance(object): return found + def can_cause_bunny(self, player): + return self.parent_region.can_cause_bunny(player) + def connect(self, region, addresses=None, target=None, vanilla=None): self.connected_region = region self.target = target @@ -2681,6 +2555,9 @@ class Location(object): name += f' ({world.get_player_names(self.player)})' return name + def can_cause_bunny(self, player): + return self.parent_region.can_cause_bunny(player) + def __str__(self): return str(self.__unicode__()) diff --git a/ItemList.py b/ItemList.py index 7c252859..bd0aec53 100644 --- a/ItemList.py +++ b/ItemList.py @@ -505,6 +505,111 @@ def create_dynamic_shop_locations(world, player): loc.locked = True +def create_farm_locations(world, player): + bush_bombs = ['Flute Boy Approach Area', + 'Kakariko Area', + 'Village of Outcasts Area', + 'Forgotten Forest Area', + 'Bat Cave Ledge', + 'East Dark Death Mountain (Bottom)'] + rock_bombs = ['Links House Area', + 'Dark Chapel Area', + 'Wooden Bridge Area', + 'Ice Cave Area', + 'Eastern Nook Area', + 'West Death Mountain (Bottom)', + 'Kakariko Fortune Area', + 'Skull Woods Forest', + 'Catfish Area', + 'Dark Fortune Area', + 'Qirn Jump Area', + 'Shield Shop Area', + 'Palace of Darkness Nook Area', + 'Swamp Nook Area', + 'Dark South Pass Area'] + bonk_bombs = ['Kakariko Fortune Area', 'Dark Graveyard Area'] #TODO: Flute Boy Approach Area and Bonk Rock Ledge are available post-Aga + bomb_caves = ['Graveyard Cave', 'Light World Bomb Hut'] + + rupee_caves = ['50 Rupee Cave', '20 Rupee Cave'] + rupee_games = ['Archery Game'] + + tree_pulls = ['Lost Woods East Area', + 'Snitch Lady (East)', + 'Turtle Rock Area', + 'Pyramid Area', + 'Hype Cave Area', + 'Dark South Pass Area', + 'Bumper Cave Area'] + pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] + post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] + + bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] + pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] + rock_crabs = ['Desert Pass Area'] + + # NOTE: Altho pre-Aga locations cannot technically be guaranteed by the player, the + # goal here is just to ensure access to early rupees/bombs to get the player started, + # and hopefully access to more permanent farm locations + + def create_and_fill_location(region_name, loc_description, item_name): + region = world.get_region(region_name, player) + loc = Location(player, f'{region_name} {loc_description}', 0, region) + loc.type = LocationType.Logical + loc.parent_region = region + loc.event = True + loc.locked = True + loc.address = None + + world.push_item(loc, ItemFactory(item_name, player), False) + + region.locations.append(loc) + world.dynamic_locations.append(loc) + + return loc + + from Rules import set_rule, add_rule, add_bunny_rule + for region in bush_bombs: + loc = create_and_fill_location(region, 'Bush Drop', 'Farmable Bombs') + add_bunny_rule(loc, player) + for region in rock_bombs: + loc = create_and_fill_location(region, 'Rock Drop', 'Farmable Bombs') + set_rule(loc, lambda state: state.can_lift_rocks(player)) + add_bunny_rule(loc, player) + if not world.shuffle_bonk_drops[player]: + for region in bonk_bombs: + loc = create_and_fill_location(region, 'Bonk Drop', 'Farmable Bombs') + set_rule(loc, lambda state: state.can_collect_bonkdrops(player)) + add_bunny_rule(loc, player) + if world.pottery[player] in ['none', 'keys', 'dungeon']: + for region in bomb_caves + rupee_caves: + loc = create_and_fill_location(region, 'Pot Drop', 'Farmable Rupees' if region in rupee_caves else 'Farmable Bombs') + add_bunny_rule(loc, player) + for region in rupee_games: + loc = create_and_fill_location(region, 'Prize', 'Farmable Rupees') + add_bunny_rule(loc, player) + if any(i in [0xda, 0xdb, 0xdc, 0xdd, 0xde] for i in world.prizes[player]['pull']): + rupee_farm = any(i in [0xda, 0xdb] for i in world.prizes[player]['pull']) + for region in tree_pulls + pre_aga_tree_pulls + post_aga_tree_pulls: + loc = create_and_fill_location(region, 'Tree Pull', 'Farmable Rupees' if rupee_farm else 'Farmable Bombs') + set_rule(loc, lambda state: state.can_kill_most_things(player)) + if region in pre_aga_tree_pulls: + add_rule(loc, lambda state: not state.has_beaten_aga(player)) + elif region in post_aga_tree_pulls: + add_rule(loc, lambda state: state.has_beaten_aga(player)) + add_bunny_rule(loc, player) + if world.enemy_shuffle[player] == 'none' and any(i in [0xda, 0xdb, 0xdc, 0xdd, 0xde] for i in world.prizes[player]['crab']): + rupee_farm = any(i in [0xda, 0xdb] for i in world.prizes[player]['crab']) + for region in bush_crabs + pre_aga_bush_crabs + rock_crabs: + loc = create_and_fill_location(region, 'Crab Drop', 'Farmable Rupees' if rupee_farm else 'Farmable Bombs') + if region in pre_aga_bush_crabs: + set_rule(loc, lambda state: not state.has_beaten_aga(player)) + elif region in rock_crabs: + set_rule(loc, lambda state: state.can_lift_rocks(player)) + add_bunny_rule(loc, player) + + world.clear_location_cache() + + def create_dynamic_bonkdrop_locations(world, player): from Regions import bonk_prize_table for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items(): diff --git a/Items.py b/Items.py index 10d93159..59e6c228 100644 --- a/Items.py +++ b/Items.py @@ -198,4 +198,6 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), + 'Farmable Bombs': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), + 'Farmable Rupees': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), } diff --git a/Main.py b/Main.py index f1762147..f91f29c7 100644 --- a/Main.py +++ b/Main.py @@ -26,7 +26,7 @@ from Rules import set_rules from Dungeons import create_dungeons from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_pots from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops -from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops +from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, create_farm_locations from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config @@ -212,6 +212,10 @@ def main(args, seed=None, fish=None): mark_dark_world_regions(world, player) logger.info(world.fish.translate("cli", "cli", "generating.itempool")) + for player in range(1, world.players + 1): + set_prize_drops(world, player) + create_farm_locations(world, player) + for player in range(1, world.players + 1): generate_itempool(world, player) @@ -229,9 +233,6 @@ def main(args, seed=None, fish=None): else: lock_shop_locations(world, player) - for player in range(1, world.players + 1): - set_prize_drops(world, player) - massage_item_pool(world) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) diff --git a/Rules.py b/Rules.py index 5a7516f5..a0aafd7e 100644 --- a/Rules.py +++ b/Rules.py @@ -135,7 +135,8 @@ def add_rule(spot, rule, combine='and'): spot.access_rule = lambda state: rule(state) and old_rule(state) def add_bunny_rule(spot, player): - add_rule(spot, lambda state: state.is_not_bunny(spot.parent_region, player)) + if spot.can_cause_bunny(player): + add_rule(spot, lambda state: state.has_Pearl(player)) def or_rule(rule1, rule2):