Merge branch 'main' of https://github.com/ArchipelagoMW/Archipelago into minecraft
This commit is contained in:
commit
6211760922
|
@ -23,6 +23,7 @@ class MultiWorld():
|
||||||
plando_items: List[PlandoItem]
|
plando_items: List[PlandoItem]
|
||||||
plando_connections: List[PlandoConnection]
|
plando_connections: List[PlandoConnection]
|
||||||
er_seeds: Dict[int, str]
|
er_seeds: Dict[int, str]
|
||||||
|
worlds: Dict[int, "AutoWorld.World"]
|
||||||
|
|
||||||
class AttributeProxy():
|
class AttributeProxy():
|
||||||
def __init__(self, rule):
|
def __init__(self, rule):
|
||||||
|
@ -32,8 +33,6 @@ class MultiWorld():
|
||||||
return self.rule(player)
|
return self.rule(player)
|
||||||
|
|
||||||
def __init__(self, players: int):
|
def __init__(self, players: int):
|
||||||
# TODO: move per-player settings into new classes per game-type instead of clumping it all together here
|
|
||||||
|
|
||||||
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
||||||
self.players = players
|
self.players = players
|
||||||
self.teams = 1
|
self.teams = 1
|
||||||
|
@ -113,8 +112,6 @@ class MultiWorld():
|
||||||
set_player_attr('bush_shuffle', False)
|
set_player_attr('bush_shuffle', False)
|
||||||
set_player_attr('beemizer', 0)
|
set_player_attr('beemizer', 0)
|
||||||
set_player_attr('escape_assist', [])
|
set_player_attr('escape_assist', [])
|
||||||
set_player_attr('crystals_needed_for_ganon', 7)
|
|
||||||
set_player_attr('crystals_needed_for_gt', 7)
|
|
||||||
set_player_attr('open_pyramid', False)
|
set_player_attr('open_pyramid', False)
|
||||||
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||||
set_player_attr('treasure_hunt_count', 0)
|
set_player_attr('treasure_hunt_count', 0)
|
||||||
|
@ -131,7 +128,6 @@ class MultiWorld():
|
||||||
set_player_attr('triforce_pieces_available', 30)
|
set_player_attr('triforce_pieces_available', 30)
|
||||||
set_player_attr('triforce_pieces_required', 20)
|
set_player_attr('triforce_pieces_required', 20)
|
||||||
set_player_attr('shop_shuffle', 'off')
|
set_player_attr('shop_shuffle', 'off')
|
||||||
set_player_attr('shop_shuffle_slots', 0)
|
|
||||||
set_player_attr('shuffle_prizes', "g")
|
set_player_attr('shuffle_prizes', "g")
|
||||||
set_player_attr('sprite_pool', [])
|
set_player_attr('sprite_pool', [])
|
||||||
set_player_attr('dark_room_logic', "lamp")
|
set_player_attr('dark_room_logic', "lamp")
|
||||||
|
@ -141,15 +137,19 @@ class MultiWorld():
|
||||||
set_player_attr('plando_connections', [])
|
set_player_attr('plando_connections', [])
|
||||||
set_player_attr('game', "A Link to the Past")
|
set_player_attr('game', "A Link to the Past")
|
||||||
set_player_attr('completion_condition', lambda state: True)
|
set_player_attr('completion_condition', lambda state: True)
|
||||||
import Options
|
|
||||||
for hk_option in Options.hollow_knight_options:
|
|
||||||
set_player_attr(hk_option, False)
|
|
||||||
self.custom_data = {}
|
self.custom_data = {}
|
||||||
for player in range(1, players+1):
|
self.worlds = {}
|
||||||
|
|
||||||
|
|
||||||
|
def set_options(self, args):
|
||||||
|
import Options
|
||||||
|
from worlds import AutoWorld
|
||||||
|
for option_set in Options.option_sets:
|
||||||
|
for option in option_set:
|
||||||
|
setattr(self, option, getattr(args, option, {}))
|
||||||
|
for player in self.player_ids:
|
||||||
self.custom_data[player] = {}
|
self.custom_data[player] = {}
|
||||||
# self.worlds = []
|
self.worlds[player] = AutoWorld.AutoWorldRegister.world_types[self.game[player]](self, player)
|
||||||
# for i in range(players):
|
|
||||||
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
|
|
||||||
|
|
||||||
def secure(self):
|
def secure(self):
|
||||||
self.random = secrets.SystemRandom()
|
self.random = secrets.SystemRandom()
|
||||||
|
@ -1237,11 +1237,11 @@ class Item():
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hint_text(self):
|
def hint_text(self):
|
||||||
return getattr(self, "_hint_text", self.name)
|
return getattr(self, "_hint_text", self.name.replace("_", " ").replace("-", " "))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pedestal_hint_text(self):
|
def pedestal_hint_text(self):
|
||||||
return getattr(self, "_pedestal_hint_text", self.name)
|
return getattr(self, "_pedestal_hint_text", self.name.replace("_", " ").replace("-", " "))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.name == other.name and self.player == other.player
|
return self.name == other.name and self.player == other.player
|
||||||
|
@ -1448,7 +1448,7 @@ class Spoiler(object):
|
||||||
'triforce_pieces_available': self.world.triforce_pieces_available,
|
'triforce_pieces_available': self.world.triforce_pieces_available,
|
||||||
'triforce_pieces_required': self.world.triforce_pieces_required,
|
'triforce_pieces_required': self.world.triforce_pieces_required,
|
||||||
'shop_shuffle': self.world.shop_shuffle,
|
'shop_shuffle': self.world.shop_shuffle,
|
||||||
'shop_shuffle_slots': self.world.shop_shuffle_slots,
|
'shop_item_slots': self.world.shop_item_slots,
|
||||||
'shuffle_prizes': self.world.shuffle_prizes,
|
'shuffle_prizes': self.world.shuffle_prizes,
|
||||||
'sprite_pool': self.world.sprite_pool,
|
'sprite_pool': self.world.sprite_pool,
|
||||||
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss,
|
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss,
|
||||||
|
@ -1565,8 +1565,8 @@ class Spoiler(object):
|
||||||
"f" in self.metadata["shop_shuffle"][player]))
|
"f" in self.metadata["shop_shuffle"][player]))
|
||||||
outfile.write('Custom Potion Shop: %s\n' %
|
outfile.write('Custom Potion Shop: %s\n' %
|
||||||
bool_to_text("w" in self.metadata["shop_shuffle"][player]))
|
bool_to_text("w" in self.metadata["shop_shuffle"][player]))
|
||||||
outfile.write('Shop Slots: %s\n' %
|
outfile.write('Shop Item Slots: %s\n' %
|
||||||
self.metadata["shop_shuffle_slots"][player])
|
self.metadata["shop_item_slots"][player])
|
||||||
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player])
|
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player])
|
||||||
outfile.write(
|
outfile.write(
|
||||||
'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player]))
|
'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player]))
|
||||||
|
|
141
Fill.py
141
Fill.py
|
@ -3,7 +3,7 @@ import typing
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from BaseClasses import CollectionState, PlandoItem, Location
|
from BaseClasses import CollectionState, PlandoItem, Location, MultiWorld
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import key_drop_data
|
from worlds.alttp.Regions import key_drop_data
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class FillError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def fill_restrictive(world, base_state: CollectionState, locations, itempool, single_player_placement=False,
|
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool, single_player_placement=False,
|
||||||
lock=False):
|
lock=False):
|
||||||
def sweep_from_pool():
|
def sweep_from_pool():
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
|
@ -68,7 +68,7 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
|
||||||
itempool.extend(unplaced_items)
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
|
||||||
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
def distribute_items_restrictive(world: MultiWorld, gftower_trash=False, fill_locations=None):
|
||||||
# If not passed in, then get a shuffled list of locations to fill in
|
# If not passed in, then get a shuffled list of locations to fill in
|
||||||
if not fill_locations:
|
if not fill_locations:
|
||||||
fill_locations = world.get_unfilled_locations()
|
fill_locations = world.get_unfilled_locations()
|
||||||
|
@ -167,14 +167,14 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||||
logging.warning(f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
|
logging.warning(f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
|
||||||
|
|
||||||
|
|
||||||
def fast_fill(world, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]:
|
def fast_fill(world: MultiWorld, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]:
|
||||||
placing = min(len(item_pool), len(fill_locations))
|
placing = min(len(item_pool), len(fill_locations))
|
||||||
for item, location in zip(item_pool, fill_locations):
|
for item, location in zip(item_pool, fill_locations):
|
||||||
world.push_item(location, item, False)
|
world.push_item(location, item, False)
|
||||||
return item_pool[placing:], fill_locations[placing:]
|
return item_pool[placing:], fill_locations[placing:]
|
||||||
|
|
||||||
|
|
||||||
def flood_items(world):
|
def flood_items(world: MultiWorld):
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
world.random.shuffle(world.itempool)
|
world.random.shuffle(world.itempool)
|
||||||
itempool = world.itempool
|
itempool = world.itempool
|
||||||
|
@ -234,7 +234,7 @@ def flood_items(world):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def balance_multiworld_progression(world):
|
def balance_multiworld_progression(world: MultiWorld):
|
||||||
balanceable_players = {player for player in range(1, world.players + 1) if world.progression_balancing[player]}
|
balanceable_players = {player for player in range(1, world.players + 1) if world.progression_balancing[player]}
|
||||||
if not balanceable_players:
|
if not balanceable_players:
|
||||||
logging.info('Skipping multiworld progression balancing.')
|
logging.info('Skipping multiworld progression balancing.')
|
||||||
|
@ -363,73 +363,76 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked=
|
||||||
location_1.event, location_2.event = location_2.event, location_1.event
|
location_1.event, location_2.event = location_2.event, location_1.event
|
||||||
|
|
||||||
|
|
||||||
def distribute_planned(world):
|
def distribute_planned(world: MultiWorld):
|
||||||
world_name_lookup = world.world_name_lookup
|
world_name_lookup = world.world_name_lookup
|
||||||
|
|
||||||
for player in world.player_ids:
|
for player in world.player_ids:
|
||||||
placement: PlandoItem
|
try:
|
||||||
for placement in world.plando_items[player]:
|
placement: PlandoItem
|
||||||
if placement.location in key_drop_data:
|
for placement in world.plando_items[player]:
|
||||||
placement.warn(
|
if placement.location in key_drop_data:
|
||||||
f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
placement.warn(
|
||||||
continue
|
f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
||||||
item = ItemFactory(placement.item, player)
|
continue
|
||||||
target_world: int = placement.world
|
item = ItemFactory(placement.item, player)
|
||||||
if target_world is False or world.players == 1:
|
target_world: int = placement.world
|
||||||
target_world = player # in own world
|
if target_world is False or world.players == 1:
|
||||||
elif target_world is True: # in any other world
|
target_world = player # in own world
|
||||||
unfilled = list(location for location in world.get_unfilled_locations_for_players(
|
elif target_world is True: # in any other world
|
||||||
placement.location,
|
unfilled = list(location for location in world.get_unfilled_locations_for_players(
|
||||||
set(world.player_ids) - {player}) if location.item_rule(item)
|
placement.location,
|
||||||
)
|
set(world.player_ids) - {player}) if location.item_rule(item)
|
||||||
if not unfilled:
|
)
|
||||||
placement.failed(f"Could not find a world with an unfilled location {placement.location}",
|
if not unfilled:
|
||||||
FillError)
|
placement.failed(f"Could not find a world with an unfilled location {placement.location}",
|
||||||
|
FillError)
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_world = world.random.choice(unfilled).player
|
||||||
|
|
||||||
|
elif target_world is None: # any random world
|
||||||
|
unfilled = list(location for location in world.get_unfilled_locations_for_players(
|
||||||
|
placement.location,
|
||||||
|
set(world.player_ids)) if location.item_rule(item)
|
||||||
|
)
|
||||||
|
if not unfilled:
|
||||||
|
placement.failed(f"Could not find a world with an unfilled location {placement.location}",
|
||||||
|
FillError)
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_world = world.random.choice(unfilled).player
|
||||||
|
|
||||||
|
elif type(target_world) == int: # target world by player id
|
||||||
|
if target_world not in range(1, world.players + 1):
|
||||||
|
placement.failed(
|
||||||
|
f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
|
||||||
|
ValueError)
|
||||||
|
continue
|
||||||
|
else: # find world by name
|
||||||
|
if target_world not in world_name_lookup:
|
||||||
|
placement.failed(f"Cannot place item to {target_world}'s world as that world does not exist.",
|
||||||
|
ValueError)
|
||||||
|
continue
|
||||||
|
target_world = world_name_lookup[target_world]
|
||||||
|
|
||||||
|
location = world.get_location(placement.location, target_world)
|
||||||
|
if location.item:
|
||||||
|
placement.failed(f"Cannot place item into already filled location {location}.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
target_world = world.random.choice(unfilled).player
|
if location.can_fill(world.state, item, False):
|
||||||
|
world.push_item(location, item, collect=False)
|
||||||
elif target_world is None: # any random world
|
location.event = True # flag location to be checked during fill
|
||||||
unfilled = list(location for location in world.get_unfilled_locations_for_players(
|
location.locked = True
|
||||||
placement.location,
|
logging.debug(f"Plando placed {item} at {location}")
|
||||||
set(world.player_ids)) if location.item_rule(item)
|
else:
|
||||||
)
|
placement.failed(f"Can't place {item} at {location} due to fill condition not met.")
|
||||||
if not unfilled:
|
|
||||||
placement.failed(f"Could not find a world with an unfilled location {placement.location}",
|
|
||||||
FillError)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
target_world = world.random.choice(unfilled).player
|
if placement.from_pool: # Should happen AFTER the item is placed, in case it was allowed to skip failed placement.
|
||||||
|
try:
|
||||||
elif type(target_world) == int: # target world by player id
|
world.itempool.remove(item)
|
||||||
if target_world not in range(1, world.players + 1):
|
except ValueError:
|
||||||
placement.failed(
|
placement.warn(f"Could not remove {item} from pool as it's already missing from it.")
|
||||||
f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
|
except Exception as e:
|
||||||
ValueError)
|
raise Exception(f"Error running plando for player {player} ({world.player_names[player]})") from e
|
||||||
continue
|
|
||||||
else: # find world by name
|
|
||||||
if target_world not in world_name_lookup:
|
|
||||||
placement.failed(f"Cannot place item to {target_world}'s world as that world does not exist.",
|
|
||||||
ValueError)
|
|
||||||
continue
|
|
||||||
target_world = world_name_lookup[target_world]
|
|
||||||
|
|
||||||
location = world.get_location(placement.location, target_world)
|
|
||||||
if location.item:
|
|
||||||
placement.failed(f"Cannot place item into already filled location {location}.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if location.can_fill(world.state, item, False):
|
|
||||||
world.push_item(location, item, collect=False)
|
|
||||||
location.event = True # flag location to be checked during fill
|
|
||||||
location.locked = True
|
|
||||||
logging.debug(f"Plando placed {item} at {location}")
|
|
||||||
else:
|
|
||||||
placement.failed(f"Can't place {item} at {location} due to fill condition not met.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if placement.from_pool: # Should happen AFTER the item is placed, in case it was allowed to skip failed placement.
|
|
||||||
try:
|
|
||||||
world.itempool.remove(item)
|
|
||||||
except ValueError:
|
|
||||||
placement.warn(f"Could not remove {item} from pool as it's already missing from it.")
|
|
||||||
|
|
2
Gui.py
2
Gui.py
|
@ -468,7 +468,7 @@ def guiMain(args=None):
|
||||||
if shopWitchShuffleVar.get():
|
if shopWitchShuffleVar.get():
|
||||||
guiargs.shop_shuffle += "w"
|
guiargs.shop_shuffle += "w"
|
||||||
if shopPoolShuffleVar.get():
|
if shopPoolShuffleVar.get():
|
||||||
guiargs.shop_shuffle_slots = 30
|
guiargs.shop_item_slots = 30
|
||||||
guiargs.shuffle_prizes = {"none": "",
|
guiargs.shuffle_prizes = {"none": "",
|
||||||
"bonk": "b",
|
"bonk": "b",
|
||||||
"general": "g",
|
"general": "g",
|
||||||
|
|
|
@ -18,7 +18,7 @@ class AdjusterWorld(object):
|
||||||
def __init__(self, sprite_pool):
|
def __init__(self, sprite_pool):
|
||||||
import random
|
import random
|
||||||
self.sprite_pool = {1: sprite_pool}
|
self.sprite_pool = {1: sprite_pool}
|
||||||
self.rom_seeds = {1: random}
|
self.slot_seeds = {1: random}
|
||||||
|
|
||||||
|
|
||||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||||
|
|
|
@ -138,8 +138,6 @@ SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
||||||
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
||||||
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes
|
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes
|
||||||
|
|
||||||
location_shop_order = [name for name, info in
|
|
||||||
Shops.shop_table.items()] # probably don't leave this here. This relies on python 3.6+ dictionary keys having defined order
|
|
||||||
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
|
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
|
||||||
|
|
||||||
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||||
|
@ -704,9 +702,6 @@ def get_tags(ctx: Context):
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def track_locations(ctx: Context, roomid, roomdata):
|
async def track_locations(ctx: Context, roomid, roomdata):
|
||||||
new_locations = []
|
new_locations = []
|
||||||
|
|
||||||
|
@ -718,7 +713,7 @@ async def track_locations(ctx: Context, roomid, roomdata):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if roomid in location_shop_ids:
|
if roomid in location_shop_ids:
|
||||||
misc_data = await snes_read(ctx, SHOP_ADDR, (len(location_shop_order) * 3) + 5)
|
misc_data = await snes_read(ctx, SHOP_ADDR, (len(Shops.shop_table) * 3) + 5)
|
||||||
for cnt, b in enumerate(misc_data):
|
for cnt, b in enumerate(misc_data):
|
||||||
if int(b) and (Shops.SHOP_ID_START + cnt) not in ctx.locations_checked:
|
if int(b) and (Shops.SHOP_ID_START + cnt) not in ctx.locations_checked:
|
||||||
new_check(Shops.SHOP_ID_START + cnt)
|
new_check(Shops.SHOP_ID_START + cnt)
|
||||||
|
|
203
Main.py
203
Main.py
|
@ -1,4 +1,3 @@
|
||||||
import copy
|
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -24,12 +23,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 Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
|
||||||
from worlds.hk import gen_hollow
|
from worlds.hk import gen_hollow
|
||||||
from worlds.hk import create_regions as hk_create_regions
|
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 import gen_minecraft, fill_minecraft_slot_data, generate_mc_data
|
||||||
from worlds.minecraft.Regions import minecraft_create_regions
|
from worlds.minecraft.Regions import minecraft_create_regions
|
||||||
from worlds.generic.Rules import locality_rules
|
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
|
import Patch
|
||||||
|
|
||||||
seeddigits = 20
|
seeddigits = 20
|
||||||
|
@ -79,7 +76,7 @@ def main(args, seed=None):
|
||||||
world.progressive = args.progressive.copy()
|
world.progressive = args.progressive.copy()
|
||||||
world.goal = args.goal.copy()
|
world.goal = args.goal.copy()
|
||||||
world.local_items = args.local_items.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.algorithm = args.algorithm
|
||||||
world.shuffleganon = args.shuffleganon
|
world.shuffleganon = args.shuffleganon
|
||||||
world.custom = args.custom
|
world.custom = args.custom
|
||||||
|
@ -94,8 +91,6 @@ def main(args, seed=None):
|
||||||
world.compassshuffle = args.compassshuffle.copy()
|
world.compassshuffle = args.compassshuffle.copy()
|
||||||
world.keyshuffle = args.keyshuffle.copy()
|
world.keyshuffle = args.keyshuffle.copy()
|
||||||
world.bigkeyshuffle = args.bigkeyshuffle.copy()
|
world.bigkeyshuffle = args.bigkeyshuffle.copy()
|
||||||
world.crystals_needed_for_ganon = args.crystals_ganon.copy()
|
|
||||||
world.crystals_needed_for_gt = args.crystals_gt.copy()
|
|
||||||
world.open_pyramid = args.open_pyramid.copy()
|
world.open_pyramid = args.open_pyramid.copy()
|
||||||
world.boss_shuffle = args.shufflebosses.copy()
|
world.boss_shuffle = args.shufflebosses.copy()
|
||||||
world.enemy_shuffle = args.enemy_shuffle.copy()
|
world.enemy_shuffle = args.enemy_shuffle.copy()
|
||||||
|
@ -117,7 +112,6 @@ def main(args, seed=None):
|
||||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||||
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
||||||
world.shop_shuffle = args.shop_shuffle.copy()
|
world.shop_shuffle = args.shop_shuffle.copy()
|
||||||
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
|
||||||
world.progression_balancing = args.progression_balancing.copy()
|
world.progression_balancing = args.progression_balancing.copy()
|
||||||
world.shuffle_prizes = args.shuffle_prizes.copy()
|
world.shuffle_prizes = args.shuffle_prizes.copy()
|
||||||
world.sprite_pool = args.sprite_pool.copy()
|
world.sprite_pool = args.sprite_pool.copy()
|
||||||
|
@ -129,18 +123,13 @@ def main(args, seed=None):
|
||||||
world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
|
world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
|
||||||
world.required_medallions = args.required_medallions.copy()
|
world.required_medallions = args.required_medallions.copy()
|
||||||
world.game = args.game.copy()
|
world.game = args.game.copy()
|
||||||
import Options
|
world.set_options(args)
|
||||||
for hk_option in Options.hollow_knight_options:
|
|
||||||
setattr(world, hk_option, getattr(args, hk_option, {}))
|
|
||||||
for factorio_option in Options.factorio_options:
|
|
||||||
setattr(world, factorio_option, getattr(args, factorio_option, {}))
|
|
||||||
for minecraft_option in Options.minecraft_options:
|
|
||||||
setattr(world, minecraft_option, getattr(args, minecraft_option, {}))
|
|
||||||
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player 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.slot_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))
|
world.er_seeds[player] = str(world.random.randint(0, 2 ** 64))
|
||||||
|
|
||||||
if "-" in world.shuffle[player]:
|
if "-" in world.shuffle[player]:
|
||||||
|
@ -150,7 +139,8 @@ def main(args, seed=None):
|
||||||
world.er_seeds[player] = "vanilla"
|
world.er_seeds[player] = "vanilla"
|
||||||
elif seed.startswith("group-") or args.race:
|
elif seed.startswith("group-") or args.race:
|
||||||
# renamed from team to group to not confuse with existing team name use
|
# 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.
|
else: # not a race or group seed, use set seed as is.
|
||||||
world.er_seeds[player] = seed
|
world.er_seeds[player] = seed
|
||||||
elif world.shuffle[player] == "vanilla":
|
elif world.shuffle[player] == "vanilla":
|
||||||
|
@ -158,6 +148,10 @@ def main(args, seed=None):
|
||||||
|
|
||||||
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
|
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)
|
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
||||||
world.teams = len(parsed_names)
|
world.teams = len(parsed_names)
|
||||||
for i, team in enumerate(parsed_names, 1):
|
for i, team in enumerate(parsed_names, 1):
|
||||||
|
@ -205,23 +199,26 @@ def main(args, seed=None):
|
||||||
for player in world.hk_player_ids:
|
for player in world.hk_player_ids:
|
||||||
hk_create_regions(world, player)
|
hk_create_regions(world, player)
|
||||||
|
|
||||||
for player in world.factorio_player_ids:
|
AutoWorld.call_all(world, "create_regions")
|
||||||
factorio_create_regions(world, player)
|
|
||||||
|
|
||||||
for player in world.minecraft_player_ids:
|
for player in world.minecraft_player_ids:
|
||||||
minecraft_create_regions(world, player)
|
minecraft_create_regions(world, player)
|
||||||
|
|
||||||
for player in world.alttp_player_ids:
|
for player in world.alttp_player_ids:
|
||||||
if world.open_pyramid[player] == 'goal':
|
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':
|
elif world.open_pyramid[player] == 'auto':
|
||||||
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \
|
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt',
|
||||||
(world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'} or not world.shuffle_ganon)
|
'localganontriforcehunt', 'ganonpedestal'} and \
|
||||||
|
(world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull',
|
||||||
|
'dungeonscrossed'} or not world.shuffle_ganon)
|
||||||
else:
|
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], 'auto')
|
||||||
|
|
||||||
|
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player],
|
||||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player])
|
world.triforce_pieces_required[player])
|
||||||
|
|
||||||
if world.mode[player] != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
create_regions(world, player)
|
create_regions(world, player)
|
||||||
|
@ -261,14 +258,15 @@ def main(args, seed=None):
|
||||||
for player in world.player_ids:
|
for player in world.player_ids:
|
||||||
locality_rules(world, player)
|
locality_rules(world, player)
|
||||||
|
|
||||||
|
AutoWorld.call_all(world, "set_rules")
|
||||||
|
|
||||||
for player in world.alttp_player_ids:
|
for player in world.alttp_player_ids:
|
||||||
set_rules(world, player)
|
set_rules(world, player)
|
||||||
|
|
||||||
for player in world.hk_player_ids:
|
for player in world.hk_player_ids:
|
||||||
gen_hollow(world, player)
|
gen_hollow(world, player)
|
||||||
|
|
||||||
for player in world.factorio_player_ids:
|
AutoWorld.call_all(world, "generate_basic")
|
||||||
gen_factorio(world, player)
|
|
||||||
|
|
||||||
for player in world.minecraft_player_ids:
|
for player in world.minecraft_player_ids:
|
||||||
gen_minecraft(world, player)
|
gen_minecraft(world, player)
|
||||||
|
@ -333,13 +331,13 @@ def main(args, seed=None):
|
||||||
|
|
||||||
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
||||||
|
|
||||||
palettes_options={}
|
palettes_options = {}
|
||||||
palettes_options['dungeon']=args.uw_palettes[player]
|
palettes_options['dungeon'] = args.uw_palettes[player]
|
||||||
palettes_options['overworld']=args.ow_palettes[player]
|
palettes_options['overworld'] = args.ow_palettes[player]
|
||||||
palettes_options['hud']=args.hud_palettes[player]
|
palettes_options['hud'] = args.hud_palettes[player]
|
||||||
palettes_options['sword']=args.sword_palettes[player]
|
palettes_options['sword'] = args.sword_palettes[player]
|
||||||
palettes_options['shield']=args.shield_palettes[player]
|
palettes_options['shield'] = args.shield_palettes[player]
|
||||||
palettes_options['link']=args.link_palettes[player]
|
palettes_options['link'] = args.link_palettes[player]
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
||||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
||||||
|
@ -355,8 +353,8 @@ def main(args, seed=None):
|
||||||
world.bigkeyshuffle[player]].count(True) == 1:
|
world.bigkeyshuffle[player]].count(True) == 1:
|
||||||
mcsb_name = '-mapshuffle' if world.mapshuffle[player] else \
|
mcsb_name = '-mapshuffle' if world.mapshuffle[player] else \
|
||||||
'-compassshuffle' if world.compassshuffle[player] else \
|
'-compassshuffle' if world.compassshuffle[player] else \
|
||||||
'-universal_keys' if world.keyshuffle[player] == "universal" else \
|
'-universal_keys' if world.keyshuffle[player] == "universal" else \
|
||||||
'-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle'
|
'-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle'
|
||||||
elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
||||||
world.bigkeyshuffle[player]]):
|
world.bigkeyshuffle[player]]):
|
||||||
mcsb_name = '-%s%s%s%sshuffle' % (
|
mcsb_name = '-%s%s%s%sshuffle' % (
|
||||||
|
@ -368,46 +366,46 @@ def main(args, seed=None):
|
||||||
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \
|
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \
|
||||||
if world.player_names[player][team] != 'Player%d' % player else ''
|
if world.player_names[player][team] != 'Player%d' % player else ''
|
||||||
outfilestuffs = {
|
outfilestuffs = {
|
||||||
"logic": world.logic[player], # 0
|
"logic": world.logic[player], # 0
|
||||||
"difficulty": world.difficulty[player], # 1
|
"difficulty": world.difficulty[player], # 1
|
||||||
"item_functionality": world.item_functionality[player], # 2
|
"item_functionality": world.item_functionality[player], # 2
|
||||||
"mode": world.mode[player], # 3
|
"mode": world.mode[player], # 3
|
||||||
"goal": world.goal[player], # 4
|
"goal": world.goal[player], # 4
|
||||||
"timer": str(world.timer[player]), # 5
|
"timer": str(world.timer[player]), # 5
|
||||||
"shuffle": world.shuffle[player], # 6
|
"shuffle": world.shuffle[player], # 6
|
||||||
"algorithm": world.algorithm, # 7
|
"algorithm": world.algorithm, # 7
|
||||||
"mscb": mcsb_name, # 8
|
"mscb": mcsb_name, # 8
|
||||||
"retro": world.retro[player], # 9
|
"retro": world.retro[player], # 9
|
||||||
"progressive": world.progressive, # A
|
"progressive": world.progressive, # A
|
||||||
"hints": 'True' if world.hints[player] else 'False' # B
|
"hints": 'True' if world.hints[player] else 'False' # B
|
||||||
}
|
}
|
||||||
# 0 1 2 3 4 5 6 7 8 9 A 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' % (
|
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
|
# 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-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-retro
|
||||||
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -prog_random
|
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -prog_random
|
||||||
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -nohints
|
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -nohints
|
||||||
outfilestuffs["logic"], # 0
|
outfilestuffs["logic"], # 0
|
||||||
|
|
||||||
outfilestuffs["difficulty"], # 1
|
outfilestuffs["difficulty"], # 1
|
||||||
outfilestuffs["item_functionality"], # 2
|
outfilestuffs["item_functionality"], # 2
|
||||||
outfilestuffs["mode"], # 3
|
outfilestuffs["mode"], # 3
|
||||||
outfilestuffs["goal"], # 4
|
outfilestuffs["goal"], # 4
|
||||||
"" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5
|
"" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5
|
||||||
|
|
||||||
outfilestuffs["shuffle"], # 6
|
outfilestuffs["shuffle"], # 6
|
||||||
outfilestuffs["algorithm"], # 7
|
outfilestuffs["algorithm"], # 7
|
||||||
outfilestuffs["mscb"], # 8
|
outfilestuffs["mscb"], # 8
|
||||||
|
|
||||||
"-retro" if outfilestuffs["retro"] == "True" else "", # 9
|
"-retro" if outfilestuffs["retro"] == "True" else "", # 9
|
||||||
"-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # A
|
"-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # A
|
||||||
"-nohints" if not outfilestuffs["hints"] == "True" else "") # B
|
"-nohints" if not outfilestuffs["hints"] == "True" else "") # B
|
||||||
) if not args.outputname else ''
|
) if not args.outputname else ''
|
||||||
rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
|
rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
|
||||||
rom.write_to_file(rompath, hide_enemizer=True)
|
rom.write_to_file(rompath, hide_enemizer=True)
|
||||||
if args.create_diff:
|
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)
|
return player, team, bytes(rom.name)
|
||||||
|
|
||||||
pool = concurrent.futures.ThreadPoolExecutor()
|
pool = concurrent.futures.ThreadPoolExecutor()
|
||||||
|
@ -415,12 +413,12 @@ def main(args, seed=None):
|
||||||
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
||||||
|
|
||||||
rom_futures = []
|
rom_futures = []
|
||||||
mod_futures = []
|
output_file_futures = []
|
||||||
for team in range(world.teams):
|
for team in range(world.teams):
|
||||||
for player in world.alttp_player_ids:
|
for player in world.alttp_player_ids:
|
||||||
rom_futures.append(pool.submit(_gen_rom, team, player))
|
rom_futures.append(pool.submit(_gen_rom, team, player))
|
||||||
for player in world.factorio_player_ids:
|
for player in world.player_ids:
|
||||||
mod_futures.append(pool.submit(generate_mod, world, player))
|
output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player))
|
||||||
|
|
||||||
def get_entrance_to_region(region: Region):
|
def get_entrance_to_region(region: Region):
|
||||||
for entrance in region.entrances:
|
for entrance in region.entrances:
|
||||||
|
@ -430,7 +428,8 @@ def main(args, seed=None):
|
||||||
return get_entrance_to_region(entrance.parent_region)
|
return get_entrance_to_region(entrance.parent_region)
|
||||||
|
|
||||||
# collect ER hint info
|
# 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
|
from worlds.alttp.Regions import RegionType
|
||||||
for region in world.regions:
|
for region in world.regions:
|
||||||
if region.player in er_hint_data and region.locations:
|
if region.player in er_hint_data and region.locations:
|
||||||
|
@ -456,7 +455,7 @@ def main(args, seed=None):
|
||||||
checks_in_area[location.player]["Light World"].append(location.address)
|
checks_in_area[location.player]["Light World"].append(location.address)
|
||||||
elif location.parent_region.dungeon:
|
elif location.parent_region.dungeon:
|
||||||
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
|
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)
|
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
|
||||||
checks_in_area[location.player][dungeonname].append(location.address)
|
checks_in_area[location.player][dungeonname].append(location.address)
|
||||||
elif main_entrance.parent_region.type == RegionType.LightWorld:
|
elif main_entrance.parent_region.type == RegionType.LightWorld:
|
||||||
|
@ -468,8 +467,10 @@ def main(args, seed=None):
|
||||||
oldmancaves = []
|
oldmancaves = []
|
||||||
takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"]
|
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 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]]:
|
for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if
|
||||||
item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], region.player)
|
world.retro[player]]:
|
||||||
|
item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
|
||||||
|
region.player)
|
||||||
player = region.player
|
player = region.player
|
||||||
location_id = SHOP_ID_START + total_shop_slots + index
|
location_id = SHOP_ID_START + total_shop_slots + index
|
||||||
|
|
||||||
|
@ -483,11 +484,9 @@ def main(args, seed=None):
|
||||||
er_hint_data[player][location_id] = main_entrance.name
|
er_hint_data[player][location_id] = main_entrance.name
|
||||||
oldmancaves.append(((location_id, player), (item.code, player)))
|
oldmancaves.append(((location_id, player), (item.code, player)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FillDisabledShopSlots(world)
|
FillDisabledShopSlots(world)
|
||||||
|
|
||||||
def write_multidata(roms, mods):
|
def write_multidata(roms, outputs):
|
||||||
import base64
|
import base64
|
||||||
import NetUtils
|
import NetUtils
|
||||||
for future in roms:
|
for future in roms:
|
||||||
|
@ -504,11 +503,11 @@ def main(args, seed=None):
|
||||||
client_versions[slot] = (0, 0, 3)
|
client_versions[slot] = (0, 0, 3)
|
||||||
games[slot] = world.game[slot]
|
games[slot] = world.game[slot]
|
||||||
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
|
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
|
||||||
slot, team, rom_name in rom_names}
|
slot, team, rom_name in rom_names}
|
||||||
precollected_items = {player: [] for player in range(1, world.players+1)}
|
precollected_items = {player: [] for player in range(1, world.players + 1)}
|
||||||
for item in world.precollected_items:
|
for item in world.precollected_items:
|
||||||
precollected_items[item.player].append(item.code)
|
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
|
# for now special case Factorio visibility
|
||||||
sending_visible_players = set()
|
sending_visible_players = set()
|
||||||
for player in world.factorio_player_ids:
|
for player in world.factorio_player_ids:
|
||||||
|
@ -519,11 +518,13 @@ def main(args, seed=None):
|
||||||
for player, name in enumerate(team, 1):
|
for player, name in enumerate(team, 1):
|
||||||
if player not in world.alttp_player_ids:
|
if player not in world.alttp_player_ids:
|
||||||
connect_names[name] = (i, player)
|
connect_names[name] = (i, player)
|
||||||
for slot in world.hk_player_ids:
|
if world.hk_player_ids:
|
||||||
slots_data = slot_data[slot] = {}
|
import Options
|
||||||
for option_name in Options.hollow_knight_options:
|
for slot in world.hk_player_ids:
|
||||||
option = getattr(world, option_name)[slot]
|
slots_data = slot_data[slot] = {}
|
||||||
slots_data[option_name] = int(option.value)
|
for option_name in Options.hollow_knight_options:
|
||||||
|
option = getattr(world, option_name)[slot]
|
||||||
|
slots_data[option_name] = int(option.value)
|
||||||
for slot in world.minecraft_player_ids:
|
for slot in world.minecraft_player_ids:
|
||||||
slot_data[slot] = fill_minecraft_slot_data(world, slot)
|
slot_data[slot] = fill_minecraft_slot_data(world, slot)
|
||||||
|
|
||||||
|
@ -544,7 +545,7 @@ def main(args, seed=None):
|
||||||
precollected_hints[location.item.player].add(hint)
|
precollected_hints[location.item.player].add(hint)
|
||||||
|
|
||||||
multidata = zlib.compress(pickle.dumps({
|
multidata = zlib.compress(pickle.dumps({
|
||||||
"slot_data" : slot_data,
|
"slot_data": slot_data,
|
||||||
"games": games,
|
"games": games,
|
||||||
"names": parsed_names,
|
"names": parsed_names,
|
||||||
"connect_names": connect_names,
|
"connect_names": connect_names,
|
||||||
|
@ -565,10 +566,10 @@ def main(args, seed=None):
|
||||||
with open(output_path('%s.archipelago' % outfilebase), 'wb') as f:
|
with open(output_path('%s.archipelago' % outfilebase), 'wb') as f:
|
||||||
f.write(bytes([1])) # version of format
|
f.write(bytes([1])) # version of format
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
for future in mods:
|
for future in outputs:
|
||||||
future.result() # collect errors if they occured
|
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 check_accessibility_task.result():
|
||||||
if not world.can_beat_game():
|
if not world.can_beat_game():
|
||||||
raise Exception("Game appears as unbeatable. Aborting.")
|
raise Exception("Game appears as unbeatable. Aborting.")
|
||||||
|
@ -577,7 +578,7 @@ def main(args, seed=None):
|
||||||
if multidata_task:
|
if multidata_task:
|
||||||
multidata_task.result() # retrieve exception if one exists
|
multidata_task.result() # retrieve exception if one exists
|
||||||
pool.shutdown() # wait for all queued tasks to complete
|
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)
|
generate_mc_data(world, player)
|
||||||
if not args.skip_playthrough:
|
if not args.skip_playthrough:
|
||||||
logger.info('Calculating playthrough.')
|
logger.info('Calculating playthrough.')
|
||||||
|
@ -632,7 +633,8 @@ def create_playthrough(world):
|
||||||
to_delete = set()
|
to_delete = set()
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
# we remove the item at location and check if game is still beatable
|
# 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
|
old_item = location.item
|
||||||
location.item = None
|
location.item = None
|
||||||
if world.can_beat_game(state_cache[num]):
|
if world.can_beat_game(state_cache[num]):
|
||||||
|
@ -677,7 +679,8 @@ def create_playthrough(world):
|
||||||
|
|
||||||
collection_spheres.append(sphere)
|
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:
|
if not sphere:
|
||||||
raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}')
|
raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}')
|
||||||
|
|
||||||
|
@ -696,14 +699,22 @@ def create_playthrough(world):
|
||||||
|
|
||||||
world.spoiler.paths = dict()
|
world.spoiler.paths = dict()
|
||||||
for player in range(1, world.players + 1):
|
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:
|
if player in world.alttp_player_ids:
|
||||||
for path in dict(world.spoiler.paths).values():
|
for path in dict(world.spoiler.paths).values():
|
||||||
if any(exit == 'Pyramid Fairy' for (_, exit) in path):
|
if any(exit == 'Pyramid Fairy' for (_, exit) in path):
|
||||||
if world.mode[player] != 'inverted':
|
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:
|
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
|
# we can finally output our playthrough
|
||||||
world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])}
|
world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])}
|
||||||
|
|
|
@ -75,7 +75,7 @@ if __name__ == "__main__":
|
||||||
if os.path.exists("ArchipelagoMystery.exe"):
|
if os.path.exists("ArchipelagoMystery.exe"):
|
||||||
basemysterycommand = "ArchipelagoMystery.exe" # compiled windows
|
basemysterycommand = "ArchipelagoMystery.exe" # compiled windows
|
||||||
elif os.path.exists("ArchipelagoMystery"):
|
elif os.path.exists("ArchipelagoMystery"):
|
||||||
basemysterycommand = "ArchipelagoMystery" # compiled linux
|
basemysterycommand = "./ArchipelagoMystery" # compiled linux
|
||||||
else:
|
else:
|
||||||
basemysterycommand = f"py -{py_version} Mystery.py" # source
|
basemysterycommand = f"py -{py_version} Mystery.py" # source
|
||||||
|
|
||||||
|
@ -207,14 +207,13 @@ if __name__ == "__main__":
|
||||||
if not args.disable_autohost:
|
if not args.disable_autohost:
|
||||||
if os.path.exists(os.path.join(output_path, multidataname)):
|
if os.path.exists(os.path.join(output_path, multidataname)):
|
||||||
if os.path.exists("ArchipelagoServer.exe"):
|
if os.path.exists("ArchipelagoServer.exe"):
|
||||||
baseservercommand = "ArchipelagoServer.exe" # compiled windows
|
baseservercommand = ["ArchipelagoServer.exe"] # compiled windows
|
||||||
elif os.path.exists("ArchipelagoServer"):
|
elif os.path.exists("ArchipelagoServer"):
|
||||||
baseservercommand = "ArchipelagoServer" # compiled linux
|
baseservercommand = ["./ArchipelagoServer"] # compiled linux
|
||||||
else:
|
else:
|
||||||
baseservercommand = f"py -{py_version} MultiServer.py" # source
|
baseservercommand = ["py", f"-{py_version}", "MultiServer.py"] # source
|
||||||
# don't have a mac to test that. If you try to run compiled on mac, good luck.
|
# don't have a mac to test that. If you try to run compiled on mac, good luck.
|
||||||
|
subprocess.call(baseservercommand + ["--multidata", os.path.join(output_path, multidataname)])
|
||||||
subprocess.call(f"{baseservercommand} --multidata {os.path.join(output_path, multidataname)}")
|
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
195
Mystery.py
195
Mystery.py
|
@ -23,7 +23,9 @@ from worlds.alttp.Items import item_name_groups, item_table
|
||||||
from worlds.alttp import Bosses
|
from worlds.alttp import Bosses
|
||||||
from worlds.alttp.Text import TextTable
|
from worlds.alttp.Text import TextTable
|
||||||
from worlds.alttp.Regions import location_table, key_drop_data
|
from worlds.alttp.Regions import location_table, key_drop_data
|
||||||
|
from worlds.AutoWorld import AutoWorldRegister
|
||||||
|
|
||||||
|
categories = set(AutoWorldRegister.world_types)
|
||||||
|
|
||||||
def mystery_argparse():
|
def mystery_argparse():
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
@ -61,9 +63,11 @@ def mystery_argparse():
|
||||||
args.plando: typing.Set[str] = {arg.strip().lower() for arg in args.plando.split(",")}
|
args.plando: typing.Set[str] = {arg.strip().lower() for arg in args.plando.split(",")}
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def get_seed_name(random):
|
def get_seed_name(random):
|
||||||
return f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)
|
return f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)
|
||||||
|
|
||||||
|
|
||||||
def main(args=None, callback=ERmain):
|
def main(args=None, callback=ERmain):
|
||||||
if not args:
|
if not args:
|
||||||
args = mystery_argparse()
|
args = mystery_argparse()
|
||||||
|
@ -79,14 +83,14 @@ def main(args=None, callback=ERmain):
|
||||||
weights_cache = {}
|
weights_cache = {}
|
||||||
if args.weights:
|
if args.weights:
|
||||||
try:
|
try:
|
||||||
weights_cache[args.weights] = get_weights(args.weights)
|
weights_cache[args.weights] = read_weights_yaml(args.weights)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"File {args.weights} is destroyed. Please fix your yaml.") from e
|
raise ValueError(f"File {args.weights} is destroyed. Please fix your yaml.") from e
|
||||||
print(f"Weights: {args.weights} >> "
|
print(f"Weights: {args.weights} >> "
|
||||||
f"{get_choice('description', weights_cache[args.weights], 'No description specified')}")
|
f"{get_choice('description', weights_cache[args.weights], 'No description specified')}")
|
||||||
if args.meta:
|
if args.meta:
|
||||||
try:
|
try:
|
||||||
weights_cache[args.meta] = get_weights(args.meta)
|
weights_cache[args.meta] = read_weights_yaml(args.meta)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"File {args.meta} is destroyed. Please fix your yaml.") from e
|
raise ValueError(f"File {args.meta} is destroyed. Please fix your yaml.") from e
|
||||||
meta_weights = weights_cache[args.meta]
|
meta_weights = weights_cache[args.meta]
|
||||||
|
@ -99,7 +103,7 @@ def main(args=None, callback=ERmain):
|
||||||
if path:
|
if path:
|
||||||
try:
|
try:
|
||||||
if path not in weights_cache:
|
if path not in weights_cache:
|
||||||
weights_cache[path] = get_weights(path)
|
weights_cache[path] = read_weights_yaml(path)
|
||||||
print(f"P{player} Weights: {path} >> "
|
print(f"P{player} Weights: {path} >> "
|
||||||
f"{get_choice('description', weights_cache[path], 'No description specified')}")
|
f"{get_choice('description', weights_cache[path], 'No description specified')}")
|
||||||
|
|
||||||
|
@ -254,7 +258,7 @@ def main(args=None, callback=ERmain):
|
||||||
callback(erargs, seed)
|
callback(erargs, seed)
|
||||||
|
|
||||||
|
|
||||||
def get_weights(path):
|
def read_weights_yaml(path):
|
||||||
try:
|
try:
|
||||||
if urllib.parse.urlparse(path).scheme:
|
if urllib.parse.urlparse(path).scheme:
|
||||||
yaml = str(urllib.request.urlopen(path).read(), "utf-8")
|
yaml = str(urllib.request.urlopen(path).read(), "utf-8")
|
||||||
|
@ -342,19 +346,6 @@ goals = {
|
||||||
'ice_rod_hunt': 'icerodhunt',
|
'ice_rod_hunt': 'icerodhunt',
|
||||||
}
|
}
|
||||||
|
|
||||||
# remove sometime before 1.0.0, warn before
|
|
||||||
legacy_boss_shuffle_options = {
|
|
||||||
# legacy, will go away:
|
|
||||||
'simple': 'basic',
|
|
||||||
'random': 'full',
|
|
||||||
'normal': 'full'
|
|
||||||
}
|
|
||||||
|
|
||||||
legacy_goals = {
|
|
||||||
'dungeons': 'bosses',
|
|
||||||
'fast_ganon': 'crystals',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def roll_percentage(percentage: typing.Union[int, float]) -> bool:
|
def roll_percentage(percentage: typing.Union[int, float]) -> bool:
|
||||||
"""Roll a percentage chance.
|
"""Roll a percentage chance.
|
||||||
|
@ -382,13 +373,12 @@ def roll_linked_options(weights: dict) -> dict:
|
||||||
try:
|
try:
|
||||||
if roll_percentage(option_set["percentage"]):
|
if roll_percentage(option_set["percentage"]):
|
||||||
logging.debug(f"Linked option {option_set['name']} triggered.")
|
logging.debug(f"Linked option {option_set['name']} triggered.")
|
||||||
if "options" in option_set:
|
new_options = option_set["options"]
|
||||||
weights = update_weights(weights, option_set["options"], "Linked", option_set["name"])
|
for category_name, category_options in new_options.items():
|
||||||
if "rom_options" in option_set:
|
currently_targeted_weights = weights
|
||||||
rom_weights = weights.get("rom", dict())
|
if category_name:
|
||||||
rom_weights = update_weights(rom_weights, option_set["rom_options"], "Linked Rom",
|
currently_targeted_weights = currently_targeted_weights[category_name]
|
||||||
option_set["name"])
|
update_weights(currently_targeted_weights, category_options, "Linked", option_set["name"])
|
||||||
weights["rom"] = rom_weights
|
|
||||||
else:
|
else:
|
||||||
logging.debug(f"linked option {option_set['name']} skipped.")
|
logging.debug(f"linked option {option_set['name']} skipped.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -402,35 +392,32 @@ def roll_triggers(weights: dict) -> dict:
|
||||||
weights["_Generator_Version"] = "Archipelago" # Some means for triggers to know if the seed is on main or doors.
|
weights["_Generator_Version"] = "Archipelago" # Some means for triggers to know if the seed is on main or doors.
|
||||||
for i, option_set in enumerate(weights["triggers"]):
|
for i, option_set in enumerate(weights["triggers"]):
|
||||||
try:
|
try:
|
||||||
|
currently_targeted_weights = weights
|
||||||
|
category = option_set.get("option_category", None)
|
||||||
|
if category:
|
||||||
|
currently_targeted_weights = currently_targeted_weights[category]
|
||||||
key = get_choice("option_name", option_set)
|
key = get_choice("option_name", option_set)
|
||||||
if key not in weights:
|
if key not in currently_targeted_weights:
|
||||||
logging.warning(f'Specified option name {option_set["option_name"]} did not '
|
logging.warning(f'Specified option name {option_set["option_name"]} did not '
|
||||||
f'match with a root option. '
|
f'match with a root option. '
|
||||||
f'This is probably in error.')
|
f'This is probably in error.')
|
||||||
trigger_result = get_choice("option_result", option_set)
|
trigger_result = get_choice("option_result", option_set)
|
||||||
result = get_choice(key, weights)
|
result = get_choice(key, currently_targeted_weights)
|
||||||
|
currently_targeted_weights[key] = result
|
||||||
if result == trigger_result and roll_percentage(get_choice("percentage", option_set, 100)):
|
if result == trigger_result and roll_percentage(get_choice("percentage", option_set, 100)):
|
||||||
if "options" in option_set:
|
for category_name, category_options in option_set["options"].items():
|
||||||
weights = update_weights(weights, option_set["options"], "Triggered", option_set["option_name"])
|
currently_targeted_weights = weights
|
||||||
|
if category_name:
|
||||||
|
currently_targeted_weights = currently_targeted_weights[category_name]
|
||||||
|
update_weights(currently_targeted_weights, category_options, "Triggered", option_set["option_name"])
|
||||||
|
|
||||||
if "rom_options" in option_set:
|
|
||||||
rom_weights = weights.get("rom", dict())
|
|
||||||
rom_weights = update_weights(rom_weights, option_set["rom_options"], "Triggered Rom",
|
|
||||||
option_set["option_name"])
|
|
||||||
weights["rom"] = rom_weights
|
|
||||||
weights[key] = result
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Your trigger number {i+1} is destroyed. "
|
raise ValueError(f"Your trigger number {i + 1} is destroyed. "
|
||||||
f"Please fix your triggers.") from e
|
f"Please fix your triggers.") from e
|
||||||
return weights
|
return weights
|
||||||
|
|
||||||
|
|
||||||
def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str:
|
def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str:
|
||||||
if boss_shuffle in legacy_boss_shuffle_options:
|
|
||||||
new_boss_shuffle = legacy_boss_shuffle_options[boss_shuffle]
|
|
||||||
logging.warning(f"Boss shuffle {boss_shuffle} is deprecated, "
|
|
||||||
f"please use {new_boss_shuffle} instead")
|
|
||||||
return new_boss_shuffle
|
|
||||||
if boss_shuffle in boss_shuffle_options:
|
if boss_shuffle in boss_shuffle_options:
|
||||||
return boss_shuffle_options[boss_shuffle]
|
return boss_shuffle_options[boss_shuffle]
|
||||||
elif "bosses" in plando_options:
|
elif "bosses" in plando_options:
|
||||||
|
@ -438,10 +425,6 @@ def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str
|
||||||
remainder_shuffle = "none" # vanilla
|
remainder_shuffle = "none" # vanilla
|
||||||
bosses = []
|
bosses = []
|
||||||
for boss in options:
|
for boss in options:
|
||||||
if boss in legacy_boss_shuffle_options:
|
|
||||||
remainder_shuffle = legacy_boss_shuffle_options[boss_shuffle]
|
|
||||||
logging.warning(f"Boss shuffle {boss} is deprecated, "
|
|
||||||
f"please use {remainder_shuffle} instead")
|
|
||||||
if boss in boss_shuffle_options:
|
if boss in boss_shuffle_options:
|
||||||
remainder_shuffle = boss_shuffle_options[boss]
|
remainder_shuffle = boss_shuffle_options[boss]
|
||||||
elif "-" in boss:
|
elif "-" in boss:
|
||||||
|
@ -511,10 +494,12 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
ret.name = get_choice('name', weights)
|
ret.name = get_choice('name', weights)
|
||||||
ret.accessibility = get_choice('accessibility', weights)
|
ret.accessibility = get_choice('accessibility', weights)
|
||||||
ret.progression_balancing = get_choice('progression_balancing', weights, True)
|
ret.progression_balancing = get_choice('progression_balancing', weights, True)
|
||||||
ret.game = get_choice("game", weights, "A Link to the Past")
|
ret.game = get_choice("game", weights)
|
||||||
|
if ret.game not in weights:
|
||||||
|
raise Exception(f"No game options for selected game \"{ret.game}\" found.")
|
||||||
|
game_weights = weights[ret.game]
|
||||||
ret.local_items = set()
|
ret.local_items = set()
|
||||||
for item_name in weights.get('local_items', []):
|
for item_name in game_weights.get('local_items', []):
|
||||||
items = item_name_groups.get(item_name, {item_name})
|
items = item_name_groups.get(item_name, {item_name})
|
||||||
for item in items:
|
for item in items:
|
||||||
if item in lookup_any_item_name_to_id:
|
if item in lookup_any_item_name_to_id:
|
||||||
|
@ -523,7 +508,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
raise Exception(f"Could not force item {item} to be world-local, as it was not recognized.")
|
raise Exception(f"Could not force item {item} to be world-local, as it was not recognized.")
|
||||||
|
|
||||||
ret.non_local_items = set()
|
ret.non_local_items = set()
|
||||||
for item_name in weights.get('non_local_items', []):
|
for item_name in game_weights.get('non_local_items', []):
|
||||||
items = item_name_groups.get(item_name, {item_name})
|
items = item_name_groups.get(item_name, {item_name})
|
||||||
for item in items:
|
for item in items:
|
||||||
if item in lookup_any_item_name_to_id:
|
if item in lookup_any_item_name_to_id:
|
||||||
|
@ -531,7 +516,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.")
|
raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.")
|
||||||
|
|
||||||
inventoryweights = weights.get('startinventory', {})
|
inventoryweights = game_weights.get('start_inventory', {})
|
||||||
startitems = []
|
startitems = []
|
||||||
for item in inventoryweights.keys():
|
for item in inventoryweights.keys():
|
||||||
itemvalue = get_choice(item, inventoryweights)
|
itemvalue = get_choice(item, inventoryweights)
|
||||||
|
@ -541,33 +526,32 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
elif itemvalue:
|
elif itemvalue:
|
||||||
startitems.append(item)
|
startitems.append(item)
|
||||||
ret.startinventory = startitems
|
ret.startinventory = startitems
|
||||||
ret.start_hints = set(weights.get('start_hints', []))
|
ret.start_hints = set(game_weights.get('start_hints', []))
|
||||||
|
|
||||||
|
|
||||||
if ret.game == "A Link to the Past":
|
if ret.game == "A Link to the Past":
|
||||||
roll_alttp_settings(ret, weights, plando_options)
|
roll_alttp_settings(ret, game_weights, plando_options)
|
||||||
elif ret.game == "Hollow Knight":
|
elif ret.game == "Hollow Knight":
|
||||||
for option_name, option in Options.hollow_knight_options.items():
|
for option_name, option in Options.hollow_knight_options.items():
|
||||||
setattr(ret, option_name, option.from_any(get_choice(option_name, weights, True)))
|
setattr(ret, option_name, option.from_any(get_choice(option_name, game_weights, True)))
|
||||||
elif ret.game == "Factorio":
|
elif ret.game == "Factorio":
|
||||||
for option_name, option in Options.factorio_options.items():
|
for option_name, option in Options.factorio_options.items():
|
||||||
if option_name in weights:
|
if option_name in game_weights:
|
||||||
if issubclass(option, Options.OptionDict): # get_choice should probably land in the Option class
|
if issubclass(option, Options.OptionDict): # get_choice should probably land in the Option class
|
||||||
setattr(ret, option_name, option.from_any(weights[option_name]))
|
setattr(ret, option_name, option.from_any(game_weights[option_name]))
|
||||||
else:
|
else:
|
||||||
setattr(ret, option_name, option.from_any(get_choice(option_name, weights)))
|
setattr(ret, option_name, option.from_any(get_choice(option_name, game_weights)))
|
||||||
else:
|
else:
|
||||||
setattr(ret, option_name, option(option.default))
|
setattr(ret, option_name, option(option.default))
|
||||||
elif ret.game == "Minecraft":
|
elif ret.game == "Minecraft":
|
||||||
for option_name, option in Options.minecraft_options.items():
|
for option_name, option in Options.minecraft_options.items():
|
||||||
if option_name in weights:
|
if option_name in game_weights:
|
||||||
setattr(ret, option_name, option.from_any(get_choice(option_name, weights)))
|
setattr(ret, option_name, option.from_any(get_choice(option_name, game_weights)))
|
||||||
else:
|
else:
|
||||||
setattr(ret, option_name, option(option.default))
|
setattr(ret, option_name, option(option.default))
|
||||||
# bad hardcoded behavior to make this work for now
|
# bad hardcoded behavior to make this work for now
|
||||||
ret.plando_connections = []
|
ret.plando_connections = []
|
||||||
if "connections" in plando_options:
|
if "connections" in plando_options:
|
||||||
options = weights.get("plando_connections", [])
|
options = game_weights.get("plando_connections", [])
|
||||||
for placement in options:
|
for placement in options:
|
||||||
if roll_percentage(get_choice("percentage", placement, 100)):
|
if roll_percentage(get_choice("percentage", placement, 100)):
|
||||||
ret.plando_connections.append(PlandoConnection(
|
ret.plando_connections.append(PlandoConnection(
|
||||||
|
@ -581,6 +565,12 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
|
|
||||||
|
|
||||||
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
|
for option_name, option in Options.alttp_options.items():
|
||||||
|
if option_name in weights:
|
||||||
|
setattr(ret, option_name, option.from_any(get_choice(option_name, weights)))
|
||||||
|
else:
|
||||||
|
setattr(ret, option_name, option(option.default))
|
||||||
|
|
||||||
glitches_required = get_choice('glitches_required', weights)
|
glitches_required = get_choice('glitches_required', weights)
|
||||||
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
||||||
logging.warning("Only NMG, OWG and No Logic supported")
|
logging.warning("Only NMG, OWG and No Logic supported")
|
||||||
|
@ -623,21 +613,15 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
|
|
||||||
goal = get_choice('goals', weights, 'ganon')
|
goal = get_choice('goals', weights, 'ganon')
|
||||||
|
|
||||||
if goal in legacy_goals:
|
|
||||||
logging.warning(f"Goal {goal} is depcrecated, please use {legacy_goals[goal]} instead.")
|
|
||||||
goal = legacy_goals[goal]
|
|
||||||
ret.goal = goals[goal]
|
ret.goal = goals[goal]
|
||||||
|
|
||||||
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
||||||
# fast ganon + ganon at hole
|
# fast ganon + ganon at hole
|
||||||
ret.open_pyramid = get_choice('open_pyramid', weights, 'goal')
|
ret.open_pyramid = get_choice('open_pyramid', weights, 'goal')
|
||||||
|
|
||||||
ret.crystals_gt = Options.Crystals.from_any(get_choice('tower_open', weights)).value
|
|
||||||
ret.crystals_ganon = Options.Crystals.from_any(get_choice('ganon_open', weights)).value
|
|
||||||
|
|
||||||
extra_pieces = get_choice('triforce_pieces_mode', weights, 'available')
|
extra_pieces = get_choice('triforce_pieces_mode', weights, 'available')
|
||||||
|
|
||||||
ret.triforce_pieces_required = Options.TriforcePieces.from_any(get_choice('triforce_pieces_required', weights)).value
|
ret.triforce_pieces_required = Options.TriforcePieces.from_any(get_choice('triforce_pieces_required', weights, 20))
|
||||||
|
|
||||||
# sum a percentage to required
|
# sum a percentage to required
|
||||||
if extra_pieces == 'percentage':
|
if extra_pieces == 'percentage':
|
||||||
|
@ -645,7 +629,8 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
ret.triforce_pieces_available = int(round(ret.triforce_pieces_required * percentage, 0))
|
ret.triforce_pieces_available = int(round(ret.triforce_pieces_required * percentage, 0))
|
||||||
# vanilla mode (specify how many pieces are)
|
# vanilla mode (specify how many pieces are)
|
||||||
elif extra_pieces == 'available':
|
elif extra_pieces == 'available':
|
||||||
ret.triforce_pieces_available = Options.TriforcePieces.from_any(get_choice('triforce_pieces_available', weights)).value
|
ret.triforce_pieces_available = Options.TriforcePieces.from_any(
|
||||||
|
get_choice('triforce_pieces_available', weights, 30))
|
||||||
# required pieces + fixed extra
|
# required pieces + fixed extra
|
||||||
elif extra_pieces == 'extra':
|
elif extra_pieces == 'extra':
|
||||||
extra_pieces = max(0, int(get_choice('triforce_pieces_extra', weights, 10)))
|
extra_pieces = max(0, int(get_choice('triforce_pieces_extra', weights, 10)))
|
||||||
|
@ -653,7 +638,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
|
|
||||||
# change minimum to required pieces to avoid problems
|
# change minimum to required pieces to avoid problems
|
||||||
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
||||||
ret.shop_shuffle_slots = Options.TriforcePieces.from_any(get_choice('shop_shuffle_slots', weights)).value
|
|
||||||
|
|
||||||
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
||||||
if not ret.shop_shuffle:
|
if not ret.shop_shuffle:
|
||||||
|
@ -675,7 +659,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
|
|
||||||
ret.enemy_shuffle = bool(get_choice('enemy_shuffle', weights, False))
|
ret.enemy_shuffle = bool(get_choice('enemy_shuffle', weights, False))
|
||||||
|
|
||||||
|
|
||||||
ret.killable_thieves = get_choice('killable_thieves', weights, False)
|
ret.killable_thieves = get_choice('killable_thieves', weights, False)
|
||||||
ret.tile_shuffle = get_choice('tile_shuffle', weights, False)
|
ret.tile_shuffle = get_choice('tile_shuffle', weights, False)
|
||||||
ret.bush_shuffle = get_choice('bush_shuffle', weights, False)
|
ret.bush_shuffle = get_choice('bush_shuffle', weights, False)
|
||||||
|
@ -787,49 +770,43 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
get_choice("direction", placement, "both")
|
get_choice("direction", placement, "both")
|
||||||
))
|
))
|
||||||
|
|
||||||
if 'rom' in weights:
|
|
||||||
romweights = weights['rom']
|
|
||||||
|
|
||||||
ret.sprite_pool = romweights['sprite_pool'] if 'sprite_pool' in romweights else []
|
ret.sprite_pool = weights.get('sprite_pool', [])
|
||||||
ret.sprite = get_choice('sprite', romweights, "Link")
|
ret.sprite = get_choice('sprite', weights, "Link")
|
||||||
if 'random_sprite_on_event' in romweights:
|
if 'random_sprite_on_event' in weights:
|
||||||
randomoneventweights = romweights['random_sprite_on_event']
|
randomoneventweights = weights['random_sprite_on_event']
|
||||||
if get_choice('enabled', randomoneventweights, False):
|
if get_choice('enabled', randomoneventweights, False):
|
||||||
ret.sprite = 'randomon'
|
ret.sprite = 'randomon'
|
||||||
ret.sprite += '-hit' if get_choice('on_hit', randomoneventweights, True) else ''
|
ret.sprite += '-hit' if get_choice('on_hit', randomoneventweights, True) else ''
|
||||||
ret.sprite += '-enter' if get_choice('on_enter', randomoneventweights, False) else ''
|
ret.sprite += '-enter' if get_choice('on_enter', randomoneventweights, False) else ''
|
||||||
ret.sprite += '-exit' if get_choice('on_exit', randomoneventweights, False) else ''
|
ret.sprite += '-exit' if get_choice('on_exit', randomoneventweights, False) else ''
|
||||||
ret.sprite += '-slash' if get_choice('on_slash', randomoneventweights, False) else ''
|
ret.sprite += '-slash' if get_choice('on_slash', randomoneventweights, False) else ''
|
||||||
ret.sprite += '-item' if get_choice('on_item', randomoneventweights, False) else ''
|
ret.sprite += '-item' if get_choice('on_item', randomoneventweights, False) else ''
|
||||||
ret.sprite += '-bonk' if get_choice('on_bonk', randomoneventweights, False) else ''
|
ret.sprite += '-bonk' if get_choice('on_bonk', randomoneventweights, False) else ''
|
||||||
ret.sprite = 'randomonall' if get_choice('on_everything', randomoneventweights, False) else ret.sprite
|
ret.sprite = 'randomonall' if get_choice('on_everything', randomoneventweights, False) else ret.sprite
|
||||||
ret.sprite = 'randomonnone' if ret.sprite == 'randomon' else ret.sprite
|
ret.sprite = 'randomonnone' if ret.sprite == 'randomon' else ret.sprite
|
||||||
|
|
||||||
if (not ret.sprite_pool or get_choice('use_weighted_sprite_pool', randomoneventweights, False)) \
|
if (not ret.sprite_pool or get_choice('use_weighted_sprite_pool', randomoneventweights, False)) \
|
||||||
and 'sprite' in romweights: # Use sprite as a weighted sprite pool, if a sprite pool is not already defined.
|
and 'sprite' in weights: # Use sprite as a weighted sprite pool, if a sprite pool is not already defined.
|
||||||
for key, value in romweights['sprite'].items():
|
for key, value in weights['sprite'].items():
|
||||||
if key.startswith('random'):
|
if key.startswith('random'):
|
||||||
ret.sprite_pool += ['random'] * int(value)
|
ret.sprite_pool += ['random'] * int(value)
|
||||||
else:
|
else:
|
||||||
ret.sprite_pool += [key] * int(value)
|
ret.sprite_pool += [key] * int(value)
|
||||||
|
|
||||||
ret.disablemusic = get_choice('disablemusic', romweights, False)
|
ret.disablemusic = get_choice('disablemusic', weights, False)
|
||||||
ret.triforcehud = get_choice('triforcehud', romweights, 'hide_goal')
|
ret.triforcehud = get_choice('triforcehud', weights, 'hide_goal')
|
||||||
ret.quickswap = get_choice('quickswap', romweights, True)
|
ret.quickswap = get_choice('quickswap', weights, True)
|
||||||
ret.fastmenu = get_choice('menuspeed', romweights, "normal")
|
ret.fastmenu = get_choice('menuspeed', weights, "normal")
|
||||||
ret.reduceflashing = get_choice('reduceflashing', romweights, False)
|
ret.reduceflashing = get_choice('reduceflashing', weights, False)
|
||||||
ret.heartcolor = get_choice('heartcolor', romweights, "red")
|
ret.heartcolor = get_choice('heartcolor', weights, "red")
|
||||||
ret.heartbeep = convert_to_on_off(get_choice('heartbeep', romweights, "normal"))
|
ret.heartbeep = convert_to_on_off(get_choice('heartbeep', weights, "normal"))
|
||||||
ret.ow_palettes = get_choice('ow_palettes', romweights, "default")
|
ret.ow_palettes = get_choice('ow_palettes', weights, "default")
|
||||||
ret.uw_palettes = get_choice('uw_palettes', romweights, "default")
|
ret.uw_palettes = get_choice('uw_palettes', weights, "default")
|
||||||
ret.hud_palettes = get_choice('hud_palettes', romweights, "default")
|
ret.hud_palettes = get_choice('hud_palettes', weights, "default")
|
||||||
ret.sword_palettes = get_choice('sword_palettes', romweights, "default")
|
ret.sword_palettes = get_choice('sword_palettes', weights, "default")
|
||||||
ret.shield_palettes = get_choice('shield_palettes', romweights, "default")
|
ret.shield_palettes = get_choice('shield_palettes', weights, "default")
|
||||||
ret.link_palettes = get_choice('link_palettes', romweights, "default")
|
ret.link_palettes = get_choice('link_palettes', weights, "default")
|
||||||
|
|
||||||
else:
|
|
||||||
ret.quickswap = True
|
|
||||||
ret.sprite = "Link"
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
72
Options.py
72
Options.py
|
@ -8,8 +8,9 @@ class AssembleOptions(type):
|
||||||
options = attrs["options"] = {}
|
options = attrs["options"] = {}
|
||||||
name_lookup = attrs["name_lookup"] = {}
|
name_lookup = attrs["name_lookup"] = {}
|
||||||
for base in bases:
|
for base in bases:
|
||||||
options.update(base.options)
|
if hasattr(base, "options"):
|
||||||
name_lookup.update(name_lookup)
|
options.update(base.options)
|
||||||
|
name_lookup.update(name_lookup)
|
||||||
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
|
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
|
||||||
name.startswith("option_")}
|
name.startswith("option_")}
|
||||||
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
||||||
|
@ -21,6 +22,37 @@ class AssembleOptions(type):
|
||||||
return super(AssembleOptions, mcs).__new__(mcs, name, bases, attrs)
|
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):
|
class Option(metaclass=AssembleOptions):
|
||||||
value: int
|
value: int
|
||||||
name_lookup: typing.Dict[int, str]
|
name_lookup: typing.Dict[int, str]
|
||||||
|
@ -110,7 +142,7 @@ class Choice(Option):
|
||||||
return cls.from_text(str(data))
|
return cls.from_text(str(data))
|
||||||
|
|
||||||
|
|
||||||
class Range(Option):
|
class Range(Option, int):
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 1
|
range_end = 1
|
||||||
|
|
||||||
|
@ -119,7 +151,7 @@ class Range(Option):
|
||||||
raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}")
|
raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}")
|
||||||
elif value > self.range_end:
|
elif value > self.range_end:
|
||||||
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__}")
|
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__}")
|
||||||
self.value: int = value
|
self.value = value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_text(cls, text: str) -> Range:
|
def from_text(cls, text: str) -> Range:
|
||||||
|
@ -129,6 +161,8 @@ class Range(Option):
|
||||||
return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_start), 0)))
|
return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_start), 0)))
|
||||||
elif text == "random-high":
|
elif text == "random-high":
|
||||||
return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_end), 0)))
|
return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_end), 0)))
|
||||||
|
elif text == "random-middle":
|
||||||
|
return cls(int(round(random.triangular(cls.range_start, cls.range_end), 0)))
|
||||||
else:
|
else:
|
||||||
return cls(random.randint(cls.range_start, cls.range_end))
|
return cls(random.randint(cls.range_start, cls.range_end))
|
||||||
return cls(int(text))
|
return cls(int(text))
|
||||||
|
@ -142,6 +176,10 @@ class Range(Option):
|
||||||
def get_option_name(self):
|
def get_option_name(self):
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
class OptionNameSet(Option):
|
class OptionNameSet(Option):
|
||||||
default = frozenset()
|
default = frozenset()
|
||||||
|
|
||||||
|
@ -212,12 +250,21 @@ class Crystals(Range):
|
||||||
range_end = 7
|
range_end = 7
|
||||||
|
|
||||||
|
|
||||||
|
class CrystalsTower(Crystals):
|
||||||
|
default = 7
|
||||||
|
|
||||||
|
|
||||||
|
class CrystalsGanon(Crystals):
|
||||||
|
default = 7
|
||||||
|
|
||||||
|
|
||||||
class TriforcePieces(Range):
|
class TriforcePieces(Range):
|
||||||
|
default = 30
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 90
|
range_end = 90
|
||||||
|
|
||||||
|
|
||||||
class ShopShuffleSlots(Range):
|
class ShopItemSlots(Range):
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 30
|
range_end = 30
|
||||||
|
|
||||||
|
@ -242,6 +289,12 @@ class Enemies(Choice):
|
||||||
option_chaos = 2
|
option_chaos = 2
|
||||||
|
|
||||||
|
|
||||||
|
alttp_options: typing.Dict[str, type(Option)] = {
|
||||||
|
"crystals_needed_for_gt": CrystalsTower,
|
||||||
|
"crystals_needed_for_ganon": CrystalsGanon,
|
||||||
|
"shop_item_slots": ShopItemSlots,
|
||||||
|
}
|
||||||
|
|
||||||
mapshuffle = Toggle
|
mapshuffle = Toggle
|
||||||
compassshuffle = Toggle
|
compassshuffle = Toggle
|
||||||
keyshuffle = Toggle
|
keyshuffle = Toggle
|
||||||
|
@ -270,7 +323,7 @@ RandomizeLoreTablets = Toggle
|
||||||
RandomizeLifebloodCocoons = Toggle
|
RandomizeLifebloodCocoons = Toggle
|
||||||
RandomizeFlames = Toggle
|
RandomizeFlames = Toggle
|
||||||
|
|
||||||
hollow_knight_randomize_options: typing.Dict[str, Option] = {
|
hollow_knight_randomize_options: typing.Dict[str, type(Option)] = {
|
||||||
"RandomizeDreamers": RandomizeDreamers,
|
"RandomizeDreamers": RandomizeDreamers,
|
||||||
"RandomizeSkills": RandomizeSkills,
|
"RandomizeSkills": RandomizeSkills,
|
||||||
"RandomizeCharms": RandomizeCharms,
|
"RandomizeCharms": RandomizeCharms,
|
||||||
|
@ -410,6 +463,13 @@ minecraft_options: typing.Dict[str, type(Option)] = {
|
||||||
"shuffle_structures": Toggle
|
"shuffle_structures": Toggle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
option_sets = (
|
||||||
|
minecraft_options,
|
||||||
|
factorio_options,
|
||||||
|
alttp_options,
|
||||||
|
hollow_knight_options
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ import uuid
|
||||||
import base64
|
import base64
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
import jinja2.exceptions
|
||||||
from pony.flask import Pony
|
from pony.flask import Pony
|
||||||
from flask import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory
|
from flask import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory
|
||||||
|
from flask import Blueprint
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
from flask_compress import Compress
|
from flask_compress import Compress
|
||||||
|
|
||||||
|
@ -74,6 +76,69 @@ def register_session():
|
||||||
session["_id"] = uuid4() # uniquely identify each session without needing a login
|
session["_id"] = uuid4() # uniquely identify each session without needing a login
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
@app.errorhandler(jinja2.exceptions.TemplateNotFound)
|
||||||
|
def page_not_found(err):
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
|
||||||
|
games_list = {
|
||||||
|
"zelda3": ("The Legend of Zelda: A Link to the Past",
|
||||||
|
"""
|
||||||
|
The Legend of Zelda: A Link to the Past is an action/adventure game. Take on the role of Link,
|
||||||
|
a boy who is destined to save the land of Hyrule. Delve through three palaces and nine dungeons on
|
||||||
|
your quest to rescue the descendents of the seven wise men and defeat the evil Ganon!"""),
|
||||||
|
"factorio": ("Factorio",
|
||||||
|
"""
|
||||||
|
Factorio is a game about automation. You play as an engineer who has crash landed on the planet
|
||||||
|
Nauvis, an inhospitable world filled with dangerous creatures called biters. Build a factory,
|
||||||
|
research new technologies, and become more efficient in your quest to build a rocket and return home.
|
||||||
|
"""),
|
||||||
|
"minecraft": ("Minecraft",
|
||||||
|
"""
|
||||||
|
Minecraft is a game about creativity. In a world made entirely of cubes, you explore, discover, mine,
|
||||||
|
craft, and try not to explode. Delve deep into the earth and discover abandoned mines, ancient
|
||||||
|
structures, and materials to create a portal to another world. Defeat the Ender Dragon, and claim
|
||||||
|
victory!""")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Player settings pages
|
||||||
|
@app.route('/games/<game>/player-settings')
|
||||||
|
def player_settings(game):
|
||||||
|
return render_template(f"/games/{game}/playerSettings.html")
|
||||||
|
|
||||||
|
|
||||||
|
# Zelda3 pages
|
||||||
|
@app.route('/games/zelda3/<string:page>')
|
||||||
|
def zelda3_pages(page):
|
||||||
|
return render_template(f"/games/zelda3/{page}.html")
|
||||||
|
|
||||||
|
|
||||||
|
# Factorio pages
|
||||||
|
@app.route('/games/factorio/<string:page>')
|
||||||
|
def factorio_pages(page):
|
||||||
|
return render_template(f"/games/factorio/{page}.html")
|
||||||
|
|
||||||
|
|
||||||
|
# Minecraft pages
|
||||||
|
@app.route('/games/minecraft/<string:page>')
|
||||||
|
def minecraft_pages(page):
|
||||||
|
return render_template(f"/games/factorio/{page}.html")
|
||||||
|
|
||||||
|
|
||||||
|
# Game landing pages
|
||||||
|
@app.route('/games/<game>')
|
||||||
|
def game_page(game):
|
||||||
|
return render_template(f"/games/{game}/{game}.html")
|
||||||
|
|
||||||
|
|
||||||
|
# List of supported games
|
||||||
|
@app.route('/games')
|
||||||
|
def games():
|
||||||
|
return render_template("games/games.html", games_list=games_list)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
|
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
|
||||||
def tutorial(game, file, lang):
|
def tutorial(game, file, lang):
|
||||||
return render_template("tutorial.html", game=game, file=file, lang=lang)
|
return render_template("tutorial.html", game=game, file=file, lang=lang)
|
||||||
|
@ -84,13 +149,8 @@ def tutorial_landing():
|
||||||
return render_template("tutorialLanding.html")
|
return render_template("tutorialLanding.html")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/player-settings')
|
|
||||||
def player_settings_simple():
|
|
||||||
return render_template("playerSettings.html")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/weighted-settings')
|
@app.route('/weighted-settings')
|
||||||
def player_settings():
|
def weighted_settings():
|
||||||
return render_template("weightedSettings.html")
|
return render_template("weightedSettings.html")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
#page-not-found{
|
||||||
|
width: 40em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-not-found h1{
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
html{
|
||||||
|
background-image: url('../static/backgrounds/grass/grass-0007-large.png');
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: 650px 650px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games{
|
||||||
|
max-width: 1000px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #eeffeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games p{
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games code{
|
||||||
|
background-color: #d9cd8e;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games #user-message{
|
||||||
|
display: none;
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
background-color: #ffe86b;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #000000;
|
||||||
|
padding: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games h1{
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: normal;
|
||||||
|
border-bottom: 1px solid #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 1px 1px 4px #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games h2{
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
border-bottom: 1px solid #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #ffe993;
|
||||||
|
text-transform: lowercase;
|
||||||
|
text-shadow: 1px 1px 2px #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games h3, #games h4, #games h5, #games h6{
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#games a{
|
||||||
|
color: #ffef00;
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ html{
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-header{
|
#landing-header{
|
||||||
|
@ -53,18 +52,19 @@ html{
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#uploads-button{
|
#far-left-button{
|
||||||
top: 65px;
|
top: 115px;
|
||||||
left: calc(50% - 416px - 200px - 75px);
|
left: calc(50% - 416px - 200px - 75px);
|
||||||
background-image: url("/static/static/button-images/button-a.png");
|
background-image: url("/static/static/button-images/button-a.png");
|
||||||
background-size: 200px auto;
|
background-size: 200px auto;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: calc(156px - 40px);
|
height: calc(156px - 40px);
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
#setup-guide-button{
|
#mid-left-button{
|
||||||
top: 270px;
|
top: 320px;
|
||||||
left: calc(50% - 416px - 200px + 140px);
|
left: calc(50% - 416px - 200px + 140px);
|
||||||
background-image: url("/static/static/button-images/button-b.png");
|
background-image: url("/static/static/button-images/button-b.png");
|
||||||
background-size: 260px auto;
|
background-size: 260px auto;
|
||||||
|
@ -73,8 +73,8 @@ html{
|
||||||
padding-top: 35px;
|
padding-top: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player-settings-button{
|
#mid-button{
|
||||||
top: 350px;
|
top: 400px;
|
||||||
left: calc(50% - 100px);
|
left: calc(50% - 100px);
|
||||||
background-image: url("/static/static/button-images/button-a.png");
|
background-image: url("/static/static/button-images/button-a.png");
|
||||||
background-size: 200px auto;
|
background-size: 200px auto;
|
||||||
|
@ -83,8 +83,8 @@ html{
|
||||||
padding-top: 38px;
|
padding-top: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#discord-button{
|
#mid-right-button{
|
||||||
top: 250px;
|
top: 300px;
|
||||||
left: calc(50% + 416px - 166px);
|
left: calc(50% + 416px - 166px);
|
||||||
background-image: url("/static/static/button-images/button-c.png");
|
background-image: url("/static/static/button-images/button-c.png");
|
||||||
background-size: 250px auto;
|
background-size: 250px auto;
|
||||||
|
@ -94,14 +94,15 @@ html{
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#generate-button{
|
#far-right-button{
|
||||||
top: 75px;
|
top: 125px;
|
||||||
left: calc(50% + 416px + 75px);
|
left: calc(50% + 416px + 75px);
|
||||||
background-image: url("/static/static/button-images/button-b.png");
|
background-image: url("/static/static/button-images/button-b.png");
|
||||||
background-size: 260px auto;
|
background-size: 260px auto;
|
||||||
width: 260px;
|
width: 260px;
|
||||||
height: calc(130px - 35px);
|
height: calc(130px - 35px);
|
||||||
padding-top: 35px;
|
padding-top: 35px;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-clouds{
|
#landing-clouds{
|
||||||
|
@ -111,7 +112,7 @@ html{
|
||||||
#landing-clouds #cloud1{
|
#landing-clouds #cloud1{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
top: 265px;
|
top: 365px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
|
|
||||||
|
@ -147,23 +148,23 @@ html{
|
||||||
@keyframes c1-float{
|
@keyframes c1-float{
|
||||||
from{
|
from{
|
||||||
left: 10px;
|
left: 10px;
|
||||||
top: 265px;
|
top: 365px;
|
||||||
}
|
}
|
||||||
25%{
|
25%{
|
||||||
left: 14px;
|
left: 14px;
|
||||||
top: 267px;
|
top: 367px;
|
||||||
}
|
}
|
||||||
50%{
|
50%{
|
||||||
left: 17px;
|
left: 17px;
|
||||||
top: 265px;
|
top: 365px;
|
||||||
}
|
}
|
||||||
75%{
|
75%{
|
||||||
left: 14px;
|
left: 14px;
|
||||||
top: 262px;
|
top: 362px;
|
||||||
}
|
}
|
||||||
to{
|
to{
|
||||||
left: 10px;
|
left: 10px;
|
||||||
top: 265px;
|
top: 365px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,32 +242,32 @@ html{
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-deco-1{
|
#landing-deco-1{
|
||||||
top: 430px;
|
top: 480px;
|
||||||
left: calc(50% - 276px);
|
left: calc(50% - 276px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-deco-2{
|
#landing-deco-2{
|
||||||
top: 200px;
|
top: 250px;
|
||||||
left: calc(50% + 150px);
|
left: calc(50% + 150px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-deco-3{
|
#landing-deco-3{
|
||||||
top: 300px;
|
top: 350px;
|
||||||
left: calc(50% - 150px);
|
left: calc(50% - 150px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-deco-4{
|
#landing-deco-4{
|
||||||
top: 240px;
|
top: 290px;
|
||||||
left: calc(50% - 580px);
|
left: calc(50% - 580px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-deco-5{
|
#landing-deco-5{
|
||||||
top: 40px;
|
top: 90px;
|
||||||
left: calc(50% + 450px);
|
left: calc(50% + 450px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#landing-deco-6{
|
#landing-deco-6{
|
||||||
top: 412px;
|
top: 462px;
|
||||||
left: calc(50% + 196px);
|
left: calc(50% + 196px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
html{
|
html{
|
||||||
background-image: url('../static/backgrounds/grass/grass-0007-large.png');
|
background-image: url('../../static/backgrounds/grass/grass-0007-large.png');
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-size: 650px 650px;
|
background-size: 650px 650px;
|
||||||
}
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends 'pageWrapper.html' %}
|
||||||
|
{% import "macros.html" as macros %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<title>Page Not Found (404)</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/404.css") }}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% include 'header/oceanHeader.html' %}
|
||||||
|
<div id="page-not-found" class="grass-island">
|
||||||
|
<h1>This page is out of logic!</h1>
|
||||||
|
The page you're looking for doesn't exist.<br />
|
||||||
|
<a href="/">Click here to return to safety.</a>
|
||||||
|
</div>
|
||||||
|
{% include 'islandFooter.html' %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Factorio</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Factorio
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends 'pageWrapper.html' %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<title>Player Settings</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/games.css") }}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% include 'header/grassHeader.html' %}
|
||||||
|
<div id="games">
|
||||||
|
<h1>Currently Supported Games</h1>
|
||||||
|
{% for game, (display_name, description) in games_list.items() %}
|
||||||
|
<h3><a href="{{ url_for("game_page", game=game) }}">{{ display_name}}</a></h3>
|
||||||
|
<p>{{ description}}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Minecraft</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Minecraft
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<title>Player Settings</title>
|
<title>Player Settings</title>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/playerSettings.css") }}" />
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/zelda3/playerSettings.css") }}" />
|
||||||
<script type="application/ecmascript" src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script type="application/ecmascript" src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/js-yaml.min.js") }}"></script>
|
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/js-yaml.min.js") }}"></script>
|
||||||
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/playerSettings.js") }}"></script>
|
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/zelda3/playerSettings.js") }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Link to the Past</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Link to the Past<br />
|
||||||
|
<a href="/games/zelda3/player-settings">Player Settings</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -11,10 +11,8 @@
|
||||||
<a href="/">archipelago</a>
|
<a href="/">archipelago</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="base-header-right">
|
<div id="base-header-right">
|
||||||
<a href="/player-settings">start game</a>
|
<a href="/games">games</a>
|
||||||
<a href="/uploads">host game</a>
|
|
||||||
<a href="/tutorial">setup guides</a>
|
<a href="/tutorial">setup guides</a>
|
||||||
<a href="/generate">upload config</a>
|
|
||||||
<a href="https://discord.gg/8Z65BR2">discord</a>
|
<a href="https://discord.gg/8Z65BR2">discord</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -6,17 +6,18 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
{% include 'header/oceanHeader.html' %}
|
||||||
<div id="landing-wrapper">
|
<div id="landing-wrapper">
|
||||||
<div id="landing-header">
|
<div id="landing-header">
|
||||||
<h4>the legend of zelda: a link to the past</h4>
|
<h1>ARCHIPELAGO</h1>
|
||||||
<h1>MULTIWORLD RANDOMIZER</h1>
|
<h4>multiworld randomizer ecosystem</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="landing-links">
|
<div id="landing-links">
|
||||||
<a href="/player-settings" id="player-settings-button">start<br />playing</a>
|
<a href="/games" id="mid-button">start<br />playing</a>
|
||||||
<a href="/uploads" id="uploads-button">host<br />game</a>
|
<a id="far-left-button"></a>
|
||||||
<a href="/tutorial" id="setup-guide-button">setup guides</a>
|
<a href="/tutorial" id="mid-left-button">setup guide</a>
|
||||||
<a href="/generate" id="generate-button">upload config</a>
|
<a id="far-right-button"></a>
|
||||||
<a href="https://discord.gg/8Z65BR2" id="discord-button">discord</a>
|
<a href="https://discord.gg/8Z65BR2" id="mid-right-button">discord</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="landing-clouds">
|
<div id="landing-clouds">
|
||||||
<img id="cloud1" src="/static/static/backgrounds/clouds/cloud-0001.png"/>
|
<img id="cloud1" src="/static/static/backgrounds/clouds/cloud-0001.png"/>
|
||||||
|
@ -33,14 +34,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="landing" class="grass-island">
|
<div id="landing" class="grass-island">
|
||||||
<div id="landing-body">
|
<div id="landing-body">
|
||||||
<p id="first-line">Welcome to the Archipelago Multiworld Randomizer!</p>
|
<p id="first-line">Welcome to Archipelago!</p>
|
||||||
<p>This is a <span data-tooltip="Allegedly.">randomizer</span> for The Legend of Zelda: A
|
<p>
|
||||||
Link to the Past.</p>
|
This is a cross-game modification system which randomizes different games, then uses the result to
|
||||||
<p>It is also a multi-world, meaning Link's items may have been placed into other players' games.
|
build a single unified multi-player game. Items from one game may be present in another, and
|
||||||
When a player picks up an item which does not belong to them, it is sent back to the player
|
you will need your fellow players to find items you need in their games to help you complete
|
||||||
it belongs to.</p>
|
your own.
|
||||||
<p>On this website you are able to generate and host multiworld games, and item and location
|
</p>
|
||||||
trackers are provided for games hosted here.</p>
|
|
||||||
<p>
|
<p>
|
||||||
This project is the cumulative effort of many
|
This project is the cumulative effort of many
|
||||||
<a href="https://github.com/ArchipelagoMW/Archipelago/graphs/contributors">talented people.</a>
|
<a href="https://github.com/ArchipelagoMW/Archipelago/graphs/contributors">talented people.</a>
|
||||||
|
|
|
@ -8,7 +8,7 @@ local technologies = data.raw["technology"]
|
||||||
local original_tech
|
local original_tech
|
||||||
local new_tree_copy
|
local new_tree_copy
|
||||||
allowed_ingredients = {}
|
allowed_ingredients = {}
|
||||||
{%- for tech_name, technology in custom_data["custom_technologies"].items() %}
|
{%- for tech_name, technology in custom_technologies.items() %}
|
||||||
allowed_ingredients["{{ tech_name }}"] = {
|
allowed_ingredients["{{ tech_name }}"] = {
|
||||||
{%- for ingredient in technology.ingredients %}
|
{%- for ingredient in technology.ingredients %}
|
||||||
["{{ingredient}}"] = 1,
|
["{{ingredient}}"] = 1,
|
||||||
|
@ -66,7 +66,7 @@ original_tech = technologies["{{original_tech_name}}"]
|
||||||
new_tree_copy = table.deepcopy(template_tech)
|
new_tree_copy = table.deepcopy(template_tech)
|
||||||
new_tree_copy.name = "ap-{{ tech_table[original_tech_name] }}-"{# use AP ID #}
|
new_tree_copy.name = "ap-{{ tech_table[original_tech_name] }}-"{# use AP ID #}
|
||||||
prep_copy(new_tree_copy, original_tech)
|
prep_copy(new_tree_copy, original_tech)
|
||||||
{% if tech_cost != 1 %}
|
{% if tech_cost_scale != 1 %}
|
||||||
new_tree_copy.unit.count = math.max(1, math.floor(new_tree_copy.unit.count * {{ tech_cost_scale }}))
|
new_tree_copy.unit.count = math.max(1, math.floor(new_tree_copy.unit.count * {{ tech_cost_scale }}))
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- if item_name in tech_table and visibility -%}
|
{%- if item_name in tech_table and visibility -%}
|
||||||
|
|
103
meta.yaml
103
meta.yaml
|
@ -11,54 +11,55 @@
|
||||||
# inverted
|
# inverted
|
||||||
# This means, if mode is meta-rolled and the result happens to be inverted, then defer to the player's yaml instead.
|
# This means, if mode is meta-rolled and the result happens to be inverted, then defer to the player's yaml instead.
|
||||||
meta_description: Meta-Mystery file with the intention of having similar-length completion times for a hopefully better experience
|
meta_description: Meta-Mystery file with the intention of having similar-length completion times for a hopefully better experience
|
||||||
progression_balancing: # Progression balancing tries to make sure that the player has *something* towards any players goal in each "sphere"
|
null:
|
||||||
on: 0 # Force every player into progression balancing
|
progression_balancing: # Progression balancing tries to make sure that the player has *something* towards any players goal in each "sphere"
|
||||||
off: 0 # Force every player out of progression balancing, then prepare for a lot of logical BK
|
on: 0 # Force every player into progression balancing
|
||||||
null: 1 # Let players decide via their own progression_balancing flag in their yaml, defaulting to on
|
off: 0 # Force every player out of progression balancing, then prepare for a lot of logical BK
|
||||||
goals:
|
null: 1 # Let players decide via their own progression_balancing flag in their yaml, defaulting to on
|
||||||
ganon: 100 # Climb GT, defeat Agahnim 2, and then kill Ganon
|
A Link to the Past:
|
||||||
fast_ganon: 250 # Only killing Ganon is required. The hole is always open. However, items may still be placed in GT
|
goals:
|
||||||
dungeons: 50 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
|
ganon: 100 # Climb GT, defeat Agahnim 2, and then kill Ganon
|
||||||
pedestal: 100 # Pull the Triforce from the Master Sword pedestal
|
fast_ganon: 250 # Only killing Ganon is required. The hole is always open. However, items may still be placed in GT
|
||||||
triforce-hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle
|
dungeons: 50 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
|
||||||
local_triforce_hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
pedestal: 100 # Pull the Triforce from the Master Sword pedestal
|
||||||
ganon_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
triforce-hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle
|
||||||
local_ganon_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
local_triforce_hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
||||||
ganon_pedestal: 10 # Pull the Master Sword pedestal, then kill Ganon
|
ganon_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
||||||
null: 0 # Maintain individual goals
|
local_ganon_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
||||||
mode:
|
ganon_pedestal: 10 # Pull the Master Sword pedestal, then kill Ganon
|
||||||
standard: 10
|
null: 0 # Maintain individual goals
|
||||||
open: 60
|
mode:
|
||||||
inverted: 10
|
standard: 10
|
||||||
null: 10 # Maintain individual world states
|
open: 60
|
||||||
tower_open:
|
inverted: 10
|
||||||
'0': 8
|
null: 10 # Maintain individual world states
|
||||||
'1': 7
|
tower_open:
|
||||||
'2': 6
|
'0': 8
|
||||||
'3': 5
|
'1': 7
|
||||||
'4': 4
|
'2': 6
|
||||||
'5': 3
|
'3': 5
|
||||||
'6': 2
|
'4': 4
|
||||||
'7': 1
|
'5': 3
|
||||||
random: 10 # A different GT open time should not usually result in a vastly different completion time, unless ganon goal and tower_open > ganon_open
|
'6': 2
|
||||||
ganon_open:
|
'7': 1
|
||||||
'0': 3
|
random: 10 # A different GT open time should not usually result in a vastly different completion time, unless ganon goal and tower_open > ganon_open
|
||||||
'1': 4
|
ganon_open:
|
||||||
'2': 5
|
'0': 3
|
||||||
'3': 6
|
'1': 4
|
||||||
'4': 7
|
'2': 5
|
||||||
'5': 8
|
'3': 6
|
||||||
'6': 9
|
'4': 7
|
||||||
'7': 10
|
'5': 8
|
||||||
random: 5 # This will mean differing completion times. But leaving it for that surprise effect
|
'6': 9
|
||||||
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
'7': 10
|
||||||
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
random: 5 # This will mean differing completion times. But leaving it for that surprise effect
|
||||||
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
||||||
available: 50 # available = triforce_pieces_available
|
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
||||||
triforce_pieces_available: # Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1
|
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
||||||
# Format "pieces: chance"
|
available: 50 # available = triforce_pieces_available
|
||||||
30: 50
|
triforce_pieces_available: # Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1
|
||||||
triforce_pieces_required: # Set to how many out of X triforce pieces you need to win the game in a triforce hunt. Default is 20. Max is 90, Min is 1
|
# Format "pieces: chance"
|
||||||
# Format "pieces: chance"
|
30: 50
|
||||||
25: 50
|
triforce_pieces_required: # Set to how many out of X triforce pieces you need to win the game in a triforce hunt. Default is 20. Max is 90, Min is 1
|
||||||
# Do not use meta rom options at this time
|
# Format "pieces: chance"
|
||||||
|
25: 50
|
|
@ -35,449 +35,347 @@ accessibility:
|
||||||
progression_balancing:
|
progression_balancing:
|
||||||
on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do
|
on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do
|
||||||
off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch/sequence break around missing items.
|
off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch/sequence break around missing items.
|
||||||
# Can be uncommented to use it
|
# The following 4 options can be uncommented and moved into a game's section they should affect
|
||||||
# startinventory: # Begin the file with the listed items/upgrades
|
# start_inventory: # Begin the file with the listed items/upgrades
|
||||||
# Please only use items for the correct game, use triggers if need to be have seperated lists.
|
# Please only use items for the correct game, use triggers if need to be have seperated lists.
|
||||||
# Pegasus Boots: on
|
# Pegasus Boots: on
|
||||||
# Bomb Upgrade (+10): 4
|
# Bomb Upgrade (+10): 4
|
||||||
# Arrow Upgrade (+10): 4
|
# Arrow Upgrade (+10): 4
|
||||||
# start_hints: # Begin the game with these items' locations revealed to you at the start of the game. Get the info via !hint in your client.
|
# start_hints: # Begin the game with these items' locations revealed to you at the start of the game. Get the info via !hint in your client.
|
||||||
# - Moon Pearl
|
# - Moon Pearl
|
||||||
# Factorio options:
|
|
||||||
tech_tree_layout:
|
|
||||||
single: 1
|
|
||||||
small_diamonds: 1
|
|
||||||
medium_diamonds: 1
|
|
||||||
large_diamonds: 1
|
|
||||||
small_pyramids: 1
|
|
||||||
medium_pyramids: 1
|
|
||||||
large_pyramids: 1
|
|
||||||
small_funnels: 1
|
|
||||||
medium_funnels: 1
|
|
||||||
large_funnels: 1
|
|
||||||
recipe_time: # randomize the time it takes for any recipe to craft, this includes smelting, chemical lab, hand crafting etc.
|
|
||||||
vanilla: 1
|
|
||||||
fast: 0 # 25% to 100% of original time
|
|
||||||
normal: 0 # 50 % to 200% of original time
|
|
||||||
slow: 0 # 100% to 400% of original time
|
|
||||||
chaos: 0 # 25% to 400% of original time
|
|
||||||
max_science_pack:
|
|
||||||
automation_science_pack: 0
|
|
||||||
logistic_science_pack: 0
|
|
||||||
military_science_pack: 0
|
|
||||||
chemical_science_pack: 0
|
|
||||||
production_science_pack: 0
|
|
||||||
utility_science_pack: 0
|
|
||||||
space_science_pack: 1
|
|
||||||
tech_cost:
|
|
||||||
very_easy : 0
|
|
||||||
easy : 0
|
|
||||||
kind : 0
|
|
||||||
normal : 1
|
|
||||||
hard : 0
|
|
||||||
very_hard : 0
|
|
||||||
insane : 0
|
|
||||||
free_samples:
|
|
||||||
none: 1
|
|
||||||
single_craft: 0
|
|
||||||
half_stack: 0
|
|
||||||
stack: 0
|
|
||||||
visibility:
|
|
||||||
none: 0
|
|
||||||
sending: 1
|
|
||||||
random_tech_ingredients:
|
|
||||||
on: 1
|
|
||||||
off: 0
|
|
||||||
starting_items:
|
|
||||||
burner-mining-drill: 19
|
|
||||||
stone-furnace: 19
|
|
||||||
# Minecraft options:
|
|
||||||
advancement_goal: 50 # Number of advancements required to spawn the Ender Dragon and complete the game (currently 87 max)
|
|
||||||
combat_difficulty: # Modifies the level of items logically required for exploring dangerous areas and fighting bosses.
|
|
||||||
easy: 0
|
|
||||||
normal: 1
|
|
||||||
hard: 0
|
|
||||||
include_hard_advancements: # Junk-fills certain RNG-reliant or tedious advancements with XP rewards.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
include_insane_advancements: # Junk-fills extremely difficult advancements; this is only How Did We Get Here? and Adventuring Time.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
include_postgame_advancements: # Some advancements require defeating the Ender Dragon; this will junk-fill them so you won't have to finish to send some items.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
shuffle_structures: # Enables shuffling of villages, outposts, fortresses, bastions, and end cities.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
# A Link to the Past options:
|
|
||||||
### Logic Section ###
|
|
||||||
# Warning: overworld_glitches is not available and minor_glitches is only partially implemented on the door-rando version
|
|
||||||
glitches_required: # Determine the logic required to complete the seed
|
|
||||||
none: 50 # No glitches required
|
|
||||||
minor_glitches: 0 # Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic
|
|
||||||
overworld_glitches: 0 # Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches
|
|
||||||
no_logic: 0 # Your own items are placed with no regard to any logic; such as your Fire Rod can be on your Trinexx.
|
|
||||||
# Other players items are placed into your world under OWG logic
|
|
||||||
dark_room_logic: # Logic for unlit dark rooms
|
|
||||||
lamp: 50 # require the Lamp for these rooms to be considered accessible.
|
|
||||||
torches: 0 # in addition to lamp, allow the fire rod and presence of easily accessible torches for access
|
|
||||||
none: 0 # all dark rooms are always considered doable, meaning this may force completion of rooms in complete darkness
|
|
||||||
restrict_dungeon_item_on_boss: # aka ambrosia boss items
|
|
||||||
on: 0 # prevents unshuffled compasses, maps and keys to be boss drops, they can still drop keysanity and other players' items
|
|
||||||
off: 50
|
|
||||||
### End of Logic Section ###
|
|
||||||
map_shuffle: # Shuffle dungeon maps into the world and other dungeons, including other players' worlds
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
compass_shuffle: # Shuffle compasses into the world and other dungeons, including other players' worlds
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
smallkey_shuffle: # Shuffle small keys into the world and other dungeons, including other players' worlds
|
|
||||||
on: 0
|
|
||||||
universal: 0 # allows small keys to be used in any dungeon and adds shops to buy more
|
|
||||||
off: 50
|
|
||||||
bigkey_shuffle: # Shuffle big keys into the world and other dungeons, including other players' worlds
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
local_keys: # Keep small keys and big keys local to your world
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
dungeon_items: # Alternative to the 4 shuffles and local_keys above this, does nothing until the respective 4 shuffles and local_keys above are deleted
|
|
||||||
mc: 0 # Shuffle maps and compasses
|
|
||||||
none: 50 # Shuffle none of the 4
|
|
||||||
mcsb: 0 # Shuffle all of the 4, any combination of m, c, s and b will shuffle the respective item, or not if it's missing, so you can add more options here
|
|
||||||
lmcsb: 0 # Like mcsb above, but with keys kept local to your world. l is what makes your keys local, or not if it's missing
|
|
||||||
ub: 0 # universal small keys and shuffled big keys
|
|
||||||
# you can add more combos of these letters here
|
|
||||||
dungeon_counters:
|
|
||||||
on: 0 # Always display amount of items checked in a dungeon
|
|
||||||
pickup: 50 # Show when compass is picked up
|
|
||||||
default: 0 # Show when compass is picked up if the compass itself is shuffled
|
|
||||||
off: 0 # Never show item count in dungeons
|
|
||||||
progressive: # Enable or disable progressive items (swords, shields, bow)
|
|
||||||
on: 50 # All items are progressive
|
|
||||||
off: 0 # No items are progressive
|
|
||||||
random: 0 # Randomly decides for all items. Swords could be progressive, shields might not be
|
|
||||||
entrance_shuffle:
|
|
||||||
none: 50 # Vanilla game map. All entrances and exits lead to their original locations. You probably want this option
|
|
||||||
dungeonssimple: 0 # Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon
|
|
||||||
dungeonsfull: 0 # Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle can be 4 different dungeons, but keep dungeons to a specific world
|
|
||||||
dungeonscrossed: 0 # like dungeonsfull, but allow cross-world traversal through a dungeon. Warning: May force repeated dungeon traversal
|
|
||||||
simple: 0 # Entrances are grouped together before being randomized. Simple uses the most strict grouping rules
|
|
||||||
restricted: 0 # Less strict than simple
|
|
||||||
full: 0 # Less strict than restricted
|
|
||||||
crossed: 0 # Less strict than full
|
|
||||||
insanity: 0 # Very few grouping rules. Good luck
|
|
||||||
# you can also define entrance shuffle seed, like so:
|
|
||||||
crossed-1000: 0 # using this method, you can have the same layout as another player and share entrance information
|
|
||||||
# however, many other settings like logic, world state, retro etc. may affect the shuffle result as well.
|
|
||||||
crossed-group-myfriends: 0 # using this method, everyone with "group-myfriends" will share the same seed
|
|
||||||
goals:
|
|
||||||
ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon
|
|
||||||
crystals: 0 # Only killing Ganon is required. However, items may still be placed in GT
|
|
||||||
bosses: 0 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
|
|
||||||
pedestal: 0 # Pull the Triforce from the Master Sword pedestal
|
|
||||||
ganon_pedestal: 0 # Pull the Master Sword pedestal, then kill Ganon
|
|
||||||
triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle
|
|
||||||
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
|
||||||
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
|
||||||
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
|
||||||
ice_rod_hunt: 0 # You start with everything needed to 216 the seed. Find the Ice rod, then kill Trinexx at Turtle rock.
|
|
||||||
open_pyramid:
|
|
||||||
goal: 50 # Opens the pyramid if the goal requires you to kill Ganon, unless the goal is Slow Ganon or All Dungeons
|
|
||||||
auto: 0 # Same as Goal, but also is closed if holes are shuffled and ganon is part of the shuffle pool
|
|
||||||
yes: 0 # Pyramid hole is always open. Ganon's vulnerable condition is still required before he can he hurt
|
|
||||||
no: 0 # Pyramid hole is always closed until you defeat Agahnim atop Ganon's Tower
|
|
||||||
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
|
||||||
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
|
||||||
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
|
||||||
available: 50 # available = triforce_pieces_available
|
|
||||||
triforce_pieces_extra: # Set to how many extra triforces pieces are available to collect in the world.
|
|
||||||
# Format "pieces: chance"
|
|
||||||
0: 0
|
|
||||||
5: 50
|
|
||||||
10: 50
|
|
||||||
15: 0
|
|
||||||
20: 0
|
|
||||||
triforce_pieces_percentage: # Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world.
|
|
||||||
# Format "pieces: chance"
|
|
||||||
100: 0 #No extra
|
|
||||||
150: 50 #Half the required will be added as extra
|
|
||||||
200: 0 #There are the double of the required ones available.
|
|
||||||
triforce_pieces_available: # Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1
|
|
||||||
# Format "pieces: chance"
|
|
||||||
25: 0
|
|
||||||
30: 50
|
|
||||||
40: 0
|
|
||||||
50: 0
|
|
||||||
triforce_pieces_required: # Set to how many out of X triforce pieces you need to win the game in a triforce hunt. Default is 20. Max is 90, Min is 1
|
|
||||||
# Format "pieces: chance"
|
|
||||||
15: 0
|
|
||||||
20: 50
|
|
||||||
30: 0
|
|
||||||
40: 0
|
|
||||||
50: 0
|
|
||||||
tower_open: # Crystals required to open GT
|
|
||||||
0: 0
|
|
||||||
1: 0
|
|
||||||
2: 0
|
|
||||||
3: 0
|
|
||||||
4: 0
|
|
||||||
5: 0
|
|
||||||
6: 0
|
|
||||||
7: 0
|
|
||||||
random: 0
|
|
||||||
random-low: 50 # any valid number, weighted towards the lower end
|
|
||||||
random-high: 0
|
|
||||||
ganon_open: # Crystals required to hurt Ganon
|
|
||||||
0: 0
|
|
||||||
1: 0
|
|
||||||
2: 0
|
|
||||||
3: 0
|
|
||||||
4: 0
|
|
||||||
5: 0
|
|
||||||
6: 0
|
|
||||||
7: 0
|
|
||||||
random: 0
|
|
||||||
random-low: 0
|
|
||||||
random-high: 50 # any valid number, weighted towards the higher end
|
|
||||||
mode:
|
|
||||||
standard: 50 # Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary
|
|
||||||
open: 50 # Begin the game from your choice of Link's House or the Sanctuary
|
|
||||||
inverted: 0 # Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered
|
|
||||||
retro:
|
|
||||||
on: 0 # you must buy a quiver to use the bow, take-any caves and an old-man cave are added to the world. You may need to find your sword from the old man's cave
|
|
||||||
off: 50
|
|
||||||
hints:
|
|
||||||
'on': 50 # Hint tiles sometimes give item location hints
|
|
||||||
'off': 0 # Hint tiles provide gameplay tips
|
|
||||||
swordless:
|
|
||||||
on: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change
|
|
||||||
off: 1
|
|
||||||
item_pool:
|
|
||||||
easy: 0 # Doubled upgrades, progressives, and etc
|
|
||||||
normal: 50 # Item availability remains unchanged from vanilla game
|
|
||||||
hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless)
|
|
||||||
expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless)
|
|
||||||
item_functionality:
|
|
||||||
easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere.
|
|
||||||
normal: 50 # Vanilla item functionality
|
|
||||||
hard: 0 # Reduced helpfulness of items (potions less effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs do not stun, silvers disabled outside ganon)
|
|
||||||
expert: 0 # Vastly reduces the helpfulness of items (potions barely effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs and hookshot do not stun, silvers disabled outside ganon)
|
|
||||||
tile_shuffle: # Randomize the tile layouts in flying tile rooms
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
misery_mire_medallion: # required medallion to open Misery Mire front entrance
|
|
||||||
random: 50
|
|
||||||
Ether: 0
|
|
||||||
Bombos: 0
|
|
||||||
Quake: 0
|
|
||||||
turtle_rock_medallion: # required medallion to open Turtle Rock front entrance
|
|
||||||
random: 50
|
|
||||||
Ether: 0
|
|
||||||
Bombos: 0
|
|
||||||
Quake: 0
|
|
||||||
### Enemizer Section ###
|
|
||||||
boss_shuffle:
|
|
||||||
none: 50 # Vanilla bosses
|
|
||||||
basic: 0 # Existing bosses except Ganon and Agahnim are shuffled throughout dungeons
|
|
||||||
normal: 0 # 3 bosses can occur twice
|
|
||||||
chaos: 0 # Any boss can appear any amount of times
|
|
||||||
singularity: 0 # Picks a boss, tries to put it everywhere that works, if there's spaces remaining it picks a boss to fill those
|
|
||||||
enemy_shuffle: # Randomize enemy placement
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
killable_thieves: # Make thieves killable
|
|
||||||
on: 0 # Usually turned on together with enemy_shuffle to make annoying thief placement more manageable
|
|
||||||
off: 50
|
|
||||||
bush_shuffle: # Randomize the chance that bushes have enemies and the enemies under said bush
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
enemy_damage:
|
|
||||||
default: 50 # Vanilla enemy damage
|
|
||||||
shuffled: 0 # Enemies deal 0 to 4 hearts and armor helps
|
|
||||||
random: 0 # Enemies deal 0 to 8 hearts and armor just reshuffles the damage
|
|
||||||
enemy_health:
|
|
||||||
default: 50 # Vanilla enemy HP
|
|
||||||
easy: 0 # Enemies have reduced health
|
|
||||||
hard: 0 # Enemies have increased health
|
|
||||||
expert: 0 # Enemies have greatly increased health
|
|
||||||
pot_shuffle:
|
|
||||||
'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile
|
|
||||||
'off': 50 # Default pot item locations
|
|
||||||
### End of Enemizer Section ###
|
|
||||||
beemizer: # Remove items from the global item pool and replace them with single bees (fill bottles) and bee traps
|
|
||||||
0: 50 # No bee traps are placed
|
|
||||||
1: 0 # 25% of rupees, bombs and arrows are replaced with bees, of which 60% are traps and 40% single bees
|
|
||||||
2: 0 # 50% of rupees, bombs and arrows are replaced with bees, of which 70% are traps and 30% single bees
|
|
||||||
3: 0 # 75% of rupees, bombs and arrows are replaced with bees, of which 80% are traps and 20% single bees
|
|
||||||
4: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 90% are traps and 10% single bees
|
|
||||||
5: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 100% are traps and 0% single bees
|
|
||||||
### Shop Settings ###
|
|
||||||
shop_shuffle_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl)
|
|
||||||
0: 50
|
|
||||||
5: 0
|
|
||||||
15: 0
|
|
||||||
30: 0
|
|
||||||
random: 0 # 0 to 30 evenly distributed
|
|
||||||
shop_shuffle:
|
|
||||||
none: 50
|
|
||||||
g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops
|
|
||||||
f: 0 # Generate new default inventories for every shop independently
|
|
||||||
i: 0 # Shuffle default inventories of the shops around
|
|
||||||
p: 0 # Randomize the prices of the items in shop inventories
|
|
||||||
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
|
||||||
w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too
|
|
||||||
ip: 0 # Shuffle inventories and randomize prices
|
|
||||||
fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool
|
|
||||||
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
|
||||||
# You can add more combos
|
|
||||||
### End of Shop Section ###
|
|
||||||
shuffle_prizes: # aka drops
|
|
||||||
none: 0 # do not shuffle prize packs
|
|
||||||
g: 50 # shuffle "general" prize packs, as in enemy, tree pull, dig etc.
|
|
||||||
b: 0 # shuffle "bonk" prize packs
|
|
||||||
bg: 0 # shuffle both
|
|
||||||
timer:
|
|
||||||
none: 50 # No timer will be displayed.
|
|
||||||
timed: 0 # Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.
|
|
||||||
timed_ohko: 0 # Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.
|
|
||||||
ohko: 0 # Timer always at zero. Permanent OHKO.
|
|
||||||
timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.
|
|
||||||
display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool.
|
|
||||||
countdown_start_time: # For timed_ohko and timed_countdown timer modes, the amount of time in minutes to start with
|
|
||||||
0: 0 # For timed_ohko, starts in OHKO mode when starting the game
|
|
||||||
10: 50
|
|
||||||
20: 0
|
|
||||||
30: 0
|
|
||||||
60: 0
|
|
||||||
red_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock
|
|
||||||
-2: 50
|
|
||||||
1: 0
|
|
||||||
blue_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock
|
|
||||||
1: 0
|
|
||||||
2: 50
|
|
||||||
green_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock
|
|
||||||
4: 50
|
|
||||||
10: 0
|
|
||||||
15: 0
|
|
||||||
# Can be uncommented to use it
|
|
||||||
# local_items: # Force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords"
|
# local_items: # Force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords"
|
||||||
# - "Moon Pearl"
|
# - "Moon Pearl"
|
||||||
# - "Small Keys"
|
# - "Small Keys"
|
||||||
# - "Big Keys"
|
# - "Big Keys"
|
||||||
glitch_boots:
|
# non_local_items: # Force certain items to appear outside your world only, unless in single-player. Recognizes some group names, like "Swords"
|
||||||
on: 50 # Start with Pegasus Boots in any glitched logic mode that makes use of them
|
# - "Progressive Weapons"
|
||||||
off: 0
|
|
||||||
# meta_ignore, linked_options and triggers work for any game
|
Factorio:
|
||||||
meta_ignore: # Nullify options specified in the meta.yaml file. Adding an option here guarantees it will not occur in your seed, even if the .yaml file specifies it
|
tech_tree_layout:
|
||||||
|
single: 1
|
||||||
|
small_diamonds: 1
|
||||||
|
medium_diamonds: 1
|
||||||
|
large_diamonds: 1
|
||||||
|
small_pyramids: 1
|
||||||
|
medium_pyramids: 1
|
||||||
|
large_pyramids: 1
|
||||||
|
small_funnels: 1
|
||||||
|
medium_funnels: 1
|
||||||
|
large_funnels: 1
|
||||||
|
recipe_time: # randomize the time it takes for any recipe to craft, this includes smelting, chemical lab, hand crafting etc.
|
||||||
|
vanilla: 1
|
||||||
|
fast: 0 # 25% to 100% of original time
|
||||||
|
normal: 0 # 50 % to 200% of original time
|
||||||
|
slow: 0 # 100% to 400% of original time
|
||||||
|
chaos: 0 # 25% to 400% of original time
|
||||||
|
max_science_pack:
|
||||||
|
automation_science_pack: 0
|
||||||
|
logistic_science_pack: 0
|
||||||
|
military_science_pack: 0
|
||||||
|
chemical_science_pack: 0
|
||||||
|
production_science_pack: 0
|
||||||
|
utility_science_pack: 0
|
||||||
|
space_science_pack: 1
|
||||||
|
tech_cost:
|
||||||
|
very_easy : 0
|
||||||
|
easy : 0
|
||||||
|
kind : 0
|
||||||
|
normal : 1
|
||||||
|
hard : 0
|
||||||
|
very_hard : 0
|
||||||
|
insane : 0
|
||||||
|
free_samples:
|
||||||
|
none: 1
|
||||||
|
single_craft: 0
|
||||||
|
half_stack: 0
|
||||||
|
stack: 0
|
||||||
|
visibility:
|
||||||
|
none: 0
|
||||||
|
sending: 1
|
||||||
|
random_tech_ingredients:
|
||||||
|
on: 1
|
||||||
|
off: 0
|
||||||
|
starting_items:
|
||||||
|
burner-mining-drill: 19
|
||||||
|
stone-furnace: 19
|
||||||
|
Minecraft:
|
||||||
|
advancement_goal: # Number of advancements required (out of 92 total) to spawn the Ender Dragon and complete the game.
|
||||||
|
few: 0 # 30 advancements
|
||||||
|
normal: 1 # 50
|
||||||
|
many: 0 # 70
|
||||||
|
combat_difficulty: # Modifies the level of items logically required for exploring dangerous areas and fighting bosses.
|
||||||
|
easy: 0
|
||||||
|
normal: 1
|
||||||
|
hard: 0
|
||||||
|
include_hard_advancements: # Junk-fills certain RNG-reliant or tedious advancements with XP rewards.
|
||||||
|
on: 0
|
||||||
|
off: 1
|
||||||
|
include_insane_advancements: # Junk-fills extremely difficult advancements; this is only How Did We Get Here? and Adventuring Time.
|
||||||
|
on: 0
|
||||||
|
off: 1
|
||||||
|
include_postgame_advancements: # Some advancements require defeating the Ender Dragon; this will junk-fill them so you won't have to finish to send some items.
|
||||||
|
on: 0
|
||||||
|
off: 1
|
||||||
|
shuffle_structures: # CURRENTLY DISABLED; enables shuffling of villages, outposts, fortresses, bastions, and end cities.
|
||||||
|
on: 0
|
||||||
|
off: 1
|
||||||
|
A Link to the Past:
|
||||||
|
### Logic Section ###
|
||||||
|
glitches_required: # Determine the logic required to complete the seed
|
||||||
|
none: 50 # No glitches required
|
||||||
|
minor_glitches: 0 # Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic
|
||||||
|
overworld_glitches: 0 # Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches
|
||||||
|
no_logic: 0 # Your own items are placed with no regard to any logic; such as your Fire Rod can be on your Trinexx.
|
||||||
|
# Other players items are placed into your world under OWG logic
|
||||||
|
dark_room_logic: # Logic for unlit dark rooms
|
||||||
|
lamp: 50 # require the Lamp for these rooms to be considered accessible.
|
||||||
|
torches: 0 # in addition to lamp, allow the fire rod and presence of easily accessible torches for access
|
||||||
|
none: 0 # all dark rooms are always considered doable, meaning this may force completion of rooms in complete darkness
|
||||||
|
restrict_dungeon_item_on_boss: # aka ambrosia boss items
|
||||||
|
on: 0 # prevents unshuffled compasses, maps and keys to be boss drops, they can still drop keysanity and other players' items
|
||||||
|
off: 50
|
||||||
|
### End of Logic Section ###
|
||||||
|
map_shuffle: # Shuffle dungeon maps into the world and other dungeons, including other players' worlds
|
||||||
|
on: 0
|
||||||
|
off: 50
|
||||||
|
compass_shuffle: # Shuffle compasses into the world and other dungeons, including other players' worlds
|
||||||
|
on: 0
|
||||||
|
off: 50
|
||||||
|
smallkey_shuffle: # Shuffle small keys into the world and other dungeons, including other players' worlds
|
||||||
|
on: 0
|
||||||
|
universal: 0 # allows small keys to be used in any dungeon and adds shops to buy more
|
||||||
|
off: 50
|
||||||
|
bigkey_shuffle: # Shuffle big keys into the world and other dungeons, including other players' worlds
|
||||||
|
on: 0
|
||||||
|
off: 50
|
||||||
|
local_keys: # Keep small keys and big keys local to your world
|
||||||
|
on: 0
|
||||||
|
off: 50
|
||||||
|
dungeon_items: # Alternative to the 4 shuffles and local_keys above this, does nothing until the respective 4 shuffles and local_keys above are deleted
|
||||||
|
mc: 0 # Shuffle maps and compasses
|
||||||
|
none: 50 # Shuffle none of the 4
|
||||||
|
mcsb: 0 # Shuffle all of the 4, any combination of m, c, s and b will shuffle the respective item, or not if it's missing, so you can add more options here
|
||||||
|
lmcsb: 0 # Like mcsb above, but with keys kept local to your world. l is what makes your keys local, or not if it's missing
|
||||||
|
ub: 0 # universal small keys and shuffled big keys
|
||||||
|
# you can add more combos of these letters here
|
||||||
|
dungeon_counters:
|
||||||
|
on: 0 # Always display amount of items checked in a dungeon
|
||||||
|
pickup: 50 # Show when compass is picked up
|
||||||
|
default: 0 # Show when compass is picked up if the compass itself is shuffled
|
||||||
|
off: 0 # Never show item count in dungeons
|
||||||
|
progressive: # Enable or disable progressive items (swords, shields, bow)
|
||||||
|
on: 50 # All items are progressive
|
||||||
|
off: 0 # No items are progressive
|
||||||
|
random: 0 # Randomly decides for all items. Swords could be progressive, shields might not be
|
||||||
|
entrance_shuffle:
|
||||||
|
none: 50 # Vanilla game map. All entrances and exits lead to their original locations. You probably want this option
|
||||||
|
dungeonssimple: 0 # Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon
|
||||||
|
dungeonsfull: 0 # Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle can be 4 different dungeons, but keep dungeons to a specific world
|
||||||
|
dungeonscrossed: 0 # like dungeonsfull, but allow cross-world traversal through a dungeon. Warning: May force repeated dungeon traversal
|
||||||
|
simple: 0 # Entrances are grouped together before being randomized. Simple uses the most strict grouping rules
|
||||||
|
restricted: 0 # Less strict than simple
|
||||||
|
full: 0 # Less strict than restricted
|
||||||
|
crossed: 0 # Less strict than full
|
||||||
|
insanity: 0 # Very few grouping rules. Good luck
|
||||||
|
# you can also define entrance shuffle seed, like so:
|
||||||
|
crossed-1000: 0 # using this method, you can have the same layout as another player and share entrance information
|
||||||
|
# however, many other settings like logic, world state, retro etc. may affect the shuffle result as well.
|
||||||
|
crossed-group-myfriends: 0 # using this method, everyone with "group-myfriends" will share the same seed
|
||||||
|
goals:
|
||||||
|
ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon
|
||||||
|
crystals: 0 # Only killing Ganon is required. However, items may still be placed in GT
|
||||||
|
bosses: 0 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
|
||||||
|
pedestal: 0 # Pull the Triforce from the Master Sword pedestal
|
||||||
|
ganon_pedestal: 0 # Pull the Master Sword pedestal, then kill Ganon
|
||||||
|
triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle
|
||||||
|
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
||||||
|
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
||||||
|
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
||||||
|
ice_rod_hunt: 0 # You start with everything needed to 216 the seed. Find the Ice rod, then kill Trinexx at Turtle rock.
|
||||||
|
open_pyramid:
|
||||||
|
goal: 50 # Opens the pyramid if the goal requires you to kill Ganon, unless the goal is Slow Ganon or All Dungeons
|
||||||
|
auto: 0 # Same as Goal, but also is closed if holes are shuffled and ganon is part of the shuffle pool
|
||||||
|
yes: 0 # Pyramid hole is always open. Ganon's vulnerable condition is still required before he can he hurt
|
||||||
|
no: 0 # Pyramid hole is always closed until you defeat Agahnim atop Ganon's Tower
|
||||||
|
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
||||||
|
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
||||||
|
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
||||||
|
available: 50 # available = triforce_pieces_available
|
||||||
|
triforce_pieces_extra: # Set to how many extra triforces pieces are available to collect in the world.
|
||||||
|
# Format "pieces: chance"
|
||||||
|
0: 0
|
||||||
|
5: 50
|
||||||
|
10: 50
|
||||||
|
15: 0
|
||||||
|
20: 0
|
||||||
|
triforce_pieces_percentage: # Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world.
|
||||||
|
# Format "pieces: chance"
|
||||||
|
100: 0 #No extra
|
||||||
|
150: 50 #Half the required will be added as extra
|
||||||
|
200: 0 #There are the double of the required ones available.
|
||||||
|
triforce_pieces_available: # Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1
|
||||||
|
# Format "pieces: chance"
|
||||||
|
25: 0
|
||||||
|
30: 50
|
||||||
|
40: 0
|
||||||
|
50: 0
|
||||||
|
triforce_pieces_required: # Set to how many out of X triforce pieces you need to win the game in a triforce hunt. Default is 20. Max is 90, Min is 1
|
||||||
|
# Format "pieces: chance"
|
||||||
|
15: 0
|
||||||
|
20: 50
|
||||||
|
30: 0
|
||||||
|
40: 0
|
||||||
|
50: 0
|
||||||
|
crystals_needed_for_gt: # Crystals required to open GT
|
||||||
|
0: 0
|
||||||
|
1: 0
|
||||||
|
2: 0
|
||||||
|
3: 0
|
||||||
|
4: 0
|
||||||
|
5: 0
|
||||||
|
6: 0
|
||||||
|
7: 0
|
||||||
|
random: 0
|
||||||
|
random-low: 50 # any valid number, weighted towards the lower end
|
||||||
|
random-middle: 0 # any valid number, weighted towards the central range
|
||||||
|
random-high: 0 # any valid number, weighted towards the higher end
|
||||||
|
crystals_needed_for_ganon: # Crystals required to hurt Ganon
|
||||||
|
0: 0
|
||||||
|
1: 0
|
||||||
|
2: 0
|
||||||
|
3: 0
|
||||||
|
4: 0
|
||||||
|
5: 0
|
||||||
|
6: 0
|
||||||
|
7: 0
|
||||||
|
random: 0
|
||||||
|
random-low: 0
|
||||||
|
random-middle: 0
|
||||||
|
random-high: 50
|
||||||
mode:
|
mode:
|
||||||
- inverted # Never play inverted seeds
|
standard: 50 # Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary
|
||||||
|
open: 50 # Begin the game from your choice of Link's House or the Sanctuary
|
||||||
|
inverted: 0 # Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered
|
||||||
retro:
|
retro:
|
||||||
- on # Never play retro seeds
|
on: 0 # you must buy a quiver to use the bow, take-any caves and an old-man cave are added to the world. You may need to find your sword from the old man's cave
|
||||||
|
off: 50
|
||||||
|
hints:
|
||||||
|
'on': 50 # Hint tiles sometimes give item location hints
|
||||||
|
'off': 0 # Hint tiles provide gameplay tips
|
||||||
swordless:
|
swordless:
|
||||||
- on # Never play a swordless seed
|
on: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change
|
||||||
linked_options:
|
off: 1
|
||||||
- name: crosskeys
|
item_pool:
|
||||||
options: # These overwrite earlier options if the percentage chance triggers
|
easy: 0 # Doubled upgrades, progressives, and etc
|
||||||
entrance_shuffle: crossed
|
normal: 50 # Item availability remains unchanged from vanilla game
|
||||||
bigkey_shuffle: true
|
hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless)
|
||||||
compass_shuffle: true
|
expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless)
|
||||||
map_shuffle: true
|
item_functionality:
|
||||||
smallkey_shuffle: true
|
easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere.
|
||||||
percentage: 0 # Set this to the percentage chance you want crosskeys
|
normal: 50 # Vanilla item functionality
|
||||||
- name: localcrosskeys
|
hard: 0 # Reduced helpfulness of items (potions less effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs do not stun, silvers disabled outside ganon)
|
||||||
options: # These overwrite earlier options if the percentage chance triggers
|
expert: 0 # Vastly reduces the helpfulness of items (potions barely effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs and hookshot do not stun, silvers disabled outside ganon)
|
||||||
entrance_shuffle: crossed
|
tile_shuffle: # Randomize the tile layouts in flying tile rooms
|
||||||
bigkey_shuffle: true
|
on: 0
|
||||||
compass_shuffle: true
|
off: 50
|
||||||
map_shuffle: true
|
misery_mire_medallion: # required medallion to open Misery Mire front entrance
|
||||||
smallkey_shuffle: true
|
random: 50
|
||||||
local_items: # Forces keys to be local to your own world
|
Ether: 0
|
||||||
- "Small Keys"
|
Bombos: 0
|
||||||
- "Big Keys"
|
Quake: 0
|
||||||
percentage: 0 # Set this to the percentage chance you want local crosskeys
|
turtle_rock_medallion: # required medallion to open Turtle Rock front entrance
|
||||||
- name: enemizer
|
random: 50
|
||||||
options:
|
Ether: 0
|
||||||
boss_shuffle: # Subchances can be injected too, which then get rolled
|
Bombos: 0
|
||||||
basic: 1
|
Quake: 0
|
||||||
full: 1
|
### Enemizer Section ###
|
||||||
chaos: 1
|
boss_shuffle:
|
||||||
singularity: 1
|
none: 50 # Vanilla bosses
|
||||||
enemy_damage:
|
basic: 0 # Existing bosses except Ganon and Agahnim are shuffled throughout dungeons
|
||||||
shuffled: 1
|
full: 0 # 3 bosses can occur twice
|
||||||
random: 1
|
chaos: 0 # Any boss can appear any amount of times
|
||||||
enemy_health:
|
singularity: 0 # Picks a boss, tries to put it everywhere that works, if there's spaces remaining it picks a boss to fill those
|
||||||
easy: 1
|
enemy_shuffle: # Randomize enemy placement
|
||||||
hard: 1
|
on: 0
|
||||||
expert: 1
|
off: 50
|
||||||
percentage: 0 # Set this to the percentage chance you want enemizer
|
killable_thieves: # Make thieves killable
|
||||||
# triggers that replace options upon rolling certain options
|
on: 0 # Usually turned on together with enemy_shuffle to make annoying thief placement more manageable
|
||||||
legacy_weapons: # this is not an actual option, just a set of weights to trigger from
|
off: 50
|
||||||
trigger_disabled: 50
|
bush_shuffle: # Randomize the chance that bushes have enemies and the enemies under said bush
|
||||||
randomized: 0 # Swords are placed randomly throughout the world
|
on: 0
|
||||||
assured: 0 # Begin with a sword, the rest are placed randomly throughout the world
|
off: 50
|
||||||
vanilla: 0 # Swords are placed in vanilla locations in your own game (Uncle, Pyramid Fairy, Smiths, Pedestal)
|
enemy_damage:
|
||||||
swordless: 0 # swordless mode
|
default: 50 # Vanilla enemy damage
|
||||||
triggers:
|
shuffled: 0 # Enemies deal 0 to 4 hearts and armor helps
|
||||||
# trigger block for legacy weapons mode, to enable these add weights to legacy_weapons
|
random: 0 # Enemies deal 0 to 8 hearts and armor just reshuffles the damage
|
||||||
- option_name: legacy_weapons
|
enemy_health:
|
||||||
option_result: randomized
|
default: 50 # Vanilla enemy HP
|
||||||
options:
|
easy: 0 # Enemies have reduced health
|
||||||
swordless: off
|
hard: 0 # Enemies have increased health
|
||||||
- option_name: legacy_weapons
|
expert: 0 # Enemies have greatly increased health
|
||||||
option_result: assured
|
pot_shuffle:
|
||||||
options:
|
'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile
|
||||||
swordless: off
|
'off': 50 # Default pot item locations
|
||||||
startinventory:
|
### End of Enemizer Section ###
|
||||||
Progressive Sword: 1
|
beemizer: # Remove items from the global item pool and replace them with single bees (fill bottles) and bee traps
|
||||||
- option_name: legacy_weapons
|
0: 50 # No bee traps are placed
|
||||||
option_result: vanilla
|
1: 0 # 25% of rupees, bombs and arrows are replaced with bees, of which 60% are traps and 40% single bees
|
||||||
options:
|
2: 0 # 50% of rupees, bombs and arrows are replaced with bees, of which 70% are traps and 30% single bees
|
||||||
swordless: off
|
3: 0 # 75% of rupees, bombs and arrows are replaced with bees, of which 80% are traps and 20% single bees
|
||||||
plando_items:
|
4: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 90% are traps and 10% single bees
|
||||||
- items:
|
5: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 100% are traps and 0% single bees
|
||||||
Progressive Sword: 4
|
### Shop Settings ###
|
||||||
locations:
|
shop_item_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl)
|
||||||
- Master Sword Pedestal
|
0: 50
|
||||||
- Pyramid Fairy - Left
|
5: 0
|
||||||
- Blacksmith
|
15: 0
|
||||||
- Link's Uncle
|
30: 0
|
||||||
- option_name: legacy_weapons
|
random: 0 # 0 to 30 evenly distributed
|
||||||
option_result: swordless
|
shop_shuffle:
|
||||||
options:
|
none: 50
|
||||||
swordless: on
|
g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops
|
||||||
# end of legacy weapons block
|
f: 0 # Generate new default inventories for every shop independently
|
||||||
- option_name: enemy_damage # targets enemy_damage
|
i: 0 # Shuffle default inventories of the shops around
|
||||||
option_result: shuffled # if it rolls shuffled
|
p: 0 # Randomize the prices of the items in shop inventories
|
||||||
percentage: 0 # AND has a 0 percent chance (meaning this is default disabled, just to show how it works)
|
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
||||||
options: # then inserts these options
|
w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too
|
||||||
swordless: off
|
ip: 0 # Shuffle inventories and randomize prices
|
||||||
### door rando only options (not supported at all yet on this branch) ###
|
fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool
|
||||||
door_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise
|
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
||||||
vanilla: 50 # Everything should be like in vanilla
|
# You can add more combos
|
||||||
basic: 0 # Dungeons are shuffled within themselves
|
### End of Shop Section ###
|
||||||
crossed: 0 # Dungeons are shuffled across each other
|
shuffle_prizes: # aka drops
|
||||||
# you can also define door shuffle seed, like so:
|
none: 0 # do not shuffle prize packs
|
||||||
crossed-1000: 0 # using this method, you can have the same dungeon layout as another player and share dungeon layout information.
|
g: 50 # shuffle "general" prize packs, as in enemy, tree pull, dig etc.
|
||||||
# however, other settings like intensity, universal keys, etc. may affect the shuffle result as well.
|
b: 0 # shuffle "bonk" prize packs
|
||||||
crossed-group-myfriends: 0 # using this method, everyone with "group-myfriends" will share the same seed
|
bg: 0 # shuffle both
|
||||||
intensity: # Only available if the host uses the doors branch, it is ignored otherwise
|
timer:
|
||||||
1: 50 # Shuffles normal doors and spiral staircases
|
none: 50 # No timer will be displayed.
|
||||||
2: 0 # And shuffles open edges and straight staircases
|
timed: 0 # Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.
|
||||||
3: 0 # And shuffles dungeon lobbies
|
timed_ohko: 0 # Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.
|
||||||
random: 0 # Picks one of those at random
|
ohko: 0 # Timer always at zero. Permanent OHKO.
|
||||||
key_drop_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise
|
timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.
|
||||||
on: 0 # Enables the small keys dropped by enemies or under pots, and the big key dropped by the Ball & Chain guard to be shuffled into the pool. This extends the number of checks to 249.
|
display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool.
|
||||||
off: 50
|
countdown_start_time: # For timed_ohko and timed_countdown timer modes, the amount of time in minutes to start with
|
||||||
experimental: # Only available if the host uses the doors branch, it is ignored otherwise
|
0: 0 # For timed_ohko, starts in OHKO mode when starting the game
|
||||||
on: 0 # Enables experimental features.
|
10: 50
|
||||||
off: 50
|
20: 0
|
||||||
debug: # Only available if the host uses the doors branch, it is ignored otherwise
|
30: 0
|
||||||
on: 0 # Enables debugging features. Currently, these are the Item collection counter. (overwrites total triforce pieces) and Castle Gate closed indicator.
|
60: 0
|
||||||
off: 50
|
red_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock
|
||||||
### end of door rando only options ###
|
-2: 50
|
||||||
rom:
|
1: 0
|
||||||
|
blue_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock
|
||||||
|
1: 0
|
||||||
|
2: 50
|
||||||
|
green_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock
|
||||||
|
4: 50
|
||||||
|
10: 0
|
||||||
|
15: 0
|
||||||
|
glitch_boots:
|
||||||
|
on: 50 # Start with Pegasus Boots in any glitched logic mode that makes use of them
|
||||||
|
off: 0
|
||||||
|
# rom options section
|
||||||
random_sprite_on_event: # An alternative to specifying randomonhit / randomonexit / etc... in sprite down below.
|
random_sprite_on_event: # An alternative to specifying randomonhit / randomonexit / etc... in sprite down below.
|
||||||
enabled: # If enabled, sprite down below is ignored completely, (although it may become the sprite pool)
|
enabled: # If enabled, sprite down below is ignored completely, (although it may become the sprite pool)
|
||||||
on: 0
|
on: 0
|
||||||
|
@ -519,7 +417,7 @@ rom:
|
||||||
randomonslash: 0 # Random sprite on sword slashes
|
randomonslash: 0 # Random sprite on sword slashes
|
||||||
randomonitem: 0 # Random sprite on getting items.
|
randomonitem: 0 # Random sprite on getting items.
|
||||||
randomonbonk: 0 # Random sprite on bonk.
|
randomonbonk: 0 # Random sprite on bonk.
|
||||||
# You can combine these events like this. randomonhit-enter-exit if you want it on hit, enter, exit.
|
# You can combine these events like this. randomonhit-enter-exit if you want it on hit, enter, exit.
|
||||||
randomonall: 0 # Random sprite on any and all currently supported events. Refer to above for the supported events.
|
randomonall: 0 # Random sprite on any and all currently supported events. Refer to above for the supported events.
|
||||||
Link: 50 # To add other sprites: open the gui/Creator, go to adjust, select a sprite and write down the name the gui calls it
|
Link: 50 # To add other sprites: open the gui/Creator, go to adjust, select a sprite and write down the name the gui calls it
|
||||||
disablemusic: # If "on", all in-game music will be disabled
|
disablemusic: # If "on", all in-game music will be disabled
|
||||||
|
@ -605,3 +503,102 @@ rom:
|
||||||
dizzy: 0
|
dizzy: 0
|
||||||
sick: 0
|
sick: 0
|
||||||
puke: 0
|
puke: 0
|
||||||
|
|
||||||
|
# triggers that replace options upon rolling certain options
|
||||||
|
legacy_weapons: # this is not an actual option, just a set of weights to trigger from
|
||||||
|
trigger_disabled: 50
|
||||||
|
randomized: 0 # Swords are placed randomly throughout the world
|
||||||
|
assured: 0 # Begin with a sword, the rest are placed randomly throughout the world
|
||||||
|
vanilla: 0 # Swords are placed in vanilla locations in your own game (Uncle, Pyramid Fairy, Smiths, Pedestal)
|
||||||
|
swordless: 0 # swordless mode
|
||||||
|
# meta_ignore, linked_options and triggers work for any game
|
||||||
|
meta_ignore: # Nullify options specified in the meta.yaml file. Adding an option here guarantees it will not occur in your seed, even if the .yaml file specifies it
|
||||||
|
mode:
|
||||||
|
- inverted # Never play inverted seeds
|
||||||
|
retro:
|
||||||
|
- on # Never play retro seeds
|
||||||
|
swordless:
|
||||||
|
- on # Never play a swordless seed
|
||||||
|
|
||||||
|
linked_options:
|
||||||
|
- name: crosskeys
|
||||||
|
options: # These overwrite earlier options if the percentage chance triggers
|
||||||
|
A Link to the Past:
|
||||||
|
entrance_shuffle: crossed
|
||||||
|
bigkey_shuffle: true
|
||||||
|
compass_shuffle: true
|
||||||
|
map_shuffle: true
|
||||||
|
smallkey_shuffle: true
|
||||||
|
percentage: 0 # Set this to the percentage chance you want crosskeys
|
||||||
|
- name: localcrosskeys
|
||||||
|
options: # These overwrite earlier options if the percentage chance triggers
|
||||||
|
A Link to the Past:
|
||||||
|
entrance_shuffle: crossed
|
||||||
|
bigkey_shuffle: true
|
||||||
|
compass_shuffle: true
|
||||||
|
map_shuffle: true
|
||||||
|
smallkey_shuffle: true
|
||||||
|
local_items: # Forces keys to be local to your own world
|
||||||
|
- "Small Keys"
|
||||||
|
- "Big Keys"
|
||||||
|
percentage: 0 # Set this to the percentage chance you want local crosskeys
|
||||||
|
- name: enemizer
|
||||||
|
options:
|
||||||
|
A Link to the Past:
|
||||||
|
boss_shuffle: # Subchances can be injected too, which then get rolled
|
||||||
|
basic: 1
|
||||||
|
full: 1
|
||||||
|
chaos: 1
|
||||||
|
singularity: 1
|
||||||
|
enemy_damage:
|
||||||
|
shuffled: 1
|
||||||
|
random: 1
|
||||||
|
enemy_health:
|
||||||
|
easy: 1
|
||||||
|
hard: 1
|
||||||
|
expert: 1
|
||||||
|
percentage: 0 # Set this to the percentage chance you want enemizer
|
||||||
|
triggers:
|
||||||
|
# trigger block for legacy weapons mode, to enable these add weights to legacy_weapons
|
||||||
|
- option_name: legacy_weapons
|
||||||
|
option_result: randomized
|
||||||
|
option_category: A Link to the Past
|
||||||
|
options:
|
||||||
|
A Link to the Past:
|
||||||
|
swordless: off
|
||||||
|
- option_name: legacy_weapons
|
||||||
|
option_result: assured
|
||||||
|
option_category: A Link to the Past
|
||||||
|
options:
|
||||||
|
A Link to the Past:
|
||||||
|
swordless: off
|
||||||
|
start_inventory:
|
||||||
|
Progressive Sword: 1
|
||||||
|
- option_name: legacy_weapons
|
||||||
|
option_result: vanilla
|
||||||
|
option_category: A Link to the Past
|
||||||
|
options:
|
||||||
|
A Link to the Past:
|
||||||
|
swordless: off
|
||||||
|
plando_items:
|
||||||
|
- items:
|
||||||
|
Progressive Sword: 4
|
||||||
|
locations:
|
||||||
|
- Master Sword Pedestal
|
||||||
|
- Pyramid Fairy - Left
|
||||||
|
- Blacksmith
|
||||||
|
- Link's Uncle
|
||||||
|
- option_name: legacy_weapons
|
||||||
|
option_result: swordless
|
||||||
|
option_category: A Link to the Past
|
||||||
|
options:
|
||||||
|
A Link to the Past:
|
||||||
|
swordless: on
|
||||||
|
# end of legacy weapons block
|
||||||
|
- option_name: enemy_damage # targets enemy_damage
|
||||||
|
option_category: A Link to the Past
|
||||||
|
option_result: shuffled # if it rolls shuffled
|
||||||
|
percentage: 0 # AND has a 0 percent chance (meaning this is default disabled, just to show how it works)
|
||||||
|
options: # then inserts these options
|
||||||
|
A Link to the Past:
|
||||||
|
swordless: off
|
|
@ -8,11 +8,14 @@ from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import create_regions
|
from worlds.alttp.Regions import create_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
|
|
||||||
class TestDungeon(unittest.TestCase):
|
class TestDungeon(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.starting_regions = [] # Where to start exploring
|
self.starting_regions = [] # Where to start exploring
|
||||||
self.remove_exits = [] # Block dungeon exits
|
self.remove_exits = [] # Block dungeon exits
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
|
|
|
@ -11,6 +11,8 @@ class TestVanilla(TestBase):
|
||||||
self.world.game[1] = "Hollow Knight"
|
self.world.game[1] = "Hollow Knight"
|
||||||
import Options
|
import Options
|
||||||
for hk_option in Options.hollow_knight_randomize_options:
|
for hk_option in Options.hollow_knight_randomize_options:
|
||||||
getattr(self.world, hk_option)[1] = True
|
setattr(self.world, hk_option, {1: True})
|
||||||
|
for hk_option, option in Options.hollow_knight_skip_options.items():
|
||||||
|
setattr(self.world, hk_option, {1: option.default})
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
gen_hollow(self.world, 1)
|
gen_hollow(self.world, 1)
|
|
@ -8,11 +8,13 @@ from worlds.alttp.Regions import mark_light_world_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
class TestInverted(TestBase):
|
class TestInverted(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
self.world.mode[1] = "inverted"
|
self.world.mode[1] = "inverted"
|
||||||
create_inverted_regions(self.world, 1)
|
create_inverted_regions(self.world, 1)
|
||||||
|
|
|
@ -8,11 +8,13 @@ from worlds.alttp.Regions import mark_light_world_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
class TestInvertedMinor(TestBase):
|
class TestInvertedMinor(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.world.mode[1] = "inverted"
|
self.world.mode[1] = "inverted"
|
||||||
self.world.logic[1] = "minorglitches"
|
self.world.logic[1] = "minorglitches"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
|
|
|
@ -8,11 +8,14 @@ from worlds.alttp.Regions import mark_light_world_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
|
|
||||||
class TestInvertedOWG(TestBase):
|
class TestInvertedOWG(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.world.logic[1] = "owglitches"
|
self.world.logic[1] = "owglitches"
|
||||||
self.world.mode[1] = "inverted"
|
self.world.mode[1] = "inverted"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
|
|
|
@ -8,11 +8,13 @@ from worlds.alttp.Regions import create_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
class TestMinor(TestBase):
|
class TestMinor(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.world.logic[1] = "minorglitches"
|
self.world.logic[1] = "minorglitches"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
|
|
|
@ -8,11 +8,14 @@ from worlds.alttp.Regions import create_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
|
|
||||||
class TestVanillaOWG(TestBase):
|
class TestVanillaOWG(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
self.world.logic[1] = "owglitches"
|
self.world.logic[1] = "owglitches"
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
|
|
|
@ -8,11 +8,13 @@ from worlds.alttp.Regions import create_regions
|
||||||
from worlds.alttp.Shops import create_shops
|
from worlds.alttp.Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
from Options import alttp_options
|
||||||
|
|
||||||
class TestVanilla(TestBase):
|
class TestVanilla(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1)
|
self.world = MultiWorld(1)
|
||||||
|
for option_name, option in alttp_options.items():
|
||||||
|
setattr(self.world, option_name, {1: option.default})
|
||||||
self.world.logic[1] = "noglitches"
|
self.world.logic[1] = "noglitches"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
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."""
|
||||||
|
|
||||||
|
world: MultiWorld
|
||||||
|
player: int
|
||||||
|
|
||||||
|
def __init__(self, world: MultiWorld, player: int):
|
||||||
|
self.world = world
|
||||||
|
self.player = player
|
||||||
|
|
||||||
|
# overwritable methods that get called by Main.py
|
||||||
|
def generate_basic(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_rules(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_regions(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def generate_output(self):
|
||||||
|
pass
|
|
@ -196,22 +196,6 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
The dungeon variants only mix up dungeons and keep the rest of
|
The dungeon variants only mix up dungeons and keep the rest of
|
||||||
the overworld vanilla.
|
the overworld vanilla.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
|
||||||
help='''\
|
|
||||||
How many crystals are needed to defeat ganon. Any other
|
|
||||||
requirements for ganon for the selected goal still apply.
|
|
||||||
This setting does not apply when the all dungeons goal is
|
|
||||||
selected. (default: %(default)s)
|
|
||||||
Random: Picks a random value between 0 and 7 (inclusive).
|
|
||||||
0-7: Number of crystals needed
|
|
||||||
''')
|
|
||||||
parser.add_argument('--crystals_gt', default=defval('7'), const='7', nargs='?',
|
|
||||||
choices=['0', '1', '2', '3', '4', '5', '6', '7'],
|
|
||||||
help='''\
|
|
||||||
How many crystals are needed to open GT. For inverted mode
|
|
||||||
this applies to the castle tower door instead. (default: %(default)s)
|
|
||||||
0-7: Number of crystals needed
|
|
||||||
''')
|
|
||||||
parser.add_argument('--open_pyramid', default=defval('auto'), help='''\
|
parser.add_argument('--open_pyramid', default=defval('auto'), help='''\
|
||||||
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it.
|
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it.
|
||||||
Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon.
|
Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon.
|
||||||
|
@ -337,11 +321,6 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
u: shuffle capacity upgrades into the item pool
|
u: shuffle capacity upgrades into the item pool
|
||||||
w: consider witch's hut like any other shop and shuffle/randomize it too
|
w: consider witch's hut like any other shop and shuffle/randomize it too
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--shop_shuffle_slots', default=defval(0),
|
|
||||||
type=lambda value: min(max(int(value), 1), 96),
|
|
||||||
help='''
|
|
||||||
Maximum amount of shop slots able to be filled by items from the item pool.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
||||||
parser.add_argument('--sprite_pool', help='''\
|
parser.add_argument('--sprite_pool', help='''\
|
||||||
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
||||||
|
@ -397,14 +376,14 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True)
|
playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True)
|
||||||
|
|
||||||
for name in ['logic', 'mode', 'swordless', 'goal', 'difficulty', 'item_functionality',
|
for name in ['logic', 'mode', 'swordless', 'goal', 'difficulty', 'item_functionality',
|
||||||
'shuffle', 'crystals_ganon', 'crystals_gt', 'open_pyramid', 'timer',
|
'shuffle', 'open_pyramid', 'timer',
|
||||||
'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time',
|
'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
||||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'heartbeep', "progression_balancing", "triforce_pieces_available",
|
'heartbeep', "progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
|
"triforce_pieces_required", "shop_shuffle",
|
||||||
"required_medallions", "start_hints",
|
"required_medallions", "start_hints",
|
||||||
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
||||||
'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||||
|
|
|
@ -241,8 +241,6 @@ def generate_itempool(world, player: int):
|
||||||
else:
|
else:
|
||||||
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if world.goal[player] == 'icerodhunt':
|
if world.goal[player] == 'icerodhunt':
|
||||||
world.progression_balancing[player] = False
|
world.progression_balancing[player] = False
|
||||||
loc = world.get_location('Turtle Rock - Boss', player)
|
loc = world.get_location('Turtle Rock - Boss', player)
|
||||||
|
@ -255,7 +253,6 @@ def generate_itempool(world, player: int):
|
||||||
logging.warning(f'Cannot guarantee that Trinexx is the boss of Turtle Rock for player {player}')
|
logging.warning(f'Cannot guarantee that Trinexx is the boss of Turtle Rock for player {player}')
|
||||||
loc.event = True
|
loc.event = True
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
forbid_items_for_player(loc, {'Red Pendant', 'Green Pendant', 'Blue Pendant', 'Crystal 5', 'Crystal 6'}, player)
|
|
||||||
itemdiff = difficulties[world.difficulty[player]]
|
itemdiff = difficulties[world.difficulty[player]]
|
||||||
itempool = []
|
itempool = []
|
||||||
itempool.extend(itemdiff.alwaysitems)
|
itempool.extend(itemdiff.alwaysitems)
|
||||||
|
|
|
@ -80,7 +80,7 @@ class LocalRom(object):
|
||||||
self.write_bytes(startaddress + i, bytearray(data))
|
self.write_bytes(startaddress + i, bytearray(data))
|
||||||
|
|
||||||
def encrypt(self, world, player):
|
def encrypt(self, world, player):
|
||||||
local_random = world.rom_seeds[player]
|
local_random = world.slot_seeds[player]
|
||||||
key = bytes(local_random.getrandbits(8 * 16).to_bytes(16, 'big'))
|
key = bytes(local_random.getrandbits(8 * 16).to_bytes(16, 'big'))
|
||||||
self.write_bytes(0x1800B0, bytearray(key))
|
self.write_bytes(0x1800B0, bytearray(key))
|
||||||
self.write_int16(0x180087, 1)
|
self.write_int16(0x180087, 1)
|
||||||
|
@ -384,7 +384,7 @@ def patch_enemizer(world, team: int, player: int, rom: LocalRom, enemizercli):
|
||||||
|
|
||||||
max_enemizer_tries = 5
|
max_enemizer_tries = 5
|
||||||
for i in range(max_enemizer_tries):
|
for i in range(max_enemizer_tries):
|
||||||
enemizer_seed = str(world.rom_seeds[player].randint(0, 999999999))
|
enemizer_seed = str(world.slot_seeds[player].randint(0, 999999999))
|
||||||
enemizer_command = [os.path.abspath(enemizercli),
|
enemizer_command = [os.path.abspath(enemizercli),
|
||||||
'--rom', randopatch_path,
|
'--rom', randopatch_path,
|
||||||
'--seed', enemizer_seed,
|
'--seed', enemizer_seed,
|
||||||
|
@ -414,7 +414,7 @@ def patch_enemizer(world, team: int, player: int, rom: LocalRom, enemizercli):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for j in range(i + 1, max_enemizer_tries):
|
for j in range(i + 1, max_enemizer_tries):
|
||||||
world.rom_seeds[player].randint(0, 999999999)
|
world.slot_seeds[player].randint(0, 999999999)
|
||||||
# Sacrifice all remaining random numbers that would have been used for unused enemizer tries.
|
# Sacrifice all remaining random numbers that would have been used for unused enemizer tries.
|
||||||
# This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness
|
# This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness
|
||||||
break
|
break
|
||||||
|
@ -760,7 +760,7 @@ def get_nonnative_item_sprite(game):
|
||||||
return game_to_id.get(game, 0x6B) # default to Power Star
|
return game_to_id.get(game, 0x6B) # default to Power Star
|
||||||
|
|
||||||
def patch_rom(world, rom, player, team, enemized):
|
def patch_rom(world, rom, player, team, enemized):
|
||||||
local_random = world.rom_seeds[player]
|
local_random = world.slot_seeds[player]
|
||||||
|
|
||||||
# progressive bow silver arrow hint hack
|
# progressive bow silver arrow hint hack
|
||||||
prog_bow_locs = world.find_items('Progressive Bow', player)
|
prog_bow_locs = world.find_items('Progressive Bow', player)
|
||||||
|
@ -885,7 +885,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
credits_total = 216
|
credits_total = 216
|
||||||
if world.retro[player]: # Old man cave and Take any caves will count towards collection rate.
|
if world.retro[player]: # Old man cave and Take any caves will count towards collection rate.
|
||||||
credits_total += 5
|
credits_total += 5
|
||||||
if world.shop_shuffle_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle.
|
if world.shop_item_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle.
|
||||||
credits_total += 30 if 'w' in world.shop_shuffle[player] else 27
|
credits_total += 30 if 'w' in world.shop_shuffle[player] else 27
|
||||||
|
|
||||||
rom.write_byte(0x187010, credits_total) # dynamic credits
|
rom.write_byte(0x187010, credits_total) # dynamic credits
|
||||||
|
@ -1643,7 +1643,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
rom.write_byte(0xFEE41, 0x2A) # preopen bombable exit
|
rom.write_byte(0xFEE41, 0x2A) # preopen bombable exit
|
||||||
|
|
||||||
if world.tile_shuffle[player]:
|
if world.tile_shuffle[player]:
|
||||||
tile_set = TileSet.get_random_tile_set(world.rom_seeds[player])
|
tile_set = TileSet.get_random_tile_set(world.slot_seeds[player])
|
||||||
rom.write_byte(0x4BA21, tile_set.get_speed())
|
rom.write_byte(0x4BA21, tile_set.get_speed())
|
||||||
rom.write_byte(0x4BA1D, tile_set.get_len())
|
rom.write_byte(0x4BA1D, tile_set.get_len())
|
||||||
rom.write_bytes(0x4BA2A, tile_set.get_bytes())
|
rom.write_bytes(0x4BA2A, tile_set.get_bytes())
|
||||||
|
@ -1705,7 +1705,7 @@ def write_custom_shops(rom, world, player):
|
||||||
slot = 0 if shop.type == ShopType.TakeAny else index
|
slot = 0 if shop.type == ShopType.TakeAny else index
|
||||||
if item is None:
|
if item is None:
|
||||||
break
|
break
|
||||||
if world.shop_shuffle_slots[player] or shop.type == ShopType.TakeAny:
|
if world.shop_item_slots[player] or shop.type == ShopType.TakeAny:
|
||||||
count_shop = (shop.region.name != 'Potion Shop' or 'w' in world.shop_shuffle[player]) and \
|
count_shop = (shop.region.name != 'Potion Shop' or 'w' in world.shop_shuffle[player]) and \
|
||||||
shop.region.name != 'Capacity Upgrade'
|
shop.region.name != 'Capacity Upgrade'
|
||||||
rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0)
|
rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0)
|
||||||
|
@ -1774,7 +1774,7 @@ def hud_format_text(text):
|
||||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options,
|
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options,
|
||||||
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
|
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
|
||||||
triforcehud: str = None):
|
triforcehud: str = None):
|
||||||
local_random = random if not world else world.rom_seeds[player]
|
local_random = random if not world else world.slot_seeds[player]
|
||||||
|
|
||||||
# enable instant item menu
|
# enable instant item menu
|
||||||
if fastmenu == 'instant':
|
if fastmenu == 'instant':
|
||||||
|
@ -2091,7 +2091,7 @@ def write_string_to_rom(rom, target, string):
|
||||||
|
|
||||||
|
|
||||||
def write_strings(rom, world, player, team):
|
def write_strings(rom, world, player, team):
|
||||||
local_random = world.rom_seeds[player]
|
local_random = world.slot_seeds[player]
|
||||||
|
|
||||||
tt = TextTable()
|
tt = TextTable()
|
||||||
tt.removeUnwantedText()
|
tt.removeUnwantedText()
|
||||||
|
|
|
@ -243,7 +243,7 @@ def create_shops(world, player: int):
|
||||||
else:
|
else:
|
||||||
dynamic_shop_slots = total_dynamic_shop_slots
|
dynamic_shop_slots = total_dynamic_shop_slots
|
||||||
|
|
||||||
num_slots = min(dynamic_shop_slots, max(0, int(world.shop_shuffle_slots[player]))) # 0 to 30
|
num_slots = min(dynamic_shop_slots, world.shop_item_slots[player])
|
||||||
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
|
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
|
||||||
world.random.shuffle(single_purchase_slots)
|
world.random.shuffle(single_purchase_slots)
|
||||||
|
|
||||||
|
|
|
@ -1,96 +1,10 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from BaseClasses import Location, Item
|
from BaseClasses import Location, Item
|
||||||
|
from ..AutoWorld import World
|
||||||
|
|
||||||
|
class ALTTPWorld(World):
|
||||||
#class ALTTPWorld(World):
|
game: str = "A Link to the Past"
|
||||||
# """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 ALttPLocation(Location):
|
class ALttPLocation(Location):
|
||||||
|
|
|
@ -60,21 +60,22 @@ def generate_mod(world: MultiWorld, player: int):
|
||||||
if location.address:
|
if location.address:
|
||||||
locations.append((location.name, location.item.name, location.item.player, location.item.advancement))
|
locations.append((location.name, location.item.name, location.item.player, location.item.advancement))
|
||||||
mod_name = f"AP-{world.seed_name}-P{player}-{world.player_names[player][0]}"
|
mod_name = f"AP-{world.seed_name}-P{player}-{world.player_names[player][0]}"
|
||||||
tech_cost = {0: 0.1,
|
tech_cost_scale = {0: 0.1,
|
||||||
1: 0.25,
|
1: 0.25,
|
||||||
2: 0.5,
|
2: 0.5,
|
||||||
3: 1,
|
3: 1,
|
||||||
4: 2,
|
4: 2,
|
||||||
5: 5,
|
5: 5,
|
||||||
6: 10}[world.tech_cost[player].value]
|
6: 10}[world.tech_cost[player].value]
|
||||||
|
|
||||||
template_data = {"locations": locations, "player_names": player_names, "tech_table": tech_table,
|
template_data = {"locations": locations, "player_names": player_names, "tech_table": tech_table,
|
||||||
"mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(),
|
"mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(),
|
||||||
"tech_cost_scale": tech_cost, "custom_data": world.custom_data[player],
|
"tech_cost_scale": tech_cost_scale, "custom_technologies": world.worlds[player].custom_technologies,
|
||||||
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites[player],
|
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites[player],
|
||||||
"rocket_recipe": rocket_recipes[world.max_science_pack[player].value],
|
"rocket_recipe": rocket_recipes[world.max_science_pack[player].value],
|
||||||
"slot_name": world.player_names[player][0], "seed_name": world.seed_name,
|
"slot_name": world.player_names[player][0], "seed_name": world.seed_name,
|
||||||
"starting_items": world.starting_items[player], "recipes": recipes,
|
"starting_items": world.starting_items[player], "recipes": recipes,
|
||||||
"random": world.random,
|
"random": world.slot_seeds[player],
|
||||||
"recipe_time_scale": recipe_time_scales[world.recipe_time[player].value]}
|
"recipe_time_scale": recipe_time_scales[world.recipe_time[player].value]}
|
||||||
|
|
||||||
for factorio_option in Options.factorio_options:
|
for factorio_option in Options.factorio_options:
|
||||||
|
|
|
@ -11,11 +11,13 @@ funnel_slice_sizes = {TechTreeLayout.option_small_funnels: 6,
|
||||||
TechTreeLayout.option_medium_funnels: 10,
|
TechTreeLayout.option_medium_funnels: 10,
|
||||||
TechTreeLayout.option_large_funnels: 15}
|
TechTreeLayout.option_large_funnels: 15}
|
||||||
|
|
||||||
def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]:
|
def get_shapes(factorio_world) -> Dict[str, List[str]]:
|
||||||
|
world = factorio_world.world
|
||||||
|
player = factorio_world.player
|
||||||
prerequisites: Dict[str, Set[str]] = {}
|
prerequisites: Dict[str, Set[str]] = {}
|
||||||
layout = world.tech_tree_layout[player].value
|
layout = world.tech_tree_layout[player].value
|
||||||
custom_technologies = world.custom_data[player]["custom_technologies"]
|
custom_technologies = factorio_world.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()
|
tech_names.sort()
|
||||||
world.random.shuffle(tech_names)
|
world.random.shuffle(tech_names)
|
||||||
|
|
||||||
|
@ -171,15 +173,14 @@ def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]:
|
||||||
|
|
||||||
elif layout in funnel_layers:
|
elif layout in funnel_layers:
|
||||||
slice_size = funnel_slice_sizes[layout]
|
slice_size = funnel_slice_sizes[layout]
|
||||||
|
|
||||||
world.random.shuffle(tech_names)
|
world.random.shuffle(tech_names)
|
||||||
tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
|
|
||||||
|
|
||||||
while len(tech_names) > slice_size:
|
while len(tech_names) > slice_size:
|
||||||
tech_names = tech_names[slice_size:]
|
tech_names = tech_names[slice_size:]
|
||||||
current_tech_names = tech_names[:slice_size]
|
current_tech_names = tech_names[:slice_size]
|
||||||
layer_size = funnel_layers[layout]
|
layer_size = funnel_layers[layout]
|
||||||
previous_slice = []
|
previous_slice = []
|
||||||
|
current_tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
|
||||||
for layer in range(funnel_layers[layout]):
|
for layer in range(funnel_layers[layout]):
|
||||||
slice = current_tech_names[:layer_size]
|
slice = current_tech_names[:layer_size]
|
||||||
current_tech_names = current_tech_names[layer_size:]
|
current_tech_names = current_tech_names[layer_size:]
|
||||||
|
|
|
@ -66,7 +66,7 @@ class CustomTechnology(Technology):
|
||||||
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
|
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
|
||||||
ingredients = origin.ingredients & allowed_packs
|
ingredients = origin.ingredients & allowed_packs
|
||||||
self.player = player
|
self.player = player
|
||||||
if world.random_tech_ingredients[player]:
|
if world.random_tech_ingredients[player] and origin.name not in world.worlds[player].static_nodes:
|
||||||
ingredients = list(ingredients)
|
ingredients = list(ingredients)
|
||||||
ingredients.sort() # deterministic sample
|
ingredients.sort() # deterministic sample
|
||||||
ingredients = world.random.sample(ingredients, world.random.randint(1, len(ingredients)))
|
ingredients = world.random.sample(ingredients, world.random.randint(1, len(ingredients)))
|
||||||
|
|
|
@ -1,81 +1,88 @@
|
||||||
|
from ..AutoWorld import World
|
||||||
|
|
||||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||||
from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies, \
|
from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies, \
|
||||||
all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes
|
all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes
|
||||||
from .Shapes import get_shapes
|
from .Shapes import get_shapes
|
||||||
|
from .Mod import generate_mod
|
||||||
|
|
||||||
|
|
||||||
def gen_factorio(world: MultiWorld, player: int):
|
class Factorio(World):
|
||||||
static_nodes = world._static_nodes = {"automation", "logistics"} # turn dynamic/option?
|
game: str = "Factorio"
|
||||||
victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value]))
|
static_nodes = {"automation", "logistics", "rocket-silo"}
|
||||||
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_basic(self):
|
||||||
|
victory_tech_names = get_rocket_requirements(
|
||||||
|
frozenset(rocket_recipes[self.world.max_science_pack[self.player].value]))
|
||||||
|
|
||||||
def factorio_create_regions(world: MultiWorld, player: int):
|
for tech_name, tech_id in tech_table.items():
|
||||||
menu = Region("Menu", None, "Menu", player)
|
tech_item = Item(tech_name, tech_name in advancement_technologies or tech_name in victory_tech_names,
|
||||||
crash = Entrance(player, "Crash Land", menu)
|
tech_id, self.player)
|
||||||
menu.exits.append(crash)
|
tech_item.game = "Factorio"
|
||||||
nauvis = Region("Nauvis", None, "Nauvis", player)
|
if tech_name in self.static_nodes:
|
||||||
nauvis.world = menu.world = world
|
self.world.get_location(tech_name, self.player).place_locked_item(tech_item)
|
||||||
|
else:
|
||||||
|
self.world.itempool.append(tech_item)
|
||||||
|
|
||||||
for tech_name, tech_id in tech_table.items():
|
def generate_output(self):
|
||||||
tech = Location(player, tech_name, tech_id, nauvis)
|
generate_mod(self.world, self.player)
|
||||||
nauvis.locations.append(tech)
|
|
||||||
tech.game = "Factorio"
|
def create_regions(self):
|
||||||
location = Location(player, "Rocket Launch", None, nauvis)
|
player = self.player
|
||||||
nauvis.locations.append(location)
|
menu = Region("Menu", None, "Menu", player)
|
||||||
event = Item("Victory", True, None, player)
|
crash = Entrance(player, "Crash Land", menu)
|
||||||
world.push_item(location, event, False)
|
menu.exits.append(crash)
|
||||||
location.event = location.locked = True
|
nauvis = Region("Nauvis", None, "Nauvis", player)
|
||||||
for ingredient in all_ingredient_names:
|
nauvis.world = menu.world = self.world
|
||||||
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)
|
nauvis.locations.append(location)
|
||||||
event = Item(f"Automated {ingredient}", True, None, player)
|
event = Item("Victory", True, None, player)
|
||||||
world.push_item(location, event, False)
|
self.world.push_item(location, event, False)
|
||||||
location.event = location.locked = True
|
location.event = location.locked = True
|
||||||
crash.connect(nauvis)
|
for ingredient in all_ingredient_names:
|
||||||
world.regions += [menu, nauvis]
|
location = Location(player, f"Automate {ingredient}", None, nauvis)
|
||||||
|
nauvis.locations.append(location)
|
||||||
|
event = Item(f"Automated {ingredient}", True, None, player)
|
||||||
|
self.world.push_item(location, event, False)
|
||||||
|
location.event = location.locked = True
|
||||||
|
crash.connect(nauvis)
|
||||||
|
self.world.regions += [menu, nauvis]
|
||||||
|
|
||||||
|
def set_rules(self):
|
||||||
|
world = self.world
|
||||||
|
player = self.player
|
||||||
|
self.custom_technologies = set_custom_technologies(self.world, self.player)
|
||||||
|
shapes = get_shapes(self)
|
||||||
|
if world.logic[player] != 'nologic':
|
||||||
|
from worlds.generic import Rules
|
||||||
|
for ingredient in all_ingredient_names:
|
||||||
|
location = world.get_location(f"Automate {ingredient}", player)
|
||||||
|
location.access_rule = lambda state, ingredient=ingredient: \
|
||||||
|
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
|
||||||
|
for tech_name, technology in self.custom_technologies.items():
|
||||||
|
location = world.get_location(tech_name, player)
|
||||||
|
Rules.set_rule(location, technology.build_rule(player))
|
||||||
|
prequisites = shapes.get(tech_name)
|
||||||
|
if prequisites:
|
||||||
|
locations = {world.get_location(requisite, player) for requisite in prequisites}
|
||||||
|
Rules.add_rule(location, lambda state,
|
||||||
|
locations=locations: all(state.can_reach(loc) for loc in locations))
|
||||||
|
# get all science pack technologies (but not the ability to craft them)
|
||||||
|
victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value]))
|
||||||
|
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
|
||||||
|
for technology in
|
||||||
|
victory_tech_names)
|
||||||
|
|
||||||
|
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||||
|
|
||||||
def set_custom_technologies(world: MultiWorld, player: int):
|
def set_custom_technologies(world: MultiWorld, player: int):
|
||||||
custom_technologies = {}
|
custom_technologies = {}
|
||||||
world_custom = getattr(world, "_custom_technologies", {})
|
|
||||||
world_custom[player] = custom_technologies
|
|
||||||
world._custom_technologies = world_custom
|
|
||||||
allowed_packs = world.max_science_pack[player].get_allowed_packs()
|
allowed_packs = world.max_science_pack[player].get_allowed_packs()
|
||||||
for technology_name, technology in technology_table.items():
|
for technology_name, technology in technology_table.items():
|
||||||
custom_technologies[technology_name] = technology.get_custom(world, allowed_packs, player)
|
custom_technologies[technology_name] = technology.get_custom(world, allowed_packs, player)
|
||||||
return custom_technologies
|
return custom_technologies
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world: MultiWorld, player: int, custom_technologies):
|
|
||||||
shapes = get_shapes(world, player)
|
|
||||||
if world.logic[player] != 'nologic':
|
|
||||||
from worlds.generic import Rules
|
|
||||||
for ingredient in all_ingredient_names:
|
|
||||||
location = world.get_location(f"Automate {ingredient}", player)
|
|
||||||
location.access_rule = lambda state, ingredient=ingredient: \
|
|
||||||
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
|
|
||||||
for tech_name, technology in custom_technologies.items():
|
|
||||||
location = world.get_location(tech_name, player)
|
|
||||||
Rules.set_rule(location, technology.build_rule(player))
|
|
||||||
prequisites = shapes.get(tech_name)
|
|
||||||
if prequisites:
|
|
||||||
locations = {world.get_location(requisite, player) for requisite in prequisites}
|
|
||||||
Rules.add_rule(location, lambda state,
|
|
||||||
locations=locations: all(state.can_reach(loc) for loc in locations))
|
|
||||||
# get all science pack technologies (but not the ability to craft them)
|
|
||||||
victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value]))
|
|
||||||
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
|
|
||||||
for technology in
|
|
||||||
victory_tech_names)
|
|
||||||
|
|
||||||
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
|
||||||
|
|
|
@ -8,7 +8,10 @@ from .Regions import create_regions
|
||||||
from .Rules import set_rules
|
from .Rules import set_rules
|
||||||
|
|
||||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
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):
|
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||||
ret = Region(name, None, name, player)
|
ret = Region(name, None, name, player)
|
||||||
|
|
|
@ -4,15 +4,24 @@ from .Locations import exclusion_table, events_table
|
||||||
from .Regions import link_minecraft_structures
|
from .Regions import link_minecraft_structures
|
||||||
from .Rules import set_rules
|
from .Rules import set_rules
|
||||||
|
|
||||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
from BaseClasses import MultiWorld
|
||||||
from Options import minecraft_options
|
from Options import minecraft_options
|
||||||
|
from ..AutoWorld import World
|
||||||
|
|
||||||
|
|
||||||
|
class MinecraftWorld(World):
|
||||||
|
game: str = "Minecraft"
|
||||||
|
|
||||||
|
|
||||||
client_version = (0, 3)
|
client_version = (0, 3)
|
||||||
|
|
||||||
|
|
||||||
def get_mc_data(world: MultiWorld, player: int):
|
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 {
|
return {
|
||||||
'world_seed': Random(world.rom_seeds[player]).getrandbits(32), # consistent and doesn't interfere with other generation
|
'world_seed': world.slot_seeds[player].getrandbits(32),
|
||||||
|
# consistent and doesn't interfere with other generation
|
||||||
'seed_name': world.seed_name,
|
'seed_name': world.seed_name,
|
||||||
'player_name': world.get_player_names(player),
|
'player_name': world.get_player_names(player),
|
||||||
'player_id': 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}
|
'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
|
import base64, json
|
||||||
from Utils import output_path
|
from Utils import output_path
|
||||||
|
|
||||||
data = get_mc_data(world, player)
|
data = get_mc_data(world, player)
|
||||||
filename = f"AP_{world.seed_name}_P{player}_{world.get_player_names(player)}.apmc"
|
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')))
|
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)
|
slot_data = get_mc_data(world, player)
|
||||||
for option_name in minecraft_options:
|
for option_name in minecraft_options:
|
||||||
option = getattr(world, option_name)[player]
|
option = getattr(world, option_name)[player]
|
||||||
slot_data[option_name] = int(option.value)
|
slot_data[option_name] = int(option.value)
|
||||||
return slot_data
|
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 = []
|
pool = []
|
||||||
for item_name, item_data in item_table.items():
|
for item_name, item_data in item_table.items():
|
||||||
for count in range(item_frequencies.get(item_name, 1)):
|
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 = {}
|
||||||
prefill_pool.update(events_table)
|
prefill_pool.update(events_table)
|
||||||
exclusion_pools = ['hard', 'insane', 'postgame']
|
exclusion_pools = ['hard', 'insane', 'postgame']
|
||||||
for key in exclusion_pools:
|
for key in exclusion_pools:
|
||||||
if not getattr(world, f"include_{key}_advancements")[player]:
|
if not getattr(world, f"include_{key}_advancements")[player]:
|
||||||
prefill_pool.update(exclusion_table[key])
|
prefill_pool.update(exclusion_table[key])
|
||||||
|
|
||||||
for loc_name, item_name in prefill_pool.items():
|
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
|
world.itempool += pool
|
||||||
|
|
||||||
# Generate Minecraft world.
|
|
||||||
|
# Generate Minecraft world.
|
||||||
def gen_minecraft(world: MultiWorld, player: int):
|
def gen_minecraft(world: MultiWorld, player: int):
|
||||||
link_minecraft_structures(world, player)
|
link_minecraft_structures(world, player)
|
||||||
minecraft_gen_item_pool(world, player)
|
minecraft_gen_item_pool(world, player)
|
||||||
set_rules(world, player)
|
set_rules(world, player)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue