Item Plando Support
This commit is contained in:
parent
3d53adf45c
commit
f3b6be2b20
|
@ -5,7 +5,7 @@ from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict, Counter, deque
|
from collections import OrderedDict, Counter, deque
|
||||||
from typing import Union, Optional, List, Set, Dict
|
from typing import Union, Optional, List, Set, Dict, NamedTuple
|
||||||
import secrets
|
import secrets
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from Items import item_name_groups
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
debug_types = False
|
debug_types = False
|
||||||
player_names: list
|
player_names: Dict[int, List[str]]
|
||||||
_region_cache: dict
|
_region_cache: dict
|
||||||
difficulty_requirements: dict
|
difficulty_requirements: dict
|
||||||
required_medallions: dict
|
required_medallions: dict
|
||||||
|
@ -135,6 +135,7 @@ class World(object):
|
||||||
set_player_attr('sprite_pool', [])
|
set_player_attr('sprite_pool', [])
|
||||||
set_player_attr('dark_room_logic', "lamp")
|
set_player_attr('dark_room_logic', "lamp")
|
||||||
set_player_attr('restrict_dungeon_item_on_boss', False)
|
set_player_attr('restrict_dungeon_item_on_boss', False)
|
||||||
|
set_player_attr('plando_items', [])
|
||||||
|
|
||||||
def secure(self):
|
def secure(self):
|
||||||
self.random = secrets.SystemRandom()
|
self.random = secrets.SystemRandom()
|
||||||
|
@ -1037,6 +1038,9 @@ class Item(object):
|
||||||
self.world = None
|
self.world = None
|
||||||
self.player = player
|
self.player = player
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.name == other.name and self.player == other.player
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def crystal(self) -> bool:
|
def crystal(self) -> bool:
|
||||||
return self.type == 'Crystal'
|
return self.type == 'Crystal'
|
||||||
|
@ -1452,3 +1456,10 @@ class Spoiler(object):
|
||||||
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
|
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
|
||||||
|
|
||||||
outfile.write('\n'.join(path_listings))
|
outfile.write('\n'.join(path_listings))
|
||||||
|
|
||||||
|
|
||||||
|
class PlandoItem(NamedTuple):
|
||||||
|
item: str
|
||||||
|
location: str
|
||||||
|
world: Union[bool, str] = False # False -> own world, True -> not own world
|
||||||
|
from_pool: bool = True # if item should be removed from item pool
|
||||||
|
|
|
@ -355,6 +355,10 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
|
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
|
||||||
|
|
||||||
ret = parser.parse_args(argv)
|
ret = parser.parse_args(argv)
|
||||||
|
|
||||||
|
# cannot be set through CLI currently
|
||||||
|
ret.plando_items = {}
|
||||||
|
|
||||||
ret.glitch_boots = not ret.disable_glitch_boots
|
ret.glitch_boots = not ret.disable_glitch_boots
|
||||||
if ret.timer == "none":
|
if ret.timer == "none":
|
||||||
ret.timer = False
|
ret.timer = False
|
||||||
|
@ -382,9 +386,10 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
'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', "skip_progression_balancing", "triforce_pieces_available",
|
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle",
|
"triforce_pieces_required", "shop_shuffle", "plando_items",
|
||||||
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||||
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', 'restrict_dungeon_item_on_boss',
|
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
||||||
|
'restrict_dungeon_item_on_boss',
|
||||||
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']:
|
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']:
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
|
|
10
Fill.py
10
Fill.py
|
@ -54,7 +54,8 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
|
||||||
for location in region.locations:
|
for location in region.locations:
|
||||||
if location.item and not location.event:
|
if location.item and not location.event:
|
||||||
placements.append(location)
|
placements.append(location)
|
||||||
|
# fill in name of world for item
|
||||||
|
item_to_place.world = world
|
||||||
raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
|
raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
|
||||||
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
|
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
|
||||||
|
|
||||||
|
@ -128,9 +129,12 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||||
world.random.shuffle(fill_locations)
|
world.random.shuffle(fill_locations)
|
||||||
|
|
||||||
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
|
standard_keyshuffle_players = {player for player, mode in world.mode.items() if mode == 'standard' and
|
||||||
|
world.keyshuffle[player] is True}
|
||||||
|
if standard_keyshuffle_players:
|
||||||
progitempool.sort(
|
progitempool.sort(
|
||||||
key=lambda item: 1 if item.name == 'Small Key (Hyrule Castle)' and world.mode[item.player] == 'standard' and
|
key=lambda item: 1 if item.name == 'Small Key (Hyrule Castle)' and
|
||||||
world.keyshuffle[item.player] else 0)
|
item.player in standard_keyshuffle_players else 0)
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||||
|
|
||||||
|
|
48
Main.py
48
Main.py
|
@ -9,7 +9,7 @@ import time
|
||||||
import zlib
|
import zlib
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
|
||||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
from BaseClasses import World, CollectionState, Item, Region, Location, PlandoItem
|
||||||
from Items import ItemFactory, item_table, item_name_groups
|
from Items import ItemFactory, item_table, item_name_groups
|
||||||
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance
|
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance
|
||||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||||
|
@ -87,6 +87,7 @@ def main(args, seed=None):
|
||||||
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()
|
||||||
world.dark_room_logic = args.dark_room_logic.copy()
|
world.dark_room_logic = args.dark_room_logic.copy()
|
||||||
|
world.plando_items = args.plando_items.copy()
|
||||||
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.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
||||||
|
@ -172,10 +173,49 @@ def main(args, seed=None):
|
||||||
|
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
|
logger.info("Running Item Plando")
|
||||||
|
|
||||||
|
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids}
|
||||||
|
|
||||||
|
for player in world.player_ids:
|
||||||
|
placement: PlandoItem
|
||||||
|
for placement in world.plando_items[player]:
|
||||||
|
target_world: int = placement.world
|
||||||
|
if target_world is False or world.players == 1:
|
||||||
|
target_world = player # in own world
|
||||||
|
elif target_world is True: # in any other world
|
||||||
|
target_world = player
|
||||||
|
while target_world == player:
|
||||||
|
target_world = world.random.randint(1, world.players + 1)
|
||||||
|
elif target_world is None: # any random world
|
||||||
|
target_world = world.random.randint(1, world.players + 1)
|
||||||
|
elif type(target_world) == int: # target world by player id
|
||||||
|
pass
|
||||||
|
else: # find world by name
|
||||||
|
target_world = world_name_lookup[target_world]
|
||||||
|
|
||||||
|
location = world.get_location(placement.location, target_world)
|
||||||
|
if location.item:
|
||||||
|
raise Exception(f"Cannot place item into already filled location {location}.")
|
||||||
|
item = ItemFactory(placement.item, player)
|
||||||
|
if placement.from_pool:
|
||||||
|
try:
|
||||||
|
world.itempool.remove(item)
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(f"Could not remove {item} from pool as it's already missing from it.")
|
||||||
|
|
||||||
|
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
|
||||||
|
logger.debug(f"Plando placed {item} at {location}")
|
||||||
|
else:
|
||||||
|
raise Exception(f"Can't place {item} at {location} due to fill condition not met.")
|
||||||
|
|
||||||
logger.info('Placing Dungeon Items.')
|
logger.info('Placing Dungeon Items.')
|
||||||
|
|
||||||
shuffled_locations = None
|
if args.algorithm in ['balanced', 'vt26'] or any(
|
||||||
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
||||||
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
||||||
fill_dungeons_restrictive(world)
|
fill_dungeons_restrictive(world)
|
||||||
else:
|
else:
|
||||||
|
@ -188,7 +228,7 @@ def main(args, seed=None):
|
||||||
elif args.algorithm == 'vt25':
|
elif args.algorithm == 'vt25':
|
||||||
distribute_items_restrictive(world, False)
|
distribute_items_restrictive(world, False)
|
||||||
elif args.algorithm == 'vt26':
|
elif args.algorithm == 'vt26':
|
||||||
distribute_items_restrictive(world, True, shuffled_locations)
|
distribute_items_restrictive(world, True)
|
||||||
elif args.algorithm == 'balanced':
|
elif args.algorithm == 'balanced':
|
||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world, True)
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ if __name__ == "__main__":
|
||||||
player_files_path = multi_mystery_options["player_files_path"]
|
player_files_path = multi_mystery_options["player_files_path"]
|
||||||
target_player_count = multi_mystery_options["players"]
|
target_player_count = multi_mystery_options["players"]
|
||||||
race = multi_mystery_options["race"]
|
race = multi_mystery_options["race"]
|
||||||
|
plando_options = multi_mystery_options["plando_options"]
|
||||||
create_spoiler = multi_mystery_options["create_spoiler"]
|
create_spoiler = multi_mystery_options["create_spoiler"]
|
||||||
zip_roms = multi_mystery_options["zip_roms"]
|
zip_roms = multi_mystery_options["zip_roms"]
|
||||||
zip_diffs = multi_mystery_options["zip_diffs"]
|
zip_diffs = multi_mystery_options["zip_diffs"]
|
||||||
|
@ -104,7 +105,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
command = f"{basemysterycommand} --multi {target_player_count} {player_string} " \
|
command = f"{basemysterycommand} --multi {target_player_count} {player_string} " \
|
||||||
f"--rom \"{rom_file}\" --enemizercli \"{enemizer_path}\" " \
|
f"--rom \"{rom_file}\" --enemizercli \"{enemizer_path}\" " \
|
||||||
f"--outputpath \"{output_path}\" --teams {teams}"
|
f"--outputpath \"{output_path}\" --teams {teams} --plando \"{plando_options}\""
|
||||||
|
|
||||||
if create_spoiler:
|
if create_spoiler:
|
||||||
command += " --create_spoiler"
|
command += " --create_spoiler"
|
||||||
|
|
35
Mystery.py
35
Mystery.py
|
@ -7,6 +7,7 @@ import typing
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import ModuleUpdate
|
import ModuleUpdate
|
||||||
|
from BaseClasses import PlandoItem
|
||||||
|
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
|
||||||
|
@ -44,10 +45,13 @@ def mystery_argparse():
|
||||||
parser.add_argument('--create_diff', action="store_true")
|
parser.add_argument('--create_diff', action="store_true")
|
||||||
parser.add_argument('--yaml_output', default=0, type=lambda value: min(max(int(value), 0), 255),
|
parser.add_argument('--yaml_output', default=0, type=lambda value: min(max(int(value), 0), 255),
|
||||||
help='Output rolled mystery results to yaml up to specified number (made for async multiworld)')
|
help='Output rolled mystery results to yaml up to specified number (made for async multiworld)')
|
||||||
|
parser.add_argument('--plando', default="bosses",
|
||||||
|
help='List of options that can be set manually. Can be combined, for example "bosses, items"')
|
||||||
|
|
||||||
for player in range(1, multiargs.multi + 1):
|
for player in range(1, multiargs.multi + 1):
|
||||||
parser.add_argument(f'--p{player}', help=argparse.SUPPRESS)
|
parser.add_argument(f'--p{player}', help=argparse.SUPPRESS)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
args.plando: typing.Set[str] = {arg.strip().lower() for arg in args.plando.split(",")}
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,7 +148,8 @@ def main(args=None, callback=ERmain):
|
||||||
if args.enemizercli:
|
if args.enemizercli:
|
||||||
erargs.enemizercli = args.enemizercli
|
erargs.enemizercli = args.enemizercli
|
||||||
|
|
||||||
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
|
settings_cache = {k: (roll_settings(v, args.plando) if args.samesettings else None)
|
||||||
|
for k, v in weights_cache.items()}
|
||||||
player_path_cache = {}
|
player_path_cache = {}
|
||||||
for player in range(1, args.multi + 1):
|
for player in range(1, args.multi + 1):
|
||||||
player_path_cache[player] = getattr(args, f'p{player}') if getattr(args, f'p{player}') else args.weights
|
player_path_cache[player] = getattr(args, f'p{player}') if getattr(args, f'p{player}') else args.weights
|
||||||
|
@ -167,7 +172,8 @@ def main(args=None, callback=ERmain):
|
||||||
path = player_path_cache[player]
|
path = player_path_cache[player]
|
||||||
if path:
|
if path:
|
||||||
try:
|
try:
|
||||||
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
settings = settings_cache[path] if settings_cache[path] else \
|
||||||
|
roll_settings(weights_cache[path], args.plando)
|
||||||
if settings.sprite and not os.path.isfile(settings.sprite) and not Sprite.get_sprite_from_name(
|
if settings.sprite and not os.path.isfile(settings.sprite) and not Sprite.get_sprite_from_name(
|
||||||
settings.sprite):
|
settings.sprite):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
|
@ -275,7 +281,13 @@ boss_shuffle_options = {None: 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def roll_settings(weights):
|
def roll_percentage(percentage: typing.Union[int, float]) -> bool:
|
||||||
|
"""Roll a percentage chance.
|
||||||
|
percentage is expected to be in range [0, 100]"""
|
||||||
|
return random.random() < (float(percentage) / 100)
|
||||||
|
|
||||||
|
|
||||||
|
def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"))):
|
||||||
ret = argparse.Namespace()
|
ret = argparse.Namespace()
|
||||||
if "linked_options" in weights:
|
if "linked_options" in weights:
|
||||||
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
|
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
|
||||||
|
@ -283,7 +295,7 @@ def roll_settings(weights):
|
||||||
if "name" not in option_set:
|
if "name" not in option_set:
|
||||||
raise ValueError("One of your linked options does not have a name.")
|
raise ValueError("One of your linked options does not have a name.")
|
||||||
try:
|
try:
|
||||||
if random.random() < (float(option_set["percentage"]) / 100):
|
if roll_percentage(option_set["percentage"]):
|
||||||
logging.debug(f"Linked option {option_set['name']} triggered.")
|
logging.debug(f"Linked option {option_set['name']} triggered.")
|
||||||
logging.debug(f'Applying {option_set["options"]}')
|
logging.debug(f'Applying {option_set["options"]}')
|
||||||
new_options = set(option_set["options"]) - set(weights)
|
new_options = set(option_set["options"]) - set(weights)
|
||||||
|
@ -415,7 +427,7 @@ def roll_settings(weights):
|
||||||
|
|
||||||
if boss_shuffle in boss_shuffle_options:
|
if boss_shuffle in boss_shuffle_options:
|
||||||
ret.shufflebosses = boss_shuffle_options[boss_shuffle]
|
ret.shufflebosses = boss_shuffle_options[boss_shuffle]
|
||||||
else:
|
elif "bosses" in plando_options:
|
||||||
options = boss_shuffle.lower().split(";")
|
options = boss_shuffle.lower().split(";")
|
||||||
remainder_shuffle = "none" # vanilla
|
remainder_shuffle = "none" # vanilla
|
||||||
bosses = []
|
bosses = []
|
||||||
|
@ -427,6 +439,8 @@ def roll_settings(weights):
|
||||||
else:
|
else:
|
||||||
bosses.append(boss)
|
bosses.append(boss)
|
||||||
ret.shufflebosses = ";".join(bosses + [remainder_shuffle])
|
ret.shufflebosses = ";".join(bosses + [remainder_shuffle])
|
||||||
|
else:
|
||||||
|
raise Exception(f"Boss Shuffle {boss_shuffle} is unknown and boss plando is turned off.")
|
||||||
|
|
||||||
ret.enemy_shuffle = {'none': False,
|
ret.enemy_shuffle = {'none': False,
|
||||||
'shuffled': 'shuffled',
|
'shuffled': 'shuffled',
|
||||||
|
@ -534,6 +548,17 @@ def roll_settings(weights):
|
||||||
|
|
||||||
ret.non_local_items = ",".join(ret.non_local_items)
|
ret.non_local_items = ",".join(ret.non_local_items)
|
||||||
|
|
||||||
|
ret.plando_items = []
|
||||||
|
if "items" in plando_options:
|
||||||
|
options = weights.get("plando_items", [])
|
||||||
|
for placement in options:
|
||||||
|
if roll_percentage(get_choice("percentage", placement, 100)):
|
||||||
|
item = get_choice("item", placement)
|
||||||
|
location = get_choice("location", placement)
|
||||||
|
from_pool = get_choice("from_pool", placement, True)
|
||||||
|
location_world = get_choice("world", placement, False)
|
||||||
|
ret.plando_items.append(PlandoItem(item, location, location_world, from_pool))
|
||||||
|
|
||||||
if 'rom' in weights:
|
if 'rom' in weights:
|
||||||
romweights = weights['rom']
|
romweights = weights['rom']
|
||||||
|
|
||||||
|
|
106
Utils.py
106
Utils.py
|
@ -1,10 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
|
||||||
def tuplize_version(version: str) -> typing.Tuple[int, ...]:
|
def tuplize_version(version: str) -> typing.Tuple[int, ...]:
|
||||||
return Version(*(int(piece, 10) for piece in version.split(".")))
|
return Version(*(int(piece, 10) for piece in version.split(".")))
|
||||||
|
|
||||||
|
|
||||||
class Version(typing.NamedTuple):
|
class Version(typing.NamedTuple):
|
||||||
major: int
|
major: int
|
||||||
minor: int
|
minor: int
|
||||||
|
@ -87,6 +89,7 @@ def local_path(*path):
|
||||||
|
|
||||||
return os.path.join(local_path.cached_path, *path)
|
return os.path.join(local_path.cached_path, *path)
|
||||||
|
|
||||||
|
|
||||||
local_path.cached_path = None
|
local_path.cached_path = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,8 +101,10 @@ def output_path(*path):
|
||||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
output_path.cached_path = None
|
output_path.cached_path = None
|
||||||
|
|
||||||
|
|
||||||
def open_file(filename):
|
def open_file(filename):
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
os.startfile(filename)
|
os.startfile(filename)
|
||||||
|
@ -107,6 +112,7 @@ def open_file(filename):
|
||||||
open_command = 'open' if sys.platform == 'darwin' else 'xdg-open'
|
open_command = 'open' if sys.platform == 'darwin' else 'xdg-open'
|
||||||
subprocess.call([open_command, filename])
|
subprocess.call([open_command, filename])
|
||||||
|
|
||||||
|
|
||||||
def close_console():
|
def close_console():
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
# windows
|
# windows
|
||||||
|
@ -143,6 +149,7 @@ class Hint(typing.NamedTuple):
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self.receiving_player, self.finding_player, self.location, self.item, self.entrance))
|
return hash((self.receiving_player, self.finding_player, self.location, self.item, self.entrance))
|
||||||
|
|
||||||
|
|
||||||
def get_public_ipv4() -> str:
|
def get_public_ipv4() -> str:
|
||||||
import socket
|
import socket
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
@ -158,6 +165,7 @@ def get_public_ipv4() -> str:
|
||||||
pass # we could be offline, in a local game, so no point in erroring out
|
pass # we could be offline, in a local game, so no point in erroring out
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def get_public_ipv6() -> str:
|
def get_public_ipv6() -> str:
|
||||||
import socket
|
import socket
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
@ -173,56 +181,56 @@ def get_public_ipv6() -> str:
|
||||||
|
|
||||||
def get_default_options() -> dict:
|
def get_default_options() -> dict:
|
||||||
if not hasattr(get_default_options, "options"):
|
if not hasattr(get_default_options, "options"):
|
||||||
options = dict()
|
|
||||||
|
|
||||||
# Refer to host.yaml for comments as to what all these options mean.
|
# Refer to host.yaml for comments as to what all these options mean.
|
||||||
generaloptions = dict()
|
options = {
|
||||||
generaloptions["rom_file"] = "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"
|
"general_options": {
|
||||||
generaloptions["qusb2snes"] = "QUsb2Snes\\QUsb2Snes.exe"
|
"rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc",
|
||||||
generaloptions["rom_start"] = True
|
"qusb2snes": "QUsb2Snes\\QUsb2Snes.exe",
|
||||||
generaloptions["output_path"] = "output"
|
"rom_start": True,
|
||||||
options["general_options"] = generaloptions
|
"output_path": "output",
|
||||||
|
},
|
||||||
|
"server_options": {
|
||||||
|
"host": None,
|
||||||
|
"port": 38281,
|
||||||
|
"password": None,
|
||||||
|
"multidata": None,
|
||||||
|
"savefile": None,
|
||||||
|
"disable_save": False,
|
||||||
|
"loglevel": "info",
|
||||||
|
"server_password": None,
|
||||||
|
"disable_item_cheat": False,
|
||||||
|
"location_check_points": 1,
|
||||||
|
"hint_cost": 1000,
|
||||||
|
"forfeit_mode": "goal",
|
||||||
|
"remaining_mode": "goal",
|
||||||
|
"auto_shutdown": 0,
|
||||||
|
"compatibility": 2,
|
||||||
|
},
|
||||||
|
"multi_mystery_options": {
|
||||||
|
"teams": 1,
|
||||||
|
"enemizer_path": "EnemizerCLI/EnemizerCLI.Core.exe",
|
||||||
|
"player_files_path": "Players",
|
||||||
|
"players": 0,
|
||||||
|
"weights_file_path": "weights.yaml",
|
||||||
|
"meta_file_path": "meta.yaml",
|
||||||
|
"player_name": "",
|
||||||
|
"create_spoiler": 1,
|
||||||
|
"zip_roms": 0,
|
||||||
|
"zip_diffs": 2,
|
||||||
|
"zip_spoiler": 0,
|
||||||
|
"zip_multidata": 1,
|
||||||
|
"zip_format": 1,
|
||||||
|
"race": 0,
|
||||||
|
"cpu_threads": 0,
|
||||||
|
"max_attempts": 0,
|
||||||
|
"take_first_working": False,
|
||||||
|
"keep_all_seeds": False,
|
||||||
|
"log_output_path": "Output Logs",
|
||||||
|
"log_level": None,
|
||||||
|
"plando_options": "bosses",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serveroptions = dict()
|
|
||||||
serveroptions["host"] = None
|
|
||||||
serveroptions["port"] = 38281
|
|
||||||
serveroptions["password"] = None
|
|
||||||
serveroptions["multidata"] = None
|
|
||||||
serveroptions["savefile"] = None
|
|
||||||
serveroptions["disable_save"] = False
|
|
||||||
serveroptions["loglevel"] = "info"
|
|
||||||
serveroptions["server_password"] = None
|
|
||||||
serveroptions["disable_item_cheat"] = False
|
|
||||||
serveroptions["location_check_points"] = 1
|
|
||||||
serveroptions["hint_cost"] = 1000
|
|
||||||
serveroptions["forfeit_mode"] = "goal"
|
|
||||||
serveroptions["remaining_mode"] = "goal"
|
|
||||||
serveroptions["auto_shutdown"] = 0
|
|
||||||
serveroptions["compatibility"] = 2
|
|
||||||
options["server_options"] = serveroptions
|
|
||||||
|
|
||||||
multimysteryoptions = dict()
|
|
||||||
multimysteryoptions["teams"] = 1
|
|
||||||
multimysteryoptions["enemizer_path"] = "EnemizerCLI/EnemizerCLI.Core.exe"
|
|
||||||
multimysteryoptions["player_files_path"] = "Players"
|
|
||||||
multimysteryoptions["players"] = 0
|
|
||||||
multimysteryoptions["weights_file_path"] = "weights.yaml"
|
|
||||||
multimysteryoptions["meta_file_path"] = "meta.yaml"
|
|
||||||
multimysteryoptions["player_name"] = ""
|
|
||||||
multimysteryoptions["create_spoiler"] = 1
|
|
||||||
multimysteryoptions["zip_roms"] = 0
|
|
||||||
multimysteryoptions["zip_diffs"] = 2
|
|
||||||
multimysteryoptions["zip_spoiler"] = 0
|
|
||||||
multimysteryoptions["zip_multidata"] = 1
|
|
||||||
multimysteryoptions["zip_format"] = 1
|
|
||||||
multimysteryoptions["race"] = 0
|
|
||||||
multimysteryoptions["cpu_threads"] = 0
|
|
||||||
multimysteryoptions["max_attempts"] = 0
|
|
||||||
multimysteryoptions["take_first_working"] = False
|
|
||||||
multimysteryoptions["keep_all_seeds"] = False
|
|
||||||
multimysteryoptions["log_output_path"] = "Output Logs"
|
|
||||||
multimysteryoptions["log_level"] = None
|
|
||||||
options["multi_mystery_options"] = multimysteryoptions
|
|
||||||
get_default_options.options = options
|
get_default_options.options = options
|
||||||
return get_default_options.options
|
return get_default_options.options
|
||||||
|
|
||||||
|
@ -254,6 +262,7 @@ def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict:
|
||||||
dest[key] = update_options(value, dest[key], filename, new_keys)
|
dest[key] = update_options(value, dest[key], filename, new_keys)
|
||||||
return dest
|
return dest
|
||||||
|
|
||||||
|
|
||||||
def get_options() -> dict:
|
def get_options() -> dict:
|
||||||
if not hasattr(get_options, "options"):
|
if not hasattr(get_options, "options"):
|
||||||
locations = ("options.yaml", "host.yaml",
|
locations = ("options.yaml", "host.yaml",
|
||||||
|
@ -350,7 +359,6 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]:
|
||||||
return romfile, False
|
return romfile, False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ReceivedItem(typing.NamedTuple):
|
class ReceivedItem(typing.NamedTuple):
|
||||||
item: int
|
item: int
|
||||||
location: int
|
location: int
|
||||||
|
|
|
@ -73,7 +73,7 @@ def roll_options(options: Dict[str, Union[dict, str]]) -> Tuple[Dict[str, Union[
|
||||||
results[filename] = f"Failed to parse YAML data in {filename}: {e}"
|
results[filename] = f"Failed to parse YAML data in {filename}: {e}"
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
rolled_results[filename] = roll_settings(yaml_data)
|
rolled_results[filename] = roll_settings(yaml_data, plando_options={"bosses"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
results[filename] = f"Failed to generate mystery in {filename}: {e}"
|
results[filename] = f"Failed to generate mystery in {filename}: {e}"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -99,3 +99,6 @@ multi_mystery_options:
|
||||||
zip_format: 1
|
zip_format: 1
|
||||||
# Create encrypted race roms
|
# Create encrypted race roms
|
||||||
race: 0
|
race: 0
|
||||||
|
# List of options that can be plando'd. Can be combined, for example "bosses, items"
|
||||||
|
# Available options: bosses
|
||||||
|
plando_options: "bosses"
|
||||||
|
|
Loading…
Reference in New Issue