Skip to content

Commit

Permalink
Merge branch 'precompleted-refactor' into dev-fenhl
Browse files Browse the repository at this point in the history
# Conflicts:
#	Fill.py
#	ItemPool.py
#	SettingsList.py
#	World.py
  • Loading branch information
fenhl committed Nov 18, 2024
2 parents 93019e8 + 3fedc69 commit 91b5ef1
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 132 deletions.
22 changes: 11 additions & 11 deletions Dungeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def shuffle_dungeon_rewards(self) -> str:
return self.world.settings.shuffle_dungeon_rewards

@property
def empty(self) -> bool:
return self.world.empty_dungeons[self.name].empty
def precompleted(self) -> bool:
return self.world.precompleted_dungeons.get(self.name, False)

@property
def keys(self) -> list[Item]:
Expand Down Expand Up @@ -112,28 +112,28 @@ def is_dungeon_item(self, item: Item) -> bool:
return item.name in [dungeon_item.name for dungeon_item in self.all_items]

def get_restricted_dungeon_items(self) -> Iterator[Item]:
if self.shuffle_mapcompass == 'dungeon' or (self.empty and self.shuffle_mapcompass in ['any_dungeon', 'overworld', 'keysanity', 'regional']):
if self.shuffle_mapcompass == 'dungeon' or (self.precompleted and self.shuffle_mapcompass in ('any_dungeon', 'overworld', 'keysanity', 'regional')):
yield from self.dungeon_items
if self.shuffle_smallkeys == 'dungeon' or (self.empty and self.shuffle_smallkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']):
if self.shuffle_smallkeys == 'dungeon' or (self.precompleted and self.shuffle_smallkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional')):
yield from self.small_keys
if self.shuffle_bosskeys == 'dungeon' or (self.empty and self.shuffle_bosskeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']):
if self.shuffle_bosskeys == 'dungeon' or (self.precompleted and self.shuffle_bosskeys in ('any_dungeon', 'overworld', 'keysanity', 'regional')):
yield from self.boss_key
if self.shuffle_silver_rupees == 'dungeon' or (self.empty and self.shuffle_silver_rupees in ['any_dungeon', 'overworld', 'anywhere', 'regional']):
if self.shuffle_silver_rupees == 'dungeon' or (self.precompleted and self.shuffle_silver_rupees in ('any_dungeon', 'overworld', 'anywhere', 'regional')):
yield from self.silver_rupees
if self.shuffle_dungeon_rewards in ('vanilla', 'dungeon'): # we don't lock rewards inside pre-completed dungeons since they're still useful outside
yield from self.reward

# get a list of items that don't have to be in their proper dungeon
def get_unrestricted_dungeon_items(self) -> Iterator[Item]:
if self.empty:
if self.precompleted:
return
if self.shuffle_mapcompass in ['any_dungeon', 'overworld', 'keysanity', 'regional']:
if self.shuffle_mapcompass in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
yield from self.dungeon_items
if self.shuffle_smallkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']:
if self.shuffle_smallkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
yield from self.small_keys
if self.shuffle_bosskeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']:
if self.shuffle_bosskeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
yield from self.boss_key
if self.shuffle_silver_rupees in ['any_dungeon', 'overworld', 'anywhere', 'regional']:
if self.shuffle_silver_rupees in ('any_dungeon', 'overworld', 'anywhere', 'regional'):
yield from self.silver_rupees
if self.shuffle_dungeon_rewards in ('any_dungeon', 'overworld', 'anywhere', 'regional'):
yield from self.reward
Expand Down
9 changes: 6 additions & 3 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,11 @@ def distribute_items_restrictive(worlds: list[World], fill_locations: Optional[l

# If some dungeons are supposed to be empty, fill them with useless items.
if any(world.settings.empty_dungeons_mode != 'none' for world in worlds):
empty_locations = [location for location in fill_locations
if location.world.empty_dungeons[HintArea.at(location).dungeon_name].empty]
empty_locations = [
location
for location in fill_locations
if location.world.precompleted_dungeons.get(HintArea.at(location).dungeon_name, False)
]
for location in empty_locations:
fill_locations.remove(location)

Expand Down Expand Up @@ -289,7 +292,7 @@ def fill_dungeon_unique_item(worlds: list[World], search: Search, fill_locations
if world.settings.one_item_per_dungeon
and world.settings.empty_dungeons_mode != 'none'
for dungeon in world.dungeons
if not world.empty_dungeons[dungeon.name].empty
if not world.precompleted_dungeons.get(dungeon.name, False)
]

double_dungeons = []
Expand Down
113 changes: 57 additions & 56 deletions Hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ def get_barren_hint(spoiler: Spoiler, world: World, checked: set[str], all_check
areas = list(filter(lambda area:
area not in checked_areas
and str(area) not in world.hint_type_overrides['barren']
and not world.precompleted_dungeons.get(area.dungeon_name, False)
and not (world.barren_dungeon >= world.hint_dist_user['dungeons_barren_limit'] and world.empty_areas[area]['dungeon'])
and any(
location.name not in all_checked
Expand Down Expand Up @@ -1045,13 +1046,22 @@ def get_barren_hint(spoiler: Spoiler, world: World, checked: set[str], all_check
return GossipText("plundering %s is a foolish choice." % area.text(world.settings.clearer_hints), ['Pink']), None


def is_not_checked(locations: Iterable[Location], checked: set[HintArea | str]) -> bool:
return not any(location.name in checked or HintArea.at(location) in checked for location in locations)
def is_checked(locations: Iterable[Location], checked: set[HintArea | str]) -> bool:
for location in locations:
if location.name in checked:
return True
hint_area = HintArea.at(location)
if hint_area in checked:
return True
if location.world.precompleted_dungeons.get(hint_area.dungeon_name, False):
# don't hint locations in precompleted dungeons
return True
return False


def get_good_item_hint(spoiler: Spoiler, world: World, checked: set[str]) -> HintReturn:
locations = list(filter(lambda location:
is_not_checked([location], checked)
not is_checked([location], checked)
and ((location.item.majoritem
and location.item.name not in unHintableWothItems)
or location.name in world.added_hint_types['item']
Expand Down Expand Up @@ -1088,22 +1098,20 @@ def get_specific_item_hint(spoiler: Spoiler, world: World, checked: set[str]) ->
if itemname == "Bottle" and world.settings.hint_dist == "bingo":
locations = [
location for location in world.get_filled_locations()
if (is_not_checked([location], checked)
and location.name not in world.hint_exclusions
and location.item.name in bingoBottlesForHints
and not location.locked
and location.name not in world.hint_type_overrides['named-item']
)
if not is_checked([location], checked)
and location.name not in world.hint_exclusions
and location.item.name in bingoBottlesForHints
and not location.locked
and location.name not in world.hint_type_overrides['named-item']
]
else:
locations = [
location for location in world.get_filled_locations()
if (is_not_checked([location], checked)
and location.name not in world.hint_exclusions
and location.item.name == itemname
and not location.locked
and location.name not in world.hint_type_overrides['named-item']
)
if not is_checked([location], checked)
and location.name not in world.hint_exclusions
and location.item.name == itemname
and not location.locked
and location.name not in world.hint_type_overrides['named-item']
]

if len(locations) > 0:
Expand Down Expand Up @@ -1164,24 +1172,24 @@ def get_specific_item_hint(spoiler: Spoiler, world: World, checked: set[str]) ->
if itemname == "Bottle" and world.settings.hint_dist == "bingo":
locations = [
location for location in named_item_locations
if (is_not_checked([location], checked)
and location.item.world.id == world.id
and location.name not in world.hint_exclusions
and location.item.name in bingoBottlesForHints
and not location.locked
and (itemname, world.id) not in always_locations
and location.name not in world.hint_type_overrides['named-item'])
if not is_checked([location], checked)
and location.item.world.id == world.id
and location.name not in world.hint_exclusions
and location.item.name in bingoBottlesForHints
and not location.locked
and (itemname, world.id) not in always_locations
and location.name not in world.hint_type_overrides['named-item']
]
else:
locations = [
location for location in named_item_locations
if (is_not_checked([location], checked)
and location.item.world.id == world.id
and location.name not in world.hint_exclusions
and location.item.name == itemname
and not location.locked
and (itemname, world.id) not in always_locations
and location.name not in world.hint_type_overrides['named-item'])
if not is_checked([location], checked)
and location.item.world.id == world.id
and location.name not in world.hint_exclusions
and location.item.name == itemname
and not location.locked
and (itemname, world.id) not in always_locations
and location.name not in world.hint_type_overrides['named-item']
]

if len(locations) > 0:
Expand Down Expand Up @@ -1218,14 +1226,13 @@ def get_specific_item_hint(spoiler: Spoiler, world: World, checked: set[str]) ->

def get_random_location_hint(spoiler: Spoiler, world: World, checked: set[str]) -> HintReturn:
locations = list(filter(lambda location:
is_not_checked([location], checked)
not is_checked([location], checked)
and location.item.type not in ('Drop', 'Event', 'Shop')
and not is_restricted_dungeon_item(location.item)
and not location.locked
and location.name not in world.hint_exclusions
and location.name not in world.hint_type_overrides['item']
and location.item.name not in world.item_hint_type_overrides['item']
and (location.world.settings.empty_dungeons_mode == 'none' or not location.world.empty_dungeons[HintArea.at(location).dungeon_name].empty),
and location.item.name not in world.item_hint_type_overrides['item'],
world.get_filled_locations()))
if not locations:
return None
Expand All @@ -1244,25 +1251,19 @@ def get_random_location_hint(spoiler: Spoiler, world: World, checked: set[str])


def get_specific_hint(spoiler: Spoiler, world: World, checked: set[str], hint_type: str) -> HintReturn:
def is_valid_hint(hint: Hint) -> bool:
location = world.get_location(hint.name)
if not is_not_checked([world.get_location(hint.name)], checked):
return False
if location.world.settings.empty_dungeons_mode != 'none' and location.world.empty_dungeons[HintArea.at(location).dungeon_name].empty:
return False
return True

hint_group = get_hint_group(hint_type, world)
hint_group = list(filter(is_valid_hint, hint_group))
hint_group = list(filter(lambda hint: not is_checked([world.get_location(hint.name)], checked), hint_group))
if not hint_group:
return None

hint = random.choice(hint_group)

if world.hint_dist_user['upgrade_hints'] in ['on', 'limited']:
if world.hint_dist_user['upgrade_hints'] in ('on', 'limited'):
upgrade_list = get_upgrade_hint_list(world, [hint.name])
upgrade_list = list(filter(lambda upgrade: is_not_checked([world.get_location(location) for location in get_multi(
upgrade.name).locations], checked), upgrade_list))
upgrade_list = list(filter(
lambda upgrade: not is_checked([world.get_location(location) for location in get_multi(upgrade.name).locations], checked),
upgrade_list,
))

if upgrade_list is not None:
multi = None
Expand Down Expand Up @@ -1309,8 +1310,10 @@ def get_dungeon_hint(spoiler: Spoiler, world: World, checked: set[str]) -> HintR

def get_random_multi_hint(spoiler: Spoiler, world: World, checked: set[str], hint_type: str) -> HintReturn:
hint_group = get_hint_group(hint_type, world)
multi_hints = list(filter(lambda hint: is_not_checked([world.get_location(location) for location in get_multi(
hint.name).locations], checked), hint_group))
multi_hints = list(filter(
lambda hint: not is_checked([world.get_location(location) for location in get_multi(hint.name).locations], checked),
hint_group,
))

if not multi_hints:
return None
Expand All @@ -1321,8 +1324,10 @@ def get_random_multi_hint(spoiler: Spoiler, world: World, checked: set[str], hin
multi = get_multi(hint.name)

upgrade_list = get_upgrade_hint_list(world, multi.locations)
upgrade_list = list(filter(lambda upgrade: is_not_checked([world.get_location(location) for location in get_multi(
upgrade.name).locations], checked), upgrade_list))
upgrade_list = list(filter(
lambda upgrade: not is_checked([world.get_location(location) for location in get_multi(upgrade.name).locations], checked),
upgrade_list,
))

if upgrade_list:
for upgrade in upgrade_list:
Expand Down Expand Up @@ -1608,12 +1613,6 @@ def build_gossip_hints(spoiler: Spoiler, worlds: list[World]) -> None:
if item_world.id not in checked_locations:
checked_locations[item_world.id] = set()
checked_locations[item_world.id].add(location.name)
for dungeon_name, info in world.empty_dungeons.items():
if info.empty:
for region in world.regions:
if region.dungeon != None and region.dungeon.name == dungeon_name:
precompleted_locations = list(map(lambda location: location.name, region.locations))
checked_locations[world.id].update(precompleted_locations)

# Build all the hints.
for world in worlds:
Expand Down Expand Up @@ -1773,8 +1772,10 @@ def build_world_gossip_hints(spoiler: Spoiler, world: World, checked_locations:

# Add required location hints, only if hint copies > 0
if hint_dist['always'][1] > 0:
always_locations = list(filter(lambda hint: is_not_checked([world.get_location(hint.name)], checked_always_locations),
get_hint_group('always', world)))
always_locations = list(filter(
lambda hint: not is_checked([world.get_location(hint.name)], checked_always_locations),
get_hint_group('always', world),
))
for hint in always_locations:
location = world.get_location(hint.name)
checked_always_locations.add(hint.name)
Expand Down
4 changes: 2 additions & 2 deletions ItemPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
world.state.collect(ItemFactory(item, world))
item = get_junk_item()[0]
shuffle_item = True
elif shuffle_setting in ('any_dungeon', 'overworld', 'keysanity', 'regional', 'anywhere') and not world.empty_dungeons[dungeon.name].empty:
elif shuffle_setting in ('any_dungeon', 'overworld', 'keysanity', 'regional', 'anywhere') and not world.precompleted_dungeons.get(dungeon.name, False):
shuffle_item = True
elif shuffle_item is None:
dungeon_collection.append(ItemFactory(item, world))
Expand Down Expand Up @@ -1007,7 +1007,7 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
world.state.collect(ItemFactory('Small Key (Shadow Temple)', world))

if (
(not world.keysanity or (world.empty_dungeons['Fire Temple'].empty and world.settings.shuffle_smallkeys != 'remove'))
(not world.keysanity or (world.precompleted_dungeons['Fire Temple'] and world.settings.shuffle_smallkeys != 'remove'))
and (world.settings.shuffle_base_item_pool or world.settings.shuffle_smallkeys != 'vanilla')
and not world.dungeon_mq['Fire Temple']
):
Expand Down
4 changes: 2 additions & 2 deletions Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -2926,8 +2926,8 @@ def configure_dungeon_info(rom: Rom, world: World) -> None:
if location.world.id == world.id and area.is_dungeon:
dungeon_rewards[codes.index(area.dungeon_name)] = boss_reward_index(location.item)

dungeon_is_mq = [1 if world.dungeon_mq.get(c) else 0 for c in codes]
dungeon_precompleted = [1 if world.empty_dungeons[c].empty else 0 for c in codes]
dungeon_is_mq = [int(world.dungeon_mq.get(c, False)) for c in codes]
dungeon_precompleted = [int(world.precompleted_dungeons.get(c, False)) for c in codes]

rom.write_int32(rom.sym('CFG_DUNGEON_INFO_ENABLE'), 2)
rom.write_int32(rom.sym('CFG_DUNGEON_INFO_MQ_ENABLE'), int(mq_enable))
Expand Down
Loading

0 comments on commit 91b5ef1

Please sign in to comment.