commit
ae7c65aa1a
|
@ -3,7 +3,7 @@ from enum import Enum, unique
|
|||
import logging
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from Utils import int16_as_bytes
|
||||
|
||||
class World(object):
|
||||
|
||||
|
@ -18,6 +18,7 @@ class World(object):
|
|||
self.algorithm = algorithm
|
||||
self.dungeons = []
|
||||
self.regions = []
|
||||
self.shops = []
|
||||
self.itempool = []
|
||||
self.seed = None
|
||||
self.state = CollectionState(self)
|
||||
|
@ -379,11 +380,17 @@ class CollectionState(object):
|
|||
|
||||
def has_key(self, item, count=1):
|
||||
if self.world.retro:
|
||||
return True #FIXME: This needs to check for shop access to a small key shop
|
||||
return self.can_buy_unlimited('Small Key (Universal)')
|
||||
if count == 1:
|
||||
return item in self.prog_items
|
||||
return self.item_count(item) >= count
|
||||
|
||||
def can_buy_unlimited(self, item):
|
||||
for shop in self.world.shops:
|
||||
if shop.has_unlimited(item) and shop.region.can_reach(self):
|
||||
return True
|
||||
return False
|
||||
|
||||
def item_count(self, item):
|
||||
return len([pritem for pritem in self.prog_items if pritem == item])
|
||||
|
||||
|
@ -424,18 +431,25 @@ class CollectionState(object):
|
|||
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count())
|
||||
elif self.world.difficulty == 'insane' and not fullrefill:
|
||||
basemagic = basemagic
|
||||
else:
|
||||
elif self.can_buy_unlimited('Green Potion') or self.can_buy_unlimited('Red Potion'):
|
||||
basemagic = basemagic + basemagic * self.bottle_count()
|
||||
return basemagic >= smallmagic # FIXME bottle should really also have a requirement that we can reach some shop that sells green or blue potions
|
||||
return basemagic >= smallmagic
|
||||
|
||||
def can_kill_most_things(self, enemies=5):
|
||||
return (self.has_blunt_weapon()
|
||||
or self.has('Cane of Somaria')
|
||||
or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic()))
|
||||
or self.has('Bow')
|
||||
or self.can_shoot_arrows()
|
||||
or self.has('Fire Rod')
|
||||
)
|
||||
|
||||
def can_shoot_arrows(self):
|
||||
if self.world.retro:
|
||||
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location)
|
||||
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe
|
||||
return self.has('Bow') and (self.has('Silver Arrows') or self.can_buy_unlimited('Single Arrow'))
|
||||
return self.has('Bow')
|
||||
|
||||
def has_sword(self):
|
||||
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
||||
|
||||
|
@ -574,7 +588,6 @@ class RegionType(Enum):
|
|||
return self in (RegionType.Cave, RegionType.Dungeon)
|
||||
|
||||
|
||||
|
||||
class Region(object):
|
||||
|
||||
def __init__(self, name, type):
|
||||
|
@ -584,6 +597,7 @@ class Region(object):
|
|||
self.exits = []
|
||||
self.locations = []
|
||||
self.dungeon = None
|
||||
self.shop = None
|
||||
self.world = None
|
||||
self.is_light_world = False # will be set aftermaking connections.
|
||||
self.is_dark_world = False
|
||||
|
@ -751,6 +765,64 @@ class Item(object):
|
|||
class Crystal(Item):
|
||||
pass
|
||||
|
||||
@unique
|
||||
class ShopType(Enum):
|
||||
Shop = 0
|
||||
TakeAny = 1
|
||||
|
||||
class Shop(object):
|
||||
def __init__(self, region, room_id, type, shopkeeper_config, replaceable):
|
||||
self.region = region
|
||||
self.room_id = room_id
|
||||
self.type = type
|
||||
self.inventory = [None, None, None]
|
||||
self.shopkeeper_config = shopkeeper_config
|
||||
self.replaceable = replaceable
|
||||
self.active = False
|
||||
|
||||
@property
|
||||
def item_count(self):
|
||||
return (3 if self.inventory[2] else
|
||||
2 if self.inventory[1] else
|
||||
1 if self.inventory[0] else
|
||||
0)
|
||||
|
||||
def get_bytes(self):
|
||||
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
||||
entrances = self.region.entrances
|
||||
config = self.item_count
|
||||
if len(entrances) == 1 and entrances[0].addresses:
|
||||
door_id = entrances[0].addresses+1
|
||||
else:
|
||||
door_id = 0
|
||||
config |= 0x40 # ignore door id
|
||||
if self.type == ShopType.TakeAny:
|
||||
config |= 0x80
|
||||
return [0x00]+int16_as_bytes(self.room_id)+[door_id, 0x00, config, self.shopkeeper_config, 0x00]
|
||||
|
||||
def has_unlimited(self, item):
|
||||
for inv in self.inventory:
|
||||
if inv is None:
|
||||
continue
|
||||
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == item:
|
||||
return True
|
||||
elif inv['item'] is not None and inv['item'] == item:
|
||||
return True
|
||||
return False
|
||||
|
||||
def clear_inventory(self):
|
||||
self.inventory = [None, None, None]
|
||||
|
||||
def add_inventory(self, slot, item, price, max=0, replacement=None, replacement_price=0, create_location=False):
|
||||
self.inventory[slot] = {
|
||||
'item': item,
|
||||
'price': price,
|
||||
'max': max,
|
||||
'replacement': replacement,
|
||||
'replacement_price': replacement_price,
|
||||
'create_location': create_location
|
||||
}
|
||||
|
||||
|
||||
class Spoiler(object):
|
||||
|
||||
|
|
|
@ -1026,7 +1026,7 @@ def link_entrances(world):
|
|||
if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)':
|
||||
world.swamp_patch_required = True
|
||||
|
||||
# check for
|
||||
# check for potion shop location
|
||||
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
|
||||
world.powder_patch_required = True
|
||||
|
||||
|
@ -1744,6 +1744,7 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
|
|||
('Paradox Cave Push Block', 'Paradox Cave Front'),
|
||||
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
||||
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
|
||||
('Light World Death Mountain Shop', 'Light World Death Mountain Shop'),
|
||||
('Fairy Ascension Rocks', 'Fairy Ascension Plateau'),
|
||||
('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'),
|
||||
('Fairy Ascension Drop', 'East Death Mountain (Bottom)'),
|
||||
|
|
45
ItemList.py
45
ItemList.py
|
@ -217,6 +217,14 @@ def generate_itempool(world):
|
|||
world.get_location('Agahnim 1').event = True
|
||||
world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False)
|
||||
world.get_location('Agahnim 2').event = True
|
||||
world.push_item('Dark Blacksmith Ruins', ItemFactory('Pick Up Purple Chest'), False)
|
||||
world.get_location('Dark Blacksmith Ruins').event = True
|
||||
world.push_item('Frog', ItemFactory('Get Frog'), False)
|
||||
world.get_location('Frog').event = True
|
||||
world.push_item('Missing Smith', ItemFactory('Return Smith'), False)
|
||||
world.get_location('Missing Smith').event = True
|
||||
world.push_item('Floodgate', ItemFactory('Open Floodgate'), False)
|
||||
world.get_location('Floodgate').event = True
|
||||
|
||||
# set up item pool
|
||||
if world.custom:
|
||||
|
@ -254,6 +262,8 @@ def generate_itempool(world):
|
|||
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||
world.required_medallions = (mm_medallion, tr_medallion)
|
||||
|
||||
set_up_shops(world)
|
||||
|
||||
# distribute crystals
|
||||
fill_prizes(world)
|
||||
|
||||
|
@ -284,7 +294,23 @@ def fill_prizes(world, attempts=15):
|
|||
raise FillError('Unable to place dungeon prizes')
|
||||
|
||||
|
||||
def set_up_shops(world):
|
||||
# Changes to basic Shops
|
||||
# TODO: move hard+ mode changes for sheilds here, utilizing the new shops
|
||||
|
||||
if world.retro:
|
||||
rss = world.get_region('Red Shield Shop').shop
|
||||
rss.active = True
|
||||
rss.add_inventory(2, 'Single Arrow', 80)
|
||||
|
||||
# Randomized changes to Shops
|
||||
if world.retro:
|
||||
for shop in random.sample([s for s in world.shops if s.replaceable], 5):
|
||||
shop.active = True
|
||||
shop.add_inventory(0, 'Single Arrow', 80)
|
||||
shop.add_inventory(1, 'Small Key (Universal)', 100)
|
||||
|
||||
#special shop types
|
||||
|
||||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro):
|
||||
pool = []
|
||||
|
@ -558,17 +584,18 @@ def test():
|
|||
for mode in ['open', 'standard', 'swordless']:
|
||||
for progressive in ['on', 'off']:
|
||||
for shuffle in ['full', 'insane']:
|
||||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
|
||||
count = len(out[0]) + len(out[1])
|
||||
for retro in [True, False]:
|
||||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
|
||||
count = len(out[0]) + len(out[1])
|
||||
|
||||
correct_count = total_items_to_place
|
||||
if goal in ['pedestal']:
|
||||
# pedestal goals generate one extra item
|
||||
correct_count += 1
|
||||
if retro:
|
||||
correct_count += 28
|
||||
correct_count = total_items_to_place
|
||||
if goal in ['pedestal']:
|
||||
# pedestal goals generate one extra item
|
||||
correct_count += 1
|
||||
if retro:
|
||||
correct_count += 28
|
||||
|
||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode))
|
||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, retro))
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
|
12
Items.py
12
Items.py
|
@ -160,5 +160,15 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
|||
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again'),
|
||||
'Red Potion': (False, False, None, 0x2E, None, None, None, None, None, None),
|
||||
'Green Potion': (False, False, None, 0x2F, None, None, None, None, None, None),
|
||||
'Blue Potion': (False, False, None, 0x30, None, None, None, None, None, None),
|
||||
'Bee': (False, False, None, 0x0E, None, None, None, None, None, None),
|
||||
'Small Heart': (False, False, None, 0x42, None, None, None, None, None, None),
|
||||
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None)}
|
||||
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
}
|
||||
|
|
42
Main.py
42
Main.py
|
@ -1,4 +1,5 @@
|
|||
from collections import OrderedDict
|
||||
import copy
|
||||
from itertools import zip_longest
|
||||
import json
|
||||
import logging
|
||||
|
@ -17,21 +18,21 @@ from Utils import output_path
|
|||
|
||||
__version__ = '0.6.0'
|
||||
|
||||
logic_hash = [26, 76, 4, 144, 72, 105, 234, 233, 12, 184, 95, 94, 100, 13, 15, 174,
|
||||
186, 135, 130, 189, 246, 254, 123, 245, 85, 241, 101, 129, 70, 255, 55, 248,
|
||||
43, 146, 23, 179, 243, 208, 230, 176, 9, 88, 239, 226, 222, 203, 244, 183,
|
||||
205, 74, 44, 5, 122, 220, 206, 47, 221, 125, 138, 155, 98, 79, 238, 119,
|
||||
30, 24, 159, 39, 253, 27, 33, 218, 62, 82, 200, 28, 141, 191, 93, 22,
|
||||
192, 54, 227, 108, 48, 78, 242, 166, 60, 250, 75, 145, 49, 212, 41, 25,
|
||||
127, 89, 178, 157, 19, 158, 177, 231, 207, 66, 172, 17, 133, 61, 109, 86,
|
||||
57, 143, 142, 219, 148, 209, 181, 87, 163, 40, 81, 114, 240, 103, 31, 175,
|
||||
237, 185, 18, 173, 168, 45, 216, 106, 161, 16, 151, 139, 104, 134, 110, 21,
|
||||
32, 131, 118, 182, 215, 67, 3, 73, 171, 71, 150, 147, 223, 247, 42, 132,
|
||||
107, 149, 232, 153, 10, 201, 156, 225, 116, 194, 187, 204, 46, 165, 124, 92,
|
||||
7, 0, 251, 126, 162, 80, 90, 154, 252, 197, 188, 52, 137, 117, 198, 63,
|
||||
167, 38, 136, 96, 58, 11, 1, 115, 229, 224, 37, 112, 170, 59, 68, 196,
|
||||
36, 64, 91, 213, 14, 180, 190, 164, 8, 56, 214, 77, 202, 193, 97, 84,
|
||||
152, 83, 236, 211, 20, 217, 2, 228, 140, 69, 121, 111, 113, 128, 210, 51,
|
||||
logic_hash = [26, 76, 4, 144, 72, 105, 234, 233, 12, 184, 95, 94, 100, 13, 15, 174,
|
||||
186, 135, 130, 189, 246, 254, 123, 245, 85, 241, 101, 129, 70, 255, 55, 248,
|
||||
43, 146, 23, 179, 243, 208, 230, 176, 9, 88, 239, 226, 222, 203, 244, 183,
|
||||
205, 74, 44, 5, 122, 220, 206, 47, 221, 125, 138, 155, 98, 79, 238, 119,
|
||||
30, 24, 159, 39, 253, 27, 33, 218, 62, 82, 200, 28, 141, 191, 93, 22,
|
||||
192, 54, 227, 108, 48, 78, 242, 166, 60, 250, 75, 145, 49, 212, 41, 25,
|
||||
127, 89, 178, 157, 19, 158, 177, 231, 207, 66, 172, 17, 133, 61, 109, 86,
|
||||
57, 143, 142, 219, 148, 209, 181, 87, 163, 40, 81, 114, 240, 103, 31, 175,
|
||||
237, 185, 18, 173, 168, 45, 216, 106, 161, 16, 151, 139, 104, 134, 110, 21,
|
||||
32, 131, 118, 182, 215, 67, 3, 73, 171, 71, 150, 147, 223, 247, 42, 132,
|
||||
107, 149, 232, 153, 10, 201, 156, 225, 116, 194, 187, 204, 46, 165, 124, 92,
|
||||
7, 0, 251, 126, 162, 80, 90, 154, 252, 197, 188, 52, 137, 117, 198, 63,
|
||||
167, 38, 136, 96, 58, 11, 1, 115, 229, 224, 37, 112, 170, 59, 68, 196,
|
||||
36, 64, 91, 213, 14, 180, 190, 164, 8, 56, 214, 77, 202, 193, 97, 84,
|
||||
152, 83, 236, 211, 20, 217, 2, 228, 140, 69, 121, 111, 113, 128, 210, 51,
|
||||
53, 6, 235, 34, 102, 29, 120, 35, 50, 65, 160, 249, 99, 169, 199, 195]
|
||||
|
||||
|
||||
|
@ -157,6 +158,15 @@ def copy_world(world):
|
|||
create_regions(ret)
|
||||
create_dungeons(ret)
|
||||
|
||||
#TODO: copy_dynamic_regions_and_locations() # also adds some new shops
|
||||
|
||||
for shop in world.shops:
|
||||
copied_shop = ret.get_region(shop.region.name).shop
|
||||
copied_shop.active = shop.active
|
||||
copied_shop.inventory = copy.copy(shop.inventory)
|
||||
|
||||
|
||||
|
||||
# connect copied world
|
||||
for region in world.regions:
|
||||
copied_region = ret.get_region(region.name)
|
||||
|
@ -294,8 +304,6 @@ def create_playthrough(world):
|
|||
old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere}
|
||||
if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path):
|
||||
old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop'))
|
||||
if any(exit == 'Swamp Palace Moat' for path in old_world.spoiler.paths.values() for (_, exit) in path) or 'Sunken Treasure' in old_world.required_locations:
|
||||
old_world.spoiler.paths['Dam'] = get_path(state, world.get_region('Dam'))
|
||||
|
||||
# we can finally output our playthrough
|
||||
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])
|
||||
|
|
63
Regions.py
63
Regions.py
|
@ -1,5 +1,5 @@
|
|||
import collections
|
||||
from BaseClasses import Region, Location, Entrance, RegionType
|
||||
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
|
||||
|
||||
|
||||
def create_regions(world):
|
||||
|
@ -25,10 +25,10 @@ def create_regions(world):
|
|||
create_cave_region('Hyrule Castle Secret Entrance', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
||||
create_lw_region('Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||
create_cave_region('Waterfall of Wishing', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
||||
create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_cave_region('Kings Grave', ['King\'s Tomb']),
|
||||
create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']),
|
||||
create_cave_region('Dam', ['Floodgate Chest']),
|
||||
create_cave_region('Dam', ['Floodgate', 'Floodgate Chest']),
|
||||
create_cave_region('Links House', ['Link\'s House'], ['Links House Exit']),
|
||||
create_cave_region('Chris Houlihan Room', None, ['Chris Houlihan Room Exit']),
|
||||
create_cave_region('Tavern', ['Kakariko Tavern']),
|
||||
|
@ -57,7 +57,7 @@ def create_regions(world):
|
|||
create_cave_region('Kakariko Well (top)', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
||||
create_cave_region('Kakariko Well (bottom)', None, ['Kakariko Well Exit']),
|
||||
create_cave_region('Blacksmiths Hut', ['Blacksmith']),
|
||||
create_cave_region('Blacksmiths Hut', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region('Bat Cave (left)', None, ['Bat Cave Exit']),
|
||||
|
@ -95,7 +95,7 @@ def create_regions(world):
|
|||
create_lw_region('Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
|
||||
create_lw_region('Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']),
|
||||
create_dungeon_region('Desert Palace Main (Outer)', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
|
||||
create_dungeon_region('Desert Palace Main (Inner)', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region('Desert Palace East', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region('Desert Palace North', ['Desert Palace - Lanmolas', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
|
@ -125,7 +125,7 @@ def create_regions(world):
|
|||
create_cave_region('Spectacle Rock Cave (Peak)', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||
create_lw_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
||||
create_cave_region('Hookshot Fairy'),
|
||||
create_cave_region('Paradox Cave Front', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)']),
|
||||
create_cave_region('Paradox Cave Front', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
||||
create_cave_region('Paradox Cave Chest Area', ['Paradox Cave Lower - Far Left',
|
||||
'Paradox Cave Lower - Left',
|
||||
'Paradox Cave Lower - Right',
|
||||
|
@ -135,6 +135,7 @@ def create_regions(world):
|
|||
'Paradox Cave Upper - Right'],
|
||||
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
||||
create_cave_region('Paradox Cave', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
||||
create_cave_region('Light World Death Mountain Shop'),
|
||||
create_lw_region('East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']),
|
||||
create_lw_region('Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']),
|
||||
create_cave_region('Spiral Cave (Top)', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
|
||||
|
@ -164,10 +165,10 @@ def create_regions(world):
|
|||
create_cave_region('Dark Lake Hylia Ledge Spike Cave'),
|
||||
create_cave_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
|
||||
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
|
||||
create_dw_region('West Dark World', None, ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock',
|
||||
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']),
|
||||
create_dw_region('West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock',
|
||||
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']),
|
||||
create_dw_region('Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']),
|
||||
create_dw_region('Hammer Peg Area', None, ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']),
|
||||
create_dw_region('Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']),
|
||||
create_dw_region('Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']),
|
||||
create_cave_region('Fortune Teller (Dark)'),
|
||||
create_cave_region('Village of Outcasts Shop'),
|
||||
|
@ -290,6 +291,14 @@ def create_regions(world):
|
|||
create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop'])
|
||||
]
|
||||
|
||||
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
|
||||
region = world.get_region(region_name)
|
||||
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
|
||||
region.shop = shop
|
||||
world.shops.append(shop)
|
||||
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
||||
shop.add_inventory(index, item, price)
|
||||
|
||||
world.intialize_regions()
|
||||
|
||||
def create_lw_region(name, locations=None, exits=None):
|
||||
|
@ -347,6 +356,38 @@ def mark_light_world_regions(world):
|
|||
seen.add(exit.connected_region)
|
||||
queue.append(exit.connected_region)
|
||||
|
||||
# (room_id, shopkeeper, replaceable)
|
||||
shop_table = {
|
||||
'Cave Shop (Dark Death Mountain)': (0x0112, 0x51, True),
|
||||
'Red Shield Shop': (0x0110, 0x51, True),
|
||||
'Dark Lake Hylia Shop': (0x010F, 0x51, True),
|
||||
'Dark World Lumberjack Shop': (0x010F, 0x51, True),
|
||||
'Village of Outcasts Shop': (0x010F, 0x51, True),
|
||||
'Dark World Potion Shop': (0x010F, 0x51, True),
|
||||
'Light World Death Mountain Shop': (0x00FF, 0x51, True),
|
||||
'Kakariko Shop': (0x011F, 0x51, True),
|
||||
'Cave Shop (Lake Hylia)': (0x0112, 0x51, True),
|
||||
'Potion Shop': (0x0109, 0xFF, False),
|
||||
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
||||
}
|
||||
# region, [item]
|
||||
# slot, item, price, max=0, replacement=None, replacement_price=0
|
||||
# item = (item, price)
|
||||
|
||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
||||
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||
default_shop_contents = {
|
||||
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
|
||||
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
|
||||
'Dark Lake Hylia Shop': _dark_world_shop_defaults,
|
||||
'Dark World Lumberjack Shop': _dark_world_shop_defaults,
|
||||
'Village of Outcasts Shop': _dark_world_shop_defaults,
|
||||
'Dark World Potion Shop': _dark_world_shop_defaults,
|
||||
'Light World Death Mountain Shop': _basic_shop_defaults,
|
||||
'Kakariko Shop': _basic_shop_defaults,
|
||||
'Cave Shop (Lake Hylia)': _basic_shop_defaults,
|
||||
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
||||
}
|
||||
|
||||
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
||||
|
@ -567,6 +608,10 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
|||
'Ganon': (None, False, 'from me'),
|
||||
'Agahnim 1': (None, False, 'from my wizardry form'),
|
||||
'Agahnim 2': (None, False, 'from my wizardry form'),
|
||||
'Floodgate': (None, False, None),
|
||||
'Frog': (None, False, None),
|
||||
'Missing Smith': (None, False, None),
|
||||
'Dark Blacksmith Ruins': (None, False, None),
|
||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
||||
|
|
69
Rom.py
69
Rom.py
|
@ -6,16 +6,17 @@ import os
|
|||
import struct
|
||||
import random
|
||||
|
||||
from BaseClasses import ShopType
|
||||
from Dungeons import dungeon_music_addresses
|
||||
from Text import string_to_alttp_text, text_addresses, Credits
|
||||
from Text import MultiByteTextMapper, text_addresses, Credits
|
||||
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
|
||||
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
||||
from Utils import local_path
|
||||
from Utils import local_path, int16_as_bytes, int32_as_bytes
|
||||
from Items import ItemFactory
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = 'dc5840f0d1ef7b51009c5625a054b3dd'
|
||||
RANDOMIZERBASEHASH = '354bad0d01b6284c89f05c9b414d48c1'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
|
@ -259,15 +260,6 @@ class Sprite(object):
|
|||
# split into palettes of 15 colors
|
||||
return array_chunk(palette_as_colors, 15)
|
||||
|
||||
|
||||
def int16_as_bytes(value):
|
||||
value = value & 0xFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||
|
||||
def int32_as_bytes(value):
|
||||
value = value & 0xFFFFFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
|
||||
|
||||
def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||
# patch items
|
||||
for location in world.get_locations():
|
||||
|
@ -355,6 +347,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
|||
# patch door table
|
||||
rom.write_byte(0xDBB73 + exit.addresses, exit.target)
|
||||
|
||||
write_custom_shops(rom, world)
|
||||
|
||||
# patch medallion requirements
|
||||
if world.required_medallions[0] == 'Bombos':
|
||||
|
@ -567,6 +560,19 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
|||
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
|
||||
0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8,
|
||||
0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB]
|
||||
dig_prizes = [0xB2, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
|
||||
0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA,
|
||||
0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE,
|
||||
0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
|
||||
0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2,
|
||||
0xE3, 0xE3, 0xE3, 0xE3, 0xE3]
|
||||
if world.retro:
|
||||
prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee
|
||||
0xE2: 0xDB} #10 Arrows -> Red Rupee
|
||||
prizes = [prize_replacements.get(prize,prize) for prize in prizes]
|
||||
dig_prizes = [prize_replacements.get(prize,prize) for prize in dig_prizes]
|
||||
rom.write_bytes(0x180100, dig_prizes)
|
||||
random.shuffle(prizes)
|
||||
|
||||
# write tree pull prizes
|
||||
|
@ -808,15 +814,12 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
|||
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
|
||||
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||
|
||||
# Shop table
|
||||
rom.write_bytes(0x184800, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
||||
|
||||
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
||||
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required else 0x00)
|
||||
|
||||
# powder patch: remove the need to leave the scrren after powder, since it causes problems for potion shop at race game
|
||||
# temporarally we are just nopping out this check we will conver this to a rom fix soon.
|
||||
rom.write_bytes(0x02F539,[0xEA,0xEA,0xEA,0xEA,0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
|
||||
rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
|
||||
|
||||
# allow smith into multi-entrance caves in appropriate shuffles
|
||||
if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']:
|
||||
|
@ -851,6 +854,38 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
|||
|
||||
return rom
|
||||
|
||||
def write_custom_shops(rom, world):
|
||||
shops = [shop for shop in world.shops if shop.replaceable and shop.active]
|
||||
|
||||
shop_data = bytearray()
|
||||
items_data = bytearray()
|
||||
sram_offset = 0
|
||||
|
||||
for shop_id, shop in enumerate(shops):
|
||||
if shop_id == len(shops) - 1:
|
||||
shop_id = 0xFF
|
||||
bytes = shop.get_bytes()
|
||||
bytes[0] = shop_id
|
||||
bytes[-1] = sram_offset
|
||||
if shop.type == ShopType.TakeAny:
|
||||
sram_offset += 1
|
||||
else:
|
||||
sram_offset += shop.item_count
|
||||
shop_data.extend(bytes)
|
||||
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high]
|
||||
for item in shop.inventory:
|
||||
if item is None:
|
||||
break
|
||||
item_data = [shop_id, ItemFactory(item['item']).code] + int16_as_bytes(item['price']) + [item['max'], ItemFactory(item['replacement']).code if item['replacement'] else 0xFF] + int16_as_bytes(item['replacement_price'])
|
||||
items_data.extend(item_data)
|
||||
|
||||
rom.write_bytes(0x184800, shop_data)
|
||||
|
||||
items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
||||
rom.write_bytes(0x184900, items_data)
|
||||
|
||||
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite):
|
||||
|
||||
# enable instant item menu
|
||||
|
@ -979,7 +1014,7 @@ def write_sprite(rom, sprite):
|
|||
|
||||
def write_string_to_rom(rom, target, string):
|
||||
address, maxbytes = text_addresses[target]
|
||||
rom.write_bytes(address, string_to_alttp_text(string, maxbytes))
|
||||
rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
|
||||
|
||||
|
||||
def write_strings(rom, world):
|
||||
|
|
54
Rules.py
54
Rules.py
|
@ -110,11 +110,14 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer
|
||||
set_rule(world.get_entrance('Kakariko Teleporter'), lambda state: ((state.has('Hammer') and state.can_lift_rocks()) or state.can_lift_heavy_rocks()) and state.has_Pearl()) # bunny cannot lift bushes
|
||||
set_rule(world.get_location('Flute Spot'), lambda state: state.has('Shovel'))
|
||||
set_rule(world.get_location('Purple Chest'), lambda state: state.can_reach('Blacksmith', 'Location')) # Can S&Q with chest
|
||||
set_rule(world.get_location('Dark Blacksmith Ruins'), lambda state: state.has('Return Smith'))
|
||||
set_rule(world.get_location('Purple Chest'), lambda state: state.has('Pick Up Purple Chest')) # Can S&Q with chest
|
||||
|
||||
set_rule(world.get_location('Zora\'s Ledge'), lambda state: state.has('Flippers'))
|
||||
set_rule(world.get_entrance('Waterfall of Wishing'), lambda state: state.has('Flippers')) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo
|
||||
set_rule(world.get_location('Blacksmith'), lambda state: state.can_lift_heavy_rocks() and state.can_reach('West Dark World') and state.has_Pearl()) # Can S&Q with smith
|
||||
set_rule(world.get_location('Frog'), lambda state: state.can_lift_heavy_rocks()) # will get automatic moon pearl requirement
|
||||
set_rule(world.get_location('Missing Smith'), lambda state: state.has('Get Frog')) # Can S&Q with smith
|
||||
set_rule(world.get_location('Blacksmith'), lambda state: state.has('Return Smith'))
|
||||
set_rule(world.get_location('Magic Bat'), lambda state: state.has('Magic Powder'))
|
||||
set_rule(world.get_location('Sick Kid'), lambda state: state.has_bottle())
|
||||
set_rule(world.get_location('Library'), lambda state: state.has_Boots())
|
||||
|
@ -216,8 +219,8 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has_key('Small Key (Escape)'))
|
||||
|
||||
set_rule(world.get_location('Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)'))
|
||||
set_rule(world.get_location('Eastern Palace - Armos Knights'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)'))
|
||||
set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)'))
|
||||
set_rule(world.get_location('Eastern Palace - Armos Knights'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
|
||||
set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
|
||||
for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Eastern Palace)')
|
||||
|
||||
|
@ -225,9 +228,9 @@ def global_rules(world):
|
|||
set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots())
|
||||
set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has_key('Small Key (Desert Palace)'))
|
||||
set_rule(world.get_location('Desert Palace - Prize'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
|
||||
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow')))
|
||||
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.can_shoot_arrows()))
|
||||
set_rule(world.get_location('Desert Palace - Lanmolas'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
|
||||
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow')))
|
||||
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.can_shoot_arrows()))
|
||||
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Desert Palace)')
|
||||
|
||||
|
@ -246,7 +249,9 @@ def global_rules(world):
|
|||
# for location in ['Tower of Hera - Big Key Chest']:
|
||||
# forbid_item(world.get_location(location), 'Small Key (Tower of Hera)')
|
||||
|
||||
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.can_reach('Dam'))
|
||||
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.has('Open Floodgate'))
|
||||
add_rule(world.get_location('Sunken Treasure'), lambda state: state.has('Open Floodgate'))
|
||||
|
||||
set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has_key('Small Key (Swamp Palace)'))
|
||||
set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer'))
|
||||
set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)')
|
||||
|
@ -285,7 +290,7 @@ def global_rules(world):
|
|||
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
|
||||
|
||||
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.can_shoot_arrows())) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape'))
|
||||
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||
|
@ -299,7 +304,7 @@ def global_rules(world):
|
|||
(item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has_key('Small Key (Misery Mire)', 3))
|
||||
set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source())
|
||||
set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source())
|
||||
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon()))
|
||||
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.can_shoot_arrows() or state.has_blunt_weapon()))
|
||||
for location in ['Misery Mire - Big Chest', 'Misery Mire - Vitreous']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Misery Mire)')
|
||||
|
||||
|
@ -323,10 +328,10 @@ def global_rules(world):
|
|||
|
||||
set_trock_key_rules(world)
|
||||
|
||||
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.has('Bow'))
|
||||
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.can_shoot_arrows())
|
||||
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer'))
|
||||
set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has_key('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.has('Bow') and state.has('Hammer'))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.can_shoot_arrows() and state.has('Hammer'))
|
||||
set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has_key('Small Key (Palace of Darkness)', 4))
|
||||
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
|
||||
|
||||
|
@ -366,10 +371,10 @@ def global_rules(world):
|
|||
set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has_key('Small Key (Ganons Tower)', 3))))
|
||||
|
||||
set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)'))
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.has('Bow') or state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.has('Bow') or state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: state.has('Bow') or state.has_blunt_weapon())
|
||||
set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.has('Bow'))
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
|
||||
set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.can_shoot_arrows())
|
||||
set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source())
|
||||
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has_key('Small Key (Ganons Tower)', 3))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 4))
|
||||
|
@ -382,7 +387,7 @@ def global_rules(world):
|
|||
|
||||
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2')
|
||||
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')
|
||||
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.has('Bow')) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times
|
||||
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.can_shoot_arrows()) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times
|
||||
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop
|
||||
|
||||
|
||||
|
@ -459,7 +464,7 @@ def swordless_rules(world):
|
|||
open_rules(world)
|
||||
|
||||
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
|
||||
set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or (state.has('Bug Catching Net') and (state.has('Fire Rod') or state.has('Bow') or state.has('Cane of Somaria')))) and state.has_key('Small Key (Agahnims Tower)', 2))
|
||||
set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or (state.has('Bug Catching Net') and (state.has('Fire Rod') or state.can_shoot_arrows() or state.has('Cane of Somaria')))) and state.has_key('Small Key (Agahnims Tower)', 2))
|
||||
set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer'))
|
||||
set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror())
|
||||
set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!)
|
||||
|
@ -467,7 +472,7 @@ def swordless_rules(world):
|
|||
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod')) # no curtain
|
||||
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) #in swordless mode bombos pads are present in the relevant parts of ice palace
|
||||
set_rule(world.get_location('Agahnim 2'), lambda state: state.has('Hammer') or state.has('Bug Catching Net'))
|
||||
set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.has('Bow') and state.has('Crystal 1') and state.has('Crystal 2')
|
||||
set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.can_shoot_arrows() and state.has('Crystal 1') and state.has('Crystal 2')
|
||||
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7'))
|
||||
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop
|
||||
|
||||
|
@ -780,13 +785,8 @@ def set_bunny_rules(world):
|
|||
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)',
|
||||
'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)']
|
||||
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge']
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins']
|
||||
|
||||
if world.get_region('Dam').is_dark_world:
|
||||
# if Dam is is dark world, then it is required to have the pearl to get the sunken item
|
||||
add_rule(world.get_location('Sunken Treasure'), lambda state: state.has_Pearl())
|
||||
# similarly we need perl to get across the swamp palace moat
|
||||
add_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has_Pearl())
|
||||
|
||||
def path_to_access_rule(path, entrance):
|
||||
return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
|
||||
|
@ -805,7 +805,7 @@ def set_bunny_rules(world):
|
|||
|
||||
# We will search entrances recursively until we find
|
||||
# one that leads to an exclusively light world region
|
||||
# for each such entrance a new option ios aded that consist of:
|
||||
# for each such entrance a new option is added that consist of:
|
||||
# a) being able to reach it, and
|
||||
# b) being able to access all entrances from there to `region`
|
||||
seen = set([region])
|
||||
|
@ -836,6 +836,10 @@ def set_bunny_rules(world):
|
|||
for exit in region.exits:
|
||||
add_rule(exit, rule)
|
||||
|
||||
paradox_shop = world.get_region('Light World Death Mountain Shop')
|
||||
if paradox_shop.is_dark_world:
|
||||
add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop))
|
||||
|
||||
# Add requirements for all locations that are actually in the dark world, except those available to the bunny
|
||||
for location in world.get_locations():
|
||||
if location.parent_region.is_dark_world:
|
||||
|
|
8
Utils.py
8
Utils.py
|
@ -2,6 +2,14 @@ import os
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
def int16_as_bytes(value):
|
||||
value = value & 0xFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||
|
||||
def int32_as_bytes(value):
|
||||
value = value & 0xFFFFFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
|
||||
|
||||
def is_bundled():
|
||||
return getattr(sys, 'frozen', False)
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue