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...)
This commit is contained in:
Fabian Dill 2021-06-11 14:22:44 +02:00
parent 753a5f7cb2
commit 568a71cdbe
10 changed files with 233 additions and 244 deletions

View File

@ -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()

181
Main.py
View File

@ -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])}

View File

@ -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]

37
worlds/AutoWorld.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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)