parent
2e99db5403
commit
9bd9bb4f93
|
@ -3,7 +3,7 @@ from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from Utils import int16_as_bytes
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ class World(object):
|
||||||
self.algorithm = algorithm
|
self.algorithm = algorithm
|
||||||
self.dungeons = []
|
self.dungeons = []
|
||||||
self.regions = []
|
self.regions = []
|
||||||
|
self.shops = []
|
||||||
self.itempool = []
|
self.itempool = []
|
||||||
self.seed = None
|
self.seed = None
|
||||||
self.state = CollectionState(self)
|
self.state = CollectionState(self)
|
||||||
|
@ -379,11 +380,17 @@ class CollectionState(object):
|
||||||
|
|
||||||
def has_key(self, item, count=1):
|
def has_key(self, item, count=1):
|
||||||
if self.world.retro:
|
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:
|
if count == 1:
|
||||||
return item in self.prog_items
|
return item in self.prog_items
|
||||||
return self.item_count(item) >= count
|
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):
|
def item_count(self, item):
|
||||||
return len([pritem for pritem in self.prog_items if pritem == 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())
|
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count())
|
||||||
elif self.world.difficulty == 'insane' and not fullrefill:
|
elif self.world.difficulty == 'insane' and not fullrefill:
|
||||||
basemagic = basemagic
|
basemagic = basemagic
|
||||||
else:
|
elif self.can_buy_unlimited('Green Potion') or self.can_buy_unlimited('Red Potion'):
|
||||||
basemagic = basemagic + basemagic * self.bottle_count()
|
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):
|
def can_kill_most_things(self, enemies=5):
|
||||||
return (self.has_blunt_weapon()
|
return (self.has_blunt_weapon()
|
||||||
or self.has('Cane of Somaria')
|
or self.has('Cane of Somaria')
|
||||||
or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic()))
|
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')
|
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):
|
def has_sword(self):
|
||||||
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
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)
|
return self in (RegionType.Cave, RegionType.Dungeon)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Region(object):
|
class Region(object):
|
||||||
|
|
||||||
def __init__(self, name, type):
|
def __init__(self, name, type):
|
||||||
|
@ -584,6 +597,7 @@ class Region(object):
|
||||||
self.exits = []
|
self.exits = []
|
||||||
self.locations = []
|
self.locations = []
|
||||||
self.dungeon = None
|
self.dungeon = None
|
||||||
|
self.shop = None
|
||||||
self.world = None
|
self.world = None
|
||||||
self.is_light_world = False # will be set aftermaking connections.
|
self.is_light_world = False # will be set aftermaking connections.
|
||||||
self.is_dark_world = False
|
self.is_dark_world = False
|
||||||
|
@ -751,6 +765,64 @@ class Item(object):
|
||||||
class Crystal(Item):
|
class Crystal(Item):
|
||||||
pass
|
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):
|
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)':
|
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
|
world.swamp_patch_required = True
|
||||||
|
|
||||||
# check for
|
# check for potion shop location
|
||||||
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
|
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
|
||||||
world.powder_patch_required = True
|
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 Push Block', 'Paradox Cave Front'),
|
||||||
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
||||||
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
|
('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 Rocks', 'Fairy Ascension Plateau'),
|
||||||
('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'),
|
('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'),
|
||||||
('Fairy Ascension Drop', 'East Death Mountain (Bottom)'),
|
('Fairy Ascension Drop', 'East Death Mountain (Bottom)'),
|
||||||
|
|
37
ItemList.py
37
ItemList.py
|
@ -262,6 +262,8 @@ def generate_itempool(world):
|
||||||
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||||
world.required_medallions = (mm_medallion, tr_medallion)
|
world.required_medallions = (mm_medallion, tr_medallion)
|
||||||
|
|
||||||
|
set_up_shops(world)
|
||||||
|
|
||||||
# distribute crystals
|
# distribute crystals
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
|
@ -292,7 +294,23 @@ def fill_prizes(world, attempts=15):
|
||||||
raise FillError('Unable to place dungeon prizes')
|
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):
|
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro):
|
||||||
pool = []
|
pool = []
|
||||||
|
@ -566,17 +584,18 @@ def test():
|
||||||
for mode in ['open', 'standard', 'swordless']:
|
for mode in ['open', 'standard', 'swordless']:
|
||||||
for progressive in ['on', 'off']:
|
for progressive in ['on', 'off']:
|
||||||
for shuffle in ['full', 'insane']:
|
for shuffle in ['full', 'insane']:
|
||||||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
|
for retro in [True, False]:
|
||||||
count = len(out[0]) + len(out[1])
|
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
|
||||||
|
count = len(out[0]) + len(out[1])
|
||||||
|
|
||||||
correct_count = total_items_to_place
|
correct_count = total_items_to_place
|
||||||
if goal in ['pedestal']:
|
if goal in ['pedestal']:
|
||||||
# pedestal goals generate one extra item
|
# pedestal goals generate one extra item
|
||||||
correct_count += 1
|
correct_count += 1
|
||||||
if retro:
|
if retro:
|
||||||
correct_count += 28
|
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__':
|
if __name__ == '__main__':
|
||||||
test()
|
test()
|
||||||
|
|
5
Items.py
5
Items.py
|
@ -160,6 +160,11 @@ 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'),
|
'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'),
|
'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'),
|
'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 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),
|
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
|
|
10
Main.py
10
Main.py
|
@ -1,4 +1,5 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import copy
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -157,6 +158,15 @@ def copy_world(world):
|
||||||
create_regions(ret)
|
create_regions(ret)
|
||||||
create_dungeons(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
|
# connect copied world
|
||||||
for region in world.regions:
|
for region in world.regions:
|
||||||
copied_region = ret.get_region(region.name)
|
copied_region = ret.get_region(region.name)
|
||||||
|
|
49
Regions.py
49
Regions.py
|
@ -1,5 +1,5 @@
|
||||||
import collections
|
import collections
|
||||||
from BaseClasses import Region, Location, Entrance, RegionType
|
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
|
||||||
|
|
||||||
|
|
||||||
def create_regions(world):
|
def create_regions(world):
|
||||||
|
@ -25,7 +25,7 @@ def create_regions(world):
|
||||||
create_cave_region('Hyrule Castle Secret Entrance', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
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_lw_region('Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||||
create_cave_region('Waterfall of Wishing', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
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('Kings Grave', ['King\'s Tomb']),
|
||||||
create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']),
|
create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']),
|
||||||
create_cave_region('Dam', ['Floodgate', 'Floodgate Chest']),
|
create_cave_region('Dam', ['Floodgate', 'Floodgate Chest']),
|
||||||
|
@ -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 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_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'],
|
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 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 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)']),
|
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_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_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('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',
|
create_cave_region('Paradox Cave Chest Area', ['Paradox Cave Lower - Far Left',
|
||||||
'Paradox Cave Lower - Left',
|
'Paradox Cave Lower - Left',
|
||||||
'Paradox Cave Lower - Right',
|
'Paradox Cave Lower - Right',
|
||||||
|
@ -135,6 +135,7 @@ def create_regions(world):
|
||||||
'Paradox Cave Upper - Right'],
|
'Paradox Cave Upper - Right'],
|
||||||
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
['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('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('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_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)']),
|
create_cave_region('Spiral Cave (Top)', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
|
||||||
|
@ -290,6 +291,14 @@ def create_regions(world):
|
||||||
create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop'])
|
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()
|
world.intialize_regions()
|
||||||
|
|
||||||
def create_lw_region(name, locations=None, exits=None):
|
def create_lw_region(name, locations=None, exits=None):
|
||||||
|
@ -347,6 +356,38 @@ def mark_light_world_regions(world):
|
||||||
seen.add(exit.connected_region)
|
seen.add(exit.connected_region)
|
||||||
queue.append(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'),
|
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||||
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
||||||
|
|
65
Rom.py
65
Rom.py
|
@ -6,16 +6,17 @@ import os
|
||||||
import struct
|
import struct
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from BaseClasses import ShopType
|
||||||
from Dungeons import dungeon_music_addresses
|
from Dungeons import dungeon_music_addresses
|
||||||
from Text import MultiByteTextMapper, 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 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 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
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = 'dc5840f0d1ef7b51009c5625a054b3dd'
|
RANDOMIZERBASEHASH = '354bad0d01b6284c89f05c9b414d48c1'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
|
@ -259,15 +260,6 @@ class Sprite(object):
|
||||||
# split into palettes of 15 colors
|
# split into palettes of 15 colors
|
||||||
return array_chunk(palette_as_colors, 15)
|
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):
|
def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
# patch items
|
# patch items
|
||||||
for location in world.get_locations():
|
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
|
# patch door table
|
||||||
rom.write_byte(0xDBB73 + exit.addresses, exit.target)
|
rom.write_byte(0xDBB73 + exit.addresses, exit.target)
|
||||||
|
|
||||||
|
write_custom_shops(rom, world)
|
||||||
|
|
||||||
# patch medallion requirements
|
# patch medallion requirements
|
||||||
if world.required_medallions[0] == 'Bombos':
|
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,
|
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,
|
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]
|
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)
|
random.shuffle(prizes)
|
||||||
|
|
||||||
# write tree pull 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(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
|
||||||
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 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
|
# 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)
|
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
|
# 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.
|
# 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
|
# allow smith into multi-entrance caves in appropriate shuffles
|
||||||
if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']:
|
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
|
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):
|
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite):
|
||||||
|
|
||||||
# enable instant item menu
|
# enable instant item menu
|
||||||
|
|
36
Rules.py
36
Rules.py
|
@ -219,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_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 - 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 - 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.has('Bow') 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']:
|
for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Eastern Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Eastern Palace)')
|
||||||
|
|
||||||
|
@ -228,9 +228,9 @@ def global_rules(world):
|
||||||
set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots())
|
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_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
|
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
|
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']:
|
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Desert Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Desert Palace)')
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ def global_rules(world):
|
||||||
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
|
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
|
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 - 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_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)'))
|
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||||
|
@ -304,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))
|
(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 - 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_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']:
|
for location in ['Misery Mire - Big Chest', 'Misery Mire - Vitreous']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Misery Mire)')
|
forbid_item(world.get_location(location), 'Big Key (Misery Mire)')
|
||||||
|
|
||||||
|
@ -328,10 +328,10 @@ def global_rules(world):
|
||||||
|
|
||||||
set_trock_key_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 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 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_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)'))
|
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
|
||||||
|
|
||||||
|
@ -371,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(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 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 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.has('Bow') 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.has('Bow') 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.has('Bow'))
|
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_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_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))
|
set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 4))
|
||||||
|
@ -387,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')
|
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('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
|
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,7 +464,7 @@ def swordless_rules(world):
|
||||||
open_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('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('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_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 (!)
|
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 (!)
|
||||||
|
@ -472,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('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_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('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'))
|
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
|
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop
|
||||||
|
|
||||||
|
@ -805,7 +805,7 @@ def set_bunny_rules(world):
|
||||||
|
|
||||||
# We will search entrances recursively until we find
|
# We will search entrances recursively until we find
|
||||||
# one that leads to an exclusively light world region
|
# 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
|
# a) being able to reach it, and
|
||||||
# b) being able to access all entrances from there to `region`
|
# b) being able to access all entrances from there to `region`
|
||||||
seen = set([region])
|
seen = set([region])
|
||||||
|
@ -836,6 +836,10 @@ def set_bunny_rules(world):
|
||||||
for exit in region.exits:
|
for exit in region.exits:
|
||||||
add_rule(exit, rule)
|
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
|
# Add requirements for all locations that are actually in the dark world, except those available to the bunny
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.parent_region.is_dark_world:
|
if location.parent_region.is_dark_world:
|
||||||
|
|
8
Utils.py
8
Utils.py
|
@ -2,6 +2,14 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
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():
|
def is_bundled():
|
||||||
return getattr(sys, 'frozen', False)
|
return getattr(sys, 'frozen', False)
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue