Standing Items refinement - starting testing no keydrops and pot shuffle

Fairly substantial change to Crossed Sector distribution due to so many pots making it fail
This commit is contained in:
aerinon
2022-01-04 15:48:27 -07:00
parent b2f2565271
commit 515f6dafed
13 changed files with 251 additions and 97 deletions

View File

@@ -2382,11 +2382,11 @@ class Spoiler(object):
listed_locations.update(cave_locations)
for dungeon in self.world.dungeons:
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item]
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip]
self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
listed_locations.update(dungeon_locations)
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations]
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip]
if other_locations:
self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
listed_locations.update(other_locations)

View File

@@ -2302,6 +2302,8 @@ logical_connections = [
('TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
('TR Crystal Maze End to Ranged Crystal', 'TR Crystal Maze End - Ranged Crystal'),
('TR Crystal Maze End Ranged Crystal Exit', 'TR Crystal Maze End'),
('TR Final Abyss Balcony Path', 'TR Final Abyss Ledge'),
('TR Final Abyss Ledge Path', 'TR Final Abyss Balcony'),
('GT Blocked Stairs Block Path', 'GT Big Chest'),
('GT Speed Torch South Path', 'GT Speed Torch'),

View File

@@ -1048,6 +1048,8 @@ def create_doors(world, player):
create_door(player, 'TR Crystal Maze End Ranged Crystal Exit', Lgcl),
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High),
create_door(player, 'TR Final Abyss Balcony Path', Lgcl),
create_door(player, 'TR Final Abyss Ledge Path', Lgcl),
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), -enemizer doesn't work

View File

@@ -1326,7 +1326,17 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
polarized_sectors[sector] = None
if bow_sectors:
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player)
leftover = assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player)
free_location_sectors = scatter_extra_location_sectors(dungeon_map, leftover, global_pole)
for sector in free_location_sectors:
if sector.c_switch:
crystal_switches[sector] = None
elif sector.blue_barrier:
crystal_barriers[sector] = None
elif sector.polarity().is_neutral():
neutral_sectors[sector] = None
else:
polarized_sectors[sector] = None
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
for sector in leftover:
@@ -1558,33 +1568,85 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole):
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
def assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player):
def scatter_extra_location_sectors(dungeon_map, free_location_sectors, global_pole):
population = [n for n in dungeon_map.keys()]
k = round(len(free_location_sectors) * .50)
valid = False
choices = None
candidates = []
sector_list = list(free_location_sectors)
while not valid:
candidates = random.sample(sector_list, k=k)
choices = random.choices(population, k=len(candidates))
sector_dict = defaultdict(list)
for i, choice in enumerate(choices):
builder = dungeon_map[choice]
sector_dict[builder].append(candidates[i])
valid = global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), sector_dict)
for i, choice in enumerate(choices):
builder = dungeon_map[choice]
assign_sector(candidates[i], builder, free_location_sectors, global_pole)
return free_location_sectors
def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player):
valid = False
choices = defaultdict(list)
sector_list = list(free_location_sectors)
random.shuffle(sector_list)
orig_location_set = build_orig_location_set(dungeon_map)
num_dungeon_items = requested_dungeon_items(world, player)
d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())}
next_sector = sector_list.pop()
while not valid:
choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
location_set = {x: set(y) for x, y in orig_location_set.items()}
for i, sector in enumerate(sector_list):
d_name = choices[i].name
choice = d_idx[d_name]
totals[choice] += sector.chest_locations
location_set[d_name].update(sector.chest_location_set)
valid = True
for d_name, idx in d_idx.items():
free_items = count_reserved_locations(world, player, location_set[d_name])
target = max(free_items, 2) + num_dungeon_items
if totals[idx] < target:
valid = False
break
for i, choice in enumerate(choices):
builder = dungeon_map[choice.name]
assign_sector(sector_list[i], builder, free_location_sectors, global_pole)
choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player)
if not choice:
break
choices[choice].append(next_sector)
if global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), choices):
idx = d_idx[choice.name]
totals[idx] += next_sector.chest_locations
location_set[choice.name].update(next_sector.chest_location_set)
valid = True
for d_name, idx in d_idx.items():
free_items = count_reserved_locations(world, player, location_set[d_name])
target = max(free_items, 2) + num_dungeon_items
if totals[idx] < target:
valid = False
break
if not valid:
if len(sector_list) == 0:
choices = defaultdict(list)
sector_list = list(free_location_sectors)
else:
next_sector = sector_list.pop()
else:
choices[choice].remove(next_sector)
for builder, choice_list in choices.items():
for choice in choice_list:
assign_sector(choice, builder, free_location_sectors, global_pole)
return free_location_sectors
def weighted_random_location(dungeon_map, choices, orig_location_set, world, player):
population = []
totals = []
location_set = {x: set(y) for x, y in orig_location_set.items()}
num_dungeon_items = requested_dungeon_items(world, player)
for i, dungeon_builder in enumerate(dungeon_map.values()):
ttl = dungeon_builder.location_cnt + sum(sector.chest_locations for sector in choices[dungeon_builder])
totals.append(ttl)
builder_set = location_set[dungeon_builder.name]
builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
free_items = count_reserved_locations(world, player, builder_set)
target = max(free_items, 2) + num_dungeon_items
if ttl < target:
population.append(dungeon_builder)
choice = random.choice(population) if len(population) > 0 else None
return choice, totals, location_set
# deprecated
def weighted_random_locations(dungeon_map, free_location_sectors):
population = []
ttl_assigned = 0

