Merge remote-tracking branch 'origin/DoorDev' into DoorDev
This commit is contained in:
@@ -2,7 +2,9 @@ import copy
|
|||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict, deque
|
||||||
|
|
||||||
|
from EntranceShuffle import door_addresses
|
||||||
from _vendor.collections_extended import bag
|
from _vendor.collections_extended import bag
|
||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes
|
||||||
from Tables import normal_offset_table, spiral_offset_table
|
from Tables import normal_offset_table, spiral_offset_table
|
||||||
@@ -355,6 +357,7 @@ class CollectionState(object):
|
|||||||
self.prog_items = bag()
|
self.prog_items = bag()
|
||||||
self.world = parent
|
self.world = parent
|
||||||
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
||||||
|
self.colored_regions = {player: {} for player in range(1, parent.players + 1)}
|
||||||
self.events = []
|
self.events = []
|
||||||
self.path = {}
|
self.path = {}
|
||||||
self.locations_checked = set()
|
self.locations_checked = set()
|
||||||
@@ -366,6 +369,7 @@ class CollectionState(object):
|
|||||||
player_regions = [region for region in self.world.regions if region.player == player]
|
player_regions = [region for region in self.world.regions if region.player == player]
|
||||||
self.stale[player] = False
|
self.stale[player] = False
|
||||||
rrp = self.reachable_regions[player]
|
rrp = self.reachable_regions[player]
|
||||||
|
ccr = self.colored_regions[player]
|
||||||
new_regions = True
|
new_regions = True
|
||||||
reachable_regions_count = len(rrp)
|
reachable_regions_count = len(rrp)
|
||||||
while new_regions:
|
while new_regions:
|
||||||
@@ -373,13 +377,53 @@ class CollectionState(object):
|
|||||||
for candidate in possible:
|
for candidate in possible:
|
||||||
if candidate.can_reach_private(self):
|
if candidate.can_reach_private(self):
|
||||||
rrp.add(candidate)
|
rrp.add(candidate)
|
||||||
|
if candidate.type == RegionType.Dungeon:
|
||||||
|
c_switch_present = False
|
||||||
|
for ext in candidate.exits:
|
||||||
|
door = self.world.check_for_door(ext.name, player)
|
||||||
|
if door is not None and door.crystal == CrystalBarrier.Either:
|
||||||
|
c_switch_present = True
|
||||||
|
if c_switch_present:
|
||||||
|
ccr[candidate] = CrystalBarrier.Either
|
||||||
|
self.spread_crystal_access(candidate, CrystalBarrier.Either, rrp, ccr, player)
|
||||||
|
else:
|
||||||
|
for entrance in candidate.entrances:
|
||||||
|
door = self.world.check_for_door(entrance.name, player)
|
||||||
|
if door is None or entrance.parent_region.type != RegionType.Dungeon:
|
||||||
|
ccr[candidate] = CrystalBarrier.Orange
|
||||||
|
if entrance.parent_region in ccr.keys():
|
||||||
|
color_type = ccr[entrance.parent_region]
|
||||||
|
current_type = ccr[candidate] if candidate in ccr.keys() else None
|
||||||
|
ccr[candidate] = color_type if current_type is None or color_type == current_type else CrystalBarrier.Either
|
||||||
new_regions = len(rrp) > reachable_regions_count
|
new_regions = len(rrp) > reachable_regions_count
|
||||||
reachable_regions_count = len(rrp)
|
reachable_regions_count = len(rrp)
|
||||||
|
|
||||||
|
def spread_crystal_access(self, region, crystal, rrp, ccr, player):
|
||||||
|
queue = deque([(region, crystal)])
|
||||||
|
visited = set()
|
||||||
|
while len(queue) > 0:
|
||||||
|
region, crystal = queue.popleft()
|
||||||
|
visited.add(region)
|
||||||
|
for ext in region.exits:
|
||||||
|
connect = ext.connected_region
|
||||||
|
if connect not in visited and connect is not None and connect.type == RegionType.Dungeon:
|
||||||
|
if connect in rrp and ext.can_reach(self) and connect:
|
||||||
|
door = self.world.check_for_door(ext.name, player)
|
||||||
|
current_crystal = ccr[connect]
|
||||||
|
if door is not None and not door.blocked and current_crystal != crystal and current_crystal != CrystalBarrier.Either:
|
||||||
|
if door.crystal in [CrystalBarrier.Either, CrystalBarrier.Null]:
|
||||||
|
ccr[connect] = crystal
|
||||||
|
queue.append((connect, crystal))
|
||||||
|
else:
|
||||||
|
queue.append((connect, door.crystal))
|
||||||
|
if door.crystal != current_crystal:
|
||||||
|
ccr[connect] = CrystalBarrier.Either
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
ret = CollectionState(self.world)
|
ret = CollectionState(self.world)
|
||||||
ret.prog_items = self.prog_items.copy()
|
ret.prog_items = self.prog_items.copy()
|
||||||
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
|
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
|
||||||
|
ret.colored_regions = {player: copy.copy(self.colored_regions[player]) for player in range(1, self.world.players + 1)}
|
||||||
ret.events = copy.copy(self.events)
|
ret.events = copy.copy(self.events)
|
||||||
ret.path = copy.copy(self.path)
|
ret.path = copy.copy(self.path)
|
||||||
ret.locations_checked = copy.copy(self.locations_checked)
|
ret.locations_checked = copy.copy(self.locations_checked)
|
||||||
@@ -400,6 +444,15 @@ class CollectionState(object):
|
|||||||
|
|
||||||
return spot.can_reach(self)
|
return spot.can_reach(self)
|
||||||
|
|
||||||
|
def sweep_for_crystal_access(self):
|
||||||
|
for player, rrp in self.reachable_regions.items():
|
||||||
|
dungeon_regions = [x for x in rrp if x.type == RegionType.Dungeon]
|
||||||
|
ccr = self.colored_regions[player]
|
||||||
|
for region in dungeon_regions:
|
||||||
|
if region in ccr.keys():
|
||||||
|
self.spread_crystal_access(region, ccr[region], rrp, ccr, player)
|
||||||
|
self.stale[player] = True
|
||||||
|
|
||||||
def sweep_for_events(self, key_only=False, locations=None):
|
def sweep_for_events(self, key_only=False, locations=None):
|
||||||
# this may need improvement
|
# this may need improvement
|
||||||
new_locations = True
|
new_locations = True
|
||||||
@@ -415,6 +468,18 @@ class CollectionState(object):
|
|||||||
self.collect(event.item, True, event)
|
self.collect(event.item, True, event)
|
||||||
new_locations = len(reachable_events) > checked_locations
|
new_locations = len(reachable_events) > checked_locations
|
||||||
checked_locations = len(reachable_events)
|
checked_locations = len(reachable_events)
|
||||||
|
if new_locations:
|
||||||
|
self.sweep_for_crystal_access()
|
||||||
|
|
||||||
|
def can_reach_blue(self, region, player):
|
||||||
|
if region not in self.colored_regions[player].keys():
|
||||||
|
return False
|
||||||
|
return self.colored_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
|
||||||
|
|
||||||
|
def can_reach_orange(self, region, player):
|
||||||
|
if region not in self.colored_regions[player].keys():
|
||||||
|
return False
|
||||||
|
return self.colored_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
|
||||||
|
|
||||||
def _do_not_flood_the_keys(self, reachable_events):
|
def _do_not_flood_the_keys(self, reachable_events):
|
||||||
adjusted_checks = list(reachable_events)
|
adjusted_checks = list(reachable_events)
|
||||||
@@ -1244,10 +1309,9 @@ class ShopType(Enum):
|
|||||||
UpgradeShop = 2
|
UpgradeShop = 2
|
||||||
|
|
||||||
class Shop(object):
|
class Shop(object):
|
||||||
def __init__(self, region, room_id, default_door_id, type, shopkeeper_config, replaceable):
|
def __init__(self, region, room_id, type, shopkeeper_config, replaceable):
|
||||||
self.region = region
|
self.region = region
|
||||||
self.room_id = room_id
|
self.room_id = room_id
|
||||||
self.default_door_id = default_door_id
|
|
||||||
self.type = type
|
self.type = type
|
||||||
self.inventory = [None, None, None]
|
self.inventory = [None, None, None]
|
||||||
self.shopkeeper_config = shopkeeper_config
|
self.shopkeeper_config = shopkeeper_config
|
||||||
@@ -1265,10 +1329,8 @@ class Shop(object):
|
|||||||
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
||||||
entrances = self.region.entrances
|
entrances = self.region.entrances
|
||||||
config = self.item_count
|
config = self.item_count
|
||||||
if len(entrances) == 1 and entrances[0].addresses:
|
if len(entrances) == 1 and entrances[0].name in door_addresses:
|
||||||
door_id = entrances[0].addresses+1
|
door_id = door_addresses[entrances[0].name][0]+1
|
||||||
elif self.default_door_id is not None:
|
|
||||||
door_id = self.default_door_id
|
|
||||||
else:
|
else:
|
||||||
door_id = 0
|
door_id = 0
|
||||||
config |= 0x40 # ignore door id
|
config |= 0x40 # ignore door id
|
||||||
|
|||||||
@@ -1034,7 +1034,7 @@ def reassign_key_doors(builder, proposal, world, player):
|
|||||||
room.delete(d.doorListPos)
|
room.delete(d.doorListPos)
|
||||||
else:
|
else:
|
||||||
if len(room.doorList) > 1:
|
if len(room.doorList) > 1:
|
||||||
room.mirror(d.doorListPos)
|
room.mirror(d.doorListPos) # todo: I don't think this works for crossed - maybe it will
|
||||||
else:
|
else:
|
||||||
room.delete(d.doorListPos)
|
room.delete(d.doorListPos)
|
||||||
d.smallKey = False
|
d.smallKey = False
|
||||||
|
|||||||
4
Doors.py
4
Doors.py
@@ -1084,11 +1084,12 @@ def create_doors(world, player):
|
|||||||
world.get_door('Hera Startile Wide Holes', player).c_switch()
|
world.get_door('Hera Startile Wide Holes', player).c_switch()
|
||||||
|
|
||||||
world.get_door('PoD Arena Main SW', player).c_switch()
|
world.get_door('PoD Arena Main SW', player).c_switch()
|
||||||
|
world.get_door('PoD Arena Bonk Path', player).c_switch()
|
||||||
world.get_door('PoD Arena Bridge SE', player).c_switch()
|
world.get_door('PoD Arena Bridge SE', player).c_switch()
|
||||||
|
world.get_door('PoD Arena Bridge Drop Down', player).c_switch()
|
||||||
world.get_door('PoD Arena Main Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
world.get_door('PoD Arena Main Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||||
# maybe you can cross this way with blue up??
|
# maybe you can cross this way with blue up??
|
||||||
world.get_door('PoD Arena Main Crystal Path', player).barrier(CrystalBarrier.Blue)
|
world.get_door('PoD Arena Main Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('PoD Arena Crystals E', player).barrier(CrystalBarrier.Blue)
|
|
||||||
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
|
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('PoD Sexy Statue W', player).c_switch()
|
world.get_door('PoD Sexy Statue W', player).c_switch()
|
||||||
world.get_door('PoD Sexy Statue NW', player).c_switch()
|
world.get_door('PoD Sexy Statue NW', player).c_switch()
|
||||||
@@ -1185,6 +1186,7 @@ def create_doors(world, player):
|
|||||||
world.get_door('GT Hookshot Entry Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('GT Hookshot Entry Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('GT Double Switch Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
world.get_door('GT Double Switch Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('GT Double Switch Orange Barrier 2', player).barrier(CrystalBarrier.Orange)
|
world.get_door('GT Double Switch Orange Barrier 2', player).barrier(CrystalBarrier.Orange)
|
||||||
|
world.get_door('GT Double Switch Orange Path', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('GT Double Switch Key Orange Path', player).barrier(CrystalBarrier.Orange)
|
world.get_door('GT Double Switch Key Orange Path', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('GT Double Switch Key Blue Path', player).barrier(CrystalBarrier.Blue)
|
world.get_door('GT Double Switch Key Blue Path', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('GT Double Switch Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('GT Double Switch Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
|
|||||||
37
Gui.py
37
Gui.py
@@ -90,6 +90,34 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
fileDialogFrame = Frame(rightHalfFrame)
|
fileDialogFrame = Frame(rightHalfFrame)
|
||||||
|
|
||||||
|
heartbeepFrame = Frame(fileDialogFrame)
|
||||||
|
heartbeepVar = StringVar()
|
||||||
|
heartbeepVar.set('normal')
|
||||||
|
heartbeepOptionMenu = OptionMenu(heartbeepFrame, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
|
||||||
|
heartbeepOptionMenu.pack(side=RIGHT)
|
||||||
|
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep sound rate')
|
||||||
|
heartbeepLabel.pack(side=LEFT, padx=(0,52))
|
||||||
|
|
||||||
|
heartcolorFrame = Frame(fileDialogFrame)
|
||||||
|
heartcolorVar = StringVar()
|
||||||
|
heartcolorVar.set('red')
|
||||||
|
heartcolorOptionMenu = OptionMenu(heartcolorFrame, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
|
||||||
|
heartcolorOptionMenu.pack(side=RIGHT)
|
||||||
|
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
|
||||||
|
heartcolorLabel.pack(side=LEFT, padx=(0,127))
|
||||||
|
|
||||||
|
fastMenuFrame = Frame(fileDialogFrame)
|
||||||
|
fastMenuVar = StringVar()
|
||||||
|
fastMenuVar.set('normal')
|
||||||
|
fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
||||||
|
fastMenuOptionMenu.pack(side=RIGHT)
|
||||||
|
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
|
||||||
|
fastMenuLabel.pack(side=LEFT, padx=(0,100))
|
||||||
|
|
||||||
|
heartbeepFrame.pack(expand=True, anchor=E)
|
||||||
|
heartcolorFrame.pack(expand=True, anchor=E)
|
||||||
|
fastMenuFrame.pack(expand=True, anchor=E)
|
||||||
|
|
||||||
romDialogFrame = Frame(fileDialogFrame)
|
romDialogFrame = Frame(fileDialogFrame)
|
||||||
baseRomLabel = Label(romDialogFrame, text='Base Rom')
|
baseRomLabel = Label(romDialogFrame, text='Base Rom')
|
||||||
romVar = StringVar()
|
romVar = StringVar()
|
||||||
@@ -144,7 +172,7 @@ def guiMain(args=None):
|
|||||||
modeVar.set('open')
|
modeVar.set('open')
|
||||||
modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'inverted')
|
modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'inverted')
|
||||||
modeOptionMenu.pack(side=RIGHT)
|
modeOptionMenu.pack(side=RIGHT)
|
||||||
modeLabel = Label(modeFrame, text='Game Mode')
|
modeLabel = Label(modeFrame, text='Game mode')
|
||||||
modeLabel.pack(side=LEFT)
|
modeLabel.pack(side=LEFT)
|
||||||
|
|
||||||
logicFrame = Frame(drowDownFrame)
|
logicFrame = Frame(drowDownFrame)
|
||||||
@@ -208,7 +236,7 @@ def guiMain(args=None):
|
|||||||
difficultyVar.set('normal')
|
difficultyVar.set('normal')
|
||||||
difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert')
|
difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert')
|
||||||
difficultyOptionMenu.pack(side=RIGHT)
|
difficultyOptionMenu.pack(side=RIGHT)
|
||||||
difficultyLabel = Label(difficultyFrame, text='Game difficulty')
|
difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool')
|
||||||
difficultyLabel.pack(side=LEFT)
|
difficultyLabel.pack(side=LEFT)
|
||||||
|
|
||||||
timerFrame = Frame(drowDownFrame)
|
timerFrame = Frame(drowDownFrame)
|
||||||
@@ -1137,10 +1165,15 @@ def guiMain(args=None):
|
|||||||
if args.seed:
|
if args.seed:
|
||||||
seedVar.set(str(args.seed))
|
seedVar.set(str(args.seed))
|
||||||
modeVar.set(args.mode)
|
modeVar.set(args.mode)
|
||||||
|
swordsVar.set(args.swords)
|
||||||
difficultyVar.set(args.difficulty)
|
difficultyVar.set(args.difficulty)
|
||||||
|
itemFuncVar.set(args.item_functionality)
|
||||||
timerVar.set(args.timer)
|
timerVar.set(args.timer)
|
||||||
progressiveVar.set(args.progressive)
|
progressiveVar.set(args.progressive)
|
||||||
|
accessibilityVar.set(args.accessibility)
|
||||||
goalVar.set(args.goal)
|
goalVar.set(args.goal)
|
||||||
|
crystalsGTVar.set(args.crystals_gt)
|
||||||
|
crystalsGanonVar.set(args.crystals_ganon)
|
||||||
algorithmVar.set(args.algorithm)
|
algorithmVar.set(args.algorithm)
|
||||||
shuffleVar.set(args.shuffle)
|
shuffleVar.set(args.shuffle)
|
||||||
doorShuffleVar.set(args.door_shuffle)
|
doorShuffleVar.set(args.door_shuffle)
|
||||||
|
|||||||
@@ -302,16 +302,16 @@ def create_inverted_regions(world, player):
|
|||||||
create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing'])
|
create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing'])
|
||||||
]
|
]
|
||||||
|
|
||||||
for region_name, (room_id, default_door_id, shopkeeper, replaceable) in shop_table.items():
|
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
|
||||||
region = world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
shop = Shop(region, room_id, default_door_id, ShopType.Shop, shopkeeper, replaceable)
|
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
|
||||||
region.shop = shop
|
region.shop = shop
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
||||||
shop.add_inventory(index, item, price)
|
shop.add_inventory(index, item, price)
|
||||||
|
|
||||||
region = world.get_region('Capacity Upgrade', player)
|
region = world.get_region('Capacity Upgrade', player)
|
||||||
shop = Shop(region, 0x0115, 0x5D, ShopType.UpgradeShop, 0x04, True)
|
shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
|
||||||
region.shop = shop
|
region.shop = shop
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
|
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
|
||||||
@@ -373,18 +373,18 @@ def mark_dark_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, default_door_id, shopkeeper, replaceable)
|
# (room_id, shopkeeper, replaceable)
|
||||||
shop_table = {
|
shop_table = {
|
||||||
'Cave Shop (Dark Death Mountain)': (0x0112, 0x6E, 0xC1, True),
|
'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
|
||||||
'Red Shield Shop': (0x0110, 0x75, 0xC1, True),
|
'Red Shield Shop': (0x0110, 0xC1, True),
|
||||||
'Dark Lake Hylia Shop': (0x010F, 0x74, 0xC1, True),
|
'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
|
||||||
'Dark World Lumberjack Shop': (0x010F, 0x57, 0xC1, True),
|
'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
|
||||||
'Village of Outcasts Shop': (0x010F, 0x60, 0xC1, True),
|
'Village of Outcasts Shop': (0x010F, 0xC1, True),
|
||||||
'Dark World Potion Shop': (0x010F, 0x6F, 0xC1, True),
|
'Dark World Potion Shop': (0x010F, 0xC1, True),
|
||||||
'Light World Death Mountain Shop': (0x00FF, None, 0xA0, True),
|
'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
|
||||||
'Kakariko Shop': (0x011F, 0x46, 0xA0, True),
|
'Kakariko Shop': (0x011F, 0xA0, True),
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, 0x58, 0xA0, True),
|
'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
|
||||||
'Potion Shop': (0x0109, 0x4C, 0xFF, False),
|
'Potion Shop': (0x0109, 0xFF, False),
|
||||||
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
||||||
}
|
}
|
||||||
# region, [item]
|
# region, [item]
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ def set_up_take_anys(world, player):
|
|||||||
entrance = world.get_region(reg, player).entrances[0]
|
entrance = world.get_region(reg, player).entrances[0]
|
||||||
connect_entrance(world, entrance, old_man_take_any, player)
|
connect_entrance(world, entrance, old_man_take_any, player)
|
||||||
entrance.target = 0x58
|
entrance.target = 0x58
|
||||||
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, None, ShopType.TakeAny, 0xE2, True)
|
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True)
|
||||||
world.shops.append(old_man_take_any.shop)
|
world.shops.append(old_man_take_any.shop)
|
||||||
old_man_take_any.shop.active = True
|
old_man_take_any.shop.active = True
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ def set_up_take_anys(world, player):
|
|||||||
entrance = world.get_region(reg, player).entrances[0]
|
entrance = world.get_region(reg, player).entrances[0]
|
||||||
connect_entrance(world, entrance, take_any, player)
|
connect_entrance(world, entrance, take_any, player)
|
||||||
entrance.target = target
|
entrance.target = target
|
||||||
take_any.shop = Shop(take_any, room_id, None, ShopType.TakeAny, 0xE3, True)
|
take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True)
|
||||||
world.shops.append(take_any.shop)
|
world.shops.append(take_any.shop)
|
||||||
take_any.shop.active = True
|
take_any.shop.active = True
|
||||||
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
||||||
|
|||||||
@@ -118,9 +118,9 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||||
available = raw_avail - key_counter.used_keys
|
available = raw_avail - key_counter.used_keys
|
||||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||||
avail_bigs = count_unique_big_doors(key_counter)
|
avail_bigs = exist_relevant_big_doors(key_counter, key_layout)
|
||||||
if not key_counter.big_key_opened:
|
if not key_counter.big_key_opened:
|
||||||
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and avail_bigs == 0:
|
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and not avail_bigs:
|
||||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
||||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
||||||
@@ -318,6 +318,7 @@ def create_rule(key_counter, prev_counter, key_layout, world):
|
|||||||
available = raw_avail - key_counter.used_keys
|
available = raw_avail - key_counter.used_keys
|
||||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||||
required_keys = min(available, possible_smalls) + key_counter.used_keys
|
required_keys = min(available, possible_smalls) + key_counter.used_keys
|
||||||
|
# required_keys = key_counter.used_keys + 1 # this sometimes makes more sense
|
||||||
# if prev_avail < required_keys:
|
# if prev_avail < required_keys:
|
||||||
# required_keys = prev_avail + prev_counter.used_keys
|
# required_keys = prev_avail + prev_counter.used_keys
|
||||||
# return DoorRules(required_keys)
|
# return DoorRules(required_keys)
|
||||||
@@ -487,15 +488,16 @@ def count_unique_small_doors(key_counter, proposal):
|
|||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
def count_unique_big_doors(key_counter):
|
def exist_relevant_big_doors(key_counter, key_layout):
|
||||||
cnt = 0
|
bk_counter = find_counter(key_counter.open_doors, True, key_layout, False)
|
||||||
counted = set()
|
if bk_counter is not None:
|
||||||
for door in key_counter.child_doors:
|
diff = dict_difference(bk_counter.free_locations, key_counter.free_locations)
|
||||||
if door.bigKey and door not in counted:
|
if len(diff) > 0:
|
||||||
cnt += 1
|
return True
|
||||||
counted.add(door)
|
diff = dict_difference(bk_counter.key_only_locations, key_counter.key_only_locations)
|
||||||
counted.add(door.dest)
|
if len(diff) > 0:
|
||||||
return cnt
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def count_locations_big_optional(locations, bk=False):
|
def count_locations_big_optional(locations, bk=False):
|
||||||
@@ -788,7 +790,7 @@ def state_id(state, flat_proposal):
|
|||||||
return s_id
|
return s_id
|
||||||
|
|
||||||
|
|
||||||
def find_counter(opened_doors, bk_hint, key_layout):
|
def find_counter(opened_doors, bk_hint, key_layout, raise_on_error=True):
|
||||||
counter = find_counter_hint(opened_doors, bk_hint, key_layout)
|
counter = find_counter_hint(opened_doors, bk_hint, key_layout)
|
||||||
if counter is not None:
|
if counter is not None:
|
||||||
return counter
|
return counter
|
||||||
@@ -801,7 +803,9 @@ def find_counter(opened_doors, bk_hint, key_layout):
|
|||||||
counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout)
|
counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout)
|
||||||
if counter is not None:
|
if counter is not None:
|
||||||
return counter
|
return counter
|
||||||
|
if raise_on_error:
|
||||||
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop))
|
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_counter_hint(opened_doors, bk_hint, key_layout):
|
def find_counter_hint(opened_doors, bk_hint, key_layout):
|
||||||
|
|||||||
4
Main.py
4
Main.py
@@ -290,7 +290,7 @@ def copy_dynamic_regions_and_locations(world, ret):
|
|||||||
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
|
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
|
||||||
|
|
||||||
if region.shop:
|
if region.shop:
|
||||||
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.default_door_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable)
|
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable)
|
||||||
ret.shops.append(new_reg.shop)
|
ret.shops.append(new_reg.shop)
|
||||||
|
|
||||||
for location in world.dynamic_locations:
|
for location in world.dynamic_locations:
|
||||||
@@ -325,6 +325,7 @@ def create_playthrough(world):
|
|||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
if not world.keysanity:
|
if not world.keysanity:
|
||||||
state.sweep_for_events(key_only=True)
|
state.sweep_for_events(key_only=True)
|
||||||
|
state.sweep_for_crystal_access()
|
||||||
|
|
||||||
sphere = []
|
sphere = []
|
||||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
@@ -379,6 +380,7 @@ def create_playthrough(world):
|
|||||||
while required_locations:
|
while required_locations:
|
||||||
if not world.keysanity:
|
if not world.keysanity:
|
||||||
state.sweep_for_events(key_only=True)
|
state.sweep_for_events(key_only=True)
|
||||||
|
state.sweep_for_crystal_access()
|
||||||
|
|
||||||
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
||||||
|
|
||||||
|
|||||||
28
Regions.py
28
Regions.py
@@ -705,16 +705,16 @@ def create_regions(world, player):
|
|||||||
create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW'])
|
create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW'])
|
||||||
]
|
]
|
||||||
|
|
||||||
for region_name, (room_id, default_door_id, shopkeeper, replaceable) in shop_table.items():
|
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
|
||||||
region = world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
shop = Shop(region, room_id, default_door_id, ShopType.Shop, shopkeeper, replaceable)
|
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
|
||||||
region.shop = shop
|
region.shop = shop
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
||||||
shop.add_inventory(index, item, price)
|
shop.add_inventory(index, item, price)
|
||||||
|
|
||||||
region = world.get_region('Capacity Upgrade', player)
|
region = world.get_region('Capacity Upgrade', player)
|
||||||
shop = Shop(region, 0x0115, 0x5D, ShopType.UpgradeShop, 0x04, True)
|
shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
|
||||||
region.shop = shop
|
region.shop = shop
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
|
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
|
||||||
@@ -780,18 +780,18 @@ 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, default_door_id, shopkeeper, replaceable)
|
# (room_id, shopkeeper, replaceable)
|
||||||
shop_table = {
|
shop_table = {
|
||||||
'Cave Shop (Dark Death Mountain)': (0x0112, 0x6E, 0xC1, True),
|
'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
|
||||||
'Red Shield Shop': (0x0110, 0x75, 0xC1, True),
|
'Red Shield Shop': (0x0110, 0xC1, True),
|
||||||
'Dark Lake Hylia Shop': (0x010F, 0x74, 0xC1, True),
|
'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
|
||||||
'Dark World Lumberjack Shop': (0x010F, 0x57, 0xC1, True),
|
'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
|
||||||
'Village of Outcasts Shop': (0x010F, 0x60, 0xC1, True),
|
'Village of Outcasts Shop': (0x010F, 0xC1, True),
|
||||||
'Dark World Potion Shop': (0x010F, 0x6F, 0xC1, True),
|
'Dark World Potion Shop': (0x010F, 0xC1, True),
|
||||||
'Light World Death Mountain Shop': (0x00FF, None, 0xA0, True),
|
'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
|
||||||
'Kakariko Shop': (0x011F, 0x46, 0xA0, True),
|
'Kakariko Shop': (0x011F, 0xA0, True),
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, 0x58, 0xA0, True),
|
'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
|
||||||
'Potion Shop': (0x0109, 0x4C, 0xFF, False),
|
'Potion Shop': (0x0109, 0xFF, False),
|
||||||
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
||||||
}
|
}
|
||||||
# region, [item]
|
# region, [item]
|
||||||
|
|||||||
49
Rules.py
49
Rules.py
@@ -423,6 +423,55 @@ def global_rules(world, player):
|
|||||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
||||||
|
|
||||||
add_key_logic_rules(world, player)
|
add_key_logic_rules(world, player)
|
||||||
|
|
||||||
|
# crystal switch rules
|
||||||
|
set_rule(world.get_entrance('PoD Arena Crystal Path', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Crystal', player), player))
|
||||||
|
set_rule(world.get_entrance('Swamp Trench 2 Pots Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
|
||||||
|
set_rule(world.get_entrance('Swamp Shortcut Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Shortcut', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Hellway Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Hellway Crystal Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway N Crystal', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Triple Bypass SE', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Triple Bypass WN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Triple Bypass EN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Crystal Right Blue Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Right', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Left', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Backwards Room Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Backwards Room', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Hub Upper Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Crystal Dead End Right Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire South Fish Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire South Fish', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Compass Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Compass Room', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Crystal Mid Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Mid', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Left', player), player))
|
||||||
|
set_rule(world.get_entrance('TR Crystal Maze Blue Path', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze End', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Key Blue Path', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Key Spot', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Switches', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Transition Blue', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Transition', player), player))
|
||||||
|
|
||||||
|
set_rule(world.get_entrance('Swamp Barrier Ledge - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier Ledge', player), player))
|
||||||
|
set_rule(world.get_entrance('Swamp Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Hellway Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Hellway Crystal Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway S Crystal', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Bomb Jump Ledge Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Ledge', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Bomb Jump Catwalk Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Catwalk', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Right', player), player))
|
||||||
|
set_rule(world.get_entrance('Ice Crystal Left Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Left', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Right', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Crystal Mid Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Mid', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Firesnake Skip Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Firesnake Skip', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Antechamber Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Antechamber', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Orange Barrier 2', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Switches', player), player))
|
||||||
|
set_rule(world.get_entrance('GT Double Switch Key Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Key Spot', player), player))
|
||||||
|
|
||||||
# End of door rando rules.
|
# End of door rando rules.
|
||||||
|
|
||||||
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
|
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
|
||||||
|
|||||||
Reference in New Issue
Block a user