first pass at owg logic support

This commit is contained in:
qadan 2020-01-03 04:02:15 -04:00
parent 636a18cee9
commit c09fab64f8
8 changed files with 122 additions and 24 deletions

View File

@ -28,14 +28,17 @@ def parse_arguments(argv, no_defaults=False):
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'],
parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'nologic'],
help='''\
Select Enforcement of Item Requirements. (default: %(default)s)
No Glitches:
Minor Glitches: May require Fake Flippers, Bunny Revival
and Dark Room Navigation.
Overworld Glitches: May require overworld glitches. Starts with
boots.
No Logic: Distribute items without regard for
item requirements.
item requirements. Starts with
boots
''')
parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'],
help='''\

View File

@ -2987,7 +2987,8 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
('Pyramid Drop', 'East Dark World')
]
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia Island', 'Lake Hylia Island'),
('Zoras River', 'Zoras River'),
('Kings Grave Outer Rocks', 'Kings Grave Area'),
('Kings Grave Inner Rocks', 'Light World'),

2
Gui.py
View File

@ -193,7 +193,7 @@ def guiMain(args=None):
logicFrame = Frame(drowDownFrame)
logicVar = StringVar()
logicVar.set('noglitches')
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'nologic')
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'owglitches', 'nologic')
logicOptionMenu.pack(side=RIGHT)
logicLabel = Label(logicFrame, text='Game logic')
logicLabel.pack(side=LEFT)

View File

@ -9,7 +9,7 @@ def create_inverted_regions(world, player):
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump',
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier',
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', 'Lake Hylia Island',
'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop',
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',

View File

@ -177,7 +177,7 @@ def generate_itempool(world, player):
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
world.rupoor_cost = min(world.customitemarray[69], 9999)
else:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player])
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.logic[player])
for item in precollected_items:
world.push_precollected(ItemFactory(item, player))
@ -386,7 +386,7 @@ def set_up_shops(world, player):
#special shop types
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, logic):
pool = []
placed_items = {}
precollected_items = []
@ -403,6 +403,11 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
def want_progressives():
return random.choice([True, False]) if progressive == 'random' else progressive == 'on'
# provide boots to major glitch dependent seeds
if logic in ['owglitches', 'nologic']:
precollected_items.append('Pegasus Boots')
pool.remove('Pegasus Boots')
if want_progressives():
pool.extend(progressivegloves)
else:
@ -685,19 +690,20 @@ def test():
for progressive in ['on', 'off']:
for shuffle in ['full', 'insanity_legacy']:
for retro in [True, False]:
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro)
count = len(out[0]) + len(out[1])
for logic in ['noglitches', 'owglitches']:
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, logic)
count = len(out[0]) + len(out[1])
correct_count = total_items_to_place
if goal == 'pedestal' and swords != 'vanilla':
# pedestal goals generate one extra item
correct_count += 1
if retro:
correct_count += 28
try:
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro))
except AssertionError as e:
print(e)
correct_count = total_items_to_place
if goal == 'pedestal' and swords != 'vanilla':
# pedestal goals generate one extra item
correct_count += 1
if retro:
correct_count += 28
try:
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro))
except AssertionError as e:
print(e)
if __name__ == '__main__':
test()

View File

@ -114,10 +114,10 @@ def roll_settings(weights):
ret = argparse.Namespace()
glitches_required = get_choice('glitches_required')
if glitches_required not in ['none', 'no_logic']:
print("Only NMG and No Logic supported")
if glitches_required not in ['none', 'no_logic', 'overworld_glitches']:
print("Only NMG, OWG and No Logic supported")
glitches_required = 'none'
ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required]
ret.logic = {'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches'}[glitches_required]
item_placement = get_choice('item_placement')
# not supported in ER

8
Rom.py
View File

@ -901,7 +901,7 @@ def patch_rom(world, player, rom, enemized):
rom.write_byte(x, 0) # Zero the initial equipment array
rom.write_byte(0x18302C, 0x18) # starting max health
rom.write_byte(0x18302D, 0x18) # starting current health
rom.write_byte(0x183039, 0x68) # starting abilities, bit array
ability_flags = 0x68 # starting abilities, bit array; may be modified by precollected items
for item in world.precollected_items:
if item.player != player:
@ -911,9 +911,15 @@ def patch_rom(world, player, rom, enemized):
rom.write_byte(0x183000+0x19, 0x01)
rom.write_byte(0x0271A6+0x19, 0x01)
rom.write_byte(0x180043, 0x01) # special starting sword byte
elif item.name == 'Pegasus Boots':
rom.write_byte(0x183015, 0x01)
ability_flags |= 0b00000100
else:
raise RuntimeError("Unsupported pre-collected item: {}".format(item))
# write abilities after ability flags have been determined
rom.write_byte(0x183039, ability_flags)
rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
rom.write_byte(0x2AF79, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both)

View File

@ -38,6 +38,8 @@ def set_rules(world, player):
if world.logic[player] == 'noglitches':
no_glitches_rules(world, player)
elif world.logic[player] == 'owglitches':
overworld_glitches_rules(world, player)
elif world.logic[player] == 'minorglitches':
logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.')
else:
@ -357,7 +359,6 @@ def default_rules(world, player):
set_rule(world.get_entrance('Kings Grave Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player))
# Caution: If king's grave is releaxed at all to account for reaching it via a two way cave's exit in insanity mode, then the bomb shop logic will need to be updated (that would involve create a small ledge-like Region for it)
set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('Bat Cave Drop Ledge', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has('Beat Agahnim 1', player))
set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('Desert Palace Stairs', player), lambda state: state.has('Book of Mudora', player))
@ -625,8 +626,10 @@ def no_glitches_rules(world, player):
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player)))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
else:
set_rule(world.get_entrance('Bat Cave Drop Ledge', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player)))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to
set_rule(world.get_entrance('Lake Hylia Island', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player)))
@ -641,7 +644,10 @@ def no_glitches_rules(world, player):
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override
set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False)
set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False)
add_conditional_lamps(world, player)
def add_conditional_lamps(world, player):
# Light cones in standard depend on which world we actually are in, not which one the location would normally be
# We add Lamp requirements only to those locations which lie in the dark world (or everything if open
DW_Entrances = ['Bumper Cave (Bottom)', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)', 'Hookshot Cave', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance', 'Dark Death Mountain Ledge (East)',
@ -690,6 +696,82 @@ def no_glitches_rules(world, player):
add_lamp_requirement(world.get_entrance('Throne Room', player), player)
def overworld_glitches_rules(world, player):
# spots that are immediately accessible
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: True)
set_rule(world.get_region('Lake Hylia Central Island', player), lambda state: True)
set_rule(world.get_entrance('Zoras River', player), lambda state: True)
# lw boots-accessible locations
lw_boots_accessible_regions = [
'Bat Cave Drop Ledge',
'Lake Hylia Island',
'Desert Ledge',
'Desert Ledge (Northeast)',
'Desert Palace Lone Stairs',
'Desert Palace Entrance (North) Spot',
'Death Mountain',
'Death Mountain Return Ledge',
'East Death Mountain (Bottom)',
'East Death Mountain (Top)',
'Death Mountain (Top)',
'Spectacle Rock',
'Death Mountain Floating Island (Light World)',
]
# dw boots-accessible regions
dw_boots_accessible_regions = [
'East Dark World',
'Northeast Dark World',
'West Dark World',
'Hammer Peg Area',
'Bumper Cave Ledge',
'Dark Desert',
'Dark Death Mountain (Top)',
'Dark Death Mountain (East Bottom)',
'Dark Death Mountain Ledge',
'Death Mountain Floating Island (Dark World)',
'Turtle Rock (Top)',
]
# set up boots-accessible regions
if world.mode[player] != 'inverted':
lw_boots_accessible_regions.append('Cave 45 Ledge')
lw_boots_accessible_regions.append('Graveyard Ledge')
# couple other random spots
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player) and (state.has_Mirror(player) or state.has_Boots(player)))
set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: (state.has('Ocarina', player) or state.has_Boots(player)) and state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: (state.has('Hammer', player) or state.has_Boots(player)) and state.can_lift_rocks(player))
set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has_Boots(player))
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has_Boots(player) and state.has_Pearl(player), 'or')
needs_boots = lambda state: state.has_Boots(player)
needs_boots_and_pearl = lambda state: state.has_Boots(player) and state.has_Pearl(player)
for spot in lw_boots_accessible_regions:
for location in world.get_region(spot, player).locations:
add_rule(world.get_location(location, player), needs_boots_and_pearl if world.mode[player] == 'inverted' else needs_boots, 'or')
for spot in dw_boots_accessible_regions:
for location in world.get_region(spot, player).locations:
add_rule(world.get_location(location, player), needs_boots if world.mode[player] == 'inverted' else needs_boots_and_pearl, 'or')
# bunny DMD rules
if world.mode[player] != 'inverted':
# set up some mirror-accessible dw entrances.
boots_and_mirror = lambda state: state.has_Boots(player) and state.has_Mirror(player)
add_rule(world.get_entrance('Dark Sanctuary Hint', player), boots_and_mirror, 'or') # should suffice to give us west dark world access
for spot in world.get_region('Dark Death Mountain (East Bottom)', player).locations:
add_rule(world.get_location(spot, player), boots_and_mirror, 'or')
# dw entrances accessible with mirror and hookshot
mirror_hookshot_accessible_dw_locations = [
'Pyramid Fairy',
'Pyramid Entrance',
'Pyramid Drop',
]
mirror_hookshot_accessible_dw_locations.extend(world.get_region('Dark Death Mountain Ledge', player).locations)
for spot in mirror_hookshot_accessible_dw_locations:
add_rule(world.get_entrance(spot, player), lambda state: state.has_Boots(player) and state.has_Mirror(player) and state.has('Hookshot', player), 'or')
# dw entrances accessible with mirror and titans
boots_mirror_titans = lambda state: state.has_Boots(player) and state.has_Mirror(player) and state.can_lift_heavy_rocks(player)
add_rule(world.get_entrance('Mire Shed', player), boots_mirror_titans, 'or')
add_rule(world.get_location('Frog', player), boots_mirror_titans, 'or')
add_conditional_lamps(world, player)
def open_rules(world, player):
# softlock protection as you can reach the sewers small key door with a guard drop key
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player))