Merge branch 'DoorDevUnstable' into Synthesis

# Conflicts:
#	Bosses.py
#	Main.py
#	Rom.py
#	data/base2current.bps
This commit is contained in:
aerinon
2022-08-11 15:43:33 -06:00
6 changed files with 78 additions and 69 deletions

View File

@@ -223,12 +223,15 @@ def place_bosses(world, player):
for u, level in used_bosses:
if not level:
bosses.remove(u)
gt_bosses = []
for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '')
try:
if level:
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)
and b not in gt_bosses])
gt_bosses.append(boss)
else:
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
bosses.remove(boss)

85
Fill.py
View File

@@ -3,6 +3,7 @@ import collections
import itertools
import logging
import math
from contextlib import suppress
from BaseClasses import CollectionState, FillError, LocationType
from Items import ItemFactory
@@ -35,17 +36,6 @@ def dungeon_tracking(world):
def fill_dungeons_restrictive(world, shuffled_locations):
dungeon_tracking(world)
all_state_base = world.get_all_state()
# for player in range(1, world.players + 1):
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
# if world.retro[player]:
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
# else:
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
# pinball_room.event = True
# pinball_room.locked = True
# shuffled_locations.remove(pinball_room)
# with shuffled dungeon items they are distributed as part of the normal item pool
for item in world.get_items():
@@ -55,17 +45,28 @@ def fill_dungeons_restrictive(world, shuffled_locations):
item.priority = True
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
bigs, smalls, others = [], [], []
for i in dungeon_items:
(bigs if i.bigkey else smalls if i.smallkey else others).append(i)
# sort in the order Big Key, Small Key, Other before placing dungeon items
sort_order = {"BigKey": 3, "SmallKey": 2}
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
def fill(base_state, items, key_pool):
fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True)
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)},
single_player_placement=True)
all_state_base = world.get_all_state()
big_state_base = all_state_base.copy()
for x in smalls + others:
big_state_base.collect(x, True)
fill(big_state_base, bigs, smalls)
random.shuffle(shuffled_locations)
small_state_base = all_state_base.copy()
for x in others:
small_state_base.collect(x, True)
fill(small_state_base, smalls, smalls)
random.shuffle(shuffled_locations)
fill(all_state_base, others, None)
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False,
def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False,
vanilla=False):
def sweep_from_pool():
new_state = base_state.copy()
@@ -101,8 +102,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
item_locations = filter_locations(item_to_place, locations, world, vanilla)
for location in item_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
single_player_placement, perform_access_check, itempool,
keys_in_itempool, world)
single_player_placement, perform_access_check, key_pool, world)
if spot_to_fill:
break
if spot_to_fill is None:
@@ -111,7 +111,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
continue
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
base_state, itempool, perform_access_check, item_locations,
keys_in_itempool, single_player_placement)
key_pool, single_player_placement)
if spot_to_fill is None:
# we filled all reachable spots. Maybe the game can be beaten anyway?
unplaced_items.insert(0, item_to_place)
@@ -123,6 +123,10 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, False)
# todo: remove key item from key_pool
if item_to_place.smallkey:
with suppress(ValueError):
key_pool.remove(item_to_place)
track_outside_keys(item_to_place, spot_to_fill, world)
track_dungeon_items(item_to_place, spot_to_fill, world)
locations.remove(spot_to_fill)
@@ -132,7 +136,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
itempool, keys_in_itempool, world):
key_pool, world):
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
location.item = item_to_place
test_state = max_exp_state.copy()
@@ -141,8 +145,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
test_state = max_exp_state
if not single_player_placement or location.player == item_to_place.player:
if location.can_fill(test_state, item_to_place, perform_access_check):
test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool
if valid_key_placement(item_to_place, location, test_pool, world):
if valid_key_placement(item_to_place, location, key_pool, world):
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
return location
if item_to_place.smallkey or item_to_place.bigkey:
@@ -150,7 +153,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
return None
def valid_key_placement(item, location, itempool, world):
def valid_key_placement(item, location, key_pool, world):
if not valid_reserved_placement(item, location, world):
return False
if ((not item.smallkey and not item.bigkey) or item.player != location.player
@@ -161,7 +164,7 @@ def valid_key_placement(item, location, itempool, world):
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
return True
key_logic = world.key_logic[item.player][dungeon.name]
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player])
prize_loc = None
if key_logic.prize_location:
prize_loc = world.get_location(key_logic.prize_location, location.player)
@@ -216,16 +219,16 @@ def is_dungeon_item(item, world):
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
keys_in_itempool=None, single_player_placement=False):
key_pool=None, single_player_placement=False):
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
if world.algorithm in ['balanced', 'equitable']:
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool,
single_player_placement)
elif world.algorithm == 'vanilla_fill':
if item_to_place.type == 'Crystal':
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
keys_in_itempool, single_player_placement)
key_pool, single_player_placement)
else:
i, config = 0, world.item_pool_config
tried = set(attempted)
@@ -235,7 +238,7 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
other_locs = [x for x in locations if x.name in fallback_locations]
for location in other_locs:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
perform_access_check, key_pool, world)
if spot_to_fill:
return spot_to_fill
i += 1
@@ -244,14 +247,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
other_locations = vanilla_fallback(item_to_place, locations, world)
for location in other_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
perform_access_check, key_pool, world)
if spot_to_fill:
return spot_to_fill
tried.update(other_locations)
other_locations = [x for x in locations if x not in tried]
for location in other_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
perform_access_check, key_pool, world)
if spot_to_fill:
return spot_to_fill
return None
@@ -259,14 +262,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
other_locations = [x for x in locations if x not in attempted]
for location in other_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
perform_access_check, key_pool, world)
if spot_to_fill:
return spot_to_fill
return None
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
keys_in_itempool=None, single_player_placement=False):
key_pool=None, single_player_placement=False):
def location_preference(loc):
if not loc.item.advancement:
return 1
@@ -284,21 +287,21 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
swap_locations = sorted(possible_swaps, key=location_preference)
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
keys_in_itempool, single_player_placement)
key_pool, single_player_placement)
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
keys_in_itempool=None, single_player_placement=False):
key_pool=None, single_player_placement=False):
for location in swap_locations:
old_item = location.item
new_pool = list(itempool) + [old_item]
new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool,
keys_in_itempool, single_player_placement)
key_pool, single_player_placement)
if new_spot:
restore_item = new_spot.item
new_spot.item = item_to_place
swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool,
keys_in_itempool, single_player_placement)
key_pool, single_player_placement)
if swap_spot:
logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}')
world.push_item(swap_spot, old_item, False)
@@ -414,13 +417,13 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
# todo: crossed
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
key_pool = [x for x in progitempool if x.smallkey]
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
if world.algorithm == 'vanilla_fill':
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True)
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True)
fill_restrictive(world, world.state, fill_locations, progitempool, key_pool)
random.shuffle(fill_locations)
if world.algorithm == 'balanced':
fast_fill(world, prioitempool, fill_locations)

