Add experimental insanity mode that shuffles crossworld. Mirror and Pearl are provided so fake LW/DW and bunny issues in filling logic can be prevented. Should work for now, but would be much more interesting with Pearl and Mirror in pool.

This commit is contained in:
LLCoolDave 2017-05-22 19:52:50 +02:00
parent 316ffc2117
commit 090ea5282b
2 changed files with 168 additions and 6 deletions

View File

@ -496,6 +496,157 @@ def link_entrances(world):
# place remaining doors
ret.append(connect_doors(world, single_doors, door_targets))
elif world.shuffle == 'insanity':
# beware ye who enter here
ret.append('Mixed Entrances:\n\n')
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Thieves Forest Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
entrances_must_exits = DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit + ['Skull Woods Second Section Door (West)']
doors = LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Thieves Forest Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Secret Entrance Stairs'] + Old_Man_Entrances +\
DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)']
random.shuffle(doors)
old_man_entrances = list(Old_Man_Entrances)
caves = Cave_Exits + Dungeon_Exits + Cave_Three_Exits + ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)',
'Kakariko Well Exit', 'Bat Cave Exit', 'North Fairy Cave Exit', 'Thieves Forest Hideout Exit', 'Lumberjack Tree Exit', 'Sanctuary Exit']
# shuffle up holes
hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Thieves Forest Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave',
'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Thieves Forest Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section',
'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']
if world.mode == 'standard':
# cannot move uncle cave
ret.append(connect_one_way(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance'))
ret.append(connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs'))
ret.append(connect_entrance(world, doors.pop(), 'Hyrule Castle Secret Entrance Exit'))
else:
hole_entrances.append('Hyrule Castle Secret Entrance Drop')
hole_targets.append('Hyrule Castle Secret Entrance')
entrances.append('Hyrule Castle Secret Entrance Stairs')
caves.append('Hyrule Castle Secret Entrance Exit')
random.shuffle(hole_entrances)
random.shuffle(hole_targets)
random.shuffle(entrances)
# fill up holes
for hole in hole_entrances:
ret.append(connect_one_way(world, hole, hole_targets.pop()))
# hyrule castle handling
if world.mode == 'standard':
# must connect front of hyrule castle to do escape
ret.append(connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)'))
ret.append(connect_exit(world, 'Hyrule Castle Exit (South)', entrances.pop()))
caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
else:
doors.append('Hyrule Castle Entrance (South)')
caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
# now let's deal with mandatory reachable stuff
def extract_reachable_exit(cavelist):
random.shuffle(cavelist)
candidate = None
for cave in cavelist:
if isinstance(cave, tuple) and len(cave) > 1:
# special handling: TRock has two entries that we should consider entrance only
if cave[0] == 'Turtle Rock Exit (Front)' and len(cave) == 2:
continue
candidate = cave
break
if candidate is None:
raise RuntimeError('No suitable cave.')
cavelist.remove(candidate)
return candidate
def connect_reachable_exit(entrance, caves, doors):
cave = extract_reachable_exit(caves)
exit = cave[-1]
cave = cave[:-1]
ret.append(connect_exit(world, exit, entrance))
ret.append(connect_entrance(world, doors.pop(), exit))
# rest of cave now is forced to be in this world
caves.append(cave)
# connect mandatory exits
for entrance in entrances_must_exits:
connect_reachable_exit(entrance, caves, doors)
# place old man, has limited options
# exit has to come from specific set of doors, the entrance is free to move about
random.shuffle(old_man_entrances)
old_man_exit = old_man_entrances.pop()
entrances.extend(old_man_entrances)
random.shuffle(entrances)
ret.append(connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit))
ret.append(connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)'))
caves.append('Old Man Cave Exit (West)')
# Aghanim needs to be Light World to spawn
# find suitable LW Entrance
lw_entrances = LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit + Old_Man_Entrances + \
['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Thieves Forest Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)', 'Hyrule Castle Secret Entrance Stairs']
candidate = None
for entrance in entrances:
if entrance in lw_entrances:
candidate = entrance
break
# should always have enough candidates left here, should never happen
if candidate is None:
raise RuntimeError('Your Algorithm is broken.')
entrances.remove(candidate)
ret.append(connect_exit(world, 'Agahnims Tower Exit', candidate))
ret.append(connect_entrance(world, doors.pop(), 'Agahnims Tower Exit'))
# handle remaining caves
for cave in caves:
if isinstance(cave, str):
cave = (cave,)
for exit in cave:
ret.append(connect_exit(world, exit, entrances.pop()))
ret.append(connect_entrance(world, doors.pop(), exit))
# handle simple doors
single_doors = list(Single_Cave_Doors)
bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
door_targets = list(Single_Cave_Targets)
# place blacksmith, has limited options
random.shuffle(blacksmith_doors)
blacksmith_hut = blacksmith_doors.pop()
ret.append(connect_one_way(world, blacksmith_hut, 'Blacksmiths Hut'))
bomb_shop_doors.extend(blacksmith_doors)
# place dam and pyramid fairy, have limited options
# ToDo Dam might be behind fat fairy if we later check for this when placing crystal 5 and 6
random.shuffle(bomb_shop_doors)
bomb_shop = bomb_shop_doors.pop()
ret.append(connect_one_way(world, bomb_shop, 'Big Bomb Shop'))
dam = bomb_shop_doors.pop()
ret.append(connect_one_way(world, dam, 'Dam'))
single_doors.extend(bomb_shop_doors)
# tavern back door cannot be shuffled yet
ret.append(connect_doors(world, ['Tavern North'], ['Tavern']))
# place remaining doors
ret.append(connect_doors(world, single_doors, door_targets))
else:
raise NotImplementedError('Shuffling not supported yet')
@ -1074,6 +1225,8 @@ mandatory_connections = [('Links House', 'Links House'), # unshuffled. For now
('Isolated Ledge Mirror Spot', 'Death Mountain Fairy Drop Ledge'),
('Spiral Cave Mirror Spot', 'Spiral Cave Ledge'),
('Palace of Darkness Pay Kiki', 'Palace of Darkness Kiki Door'),
('Palace of Darkness Kiki Door Reverse', 'East Dark World'),
('Swamp Palace Moat', 'Swamp Palace (First Room)'),
('Swamp Palace Small Key Door', 'Swamp Palace (Starting Area)'),
('Swamp Palace (Center)', 'Swamp Palace (Center)'),

21
Main.py
View File

@ -231,10 +231,8 @@ def generate_itempool(world):
IceRod(),
Lamp(),
Cape(),
Mirror(),
Powder(),
RedBoomerang(),
Pearl(),
Mushroom(),
Rupees100(),
Rupee(), Rupee(),
@ -257,6 +255,13 @@ def generate_itempool(world):
else:
world.itempool.append(ProgressiveSword())
# provide mirror and pearl so you can avoid fake DW/LW and do dark world exploration as intended by algorithm, for now
if world.shuffle == 'insanity':
world.push_item('[cave-040] Links House', Mirror())
world.push_item('[dungeon-C-1F] Sanctuary', Pearl())
else:
world.itempool.extend([Mirror(), Pearl()])
if world.goal == 'pedestal':
world.push_item('Altar', Triforce())
items = list(world.itempool)
@ -506,10 +511,14 @@ if __name__ == '__main__':
parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['normal'], help='Select game difficulty. Affects available itempool.')
parser.add_argument('--algorithm', default='regular', const='regular', nargs='?', choices=['regular', 'flood'],
help='Select item filling algorithm. Regular is the ordinary VT algorithm. Flood pushes out items starting from Link\'s House and is slightly biased to placing progression items with less restrictions.')
parser.add_argument('--shuffle', default='full', const='full', nargs='?', choices=['default', 'simple', 'restricted', 'full', 'madness', 'dungeonsfull', 'dungeonssimple'],
help='Select Entrance Shuffling Algorithm. Default is the Vanilla layout. Simple shuffles Dungeon Entrances/Exits between each other and keeps all 4-entrance dungeons confined to one location. All caves outside of death mountain are shuffled in pairs. '
'Restricted uses Dungeons shuffling from Simple but freely connects remaining entrances. Full mixes cave and dungeon entrances freely. Madness decouples entrances and exits from each other and shuffles them freely, only ensuring that no fake Light/Dark World happens and '
'all locations are reachable. The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla.')
parser.add_argument('--shuffle', default='full', const='full', nargs='?', choices=['default', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'],
help='Select Entrance Shuffling Algorithm. Default is the Vanilla layout. \n'
'Simple shuffles Dungeon Entrances/Exits between each other and keeps all 4-entrance dungeons confined to one location. All caves outside of death mountain are shuffled in pairs.\n'
'Restricted uses Dungeons shuffling from Simple but freely connects remaining entrances.\n'
'Full mixes cave and dungeon entrances freely.\n'
'Madness decouples entrances and exits from each other and shuffles them freely, only ensuring that no fake Light/Dark World happens and all locations are reachable.\n'
'Insanity is Madness without the world restrictions. Mirror and Pearl are provided early to ensure Filling algorithm works properly. Deal with Fake LW/DW at your discretion. Experimental.\n'
'The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla.')
parser.add_argument('--openrom', default='Open_Base_Rom.sfc', help='Path to a VT21 open normal difficulty rom to use as a base.')
parser.add_argument('--standardrom', default='Standard_Base_Rom.sfc', help='Path to a VT21 standard normal difficulty rom to use as a base.')
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')