From 568a71cdbe648d685a32698b127985ab3307531f Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 11 Jun 2021 14:22:44 +0200 Subject: [PATCH] Start implementing object oriented scaffold for world types (There's still a lot of work ahead, such as: registering locations and items to the World, as well as methods to create_item_from_name() many more method names for various stages embedding Options into the world type and many more...) --- BaseClasses.py | 7 +- Main.py | 181 +++++++++++++++++++---------------- Options.py | 31 ++++++ worlds/AutoWorld.py | 37 +++++++ worlds/BaseWorld.py | 13 --- worlds/alttp/__init__.py | 92 +----------------- worlds/factorio/Shapes.py | 2 +- worlds/factorio/__init__.py | 76 +++++++-------- worlds/hk/__init__.py | 3 + worlds/minecraft/__init__.py | 35 ++++--- 10 files changed, 233 insertions(+), 244 deletions(-) create mode 100644 worlds/AutoWorld.py delete mode 100644 worlds/BaseWorld.py diff --git a/BaseClasses.py b/BaseClasses.py index 37a42068..1b4cfcc7 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -32,7 +32,7 @@ class MultiWorld(): return self.rule(player) def __init__(self, players: int): - # TODO: move per-player settings into new classes per game-type instead of clumping it all together here + from worlds import AutoWorld self.random = random.Random() # world-local random state is saved for multiple generations running concurrently self.players = players @@ -139,11 +139,10 @@ class MultiWorld(): set_player_attr('game', "A Link to the Past") set_player_attr('completion_condition', lambda state: True) self.custom_data = {} + self.worlds = {} for player in range(1, players+1): self.custom_data[player] = {} - # self.worlds = [] - # for i in range(players): - # self.worlds.append(worlds.alttp.ALTTPWorld({}, i)) + self.worlds[player] = AutoWorld.AutoWorldRegister.world_types[self.game[player]](player) def secure(self): self.random = secrets.SystemRandom() diff --git a/Main.py b/Main.py index 78d6fd1a..c3f2b567 100644 --- a/Main.py +++ b/Main.py @@ -24,12 +24,10 @@ from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple from worlds.hk import gen_hollow from worlds.hk import create_regions as hk_create_regions -# from worlds.factorio import gen_factorio, factorio_create_regions -# from worlds.factorio.Mod import generate_mod from worlds.minecraft import gen_minecraft, fill_minecraft_slot_data, generate_mc_data from worlds.minecraft.Regions import minecraft_create_regions from worlds.generic.Rules import locality_rules -from worlds import Games, lookup_any_item_name_to_id +from worlds import Games, lookup_any_item_name_to_id, AutoWorld import Patch seeddigits = 20 @@ -79,7 +77,7 @@ def main(args, seed=None): world.progressive = args.progressive.copy() world.goal = args.goal.copy() world.local_items = args.local_items.copy() - if hasattr(args, "algorithm"): # current GUI options + if hasattr(args, "algorithm"): # current GUI options world.algorithm = args.algorithm world.shuffleganon = args.shuffleganon world.custom = args.custom @@ -128,13 +126,14 @@ def main(args, seed=None): world.game = args.game.copy() import Options for option_set in Options.option_sets: - # for option in option_set: - # setattr(world, option, getattr(args, option, {})) + for option in option_set: + setattr(world, option, getattr(args, option, {})) world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. - world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)} + world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in + range(1, world.players + 1)} - for player in range(1, world.players+1): + for player in range(1, world.players + 1): world.er_seeds[player] = str(world.random.randint(0, 2 ** 64)) if "-" in world.shuffle[player]: @@ -144,7 +143,8 @@ def main(args, seed=None): world.er_seeds[player] = "vanilla" elif seed.startswith("group-") or args.race: # renamed from team to group to not confuse with existing team name use - world.er_seeds[player] = get_same_seed(world, (shuffle, seed, world.retro[player], world.mode[player], world.logic[player])) + world.er_seeds[player] = get_same_seed(world, ( + shuffle, seed, world.retro[player], world.mode[player], world.logic[player])) else: # not a race or group seed, use set seed as is. world.er_seeds[player] = seed elif world.shuffle[player] == "vanilla": @@ -152,6 +152,10 @@ def main(args, seed=None): logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) + logger.info("Found World Types:") + for name, cls in AutoWorld.AutoWorldRegister.world_types.items(): + logger.info(f" {name:30} {cls}") + parsed_names = parse_player_names(args.names, world.players, args.teams) world.teams = len(parsed_names) for i, team in enumerate(parsed_names, 1): @@ -199,23 +203,26 @@ def main(args, seed=None): for player in world.hk_player_ids: hk_create_regions(world, player) - # for player in world.factorio_player_ids: - # factorio_create_regions(world, player) + AutoWorld.call_all(world, "create_regions") for player in world.minecraft_player_ids: minecraft_create_regions(world, player) for player in world.alttp_player_ids: if world.open_pyramid[player] == 'goal': - world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} + world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', + 'localganontriforcehunt', 'ganonpedestal'} elif world.open_pyramid[player] == 'auto': - world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \ - (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'} or not world.shuffle_ganon) + world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', + 'localganontriforcehunt', 'ganonpedestal'} and \ + (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', + 'dungeonscrossed'} or not world.shuffle_ganon) else: - world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(world.open_pyramid[player], world.open_pyramid[player]) + world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get( + world.open_pyramid[player], world.open_pyramid[player]) - - world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player]) + world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], + world.triforce_pieces_required[player]) if world.mode[player] != 'inverted': create_regions(world, player) @@ -261,8 +268,7 @@ def main(args, seed=None): for player in world.hk_player_ids: gen_hollow(world, player) - # for player in world.factorio_player_ids: - # gen_factorio(world, player) + AutoWorld.call_all(world, "generate_basic") for player in world.minecraft_player_ids: gen_minecraft(world, player) @@ -327,13 +333,13 @@ def main(args, seed=None): world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) - palettes_options={} - palettes_options['dungeon']=args.uw_palettes[player] - palettes_options['overworld']=args.ow_palettes[player] - palettes_options['hud']=args.hud_palettes[player] - palettes_options['sword']=args.sword_palettes[player] - palettes_options['shield']=args.shield_palettes[player] - palettes_options['link']=args.link_palettes[player] + palettes_options = {} + palettes_options['dungeon'] = args.uw_palettes[player] + palettes_options['overworld'] = args.ow_palettes[player] + palettes_options['hud'] = args.hud_palettes[player] + palettes_options['sword'] = args.sword_palettes[player] + palettes_options['shield'] = args.shield_palettes[player] + palettes_options['link'] = args.link_palettes[player] apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], @@ -349,8 +355,8 @@ def main(args, seed=None): world.bigkeyshuffle[player]].count(True) == 1: mcsb_name = '-mapshuffle' if world.mapshuffle[player] else \ '-compassshuffle' if world.compassshuffle[player] else \ - '-universal_keys' if world.keyshuffle[player] == "universal" else \ - '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle' + '-universal_keys' if world.keyshuffle[player] == "universal" else \ + '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle' elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]): mcsb_name = '-%s%s%s%sshuffle' % ( @@ -362,46 +368,46 @@ def main(args, seed=None): outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \ if world.player_names[player][team] != 'Player%d' % player else '' outfilestuffs = { - "logic": world.logic[player], # 0 - "difficulty": world.difficulty[player], # 1 - "item_functionality": world.item_functionality[player], # 2 - "mode": world.mode[player], # 3 - "goal": world.goal[player], # 4 - "timer": str(world.timer[player]), # 5 - "shuffle": world.shuffle[player], # 6 - "algorithm": world.algorithm, # 7 - "mscb": mcsb_name, # 8 - "retro": world.retro[player], # 9 - "progressive": world.progressive, # A - "hints": 'True' if world.hints[player] else 'False' # B + "logic": world.logic[player], # 0 + "difficulty": world.difficulty[player], # 1 + "item_functionality": world.item_functionality[player], # 2 + "mode": world.mode[player], # 3 + "goal": world.goal[player], # 4 + "timer": str(world.timer[player]), # 5 + "shuffle": world.shuffle[player], # 6 + "algorithm": world.algorithm, # 7 + "mscb": mcsb_name, # 8 + "retro": world.retro[player], # 9 + "progressive": world.progressive, # A + "hints": 'True' if world.hints[player] else 'False' # B } # 0 1 2 3 4 5 6 7 8 9 A B outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % ( - # 0 1 2 3 4 5 6 7 8 9 A B C - # _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints - # _noglitches_normal-normal-open-ganon _simple-balanced-keysanity-retro - # _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -prog_random - # _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -nohints - outfilestuffs["logic"], # 0 + # 0 1 2 3 4 5 6 7 8 9 A B C + # _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints + # _noglitches_normal-normal-open-ganon _simple-balanced-keysanity-retro + # _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -prog_random + # _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -nohints + outfilestuffs["logic"], # 0 - outfilestuffs["difficulty"], # 1 - outfilestuffs["item_functionality"], # 2 - outfilestuffs["mode"], # 3 - outfilestuffs["goal"], # 4 - "" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5 + outfilestuffs["difficulty"], # 1 + outfilestuffs["item_functionality"], # 2 + outfilestuffs["mode"], # 3 + outfilestuffs["goal"], # 4 + "" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5 - outfilestuffs["shuffle"], # 6 - outfilestuffs["algorithm"], # 7 - outfilestuffs["mscb"], # 8 + outfilestuffs["shuffle"], # 6 + outfilestuffs["algorithm"], # 7 + outfilestuffs["mscb"], # 8 - "-retro" if outfilestuffs["retro"] == "True" else "", # 9 - "-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # A - "-nohints" if not outfilestuffs["hints"] == "True" else "") # B - ) if not args.outputname else '' + "-retro" if outfilestuffs["retro"] == "True" else "", # 9 + "-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # A + "-nohints" if not outfilestuffs["hints"] == "True" else "") # B + ) if not args.outputname else '' rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc') rom.write_to_file(rompath, hide_enemizer=True) if args.create_diff: - Patch.create_patch_file(rompath, player=player, player_name = world.player_names[player][team]) + Patch.create_patch_file(rompath, player=player, player_name=world.player_names[player][team]) return player, team, bytes(rom.name) pool = concurrent.futures.ThreadPoolExecutor() @@ -409,12 +415,12 @@ def main(args, seed=None): check_accessibility_task = pool.submit(world.fulfills_accessibility) rom_futures = [] - mod_futures = [] + output_file_futures = [] for team in range(world.teams): for player in world.alttp_player_ids: rom_futures.append(pool.submit(_gen_rom, team, player)) - for player in world.factorio_player_ids: - mod_futures.append(pool.submit(generate_mod, world, player)) + for player in world.player_ids: + output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player)) def get_entrance_to_region(region: Region): for entrance in region.entrances: @@ -424,7 +430,8 @@ def main(args, seed=None): return get_entrance_to_region(entrance.parent_region) # collect ER hint info - er_hint_data = {player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla" or world.retro[player]} + er_hint_data = {player: {} for player in range(1, world.players + 1) if + world.shuffle[player] != "vanilla" or world.retro[player]} from worlds.alttp.Regions import RegionType for region in world.regions: if region.player in er_hint_data and region.locations: @@ -450,7 +457,7 @@ def main(args, seed=None): checks_in_area[location.player]["Light World"].append(location.address) elif location.parent_region.dungeon: dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', - 'Inverted Ganons Tower': 'Ganons Tower'}\ + 'Inverted Ganons Tower': 'Ganons Tower'} \ .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) checks_in_area[location.player][dungeonname].append(location.address) elif main_entrance.parent_region.type == RegionType.LightWorld: @@ -462,8 +469,10 @@ def main(args, seed=None): oldmancaves = [] takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"] for index, take_any in enumerate(takeanyregions): - for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if world.retro[player]]: - item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], region.player) + for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if + world.retro[player]]: + item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], + region.player) player = region.player location_id = SHOP_ID_START + total_shop_slots + index @@ -477,11 +486,9 @@ def main(args, seed=None): er_hint_data[player][location_id] = main_entrance.name oldmancaves.append(((location_id, player), (item.code, player))) - - FillDisabledShopSlots(world) - def write_multidata(roms, mods): + def write_multidata(roms, outputs): import base64 import NetUtils for future in roms: @@ -498,11 +505,11 @@ def main(args, seed=None): client_versions[slot] = (0, 0, 3) games[slot] = world.game[slot] connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for - slot, team, rom_name in rom_names} - precollected_items = {player: [] for player in range(1, world.players+1)} + slot, team, rom_name in rom_names} + precollected_items = {player: [] for player in range(1, world.players + 1)} for item in world.precollected_items: precollected_items[item.player].append(item.code) - precollected_hints = {player: set() for player in range(1, world.players+1)} + precollected_hints = {player: set() for player in range(1, world.players + 1)} # for now special case Factorio visibility sending_visible_players = set() for player in world.factorio_player_ids: @@ -538,7 +545,7 @@ def main(args, seed=None): precollected_hints[location.item.player].add(hint) multidata = zlib.compress(pickle.dumps({ - "slot_data" : slot_data, + "slot_data": slot_data, "games": games, "names": parsed_names, "connect_names": connect_names, @@ -559,10 +566,10 @@ def main(args, seed=None): with open(output_path('%s.archipelago' % outfilebase), 'wb') as f: f.write(bytes([1])) # version of format f.write(multidata) - for future in mods: - future.result() # collect errors if they occured + for future in outputs: + future.result() # collect errors if they occured - multidata_task = pool.submit(write_multidata, rom_futures, mod_futures) + multidata_task = pool.submit(write_multidata, rom_futures, output_file_futures) if not check_accessibility_task.result(): if not world.can_beat_game(): raise Exception("Game appears as unbeatable. Aborting.") @@ -571,7 +578,7 @@ def main(args, seed=None): if multidata_task: multidata_task.result() # retrieve exception if one exists pool.shutdown() # wait for all queued tasks to complete - for player in world.minecraft_player_ids: # Doing this after shutdown prevents the .apmc from being generated if there's an error + for player in world.minecraft_player_ids: # Doing this after shutdown prevents the .apmc from being generated if there's an error generate_mc_data(world, player) if not args.skip_playthrough: logger.info('Calculating playthrough.') @@ -626,7 +633,8 @@ def create_playthrough(world): to_delete = set() for location in sphere: # we remove the item at location and check if game is still beatable - logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player) + logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, + location.item.player) old_item = location.item location.item = None if world.can_beat_game(state_cache[num]): @@ -671,7 +679,8 @@ def create_playthrough(world): collection_spheres.append(sphere) - logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations)) + logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), + len(sphere), len(required_locations)) if not sphere: raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}') @@ -690,14 +699,22 @@ def create_playthrough(world): world.spoiler.paths = dict() for player in range(1, world.players + 1): - world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) + world.spoiler.paths.update( + {str(location): get_path(state, location.parent_region) for sphere in collection_spheres for location in + sphere if location.player == player}) if player in world.alttp_player_ids: for path in dict(world.spoiler.paths).values(): if any(exit == 'Pyramid Fairy' for (_, exit) in path): if world.mode[player] != 'inverted': - world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) + world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, + world.get_region( + 'Big Bomb Shop', + player)) else: - world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player)) + world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, + world.get_region( + 'Inverted Big Bomb Shop', + player)) # we can finally output our playthrough world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])} diff --git a/Options.py b/Options.py index b0bdb767..5150ec80 100644 --- a/Options.py +++ b/Options.py @@ -22,6 +22,37 @@ class AssembleOptions(type): return super(AssembleOptions, mcs).__new__(mcs, name, bases, attrs) +class AssembleCategoryPath(type): + def __new__(mcs, name, bases, attrs): + path = [] + for base in bases: + if hasattr(base, "segment"): + path += base.segment + path += attrs["segment"] + attrs["path"] = path + return super(AssembleCategoryPath, mcs).__new__(mcs, name, bases, attrs) + + +class RootCategory(metaclass=AssembleCategoryPath): + segment = [] + + +class LttPCategory(RootCategory): + segment = ["A Link to the Past"] + + +class LttPRomCategory(LttPCategory): + segment = ["rom"] + + +class FactorioCategory(RootCategory): + segment = ["Factorio"] + + +class MinecraftCategory(RootCategory): + segment = ["Minecraft"] + + class Option(metaclass=AssembleOptions): value: int name_lookup: typing.Dict[int, str] diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py new file mode 100644 index 00000000..c5644bcd --- /dev/null +++ b/worlds/AutoWorld.py @@ -0,0 +1,37 @@ +from BaseClasses import MultiWorld + +class AutoWorldRegister(type): + world_types = {} + + def __new__(cls, name, bases, dct): + new_class = super().__new__(cls, name, bases, dct) + if "game" in dct: + AutoWorldRegister.world_types[dct["game"]] = new_class + return new_class + + +def call_single(world: MultiWorld, method_name: str, player: int): + method = getattr(world.worlds[player], method_name) + return method(world, player) + + +def call_all(world: MultiWorld, method_name: str): + for player in world.player_ids: + call_single(world, method_name, player) + + +class World(metaclass=AutoWorldRegister): + """A World object encompasses a game's Items, Locations, Rules and additional data or functionality required. + A Game should have its own subclass of World in which it defines the required data structures.""" + + def __init__(self, player: int): + self.player = int + + def generate_basic(self, world: MultiWorld, player: int): + pass + + def generate_output(self, world: MultiWorld, player: int): + pass + + def create_regions(self, world: MultiWorld, player: int): + pass diff --git a/worlds/BaseWorld.py b/worlds/BaseWorld.py deleted file mode 100644 index 34c3ef89..00000000 --- a/worlds/BaseWorld.py +++ /dev/null @@ -1,13 +0,0 @@ -class AutoWorldRegister(type): - _world_types = {} - - def __new__(cls, name, bases, dct): - new_class = super().__new__(cls, name, bases, dct) - AutoWorldRegister._world_types[name] = new_class - return new_class - -class World(metaclass=AutoWorldRegister): - """A World object encompasses a game's Items, Locations, Rules and additional data or functionality required. - A Game should have its own subclass of World in which it defines the required data structures.""" - def __init__(self): - pass \ No newline at end of file diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index c3f7edbb..11ed3404 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -1,96 +1,10 @@ from typing import Optional from BaseClasses import Location, Item +from ..AutoWorld import World - -#class ALTTPWorld(World): -# """WIP""" -# def __init__(self, options, slot: int): -# self._region_cache = {} -# self.slot = slot -# self.shuffle = shuffle -# self.logic = logic -# self.mode = mode -# self.swords = swords -# self.difficulty = difficulty -# self.difficulty_adjustments = difficulty_adjustments -# self.timer = timer -# self.progressive = progressive -# self.goal = goal -# self.dungeons = [] -# self.regions = [] -# self.shops = [] -# self.itempool = [] -# self.seed = None -# self.precollected_items = [] -# self.state = CollectionState(self) -# self._cached_entrances = None -# self._cached_locations = None -# self._entrance_cache = {} -# self._location_cache = {} -# self.required_locations = [] -# self.light_world_light_cone = False -# self.dark_world_light_cone = False -# self.rupoor_cost = 10 -# self.aga_randomness = True -# self.lock_aga_door_in_escape = False -# self.save_and_quit_from_boss = True -# self.accessibility = accessibility -# self.shuffle_ganon = shuffle_ganon -# self.fix_gtower_exit = self.shuffle_ganon -# self.retro = retro -# self.custom = custom -# self.customitemarray: List[int] = customitemarray -# self.hints = hints -# self.dynamic_regions = [] -# self.dynamic_locations = [] -# -# -# self.remote_items = False -# self.required_medallions = ['Ether', 'Quake'] -# self.swamp_patch_required = False -# self.powder_patch_required = False -# self.ganon_at_pyramid = True -# self.ganonstower_vanilla = True -# -# -# self.can_access_trock_eyebridge = None -# self.can_access_trock_front = None -# self.can_access_trock_big_chest = None -# self.can_access_trock_middle = None -# self.fix_fake_world = True -# self.mapshuffle = False -# self.compassshuffle = False -# self.keyshuffle = False -# self.bigkeyshuffle = False -# self.difficulty_requirements = None -# self.boss_shuffle = 'none' -# self.enemy_shuffle = False -# self.enemy_health = 'default' -# self.enemy_damage = 'default' -# self.killable_thieves = False -# self.tile_shuffle = False -# self.bush_shuffle = False -# self.beemizer = 0 -# self.escape_assist = [] -# self.crystals_needed_for_ganon = 7 -# self.crystals_needed_for_gt = 7 -# self.open_pyramid = False -# self.treasure_hunt_icon = 'Triforce Piece' -# self.treasure_hunt_count = 0 -# self.clock_mode = False -# self.can_take_damage = True -# self.glitch_boots = True -# self.progression_balancing = True -# self.local_items = set() -# self.triforce_pieces_available = 30 -# self.triforce_pieces_required = 20 -# self.shop_shuffle = 'off' -# self.shuffle_prizes = "g" -# self.sprite_pool = [] -# self.dark_room_logic = "lamp" -# self.restrict_dungeon_item_on_boss = False -# +class ALTTPWorld(World): + game: str = "A Link to the Past" class ALttPLocation(Location): diff --git a/worlds/factorio/Shapes.py b/worlds/factorio/Shapes.py index 2bde14eb..fea4e848 100644 --- a/worlds/factorio/Shapes.py +++ b/worlds/factorio/Shapes.py @@ -15,7 +15,7 @@ def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]: prerequisites: Dict[str, Set[str]] = {} layout = world.tech_tree_layout[player].value custom_technologies = world.custom_data[player]["custom_technologies"] - tech_names: List[str] = list(set(custom_technologies) - world._static_nodes) + tech_names: List[str] = list(set(custom_technologies) - world.worlds[player].static_nodes) tech_names.sort() world.random.shuffle(tech_names) diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 7d26862f..a1e806bb 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -1,66 +1,56 @@ -from ..BaseWorld import World - +from ..AutoWorld import World from BaseClasses import Region, Entrance, Location, MultiWorld, Item from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies, \ all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes from .Shapes import get_shapes +from .Mod import generate_mod + class Factorio(World): + game: str = "Factorio" + static_nodes = {"automation", "logistics"} + def generate_basic(self, world: MultiWorld, player: int): - static_nodes = world._static_nodes = {"automation", "logistics"} # turn dynamic/option? + victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value])) for tech_name, tech_id in tech_table.items(): - tech_item = Item(tech_name, tech_name in advancement_technologies, tech_id, player) + tech_item = Item(tech_name, tech_name in advancement_technologies or tech_name in victory_tech_names, + tech_id, player) tech_item.game = "Factorio" - if tech_name in static_nodes: - loc = world.get_location(tech_name, player) - loc.item = tech_item - loc.locked = True - loc.event = tech_item.advancement + if tech_name in self.static_nodes: + world.get_location(tech_name, player).place_locked_item(tech_item) else: world.itempool.append(tech_item) world.custom_data[player]["custom_technologies"] = custom_technologies = set_custom_technologies(world, player) set_rules(world, player, custom_technologies) -def gen_factorio(world: MultiWorld, player: int): - static_nodes = world._static_nodes = {"automation", "logistics"} # turn dynamic/option? - victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value])) - for tech_name, tech_id in tech_table.items(): - tech_item = Item(tech_name, tech_name in advancement_technologies or tech_name in victory_tech_names, - tech_id, player) - tech_item.game = "Factorio" - if tech_name in static_nodes: - world.get_location(tech_name, player).place_locked_item(tech_item) - else: - world.itempool.append(tech_item) - world.custom_data[player]["custom_technologies"] = custom_technologies = set_custom_technologies(world, player) - set_rules(world, player, custom_technologies) + def generate_output(self, world: MultiWorld, player: int): + generate_mod(world, player) + def create_regions(self, world: MultiWorld, player: int): + menu = Region("Menu", None, "Menu", player) + crash = Entrance(player, "Crash Land", menu) + menu.exits.append(crash) + nauvis = Region("Nauvis", None, "Nauvis", player) + nauvis.world = menu.world = world -def factorio_create_regions(world: MultiWorld, player: int): - menu = Region("Menu", None, "Menu", player) - crash = Entrance(player, "Crash Land", menu) - menu.exits.append(crash) - nauvis = Region("Nauvis", None, "Nauvis", player) - nauvis.world = menu.world = world - - for tech_name, tech_id in tech_table.items(): - tech = Location(player, tech_name, tech_id, nauvis) - nauvis.locations.append(tech) - tech.game = "Factorio" - location = Location(player, "Rocket Launch", None, nauvis) - nauvis.locations.append(location) - event = Item("Victory", True, None, player) - world.push_item(location, event, False) - location.event = location.locked = True - for ingredient in all_ingredient_names: - location = Location(player, f"Automate {ingredient}", None, nauvis) + for tech_name, tech_id in tech_table.items(): + tech = Location(player, tech_name, tech_id, nauvis) + nauvis.locations.append(tech) + tech.game = "Factorio" + location = Location(player, "Rocket Launch", None, nauvis) nauvis.locations.append(location) - event = Item(f"Automated {ingredient}", True, None, player) + event = Item("Victory", True, None, player) world.push_item(location, event, False) location.event = location.locked = True - crash.connect(nauvis) - world.regions += [menu, nauvis] + for ingredient in all_ingredient_names: + location = Location(player, f"Automate {ingredient}", None, nauvis) + nauvis.locations.append(location) + event = Item(f"Automated {ingredient}", True, None, player) + world.push_item(location, event, False) + location.event = location.locked = True + crash.connect(nauvis) + world.regions += [menu, nauvis] def set_custom_technologies(world: MultiWorld, player: int): diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 65ea8421..12bc6448 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -8,7 +8,10 @@ from .Regions import create_regions from .Rules import set_rules from BaseClasses import Region, Entrance, Location, MultiWorld, Item +from ..AutoWorld import World +class HKWorld(World): + game: str = "Hollow Knight" def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): ret = Region(name, None, name, player) diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index 676a72d0..a170daae 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -4,15 +4,24 @@ from .Locations import exclusion_table, events_table from .Regions import link_minecraft_structures from .Rules import set_rules -from BaseClasses import Region, Entrance, Location, MultiWorld, Item +from BaseClasses import MultiWorld from Options import minecraft_options +from ..AutoWorld import World + + +class MinecraftWorld(World): + game: str = "Minecraft" + client_version = (0, 3) + def get_mc_data(world: MultiWorld, player: int): - exits = ["Overworld Structure 1", "Overworld Structure 2", "Nether Structure 1", "Nether Structure 2", "The End Structure"] + exits = ["Overworld Structure 1", "Overworld Structure 2", "Nether Structure 1", "Nether Structure 2", + "The End Structure"] return { - 'world_seed': Random(world.rom_seeds[player]).getrandbits(32), # consistent and doesn't interfere with other generation + 'world_seed': Random(world.rom_seeds[player]).getrandbits(32), + # consistent and doesn't interfere with other generation 'seed_name': world.seed_name, 'player_name': world.get_player_names(player), 'player_id': player, @@ -20,25 +29,27 @@ def get_mc_data(world: MultiWorld, player: int): 'structures': {exit: world.get_entrance(exit, player).connected_region.name for exit in exits} } -def generate_mc_data(world: MultiWorld, player: int): + +def generate_mc_data(world: MultiWorld, player: int): import base64, json from Utils import output_path data = get_mc_data(world, player) filename = f"AP_{world.seed_name}_P{player}_{world.get_player_names(player)}.apmc" - with open(output_path(filename), 'wb') as f: + with open(output_path(filename), 'wb') as f: f.write(base64.b64encode(bytes(json.dumps(data), 'utf-8'))) -def fill_minecraft_slot_data(world: MultiWorld, player: int): + +def fill_minecraft_slot_data(world: MultiWorld, player: int): slot_data = get_mc_data(world, player) for option_name in minecraft_options: option = getattr(world, option_name)[player] slot_data[option_name] = int(option.value) return slot_data -# Generates the item pool given the table and frequencies in Items.py. -def minecraft_gen_item_pool(world: MultiWorld, player: int): +# Generates the item pool given the table and frequencies in Items.py. +def minecraft_gen_item_pool(world: MultiWorld, player: int): pool = [] for item_name, item_data in item_table.items(): for count in range(item_frequencies.get(item_name, 1)): @@ -47,8 +58,8 @@ def minecraft_gen_item_pool(world: MultiWorld, player: int): prefill_pool = {} prefill_pool.update(events_table) exclusion_pools = ['hard', 'insane', 'postgame'] - for key in exclusion_pools: - if not getattr(world, f"include_{key}_advancements")[player]: + for key in exclusion_pools: + if not getattr(world, f"include_{key}_advancements")[player]: prefill_pool.update(exclusion_table[key]) for loc_name, item_name in prefill_pool.items(): @@ -62,9 +73,9 @@ def minecraft_gen_item_pool(world: MultiWorld, player: int): world.itempool += pool -# Generate Minecraft world. + +# Generate Minecraft world. def gen_minecraft(world: MultiWorld, player: int): link_minecraft_structures(world, player) minecraft_gen_item_pool(world, player) set_rules(world, player) -