285 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			285 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | 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)) |