Replaced bomb/rupee logic with pseudo locations/items

This commit is contained in:
codemann8
2022-10-11 07:12:52 -05:00
parent fca4d0424e
commit dd6d8e508f
5 changed files with 131 additions and 145 deletions

View File

@@ -1087,150 +1087,14 @@ class CollectionState(object):
return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player)) return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))
def can_farm_rupees(self, player): def can_farm_rupees(self, player):
tree_pulls = ['Lost Woods East Area', return self.has('Farmable Rupees', player)
'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']
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): def can_farm_bombs(self, player):
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player): if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
return True return True
bush_bombs = ['Flute Boy Approach Area', if self.has('Farmable Bombs', player):
'Kakariko Area', return True
'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
# stun prize # stun prize
if self.can_stun_enemies(player) and self.world.prizes[player]['stun'] in [0xdc, 0xdd, 0xde]: 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 # 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)): 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 True
return False return False
def item_count(self, item, player): def item_count(self, item, player):
@@ -1391,7 +1256,7 @@ class CollectionState(object):
if self.has_Pearl(player): if self.has_Pearl(player):
return True 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): def can_reach_light_world(self, player):
if True in [i.is_light_world for i in self.reachable_regions[player]]: if True in [i.is_light_world for i in self.reachable_regions[player]]:
@@ -1635,6 +1500,12 @@ class Region(object):
return True 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): def __str__(self):
return str(self.__unicode__()) return str(self.__unicode__())
@@ -1830,6 +1701,9 @@ class Entrance(object):
return found 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): def connect(self, region, addresses=None, target=None, vanilla=None):
self.connected_region = region self.connected_region = region
self.target = target self.target = target
@@ -2681,6 +2555,9 @@ class Location(object):
name += f' ({world.get_player_names(self.player)})' name += f' ({world.get_player_names(self.player)})'
return name return name
def can_cause_bunny(self, player):
return self.parent_region.can_cause_bunny(player)
def __str__(self): def __str__(self):
return str(self.__unicode__()) return str(self.__unicode__())

View File

@@ -505,6 +505,111 @@ def create_dynamic_shop_locations(world, player):
loc.locked = True 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): def create_dynamic_bonkdrop_locations(world, player):
from Regions import bonk_prize_table from Regions import bonk_prize_table
for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items(): for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items():

View File

@@ -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), '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 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), '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),
} }

View File

@@ -26,7 +26,7 @@ from Rules import set_rules
from Dungeons import create_dungeons from Dungeons import create_dungeons
from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_pots 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 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 Utils import output_path, parse_player_names
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config 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) mark_dark_world_regions(world, player)
logger.info(world.fish.translate("cli", "cli", "generating.itempool")) 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): for player in range(1, world.players + 1):
generate_itempool(world, player) generate_itempool(world, player)
@@ -229,9 +233,6 @@ def main(args, seed=None, fish=None):
else: else:
lock_shop_locations(world, player) lock_shop_locations(world, player)
for player in range(1, world.players + 1):
set_prize_drops(world, player)
massage_item_pool(world) massage_item_pool(world)
logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes"))

View File

@@ -135,7 +135,8 @@ def add_rule(spot, rule, combine='and'):
spot.access_rule = lambda state: rule(state) and old_rule(state) spot.access_rule = lambda state: rule(state) and old_rule(state)
def add_bunny_rule(spot, player): 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): def or_rule(rule1, rule2):