Stonewall generation changed to pre-open wall if necessary.

GT Mini bosses no longer drop heart containers.
Crystal switch logic during generation updated.
This commit is contained in:
aerinon
2020-02-06 15:12:52 -07:00
parent 37176357b3
commit bda5b27c44
10 changed files with 92 additions and 228 deletions
+1
View File
@@ -870,6 +870,7 @@ class Region(object):
self.hint_text = hint self.hint_text = hint
self.recursion_count = 0 self.recursion_count = 0
self.player = player self.player = player
self.crystal_switch = False
def can_reach(self, state): def can_reach(self, state):
if state.stale[self.player]: if state.stale[self.player]:
+6
View File
@@ -4,6 +4,7 @@ from collections import defaultdict
import logging import logging
import operator as op import operator as op
import time import time
from enum import unique, Flag
from functools import reduce from functools import reduce
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier
@@ -1298,6 +1299,11 @@ def check_for_pinball_fix(state, bad_region, world, player):
return False return False
@unique
class DROptions(Flag):
Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
# DATA GOES DOWN HERE # DATA GOES DOWN HERE
logical_connections = [ logical_connections = [
+10 -222
View File
@@ -51,43 +51,17 @@ def validate_tr(builder, entrance_region_names, world, player):
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player): def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
builder_list = [builder] stonewall = check_for_stonewall(builder)
queue = deque(builder_list) sector = generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player)
finished_stonewalls = [] if stonewall and not stonewall_valid(stonewall):
while len(queue) > 0: builder.pre_open_stonewall = stonewall
builder = queue.popleft() return sector
stonewall = check_for_stonewall(builder, finished_stonewalls)
if stonewall is not None:
# todo: kill drop exceptions
entrances = [x for x in entrance_region_names if x not in ['Skull Back Drop']]
dungeon_map = stonewall_dungeon_builder(builder, stonewall, entrances, world, player)
builder_list.remove(builder)
for sub_builder in dungeon_map.values():
builder_list.append(sub_builder)
queue.append(sub_builder)
finished_stonewalls.append(stonewall)
if len(builder_list) == 1:
return generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player)
else:
sector_list = []
for split_builder in builder_list:
sector = generate_dungeon_main(split_builder, split_builder.stonewall_entrances, True, world, player)
sector_list.append(sector)
master_sector = sector_list.pop()
for sub_sector in sector_list:
master_sector.regions.extend(sub_sector.regions)
master_sector.outstanding_doors.clear()
master_sector.r_name_set = None
for stonewall in finished_stonewalls:
if not stonewall_valid(stonewall):
raise Exception('Stonewall still reachable from wrong side')
return master_sector
def check_for_stonewall(builder, finished_stonewalls): def check_for_stonewall(builder):
for sector in builder.sectors: for sector in builder.sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
if door.stonewall and door not in finished_stonewalls: if door.stonewall:
return door return door
return None return None
@@ -207,7 +181,8 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, va
if not door.stonewall and door not in proposed_map.keys(): if not door.stonewall and door not in proposed_map.keys():
hanger_set.add(door) hanger_set.add(door)
parent = door.entrance.parent_region parent = door.entrance.parent_region
init_state = ExplorationState(init_crystal, dungeon=name) crystal_start = CrystalBarrier.Either if parent.crystal_switch else init_crystal
init_state = ExplorationState(crystal_start, dungeon=name)
init_state.big_key_special = start.big_key_special init_state.big_key_special = start.big_key_special
o_state = extend_reachable_state_improved([parent], init_state, proposed_map, o_state = extend_reachable_state_improved([parent], init_state, proposed_map,
valid_doors, False, world, player) valid_doors, False, world, player)
@@ -1046,7 +1021,7 @@ 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.stonewall_entrances = [] # used by stonewall system self.pre_open_stonewall = None # used by stonewall system
self.candidates = None self.candidates = None
self.key_doors_num = None self.key_doors_num = None
@@ -1866,193 +1841,6 @@ def categorize_sectors(candidate_sectors):
return crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors return crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors
def stonewall_dungeon_builder(builder, stonewall, entrance_region_names, world, player):
logger = logging.getLogger('')
logger.info('Stonewall treatment')
candidate_sectors = dict.fromkeys(builder.sectors)
dungeon_map = {}
# split stonewall sector
region = stonewall.entrance.parent_region
sector = find_sector(region.name, candidate_sectors)
del candidate_sectors[sector]
stonewall_start = Sector()
stonewall_connector = Sector()
stonewall_start.outstanding_doors.append(stonewall)
stonewall_start.regions.append(region)
stonewall_start.entrance_sector = True
stonewall_start.branch_factor = 2
stonewall_connector.outstanding_doors += [x for x in sector.outstanding_doors if x != stonewall]
stonewall_connector.regions += [x for x in sector.regions if x != region]
define_sector_features([stonewall_connector, stonewall_start])
candidate_sectors[stonewall_start] = None
candidate_sectors[stonewall_connector] = None
global_pole = GlobalPolarity(candidate_sectors)
create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors, global_pole)
origin_builder = create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors, global_pole)
if stonewall.name == 'Desert Wall Slide NW':
# not true if big shuffled or split
location_needed = not builder.split_flag and not world.bigkeyshuffle[player]
for sector in origin_builder.sectors:
location_needed &= sector.chest_locations == 0 or (sector.chest_locations == 1 and sector.big_chest_present)
if location_needed:
free_location_sectors = []
for sector in candidate_sectors:
if sector.chest_locations > 1 or (sector.chest_locations == 1 and not sector.big_chest_present):
free_location_sectors.append(sector)
valid = False
while not valid:
if len(free_location_sectors) == 0:
raise Exception('Cannot place a big key sector before the wall slide, ouch')
sector = random.choice(free_location_sectors)
free_location_sectors.remove(sector)
valid = global_pole.is_valid_choice(dungeon_map, origin_builder, [sector])
assign_sector(sector, origin_builder, candidate_sectors, global_pole)
return balance_split(candidate_sectors, dungeon_map, global_pole)
# dependent sector splits
# dependency_list = []
# removal = []
# for sector in candidate_sectors.keys():
# dependency = split_sector(sector)
# if dependency is not None:
# removal.append(sector)
# parent, child = dependency
# dependency_list.append((parent, child, sector))
# for sector in removal:
# del candidate_sectors[sector]
# retry_candidates = candidate_sectors.copy()
# tries = 0
# while tries < 10:
# try:
# # re-assign dependent regions
# for parent, child, original in dependency_list:
# chosen_builder = random.choice([stone_builder, origin_builder, None])
# if chosen_builder is None:
# candidate_sectors[original] = None
# elif chosen_builder == stone_builder:
# candidate_sectors[original] = None
# assign_sector(original, chosen_builder, candidate_sectors)
# else:
# candidate_sectors[parent] = None
# candidate_sectors[child] = None
# assign_sector(child, chosen_builder, candidate_sectors)
# return stonewall_split(candidate_sectors, dungeon_map)
# except NeutralizingException:
# tries += 1
# candidate_sectors = retry_candidates.copy()
# candidate_sectors[stonewall_start] = None
# candidate_sectors[stonewall_connector] = None
# stone_builder = create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors)
# origin_builder = create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors)
#
# raise NeutralizingException('Unable to find a valid combination')
def stonewall_split(candidate_sectors, dungeon_map, global_pole):
logger = logging.getLogger('')
# categorize sectors
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, global_pole, len(crystal_barriers) > 0)
for sector in leftover:
if sector.polarity().is_neutral():
neutral_sectors[sector] = None
else:
polarized_sectors[sector] = None
# blue barriers
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
# polarity:
logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al')
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger)
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
# the rest
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
return dungeon_map
def create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors, global_pole):
key = builder.name + ' Prewall'
dungeon_map[key] = origin_builder = DungeonBuilder(key)
origin_builder.stonewall_entrances += entrance_region_names
origin_builder.all_entrances = []
for ent in entrance_region_names:
sector = find_sector(ent, candidate_sectors)
if sector is not None:
for door in sector.outstanding_doors:
if not door.blocked:
origin_builder.all_entrances.append(ent)
assign_sector(sector, origin_builder, candidate_sectors, global_pole)
break
else: # already got assigned
origin_builder.all_entrances.append(ent)
assign_sector(stonewall_connector, origin_builder, candidate_sectors, global_pole)
return origin_builder
def create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors, global_pole):
key = builder.name + ' Stonewall'
dungeon_map[key] = stone_builder = DungeonBuilder(key)
stone_builder.stonewall_entrances += [region.name]
stone_builder.all_entrances = [region.name]
stone_builder.branch_factor = 2
assign_sector(stonewall_start, stone_builder, candidate_sectors, global_pole)
return stone_builder
# def split_sector(sector):
# entrance_set = set()
# exit_map = {}
# visited_regions = {}
# min_cardinality = None
# for door in sector.outstanding_doors:
# reachable_doors = {door}
# start_region = door.entrance.parent_region
# visited = {start_region}
# queue = deque([start_region])
# while len(queue) > 0:
# region = queue.popleft()
# for ext in region.exits:
# connect = ext.connected_region
# if connect is not None and connect.type == RegionType.Dungeon and connect not in visited:
# visited.add(connect)
# queue.append(connect)
# elif ext.door in sector.outstanding_doors:
# reachable_doors.add(ext.door)
# visited_regions[door] = visited
# if len(reachable_doors) >= len(sector.outstanding_doors):
# entrance_set.add(door)
# else:
# door_cardinality = len(reachable_doors)
# if door_cardinality not in exit_map.keys():
# exit_map[door_cardinality] = set()
# exit_map[door_cardinality].add(door)
# if min_cardinality is None or door_cardinality < min_cardinality:
# min_cardinality = door_cardinality
# exit_set = set()
# if min_cardinality is not None:
# for cardinality, door_set in exit_map.items():
# if cardinality > min_cardinality:
# entrance_set.update(door_set)
# exit_set = exit_map[min_cardinality]
# if len(entrance_set) > 0 and len(exit_set) > 0:
# entrance_sector = Sector()
# exit_sector = Sector()
# entrance_sector.outstanding_doors.extend(entrance_set)
# region_set = set()
# for ent_door in entrance_set:
# region_set.update(visited_regions[ent_door])
# entrance_sector.regions.extend(region_set)
# exit_sector.outstanding_doors.extend(exit_set)
# region_set = set()
# for ext_door in exit_set:
# region_set.update(visited_regions[ext_door])
# exit_sector.regions.extend(region_set)
# define_sector_features([entrance_sector, exit_sector])
# return entrance_sector, exit_sector
# return None
class NeutralizingException(Exception): class NeutralizingException(Exception):
pass pass
+1 -1
View File
@@ -23,7 +23,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.0.c-pre' __version__ = '0.0.d-pre'
def main(args, seed=None): def main(args, seed=None):
+31
View File
@@ -715,6 +715,37 @@ def create_dungeon_regions(world, player):
] ]
world.initialize_regions() world.initialize_regions()
world.get_region('Hera Lobby', player).crystal_switch = True
world.get_region('Hera Basement Cage', player).crystal_switch = True
world.get_region('Hera Tile Room', player).crystal_switch = True # INTERIOR not accessible (maybe with cane)
world.get_region('Hera Tridorm', player).crystal_switch = True
world.get_region('Hera Startile Wide', player).crystal_switch = True
world.get_region('PoD Arena Main', player).crystal_switch = True
world.get_region('PoD Arena Bridge', player).crystal_switch = True
world.get_region('PoD Sexy Statue', player).crystal_switch = True
world.get_region('PoD Bow Statue', player).crystal_switch = True # LADDER not accessible (maybe with cane)
world.get_region('PoD Dark Pegs', player).crystal_switch = True
world.get_region('Swamp Crystal Switch', player).crystal_switch = True
world.get_region('Thieves Spike Switch', player).crystal_switch = True
world.get_region('Ice Bomb Drop', player).crystal_switch = True
world.get_region('Ice Conveyor', player).crystal_switch = True
world.get_region('Ice Refill', player).crystal_switch = True
world.get_region('Mire Fishbone', player).crystal_switch = True
world.get_region('Mire Conveyor Crystal', player).crystal_switch = True
world.get_region('Mire Tall Dark and Roomy', player).crystal_switch = True
world.get_region('Mire Crystal Top', player).crystal_switch = True
world.get_region('Mire Falling Foes', player).crystal_switch = True
world.get_region('TR Chain Chomps', player).crystal_switch = True
world.get_region('TR Pokey 2', player).crystal_switch = True
world.get_region('TR Crystaroller', player).crystal_switch = True
world.get_region('TR Crystal Maze', player).crystal_switch = True
world.get_region('GT Crystal Conveyor', player).crystal_switch = True # INTERIOR not accessible
world.get_region('GT Hookshot South Platform', player).crystal_switch = True
# world.get_region('GT Double Switch Switches', player).crystal_switch = True # this is not very relevant
world.get_region('GT Spike Crystals', player).crystal_switch = True
world.get_region('GT Crystal Paths', player).crystal_switch = True
world.get_region('GT Hidden Spikes', player).crystal_switch = True
world.get_region('GT Crystal Circles', player).crystal_switch = True
def create_lw_region(player, name, locations=None, exits=None): def create_lw_region(player, name, locations=None, exits=None):
+12 -3
View File
@@ -9,8 +9,8 @@ import struct
import sys import sys
import subprocess import subprocess
from BaseClasses import CollectionState, ShopType, Region, Location, Item, DoorType from BaseClasses import CollectionState, ShopType, Region, Location, DoorType
from DoorShuffle import compass_data from DoorShuffle import compass_data, DROptions
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses
from Regions import location_table from Regions import location_table
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'c1361fcf13239f8677bacc6f9bc5e9dd' RANDOMIZERBASEHASH = 'c06e14396839bc443a6918e736f1e5a7'
class JsonRom(object): class JsonRom(object):
@@ -591,7 +591,9 @@ def patch_rom(world, rom, player, team, enemized):
patch_shuffled_dark_sanc(world, rom, player) patch_shuffled_dark_sanc(world, rom, player)
# patch doors # patch doors
dr_flags = DROptions.Eternal_Mini_Bosses
if world.doorShuffle[player] == 'crossed': if world.doorShuffle[player] == 'crossed':
rom.write_byte(0x139004, 2)
rom.write_byte(0x151f1, 2) rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2) rom.write_byte(0x15270, 2)
rom.write_byte(0x1597b, 2) rom.write_byte(0x1597b, 2)
@@ -599,6 +601,8 @@ def patch_rom(world, rom, player, team, enemized):
update_compasses(rom, world, player) update_compasses(rom, world, player)
else: else:
logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken') logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken')
if world.doorShuffle[player] == 'basic':
rom.write_byte(0x139004, 1)
for door in world.doors: for door in world.doors:
if door.dest is not None and door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs]: if door.dest is not None and door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs]:
rom.write_bytes(door.getAddress(), door.dest.getTarget(door.toggle)) rom.write_bytes(door.getAddress(), door.dest.getTarget(door.toggle))
@@ -608,6 +612,11 @@ def patch_rom(world, rom, player, team, enemized):
for paired_door in world.paired_doors[player]: for paired_door in world.paired_doors[player]:
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))
for builder in world.dungeon_layouts[player].values():
if builder.pre_open_stonewall:
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
dr_flags |= DROptions.Open_Desert_Wall
rom.write_byte(0x139006, dr_flags.value)
# fix skull woods exit, if not fixed during exit patching # fix skull woods exit, if not fixed during exit patching
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla': if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
+4
View File
@@ -23,6 +23,10 @@ warnpc $279000
org $279000 org $279000
OffsetTable: OffsetTable:
dw -8, 8 dw -8, 8
DRMode:
dw 0
DRFlags:
dw 0
; Vert 0,6,0 Horz 2,0,8 ; Vert 0,6,0 Horz 2,0,8
org $279010 org $279010
+8
View File
@@ -61,6 +61,14 @@ Palette_SpriteAux1:
org $0DFA53 org $0DFA53
jsl.l LampCheckOverride jsl.l LampCheckOverride
org $028046 ; <- 10046 - Bank02.asm : 217 (JSL EnableForceBlank) (Start of Module_LoadFile)
jsl.l OnFileLoadOverride
org $05ef47
Sprite_HeartContainer_Override: ;sprite_heart_upgrades.asm : 96-100 (LDA $040C : CMP.b #$1A : BNE .not_in_ganons_tower)
jsl GtBossHeartCheckOverride : bcs .not_in_ganons_tower
nop : stz $0dd0, X : rts
.not_in_ganons_tower
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire ; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes ; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
+18 -1
View File
@@ -20,4 +20,21 @@ LampCheckOverride:
LDA LampConeDarkWorld LDA LampConeDarkWorld
.done .done
;BNE + : STZ $1D : + ; remember to turn cone off after a torch ;BNE + : STZ $1D : + ; remember to turn cone off after a torch
RTL RTL
GtBossHeartCheckOverride:
lda $a0 : cmp #$1c : beq ++
cmp #$6c : beq ++
cmp #$4d : bne +
++ lda DRFlags : and #$01 : bne ++ ;skip if flag on
lda $403 : ora #$80 : sta $403
++ clc
rtl
+ sec
rtl
OnFileLoadOverride:
jsl OnFileLoad ; what I wrote over
lda DRFlags : and #$80 : beq + ;flag is off
lda $7ef086 : ora #$80 : sta $7ef086
+ rtl
File diff suppressed because one or more lines are too long