Merge branch 'Ambrosia' into Bias

This commit is contained in:
aerinon
2021-09-16 16:15:10 -06:00
11 changed files with 79 additions and 77 deletions

View File

@@ -14,7 +14,7 @@ from RoomData import DoorKind, PairedDoor, reset_rooms
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
from Utils import ncr, kth_combination from Utils import ncr, kth_combination
@@ -1384,10 +1384,8 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
if recombine.master_sector is None: if recombine.master_sector is None:
recombine.master_sector = builder.master_sector recombine.master_sector = builder.master_sector
recombine.master_sector.name = recombine.name recombine.master_sector.name = recombine.name
recombine.pre_open_stonewalls = builder.pre_open_stonewalls
else: else:
recombine.master_sector.regions.extend(builder.master_sector.regions) recombine.master_sector.regions.extend(builder.master_sector.regions)
recombine.pre_open_stonewalls.update(builder.pre_open_stonewalls)
recombine.layout_starts = list(entrances_map[recombine.name]) recombine.layout_starts = list(entrances_map[recombine.name])
dungeon_builders[recombine.name] = recombine dungeon_builders[recombine.name] = recombine
@@ -1491,6 +1489,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
start_regions = [x for x in start_regions if x not in excluded.keys()] start_regions = [x for x in start_regions if x not in excluded.keys()]
key_layout = build_key_layout(builder, start_regions, proposal, world, player) key_layout = build_key_layout(builder, start_regions, proposal, world, player)
determine_prize_lock(key_layout, world, player)
while not validate_key_layout(key_layout, world, player): while not validate_key_layout(key_layout, world, player):
itr += 1 itr += 1
stop_early = False stop_early = False
@@ -2069,8 +2068,8 @@ class DROptions(Flag):
Debug = 0x08 Debug = 0x08
# Rails = 0x10 # Unused bit now # Rails = 0x10 # Unused bit now
OriginalPalettes = 0x20 OriginalPalettes = 0x20
Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required # Open_PoD_Wall = 0x40 # No longer pre-opening pod wall - unused
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required # Open_Desert_Wall = 0x80 # No longer pre-opening desert wall - unused
Hide_Total = 0x100 Hide_Total = 0x100
DarkWorld_Spawns = 0x200 DarkWorld_Spawns = 0x200

View File

@@ -56,24 +56,6 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player): def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
stonewalls = check_for_stonewalls(builder)
sector = generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player)
for stonewall in stonewalls:
if not stonewall_valid(stonewall):
builder.pre_open_stonewalls.add(stonewall)
return sector
def check_for_stonewalls(builder):
stonewalls = set()
for sector in builder.sectors:
for door in sector.outstanding_doors:
if door.stonewall:
stonewalls.add(door)
return stonewalls
def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player):
if builder.valid_proposal: # we made this earlier in gen, just use it if builder.valid_proposal: # we made this earlier in gen, just use it
proposed_map = builder.valid_proposal proposed_map = builder.valid_proposal
else: else:
@@ -112,6 +94,15 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
if (access_region.name in world.inaccessible_regions[player] and if (access_region.name in world.inaccessible_regions[player] and
region.name not in world.enabled_entrances[player]): region.name not in world.enabled_entrances[player]):
excluded[region] = None excluded[region] = None
elif len(region.entrances) == 1: # for holes
access_region = next(x.parent_region for x in region.entrances
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
or x.parent_region.name == 'Sewer Drop')
if access_region.name == 'Sewer Drop':
access_region = next(x.parent_region for x in access_region.entrances)
if (access_region.name in world.inaccessible_regions[player] and
region.name not in world.enabled_entrances[player]):
excluded[region] = None
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect = {} doors_to_connect = {}
all_regions = set() all_regions = set()
@@ -586,7 +577,8 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
paths.append(boss) paths.append(boss)
if 'Thieves Boss' in all_r_names: if 'Thieves Boss' in all_r_names:
paths.append('Thieves Boss') paths.append('Thieves Boss')
paths.append(('Thieves Blind\'s Cell', 'Thieves Boss')) if world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind':
paths.append(('Thieves Blind\'s Cell', 'Thieves Boss'))
for drop_check in drop_path_checks: for drop_check in drop_path_checks:
if drop_check in all_r_names: if drop_check in all_r_names:
paths.append((drop_check, non_hole_portals)) paths.append((drop_check, non_hole_portals))
@@ -612,35 +604,6 @@ def winnow_hangers(hangers, hooks):
hangers[hanger].remove(door) hangers[hanger].remove(door)
def stonewall_valid(stonewall):
bad_door = stonewall.dest
if bad_door.blocked:
return True # great we're done with this one
loop_region = stonewall.entrance.parent_region
start_regions = [bad_door.entrance.parent_region]
if bad_door.dependents:
for dep in bad_door.dependents:
start_regions.append(dep.entrance.parent_region)
queue = deque(start_regions)
visited = set(start_regions)
while len(queue) > 0:
region = queue.popleft()
if region == loop_region:
return False # guaranteed loop
possible_entrances = list(region.entrances)
for entrance in possible_entrances:
parent = entrance.parent_region
if parent.type != RegionType.Dungeon:
return False # you can get stuck from an entrance
else:
door = entrance.door
if (door is None or (door != stonewall and not door.blocked)) and parent not in visited:
visited.add(parent)
queue.append(parent)
# we didn't find anything bad
return True
def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player): def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player):
# todo: info about dungeon events - not sure about that # todo: info about dungeon events - not sure about that
graph_piece = GraphPiece() graph_piece = GraphPiece()
@@ -1222,8 +1185,6 @@ class DungeonBuilder(object):
self.path_entrances = None # used for pathing/key doors, I think self.path_entrances = None # used for pathing/key doors, I think
self.split_flag = False self.split_flag = False
self.pre_open_stonewalls = set() # used by stonewall system
self.candidates = None self.candidates = None
self.total_keys = None self.total_keys = None
self.key_doors_num = None self.key_doors_num = None
@@ -1300,6 +1261,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
candidate_sectors, global_pole) candidate_sectors, global_pole)
if key == 'Thieves Town' and world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind':
assign_sector(find_sector("Thieves Blind's Cell", candidate_sectors), current_dungeon,
candidate_sectors, global_pole)
entrances_map, potentials, connections = connections_tuple entrances_map, potentials, connections = connections_tuple
accessible_sectors, reverse_d_map = set(), {} accessible_sectors, reverse_d_map = set(), {}
for key in dungeon_entrances.keys(): for key in dungeon_entrances.keys():
@@ -3955,7 +3919,7 @@ dungeon_boss_sectors = {
'Palace of Darkness': ['PoD Boss'], 'Palace of Darkness': ['PoD Boss'],
'Swamp Palace': ['Swamp Boss'], 'Swamp Palace': ['Swamp Boss'],
'Skull Woods': ['Skull Boss'], 'Skull Woods': ['Skull Boss'],
'Thieves Town': ['Thieves Blind\'s Cell', 'Thieves Boss'], 'Thieves Town': ['Thieves Boss'],
'Ice Palace': ['Ice Boss'], 'Ice Palace': ['Ice Boss'],
'Misery Mire': ['Mire Boss'], 'Misery Mire': ['Mire Boss'],
'Turtle Rock': ['TR Boss'], 'Turtle Rock': ['TR Boss'],

View File

@@ -4,7 +4,6 @@ import math
import RaceRandom as random import RaceRandom as random
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
from Bosses import place_bosses
from EntranceShuffle import connect_entrance from EntranceShuffle import connect_entrance
from Regions import shop_to_location_table, retro_shops, shop_table_by_location from Regions import shop_to_location_table, retro_shops, shop_table_by_location
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
@@ -376,7 +375,6 @@ def generate_itempool(world, player):
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
world.required_medallions[player] = (mm_medallion, tr_medallion) world.required_medallions[player] = (mm_medallion, tr_medallion)
place_bosses(world, player)
set_up_shops(world, player) set_up_shops(world, player)
if world.retro[player]: if world.retro[player]:

View File

@@ -28,6 +28,7 @@ class KeyLayout(object):
self.found_doors = set() self.found_doors = set()
self.prize_relevant = None self.prize_relevant = None
self.prize_can_lock = None # if true, then you may need to beat the bo
# bk special? # bk special?
# bk required? True if big chests or big doors exists # bk required? True if big chests or big doors exists
@@ -1440,7 +1441,10 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
found_forced_bk = state.found_forced_bk() found_forced_bk = state.found_forced_bk()
smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations) smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations)
bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk) bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk)
prize_done = not key_layout.prize_relevant or state.prize_doors_opened # prize door should not be opened if the boss is reachable - but not reached yet
allow_for_prize_lock = (key_layout.prize_can_lock and
not any(x for x in state.found_locations if '- Prize' in x.name))
prize_done = not key_layout.prize_relevant or state.prize_doors_opened or allow_for_prize_lock
if smalls_done and bk_done and prize_done: if smalls_done and bk_done and prize_done:
return False return False
else: else:
@@ -1524,6 +1528,39 @@ def enough_small_locations(state, avail_small_loc):
return avail_small_loc >= len(unique_d_set) return avail_small_loc >= len(unique_d_set)
def determine_prize_lock(key_layout, world, player):
if ((world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle'))
or world.logic[player] == 'nologic'):
return # done, doesn't matter what
flat_proposal = key_layout.flat_prop
state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = key_layout.max_chests
state.big_key_special = check_bk_special(key_layout.sector.regions, world, player)
prize_lock_possible = False
for region in key_layout.start_regions:
dungeon_entrance, portal_door = find_outside_connection(region)
prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
if prize_relevant_flag:
state.append_door_to_list(portal_door, state.prize_doors)
state.prize_door_set[portal_door] = dungeon_entrance
key_layout.prize_relevant = prize_relevant_flag
prize_lock_possible = True
else:
state.visit_region(region, key_checks=True)
state.add_all_doors_check_keys(region, flat_proposal, world, player)
if not prize_lock_possible:
return # done, no prize entrances to worry about
expand_key_state(state, flat_proposal, world, player)
while len(state.small_doors) > 0 or len(state.big_doors) > 0:
if len(state.big_doors) > 0:
open_a_door(state.big_doors[0].door, state, flat_proposal, world, player)
elif len(state.small_doors) > 0:
open_a_door(state.small_doors[0].door, state, flat_proposal, world, player)
expand_key_state(state, flat_proposal, world, player)
if any(x for x in state.found_locations if '- Prize' in x.name):
key_layout.prize_can_lock = True
def cnt_avail_small_locations(free_locations, key_only, state, world, player): def cnt_avail_small_locations(free_locations, key_only, state, world, player):
if not world.keyshuffle[player] and not world.retro[player]: if not world.keyshuffle[player] and not world.retro[player]:
bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0 bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0

View File

@@ -10,6 +10,7 @@ import time
import zlib import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance, Settings from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance, Settings
from Bosses import place_bosses
from Items import ItemFactory from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement from KeyDoorShuffle import validate_key_placement
from OverworldGlitchRules import create_owg_connections from OverworldGlitchRules import create_owg_connections
@@ -31,7 +32,7 @@ from Utils import output_path, parse_player_names
from source.item.BiasedFill import create_item_pool_config, massage_item_pool from source.item.BiasedFill import create_item_pool_config, massage_item_pool
__version__ = '0.5.1.1-u' __version__ = '1.0.0.1-u'
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
@@ -150,6 +151,7 @@ def main(args, seed=None, fish=None):
create_rooms(world, player) create_rooms(world, player)
create_dungeons(world, player) create_dungeons(world, player)
adjust_locations(world, player) adjust_locations(world, player)
place_bosses(world, player)
if any(world.potshuffle.values()): if any(world.potshuffle.values()):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))

View File

@@ -15,6 +15,14 @@ CLI: ```--bombbag```
# Bug Fixes and Notes. # Bug Fixes and Notes.
* 0.5.1.2
* Allowed Blind's Cell to be shuffled anywhere if Blind is not the boss of Thieves Town
* Remove unique annotation from a FastEnum that was causing problems
* Updated prevent mixed_travel setting to prevent more mixed travel
* Prevent key door loops on the same supertile where you could have spent 2 keys on one logical door
* Promoted dynamic soft-lock prevention on "stonewalls" from experimental to be the primary prevention (Stonewalls are now never pre-opened)
* Fix to money balancing algorithm with small item_pool, thanks Catobat
* Many fixes and refinements to key logic and generation
* 0.5.1.1 * 0.5.1.1
* Shop hints in ER are now more generic instead of using "near X" because they aren't near that anymore * Shop hints in ER are now more generic instead of using "near X" because they aren't near that anymore
* Added memory location for mutliworld scripts to read what item was just obtain (longer than one frame) * Added memory location for mutliworld scripts to read what item was just obtain (longer than one frame)

9
Rom.py
View File

@@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'ef6e3e1aa59838c01dbd5b1b2387e70c' RANDOMIZERBASEHASH = '11f4f494e999a919aafd7d2624e67679'
class JsonRom(object): class JsonRom(object):
@@ -754,13 +754,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_bytes(paired_door.address_a(world, player), paired_door.rom_data_a(world, player)) rom.write_bytes(paired_door.address_a(world, player), paired_door.rom_data_a(world, player))
rom.write_bytes(paired_door.address_b(world, player), paired_door.rom_data_b(world, player)) rom.write_bytes(paired_door.address_b(world, player), paired_door.rom_data_b(world, player))
if world.doorShuffle[player] != 'vanilla': if world.doorShuffle[player] != 'vanilla':
if not world.experimental[player]:
for builder in world.dungeon_layouts[player].values():
for stonewall in builder.pre_open_stonewalls:
if stonewall.name == 'Desert Wall Slide NW':
dr_flags |= DROptions.Open_Desert_Wall
elif stonewall.name == 'PoD Bow Statue Down Ladder':
dr_flags |= DROptions.Open_PoD_Wall
for name, pair in boss_indicator.items(): for name, pair in boss_indicator.items():
dungeon_id, boss_door = pair dungeon_id, boss_door = pair
opposite_door = world.get_door(boss_door, player).dest opposite_door = world.get_door(boss_door, player).dest

View File

@@ -1973,7 +1973,7 @@ def add_key_logic_rules(world, player):
big_chest = world.get_location(chest.name, player) big_chest = world.get_location(chest.name, player)
add_rule(big_chest, create_rule(d_logic.bk_name, player)) add_rule(big_chest, create_rule(d_logic.bk_name, player))
if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1: if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1:
set_always_allow(big_chest, lambda state, item: item.name == d_logic.bk_name and item.player == player) set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player))
if world.retro[player]: if world.retro[player]:
for d_name, layout in world.key_layout[player].items(): for d_name, layout in world.key_layout[player].items():
for door in layout.flat_prop: for door in layout.flat_prop:
@@ -2011,6 +2011,10 @@ def eval_small_key_door(door_name, dungeon, player):
return lambda state: eval_small_key_door_main(state, door_name, dungeon, player) return lambda state: eval_small_key_door_main(state, door_name, dungeon, player)
def allow_big_key_in_big_chest(bk_name, player):
return lambda state, item: item.name == bk_name and item.player == player
def retro_in_hc(spot): def retro_in_hc(spot):
return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False

View File

@@ -35,11 +35,7 @@ rtl
OnFileLoadOverride: OnFileLoadOverride:
jsl OnFileLoad ; what I wrote over jsl OnFileLoad ; what I wrote over
lda.l DRFlags : and #$80 : beq + ;flag is off + lda.l DRFlags : and #$02 : beq + ; Mirror Scroll
lda $7ef086 : ora #$80 : sta $7ef086
+ lda.l DRFlags : and #$40 : beq + ;flag is off
lda $7ef036 : ora #$80 : sta $7ef036
+ lda.l DRFlags : and #$02 : beq +
lda $7ef353 : bne + lda $7ef353 : bne +
lda #$01 : sta $7ef353 lda #$01 : sta $7ef353
+ rtl + rtl

Binary file not shown.

View File

@@ -20,6 +20,7 @@ def create_item_pool_config(world):
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
config.reserved_locations[player].add(f'{d_name} - Boss') config.reserved_locations[player].add(f'{d_name} - Boss')
for dungeon in world.dungeons: for dungeon in world.dungeons:
for item in dungeon.all_items: if world.restrict_boss_items[dungeon.player] != 'none':
if item.map or item.compass: for item in dungeon.all_items:
item.advancement = True if item.map or item.compass:
item.advancement = True