View File

@@ -170,15 +170,17 @@ mire_regions = [
tr_regions = [
'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Torches Ledge', 'TR Torches', 'TR Roller Room',
'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal',
'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge', 'TR Lava Dual Pipes',
'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal', 'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal',
'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers', 'TR Big View','TR Big Chest', 'TR Big Chest Entrance',
'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull', 'TR Rupees', 'TR Crystaroller Bottom',
'TR Crystaroller Middle', 'TR Crystaroller Top', 'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest',
'TR Crystaroller Middle - Ranged Crystal', 'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge',
'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge',
'TR Lava Dual Pipes', 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal',
'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal', 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers',
'TR Big View','TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull',
'TR Rupees', 'TR Crystaroller Bottom', 'TR Crystaroller Middle', 'TR Crystaroller Top',
'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', 'TR Crystaroller Middle - Ranged Crystal',
'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge',
'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End',
'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss', 'TR Boss', 'Turtle Rock Main Portal',
'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal', 'Turtle Rock Eye Bridge Portal'
'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss',
'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal',
'Turtle Rock Eye Bridge Portal'
]
gt_regions = [

View File

@@ -7,6 +7,7 @@ from BaseClasses import CollectionState, FillError, LocationType
from Items import ItemFactory
from Regions import shop_to_location_table, retro_shops
from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
from source.item.FillUtil import filter_pot_locations
def get_dungeon_item_pool(world):
@@ -362,6 +363,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
world.itempool.remove(pot_item)
pot_locations = [location for location in fill_locations
if location.type == LocationType.Pot and location.player == player]
pot_locations = filter_pot_locations(pot_locations, world)
fast_fill_helper(world, pot_pool, pot_locations)
pots_used = True
if pots_used:

View File

@@ -14,7 +14,7 @@ from Bosses import place_bosses
from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
from OverworldGlitchRules import create_owg_connections
from PotShuffle import shuffle_pots
from PotShuffle import shuffle_pots, shuffle_pot_switches
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions, adjust_locations
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
from EntranceShuffle import link_entrances, link_inverted_entrances
@@ -157,8 +157,11 @@ def main(args, seed=None, fish=None):
if any(world.potshuffle.values()):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
for player in range(1, world.players + 1):
if world.potshuffle[player] and world.keydropshuffle[player] != 'potsanity':
shuffle_pots(world, player)
if world.potshuffle[player]:
if world.keydropshuffle[player] != 'potsanity':
shuffle_pots(world, player)
else:
shuffle_pot_switches(world, player)
logger.info(world.fish.translate("cli","cli","shuffling.world"))

View File

@@ -1,6 +1,6 @@
from collections import defaultdict
from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier
from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier, LocationType
from Utils import int16_as_bytes, pc_to_snes
movable_switch_rooms = defaultdict(lambda: [],
@@ -79,8 +79,10 @@ vanilla_pots = {
61: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle'),
Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle'), Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle')],
62: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint'), Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint'), Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint'), Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint')],
0x3F: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block'), Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block'),
Pot(12, 27, PotItem.Switch, 'Ice Hammer Block'), Pot(20, 27, PotItem.Heart, 'Ice Hammer Block'), Pot(28, 23, PotItem.Key, 'Ice Hammer Block')],
0x3F: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block'),
Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block'), Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block'),
Pot(12, 27, PotItem.Switch, 'Ice Hammer Block'), Pot(20, 27, PotItem.Heart, 'Ice Hammer Block'),
Pot(28, 23, PotItem.Key, 'Ice Hammer Block')],
0x41: [Pot(100, 10, PotItem.Heart, 'Sewers Behind Tapestry'), Pot(52, 15, PotItem.OneRupee, 'Sewers Behind Tapestry'), Pot(52, 16, PotItem.SmallMagic, 'Sewers Behind Tapestry'), Pot(148, 22, PotItem.SmallMagic, 'Sewers Behind Tapestry')],
0x43: [Pot(66, 4, PotItem.FiveArrows, 'Desert Wall Slide'), Pot(78, 4, PotItem.SmallMagic, 'Desert Wall Slide'), Pot(66, 9, PotItem.Heart, 'Desert Wall Slide'), Pot(78, 9, PotItem.Heart, 'Desert Wall Slide'),
Pot(112, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 20, PotItem.Nothing, 'Desert Tiles 2'), Pot(112, 20, PotItem.Key, 'Desert Tiles 2')],
@@ -191,7 +193,7 @@ vanilla_pots = {
145: [Pot(84, 4, PotItem.Heart, 'Mire Falling Foes'), Pot(104, 4, PotItem.SmallMagic, 'Mire Falling Foes')],
146: [Pot(86, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(92, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(98, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(104, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy')],
0x93: [Pot(28, 7, PotItem.Switch, 'Mire Dark Shooters'),
Pot(0x9C, 0x17, PotItem.Switch, 'Mire Block X', PotFlags.Block),
Pot(0x9C, 0x17, PotItem.Nothing, 'Mire Block X', PotFlags.Block),
Pot(96, 7, PotItem.Heart, 'Mire Dark Shooters', PotFlags.NoSwitch)],
0x96: [Pot(14, 18, PotItem.Nothing, 'GT Torch Cross'),
Pot(32, 5, PotItem.Nothing, 'GT Torch Cross'),
@@ -243,13 +245,13 @@ vanilla_pots = {
Pot(48, 0xA, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion),
Pot(76, 0xA, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion)],
0xB3: [Pot(12, 20, PotItem.Key, 'Mire Spikes'), Pot(48, 20, PotItem.SmallMagic, 'Mire Spikes'), Pot(48, 28, PotItem.Switch, 'Mire Spikes')],
180: [Pot(44, 28, PotItem.BigMagic, 'TR Final Abyss'), Pot(48, 28, PotItem.Heart, 'TR Final Abyss')],
181: [Pot(112, 4, PotItem.FiveRupees, 'TR Dark Ride'), Pot(112, 15, PotItem.Heart, 'TR Dark Ride'), Pot(76, 16, PotItem.Switch, 'TR Dark Ride'), Pot(112, 16, PotItem.BigMagic, 'TR Dark Ride'), Pot(112, 17, PotItem.Heart, 'TR Dark Ride'),
Pot(112, 28, PotItem.Bomb, 'TR Dark Ride')],
182: [Pot(94, 9, PotItem.BigMagic, 'TR Refill')],
183: [Pot(30, 5, PotItem.SmallMagic, 'TR Roller Room')],
184: [Pot(96, 13, PotItem.Switch, 'Eastern Big Key'), Pot(88, 16, PotItem.Heart, 'Eastern Big Key'), Pot(104, 16, PotItem.Heart, 'Eastern Big Key')],
185: [Pot(92, 18, PotItem.OneRupee, 'Eastern Cannonball'), Pot(96, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(104, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(108, 18, PotItem.OneRupee, 'Eastern Cannonball')],
0xB4: [Pot(44, 28, PotItem.BigMagic, 'TR Final Abyss Balcony'), Pot(48, 28, PotItem.Heart, 'TR Final Abyss Balcony')],
0xB5: [Pot(112, 4, PotItem.FiveRupees, 'TR Dark Ride'), Pot(112, 15, PotItem.Heart, 'TR Dark Ride'), Pot(76, 16, PotItem.Switch, 'TR Dark Ride'), Pot(112, 16, PotItem.BigMagic, 'TR Dark Ride'), Pot(112, 17, PotItem.Heart, 'TR Dark Ride'),
Pot(112, 28, PotItem.Bomb, 'TR Dark Ride')],
0xB6: [Pot(94, 9, PotItem.BigMagic, 'TR Refill')],
0xB7: [Pot(30, 5, PotItem.SmallMagic, 'TR Roller Room')],
0xB8: [Pot(96, 13, PotItem.Switch, 'Eastern Big Key'), Pot(88, 16, PotItem.Heart, 'Eastern Big Key'), Pot(104, 16, PotItem.Heart, 'Eastern Big Key')],
0xB9: [Pot(92, 18, PotItem.OneRupee, 'Eastern Cannonball'), Pot(96, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(104, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(108, 18, PotItem.OneRupee, 'Eastern Cannonball')],
0xBA: [Pot(100, 8, PotItem.Nothing, 'Eastern Dark Pots'), Pot(88, 8, PotItem.Nothing, 'Eastern Dark Pots'), Pot(94, 4, PotItem.OneRupee, 'Eastern Dark Pots'), Pot(76, 6, PotItem.Heart, 'Eastern Dark Pots'),
Pot(112, 6, PotItem.Key, 'Eastern Dark Pots'), Pot(76, 10, PotItem.Heart, 'Eastern Dark Pots'), Pot(112, 10, PotItem.SmallMagic, 'Eastern Dark Pots'), Pot(94, 12, PotItem.OneRupee, 'Eastern Dark Pots')],
0xBC: [Pot(86, 4, PotItem.Heart, 'Thieves Hallway'), Pot(102, 4, PotItem.Key, 'Thieves Hallway'), Pot(138, 3, PotItem.Bomb, 'Thieves Conveyor Maze'), Pot(178, 3, PotItem.Switch, 'Thieves Conveyor Maze'),
@@ -271,8 +273,10 @@ vanilla_pots = {
201: [Pot(30, 22, PotItem.OneRupee, 'Eastern Lobby'), Pot(94, 22, PotItem.OneRupee, 'Eastern Lobby'), Pot(60, 22, PotItem.Switch, 'Eastern Lobby')],
203: [Pot(80, 4, PotItem.Nothing, 'Thieves Ambush'), Pot(80, 28, PotItem.Nothing, 'Thieves Ambush'), Pot(88, 16, PotItem.Heart, 'Thieves Ambush'), Pot(88, 28, PotItem.FiveRupees, 'Thieves Ambush')],
204: [Pot(36, 4, PotItem.FiveRupees, 'Thieves Rail Ledge'), Pot(36, 28, PotItem.FiveRupees, 'Thieves Rail Ledge'), Pot(112, 4, PotItem.Heart, 'Thieves BK Corner'), Pot(112, 28, PotItem.Bomb, 'Thieves BK Corner')],
206: [Pot(76, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(80, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(108, 12, PotItem.Bomb, 'Ice Antechamber'), Pot(112, 12, PotItem.FiveArrows, 'Ice Antechamber'),
Pot(204, 11, PotItem.Hole, 'Ice Antechamber')], # Pot(0x6c, 8, PotItem.Nothing, 'Ice Antechamber', PotFlags.Block) this one has jellies
0xCE: [Pot(76, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(80, 8, PotItem.SmallMagic, 'Ice Antechamber'),
Pot(108, 12, PotItem.Bomb, 'Ice Antechamber'), Pot(112, 12, PotItem.FiveArrows, 'Ice Antechamber'),
Pot(204, 11, PotItem.Hole, 'Ice Antechamber'),
Pot(0x6c, 8, PotItem.Nothing, 'Ice Antechamber', PotFlags.Block)],
208: [Pot(158, 5, PotItem.SmallMagic, 'Tower Dark Maze'), Pot(140, 11, PotItem.OneRupee, 'Tower Dark Maze'), Pot(42, 13, PotItem.SmallMagic, 'Tower Dark Maze'), Pot(48, 16, PotItem.Heart, 'Tower Dark Maze'),
Pot(176, 20, PotItem.OneRupee, 'Tower Dark Maze'), Pot(146, 23, PotItem.FiveRupees, 'Tower Dark Maze'), Pot(12, 28, PotItem.Heart, 'Tower Dark Maze')],
209: [Pot(48, 4, PotItem.BigMagic, 'Mire Conveyor Barrier'), Pot(168, 7, PotItem.OneRupee, 'Mire Conveyor Barrier'), Pot(76, 4, PotItem.OneRupee, 'Mire Neglected Room'), Pot(112, 4, PotItem.FiveArrows, 'Mire Neglected Room'),
@@ -350,8 +354,8 @@ def shuffle_pots(world, player):
new_pot_contents = PotSecretTable()
for supertile in vanilla_pots:
old_pots = vanilla_pots[supertile]
for super_tile in vanilla_pots:
old_pots = vanilla_pots[super_tile]
new_pots = [Pot(pot.x, pot.y, PotItem.Nothing, pot.room, pot.flags) for pot in old_pots]
# sort in the order Hole, Switch, Key, Other, Nothing
sort_order = {PotItem.Hole: 4, PotItem.Switch: 3, PotItem.Key: 2, PotItem.Nothing: 0}
@@ -401,11 +405,54 @@ def shuffle_pots(world, player):
else:
raise Exception("Switch location in room %s requires logic change" % new_pot.room)
new_pot_contents.room_map[supertile] = new_pots
new_pot_contents.room_map[super_tile] = new_pots
world.pot_contents[player] = new_pot_contents
def shuffle_pot_switches(world, player):
import RaceRandom as random
for super_tile in vanilla_pots:
old_pots = vanilla_pots[super_tile]
new_pots = world.pot_contents[player].room_map[super_tile]
# sort in the order Hole, Switch, Key, Other, Nothing
sort_order = {PotItem.Hole: 4, PotItem.Switch: 3, PotItem.Key: 2, PotItem.Nothing: 0}
old_pots = sorted(old_pots, key=lambda pot: sort_order.get(pot.item, 1), reverse=True)
for old_pot in old_pots:
if old_pot.item != PotItem.Switch:
break
else:
available_pots = [pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch)]
new_pot = random.choice(available_pots)
new_pot.item, old_pot.item = old_pot.item, new_pot.item
if world.retro[player] and new_pot.item == PotItem.FiveArrows:
new_pot.item = PotItem.FiveRupees
if new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange):
if new_pot.room == 'PoD Basement Ledge':
basement = world.get_region(old_pot.room, player)
ledge = world.get_region(new_pot.room, player)
ledge.locations.append(basement.locations.pop())
elif new_pot.room == 'Swamp Push Statue':
from Rules import set_rule
set_rule(world.get_entrance('Swamp Push Statue NE', player), lambda state: state.has('Cane of Somaria', player))
world.get_door('Swamp Push Statue NW', player).blocked = True
elif new_pot.room == 'Thieves Attic Hint':
# Rule is created based on barrier
world.get_door('Thieves Attic ES', player).barrier(CrystalBarrier.Orange)
else:
raise Exception("Switch location in room %s requires logic change" % new_pot.room)
for location in world.get_locations():
if location.player == player and location.type == LocationType.Pot and location.pot.item == PotItem.Switch:
location.real = False
world.dynamic_locations.remove(location)
location.parent.locations.remove(location)
world.clear_location_cache()
key_drop_data = {
'Hyrule Castle - Map Guard Key Drop': ['Drop', (0x09E20C, 0x72), 'in Hyrule Castle', 'Small Key (Escape)'],
'Hyrule Castle - Boomerang Guard Key Drop': ['Drop', (0x09E204, 0x71), 'in Hyrule Castle', 'Small Key (Escape)'],
@@ -459,9 +506,8 @@ class PotSecretTable(object):
data_address = pc_to_snes(data_pointer) & 0xFFFF
rom.write_bytes(pointer_address + room * 2, int16_as_bytes(data_address))
for pot in self.room_map[room]:
if not pot.empty():
rom.write_bytes(data_pointer + list_idx * 3, pot.pot_data())
list_idx += 1
rom.write_bytes(data_pointer + list_idx * 3, pot.pot_data())
list_idx += 1
rom.write_bytes(data_pointer + list_idx * 3, [0xFF, 0xFF])
data_pointer += 3 * list_idx + 2
else:
@@ -472,7 +518,7 @@ class PotSecretTable(object):
size = 0x128 * 2
for room in range(0, 0x128):
if room in self.room_map:
pot_list = [p for p in self.room_map[room] if not p.empty()]
pot_list = [p for p in self.room_map[room]]
if pot_list:
size += len(pot_list) * 3 + 2
return size

View File

@@ -726,7 +726,8 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'TR Crystal Maze Interior', 'Turtle Rock', None, ['TR Crystal Maze Interior to End Barrier - Blue', 'TR Crystal Maze Interior to Start Barrier - Blue', 'TR Crystal Maze Interior to Start Bypass', 'TR Crystal Maze Interior to End Bypass']),
create_dungeon_region(player, 'TR Crystal Maze End', 'Turtle Rock', None, ['TR Crystal Maze North Stairs', 'TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze End to Ranged Crystal']),
create_dungeon_region(player, 'TR Crystal Maze End - Ranged Crystal', 'Turtle Rock', None, ['TR Crystal Maze End Ranged Crystal Exit']),
create_dungeon_region(player, 'TR Final Abyss', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss NW']),
create_dungeon_region(player, 'TR Final Abyss Balcony', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss Balcony Path']),
create_dungeon_region(player, 'TR Final Abyss Ledge', 'Turtle Rock', None, ['TR Final Abyss NW', 'TR Final Abyss Ledge Path']),
create_dungeon_region(player, 'TR Boss', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize'], ['TR Boss SW']),
# gt
@@ -981,49 +982,53 @@ def create_shops(world, player):
def adjust_locations(world, player):
if world.keydropshuffle[player] != 'none':
world.pot_contents[player] = PotSecretTable()
for location in key_drop_data.keys():
loc = world.get_location(location, player)
# handle pots
world.pot_contents[player] = PotSecretTable()
for location, datum in key_drop_data.items():
loc = world.get_location(location, player)
if 'Drop' == datum[0]:
loc.type = LocationType.Drop
snes_address, room = datum[1]
loc.address = snes_address
else:
loc.type = LocationType.Pot
pot = next(p for p in vanilla_pots[datum[1]] if p.item == PotItem.Key).copy()
loc.pot = pot
world.pot_contents[player].room_map[datum[1]].append(pot)
if world.keydropshuffle[player] == 'none':
loc.skip = True
else:
key_item = loc.item
key_item.location = None
loc.forced_item = None
loc.item = None
loc.event = False
datum = key_drop_data[location]
# todo: set type of location
if 'Drop' == datum[0]:
loc.type = LocationType.Drop
snes_address, room = datum[1]
loc.address = snes_address
else:
loc.type = LocationType.Pot
pot = next(p for p in vanilla_pots[datum[1]] if p.item == PotItem.Key).copy()
loc.pot = pot
world.pot_contents[player].room_map[datum[1]].append(pot)
item_dungeon = key_item.dungeon
dungeon = world.get_dungeon(item_dungeon, player)
if key_item.smallkey and not world.retro[player]:
dungeon.small_keys.append(key_item)
elif key_item.bigkey:
dungeon.big_key = key_item
if world.keydropshuffle[player] == 'potsanity':
for super_tile, pot_list in vanilla_pots.items():
for pot_index, pot_orig in enumerate(pot_list):
pot = pot_orig.copy()
if pot.item in [PotItem.Hole, PotItem.Switch]:
world.pot_contents[player].room_map[super_tile].append(pot)
elif pot.item != PotItem.Key:
parent = world.get_region(pot.room, player)
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
pot_location = Location(player, f'{pot.room} {descriptor}', player, hint_text='in a pot',
parent=parent)
world.dynamic_locations.append(pot_location)
pot_location.pot = pot
world.pot_contents[player].room_map[super_tile].append(pot)
pot_location.type = LocationType.Pot
parent.locations.append(pot_location)
for super_tile, pot_list in vanilla_pots.items():
for pot_index, pot_orig in enumerate(pot_list):
pot = pot_orig.copy()
if pot.item != PotItem.Key:
world.pot_contents[player].room_map[super_tile].append(pot)
if world.keydropshuffle[player] == 'potsanity':
if (pot.item not in [PotItem.Key, PotItem.Hole]
and (pot.item != PotItem.Switch or world.potshuffle[player])):
parent = world.get_region(pot.room, player)
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
# todo: better hints
hint_text = 'under a block' if pot.flags & PotFlags.Block else 'in a pot'
pot_location = Location(player, f'{pot.room} {descriptor}', player, hint_text=hint_text,
parent=parent)
world.dynamic_locations.append(pot_location)
pot_location.pot = pot
pot_location.type = LocationType.Pot
parent.locations.append(pot_location)
if world.shopsanity[player]:
index = 0
for shop, location_list in shop_to_location_table.items():
@@ -1041,6 +1046,8 @@ def adjust_locations(world, player):
if location:
location.type = LocationType.Logical
location.real = False
if l not in ['Ganon', 'Agahnim 1', 'Agahnim 2']:
location.skip = True

33
Rom.py
View File

@@ -35,7 +35,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '3ee527f67af25e860ffd4364d2369f0e'
RANDOMIZERBASEHASH = 'c1b18f6455af56b738d4fe8f266ef055'
class JsonRom(object):
@@ -731,10 +731,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
dr_flags |= DROptions.Fix_EG
my_locations = world.get_filled_locations(player)
valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and l.pot.indicator)
valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and not l.forced_item)
or (l.type == LocationType.Drop and not l.forced_item)
or (l.type == LocationType.Normal and not l.forced_item)
or (l.type == LocationType.Shop and world.shopsanity[player]))]
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
# fix hc big key problems (map and compass too)
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player] != 'none':
@@ -742,7 +743,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x15270, 2)
sanctuary = world.get_region('Sanctuary', player)
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
update_compasses(rom, valid_locations, world, player)
update_compasses(rom, valid_loc_by_dungeon, world, player)
def should_be_bunny(region, mode):
if mode != 'inverted':
@@ -774,10 +775,14 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
else:
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used
rom.write_byte(0x13f030+offset, layout.max_chests)
# todo: fix these for pot shuffle
builder = world.dungeon_layouts[player][name]
rom.write_byte(0x13f080+offset, builder.location_cnt % 10)
rom.write_byte(0x13f090+offset, builder.location_cnt // 10)
rom.write_byte(0x13f0a0+offset, builder.location_cnt)
valid_cnt = len(valid_loc_by_dungeon[name])
if valid_cnt > 99:
logging.getLogger('').warning(f'{name} exceeds 99 in locations ({valid_cnt})')
rom.write_byte(0x13f080+offset, valid_cnt % 10)
rom.write_byte(0x13f090+offset, valid_cnt // 10)
rom.write_byte(0x13f0a0+offset, valid_cnt)
bk_status = 1 if builder.bk_required else 0
bk_status = 2 if builder.bk_provided else bk_status
rom.write_byte(0x13f040+offset*2, bk_status)
@@ -865,19 +870,25 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.keydropshuffle[player] != 'none':
rom.write_byte(0x142A50, 1)
rom.write_byte(0x142A51, 1)
rom.write_byte(0x142A52, 216-credits_total) # todo: this is now a delta
if world.keydropshuffle[player] == 'potsanity':
rom.write_bytes(0x04DFD8, [0x18, 0x0B, 0x1C])
rom.write_byte(0x04E002, 0xFF)
rom.write_byte(0x142A52, credits_total - 216) # todo: this is now a delta
write_int16(rom, 0x187010, credits_total) # dynamic credits
if credits_total != 216:
# collection rate address (hi):
cr_address = 0x238057
cr_pc = cr_address - 0x120000 # convert to pc
first_top, first_bot = credits_digit((credits_total // 100) % 10)
mid_top, mid_bot = credits_digit((credits_total // 10) % 10)
last_top, last_bot = credits_digit(credits_total % 10)
# top half
rom.write_byte(cr_pc, first_top)
rom.write_byte(cr_pc+0x1, mid_top)
rom.write_byte(cr_pc+0x2, last_top)
# bottom half
rom.write_byte(cr_pc+0x1e, first_bot)
rom.write_byte(cr_pc+0x1f, mid_bot)
rom.write_byte(cr_pc+0x20, last_bot)
@@ -1441,7 +1452,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
elif world.dungeon_counters[player] == 'on':
compass_mode = 0x02 # always on
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla'
or world.dungeon_counters[player] == 'pickup' or world.keydropshuffle != 'none'):
or world.dungeon_counters[player] == 'pickup' or world.keydropshuffle[player] != 'none'):
compass_mode = 0x01 # show on pickup
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
compass_mode |= 0x80 # turn on locating dungeons
@@ -2615,11 +2626,15 @@ def patch_shuffled_dark_sanc(world, rom, player):
rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00])
def update_compasses(rom, valid_locations, world, player):
def valid_dungeon_locations(valid_locations):
dungeon_locations = collections.defaultdict(set)
for l in valid_locations:
if l.parent_region.dungeon:
dungeon_locations[l.parent_region.dungeon.name].add(l)
return dungeon_locations
def update_compasses(rom, dungeon_locations, world, player):
layouts = world.dungeon_layouts[player]
provided_dungeon = False
for name, builder in layouts.items():

View File

@@ -336,8 +336,8 @@ def global_rules(world, player):
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss South Stairs', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss NW', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss Ledge Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
@@ -1912,7 +1912,7 @@ bunny_revivable_entrances = {
"Ice Many Pots", "Mire South Fish", "Mire Right Bridge", "Mire Left Bridge",
"TR Boss", "Eastern Hint Tile Blocked Path", "Thieves Spike Switch",
"Thieves Boss", "Mire Spike Barrier", "Mire Cross", "Mire Hidden Shooters",
"Mire Spikes", "TR Final Abyss", "TR Dark Ride", "TR Pokey 1", "TR Tile Room",
"Mire Spikes", "TR Final Abyss Balcony", "TR Dark Ride", "TR Pokey 1", "TR Tile Room",
"TR Roller Room", "Eastern Cannonball", "Thieves Hallway", "Ice Switch Room",
"Mire Tile Room", "Mire Conveyor Crystal", "Mire Hub", "TR Dash Bridge",
"TR Hub", "Eastern Boss", "Eastern Lobby", "Thieves Ambush",
@@ -1980,7 +1980,7 @@ bunny_impassible_doors = {
'TR Pokey 2 Bottom to Top Barrier - Blue', 'TR Pokey 2 Top to Bottom Barrier - Blue', 'TR Twin Pokeys SW', 'TR Twin Pokeys EN', 'TR Big Chest Gap',
'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride Up Stairs',
'TR Dark Ride SW', 'TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze End to Interior Barrier - Blue',
'TR Final Abyss South Stairs', 'TR Final Abyss NW', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
'TR Final Abyss Balcony Path', 'TR Final Abyss Ledge Path', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
'GT Bob\'s Room Hole', 'GT Speed Torch SE', 'GT Speed Torch South Path', 'GT Speed Torch North Path',
'GT Crystal Conveyor NE', 'GT Crystal Conveyor WN', 'GT Conveyor Cross EN', 'GT Conveyor Cross WN',
'GT Hookshot East-North Path', 'GT Hookshot East-South Path', 'GT Hookshot North-East Path',

Binary file not shown.

View File

@@ -492,6 +492,19 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False):
return locations
def filter_pot_locations(locations, world):
if world.algorithm == 'district':
config = world.item_pool_config
restricted = config.location_groups[0].locations
filtered = [l for l in locations if l.name not in restricted or l.player not in restricted[l.name]]
return filtered if len(filtered) > 0 else locations
if world.algorithm == 'vanilla_fill':
# todo: vanilla pot location stuff
pass
return locations
vanilla_mapping = {
'Green Pendant': ['Eastern Palace - Prize'],
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],