View File

@@ -33,7 +33,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data
from source.classes.CustomSettings import CustomSettings
__version__ = '1.0.1.0-x'
__version__ = '1.0.1.1-x'
from source.classes.BabelFish import BabelFish

View File

@@ -787,12 +787,13 @@ vanilla_pots = {
Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))],
0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))],
0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))],
0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x23, 0xFA])),
Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x23, 0xFA])),
Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x2B, 0xFA])),
Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x2B, 0xFA])),
Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7AC, [0xBB, 0x53, 0xFA])),
Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7AF, [0xC3, 0x53, 0xFA]))],
# note: these addresses got moved thanks to waterfall fairy edit
0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])),
Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])),
Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])),
Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])),
Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])),
Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))],
0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A
Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])),
Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])),

View File

@@ -207,6 +207,12 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
#### Unstable
* 1.0.1.1
* Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be
* Certain pot items no longer reload when reloading the supertile (matches original pot behavior better)
* Changed the key distribution that made small keys placement more random when keys are in their own dungeon
* Unique boss shuffle no longer allows repeat bosses in GT (e.g. only one Trinexx in GT, so exactly 3 bosses are repeated in the seed. This is a difference process than full which does affect the probability distribution.)
* Removed text color in hints due to vanilla bug
* 1.0.1.0
* Large features
* New pottery modes - see notes above
@@ -230,7 +236,6 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
* Updated tourney winners (included Doors Async League winners)
* Some textual changes for hints (capitalization standardization)
* Item will be highlighted in red if experimental is on. This will likely be removed.
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.
* Expanded Mystery logic options (e.g. owglitches)
* Updated indicators on keysanity menu for overworld map option
@@ -259,7 +264,7 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
* Fixed a bug with shopsanity + district algorithm where pre-placed potions messed up the placeholder count
* Fixed usestartinventory flag (can be use on a per player basis)
* Sprite selector fix for systems with SSL issues
* Fix for Standard ER where locations in rain state could be in logic
* Fix for Standard ER where locations in rain state could be in logic
* 1.0.0.3
* overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown)
* Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix)

33
Rom.py
View File

@@ -663,18 +663,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.mapshuffle[player]:
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
if world.pottery[player] not in ['none']:
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
# make hammer pegs use different tiles
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
if world.pot_contents[player]:
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
and (world.colorizepots[player]
or world.pottery[player] in ['reduced', 'clustered']))
if world.pot_contents[player].size() > 0x2800:
raise Exception('Pot table is too big for current area')
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
# fix for swamp drains if necessary
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
if not swamp1location.pot.indicator:
@@ -1549,6 +1537,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if room.player == player and room.modified:
rom.write_bytes(room.address(), room.rom_data())
if world.pottery[player] not in ['none']:
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
# make hammer pegs use different tiles
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
if world.pot_contents[player]:
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
and (world.colorizepots[player]
or world.pottery[player] in ['reduced', 'clustered']))
if world.pot_contents[player].size() > 0x2800:
raise Exception('Pot table is too big for current area')
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
write_strings(rom, world, player, team)
# write initial sram
@@ -1982,8 +1983,6 @@ def write_strings(rom, world, player, team):
else:
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
hint = dest.dungeon.name
elif isinstance(dest, Item) and world.experimental[player]:
hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something'
else:
hint = dest.hint_text if dest.hint_text else "something"
if dest.player != player:
@@ -2162,8 +2161,7 @@ def write_strings(rom, world, player, team):
if this_location:
item_name = this_location[0].item.hint_text
item_name = item_name[0].upper() + item_name[1:]
item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name
this_hint = f'{item_format} can be found {hint_text(this_location[0])}.'
this_hint = f'{item_name} can be found {hint_text(this_location[0])}.'
tt[hint_locations.pop(0)] = this_hint
hint_count -= 1
@@ -2217,8 +2215,7 @@ def write_strings(rom, world, player, team):
elif hint_type == 'path':
if item_count == 1:
the_item = text_for_item(next(iter(choice_set)), world, player, team)
item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item
hint_candidates.append((hint_type, f'{name} conceals only {item_format}'))
hint_candidates.append((hint_type, f'{name} conceals only {the_item}'))
else:
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
district_hints = min(len(hint_candidates), len(hint_locations))