from . import overworld from . import dungeon1 from . import dungeon2 from . import dungeon3 from . import dungeon4 from . import dungeon5 from . import dungeon6 from . import dungeon7 from . import dungeon8 from . import dungeonColor from .requirements import AND, OR, COUNT, COUNTS, FOUND, RequirementsSettings from .location import Location from ..locations.items import * from ..locations.keyLocation import KeyLocation from ..worldSetup import WorldSetup from .. import itempool from .. import mapgen class Logic: def __init__(self, configuration_options, *, world_setup): self.world_setup = world_setup r = RequirementsSettings(configuration_options) if configuration_options.overworld == "dungeondive": world = overworld.DungeonDiveOverworld(configuration_options, r) elif configuration_options.overworld == "random": world = mapgen.LogicGenerator(configuration_options, world_setup, r, world_setup.map) else: world = overworld.World(configuration_options, world_setup, r) if configuration_options.overworld == "nodungeons": world.updateIndoorLocation("d1", dungeon1.NoDungeon1(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d2", dungeon2.NoDungeon2(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d3", dungeon3.NoDungeon3(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d4", dungeon4.NoDungeon4(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d5", dungeon5.NoDungeon5(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d6", dungeon6.NoDungeon6(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d7", dungeon7.NoDungeon7(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d8", dungeon8.NoDungeon8(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d0", dungeonColor.NoDungeonColor(configuration_options, world_setup, r).entrance) elif configuration_options.overworld != "random": world.updateIndoorLocation("d1", dungeon1.Dungeon1(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d2", dungeon2.Dungeon2(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d3", dungeon3.Dungeon3(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d4", dungeon4.Dungeon4(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d5", dungeon5.Dungeon5(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d6", dungeon6.Dungeon6(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d7", dungeon7.Dungeon7(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d8", dungeon8.Dungeon8(configuration_options, world_setup, r).entrance) world.updateIndoorLocation("d0", dungeonColor.DungeonColor(configuration_options, world_setup, r).entrance) if configuration_options.overworld != "random": for k in world.overworld_entrance.keys(): assert k in world_setup.entrance_mapping, k for k in world_setup.entrance_mapping.keys(): assert k in world.overworld_entrance, k for entrance, indoor in world_setup.entrance_mapping.items(): exterior = world.overworld_entrance[entrance] if world.indoor_location[indoor] is not None: exterior.location.connect(world.indoor_location[indoor], exterior.requirement) if exterior.enterIsSet(): exterior.location.connect(world.indoor_location[indoor], exterior.one_way_enter_requirement, one_way=True) if exterior.exitIsSet(): world.indoor_location[indoor].connect(exterior.location, exterior.one_way_exit_requirement, one_way=True) egg_trigger = AND(OCARINA, SONG1) if configuration_options.logic == 'glitched' or configuration_options.logic == 'hell': egg_trigger = OR(AND(OCARINA, SONG1), BOMB) if world_setup.goal == "seashells": world.nightmare.connect(world.egg, COUNT(SEASHELL, 20)) elif world_setup.goal in ("raft", "bingo", "bingo-full"): world.nightmare.connect(world.egg, egg_trigger) else: goal = int(world_setup.goal) if goal < 0: world.nightmare.connect(world.egg, None) elif goal == 0: world.nightmare.connect(world.egg, egg_trigger) elif goal == 8: world.nightmare.connect(world.egg, AND(egg_trigger, INSTRUMENT1, INSTRUMENT2, INSTRUMENT3, INSTRUMENT4, INSTRUMENT5, INSTRUMENT6, INSTRUMENT7, INSTRUMENT8)) else: world.nightmare.connect(world.egg, AND(egg_trigger, COUNTS([INSTRUMENT1, INSTRUMENT2, INSTRUMENT3, INSTRUMENT4, INSTRUMENT5, INSTRUMENT6, INSTRUMENT7, INSTRUMENT8], goal))) # if configuration_options.dungeon_items == 'keysy': # for n in range(9): # for count in range(9): # world.start.add(KeyLocation("KEY%d" % (n + 1))) # world.start.add(KeyLocation("NIGHTMARE_KEY%d" % (n + 1))) self.world = world self.start = world.start self.windfish = world.windfish self.location_list = [] self.iteminfo_list = [] self.__location_set = set() self.__recursiveFindAll(self.start) del self.__location_set for ii in self.iteminfo_list: ii.configure(configuration_options) def dumpFlatRequirements(self): def __rec(location, req): if hasattr(location, "flat_requirements"): new_flat_requirements = requirements.mergeFlat(location.flat_requirements, requirements.flatten(req)) if new_flat_requirements == location.flat_requirements: return location.flat_requirements = new_flat_requirements else: location.flat_requirements = requirements.flatten(req) for connection, requirement in location.simple_connections: __rec(connection, AND(req, requirement) if req else requirement) for connection, requirement in location.gated_connections: __rec(connection, AND(req, requirement) if req else requirement) __rec(self.start, None) for ii in self.iteminfo_list: print(ii) for fr in ii._location.flat_requirements: print(" " + ", ".join(sorted(map(str, fr)))) def __recursiveFindAll(self, location): if location in self.__location_set: return self.location_list.append(location) self.__location_set.add(location) for ii in location.items: self.iteminfo_list.append(ii) for connection, requirement in location.simple_connections: self.__recursiveFindAll(connection) for connection, requirement in location.gated_connections: self.__recursiveFindAll(connection) class MultiworldLogic: def __init__(self, settings, rnd=None, *, world_setups=None): assert rnd or world_setups self.worlds = [] self.start = Location() self.location_list = [self.start] self.iteminfo_list = [] for n in range(settings.multiworld): options = settings.multiworld_settings[n] world = None if world_setups: world = Logic(options, world_setup=world_setups[n]) else: for cnt in range(1000): # Try the world setup in case entrance randomization generates unsolvable logic world_setup = WorldSetup() world_setup.randomize(options, rnd) world = Logic(options, world_setup=world_setup) if options.entranceshuffle not in ("advanced", "expert", "insanity") or len(world.iteminfo_list) == sum(itempool.ItemPool(options, rnd).toDict().values()): break for ii in world.iteminfo_list: ii.world = n req_done_set = set() for loc in world.location_list: loc.simple_connections = [(target, addWorldIdToRequirements(req_done_set, n, req)) for target, req in loc.simple_connections] loc.gated_connections = [(target, addWorldIdToRequirements(req_done_set, n, req)) for target, req in loc.gated_connections] loc.items = [MultiworldItemInfoWrapper(n, options, ii) for ii in loc.items] self.iteminfo_list += loc.items self.worlds.append(world) self.start.simple_connections += world.start.simple_connections self.start.gated_connections += world.start.gated_connections self.start.items += world.start.items world.start.items.clear() self.location_list += world.location_list self.entranceMapping = None class MultiworldMetadataWrapper: def __init__(self, world, metadata): self.world = world self.metadata = metadata @property def name(self): return self.metadata.name @property def area(self): return "P%d %s" % (self.world + 1, self.metadata.area) class MultiworldItemInfoWrapper: def __init__(self, world, configuration_options, target): self.world = world self.world_count = configuration_options.multiworld self.target = target self.dungeon_items = configuration_options.dungeon_items self.MULTIWORLD_OPTIONS = None self.item = None @property def nameId(self): return self.target.nameId @property def forced_item(self): if self.target.forced_item is None: return None if "_W" in self.target.forced_item: return self.target.forced_item return "%s_W%d" % (self.target.forced_item, self.world) @property def room(self): return self.target.room @property def metadata(self): return MultiworldMetadataWrapper(self.world, self.target.metadata) @property def MULTIWORLD(self): return self.target.MULTIWORLD def read(self, rom): world = rom.banks[0x3E][0x3300 + self.target.room] if self.target.MULTIWORLD else self.world return "%s_W%d" % (self.target.read(rom), world) def getOptions(self): if self.MULTIWORLD_OPTIONS is None: options = self.target.getOptions() if self.target.MULTIWORLD and len(options) > 1: self.MULTIWORLD_OPTIONS = [] for n in range(self.world_count): self.MULTIWORLD_OPTIONS += ["%s_W%d" % (t, n) for t in options if n == self.world or self.canMultiworld(t)] else: self.MULTIWORLD_OPTIONS = ["%s_W%d" % (t, self.world) for t in options] return self.MULTIWORLD_OPTIONS def patch(self, rom, option): idx = option.rfind("_W") world = int(option[idx+2:]) option = option[:idx] if not self.target.MULTIWORLD: assert self.world == world self.target.patch(rom, option) else: self.target.patch(rom, option, multiworld=world) # Return true if the item is allowed to be placed in any world, or false if it is # world specific for this check. def canMultiworld(self, option): if self.dungeon_items in {'', 'smallkeys'}: if option.startswith("MAP"): return False if option.startswith("COMPASS"): return False if option.startswith("STONE_BEAK"): return False if self.dungeon_items in {'', 'localkeys'}: if option.startswith("KEY"): return False if self.dungeon_items in {'', 'localkeys', 'localnightmarekey', 'smallkeys'}: if option.startswith("NIGHTMARE_KEY"): return False return True @property def location(self): return self.target.location def __repr__(self): return "W%d:%s" % (self.world, repr(self.target)) def addWorldIdToRequirements(req_done_set, world, req): if req is None: return None if isinstance(req, str): return "%s_W%d" % (req, world) if req in req_done_set: return req return req.copyWithModifiedItemNames(lambda item: "%s_W%d" % (item, world))