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:
parent
753a5f7cb2
commit
568a71cdbe
|
@ -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
181
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])}
|
||||
|
|
31
Options.py
31
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]
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue