Merge branch 'main' into breaking_changes

# Conflicts:
#	BaseClasses.py
This commit is contained in:
Fabian Dill 2021-03-06 05:27:16 +01:00
commit 567954a17f
9 changed files with 142 additions and 75 deletions

78
Fill.py
View File

@ -42,9 +42,10 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
for item_to_place in items_to_place:
perform_access_check = True
if world.accessibility[item_to_place.player] == 'none':
perform_access_check = not world.has_beaten_game(maximum_exploration_state, item_to_place.player) if single_player_placement else not has_beaten_game
perform_access_check = not world.has_beaten_game(maximum_exploration_state,
item_to_place.player) if single_player_placement else not has_beaten_game
for location in locations:
if (not single_player_placement or location.player == item_to_place.player)\
if (not single_player_placement or location.player == item_to_place.player) \
and location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
spot_to_fill = location
break
@ -70,6 +71,7 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
itempool.extend(unplaced_items)
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
# If not passed in, then get a shuffled list of locations to fill in
if not fill_locations:
@ -243,15 +245,14 @@ def balance_multiworld_progression(world):
else:
logging.info(f'Balancing multiworld progression for {len(balanceable_players)} Players.')
state = CollectionState(world)
checked_locations = []
unchecked_locations = world.get_locations().copy()
world.random.shuffle(unchecked_locations)
checked_locations = set()
unchecked_locations = set(world.get_locations())
reachable_locations_count = {player: 0 for player in world.player_ids}
def get_sphere_locations(sphere_state, locations):
sphere_state.sweep_for_events(key_only=True, locations=locations)
return [loc for loc in locations if sphere_state.can_reach(loc)]
return {loc for loc in locations if sphere_state.can_reach(loc)}
while True:
sphere_locations = get_sphere_locations(state, unchecked_locations)
@ -261,14 +262,14 @@ def balance_multiworld_progression(world):
if checked_locations:
threshold = max(reachable_locations_count.values()) - 20
balancing_players = [player for player, reachables in reachable_locations_count.items() if
reachables < threshold and player in balanceable_players]
balancing_players = {player for player, reachables in reachable_locations_count.items() if
reachables < threshold and player in balanceable_players}
if balancing_players:
balancing_state = state.copy()
balancing_unchecked_locations = unchecked_locations.copy()
balancing_reachables = reachable_locations_count.copy()
balancing_sphere = sphere_locations.copy()
candidate_items = collections.defaultdict(list)
candidate_items = collections.defaultdict(set)
while True:
for location in balancing_sphere:
if location.event:
@ -276,7 +277,7 @@ def balance_multiworld_progression(world):
player = location.item.player
# only replace items that end up in another player's world
if not location.locked and player in balancing_players and location.player != player:
candidate_items[player].append(location)
candidate_items[player].add(location)
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
for location in balancing_sphere:
balancing_unchecked_locations.remove(location)
@ -286,10 +287,10 @@ def balance_multiworld_progression(world):
break
elif not balancing_sphere:
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
unlocked_locations = collections.defaultdict(list)
unlocked_locations = collections.defaultdict(set)
for l in unchecked_locations:
if l not in balancing_unchecked_locations:
unlocked_locations[l.player].append(l)
unlocked_locations[l.player].add(l)
items_to_replace = []
for player in balancing_players:
locations_to_test = unlocked_locations[player]
@ -299,7 +300,6 @@ def balance_multiworld_progression(world):
reducing_state = state.copy()
for location in itertools.chain((l for l in items_to_replace if l.item.player == player),
items_to_test):
reducing_state.collect(location.item, True, location)
reducing_state.sweep_for_events(locations=locations_to_test)
@ -313,33 +313,40 @@ def balance_multiworld_progression(world):
items_to_replace.append(testing)
replaced_items = False
replacement_locations = [l for l in checked_locations if not l.event and not l.locked]
# sort then shuffle to maintain deterministic behaviour,
# while allowing use of set for better algorithm growth behaviour elsewhere
replacement_locations = sorted(l for l in checked_locations if not l.event and not l.locked)
world.random.shuffle(replacement_locations)
items_to_replace.sort()
world.random.shuffle(items_to_replace)
while replacement_locations and items_to_replace:
new_location = replacement_locations.pop()
old_location = items_to_replace.pop()
while not new_location.can_fill(state, old_location.item, False) or (
new_location.item and not old_location.can_fill(state, new_location.item, False)):
replacement_locations.insert(0, new_location)
new_location = replacement_locations.pop()
swap_location_item(old_location, new_location)
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
f"displacing {old_location.item} into {old_location}")
state.collect(new_location.item, True, new_location)
replaced_items = True
for new_location in replacement_locations:
if new_location.can_fill(state, old_location.item, False) and \
old_location.can_fill(state, new_location.item, False):
replacement_locations.remove(new_location)
swap_location_item(old_location, new_location)
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
f"displacing {old_location.item} into {old_location}")
state.collect(new_location.item, True, new_location)
replaced_items = True
break
else:
logging.warning(f"Could not Progression Balance {old_location.item}")
if replaced_items:
unlocked = [fresh for player in balancing_players for fresh in unlocked_locations[player]]
unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]}
for location in get_sphere_locations(state, unlocked):
unchecked_locations.remove(location)
reachable_locations_count[location.player] += 1
sphere_locations.append(location)
sphere_locations.add(location)
for location in sphere_locations:
if location.event:
state.collect(location.item, True, location)
checked_locations.extend(sphere_locations)
checked_locations |= sphere_locations
if world.has_beaten_game(state):
break
@ -380,7 +387,8 @@ def distribute_planned(world):
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}", FillError)
placement.failed(f"Could not find a world with an unfilled location {placement.location}",
FillError)
continue
target_world = world.random.choice(unfilled).player
@ -391,18 +399,22 @@ def distribute_planned(world):
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)
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)
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)
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]

View File

@ -46,6 +46,7 @@ if __name__ == "__main__":
player_name = multi_mystery_options["player_name"]
meta_file_path = multi_mystery_options["meta_file_path"]
weights_file_path = multi_mystery_options["weights_file_path"]
pre_roll = multi_mystery_options["pre_roll"]
teams = multi_mystery_options["teams"]
rom_file = options["general_options"]["rom_file"]
host = options["server_options"]["host"]
@ -104,6 +105,8 @@ if __name__ == "__main__":
command += f" --meta {os.path.join(player_files_path, meta_file_path)}"
if os.path.exists(weights_file_path):
command += f" --weights {weights_file_path}"
if pre_roll:
command += " --pre_roll"
logging.info(command)
import time

View File

@ -37,6 +37,7 @@ def mystery_argparse():
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
parser.add_argument('--create_spoiler', action='store_true')
parser.add_argument('--skip_playthrough', action='store_true')
parser.add_argument('--pre_roll', action='store_true')
parser.add_argument('--rom')
parser.add_argument('--enemizercli')
parser.add_argument('--outputpath')
@ -180,6 +181,27 @@ def main(args=None, callback=ERmain):
settings.sprite):
logging.warning(
f"Warning: The chosen sprite, \"{settings.sprite}\", for yaml \"{path}\", does not exist.")
if args.pre_roll:
import yaml
if path == args.weights:
settings.name = f"Player{player}"
elif not settings.name:
settings.name = os.path.split(path)[-1].split(".")[0]
if "-" not in settings.shuffle and settings.shuffle != "vanilla":
settings.shuffle += f"-{random.randint(0, 2 ** 64)}"
pre_rolled = dict()
pre_rolled["original_seed_number"] = seed
pre_rolled["original_seed_name"] = seedname
pre_rolled["pre_rolled"] = vars(settings).copy()
if "plando_items" in pre_rolled["pre_rolled"]:
pre_rolled["pre_rolled"]["plando_items"] = [item.to_dict() for item in pre_rolled["pre_rolled"]["plando_items"]]
if "plando_connections" in pre_rolled["pre_rolled"]:
pre_rolled["pre_rolled"]["plando_connections"] = [connection.to_dict() for connection in pre_rolled["pre_rolled"]["plando_connections"]]
with open(os.path.join(args.outputpath if args.outputpath else ".", f"{os.path.split(path)[-1].split('.')[0]}_pre_rolled.yaml"), "wt") as f:
yaml.dump(pre_rolled, f)
for k, v in vars(settings).items():
if v is not None:
getattr(erargs, k)[player] = v
@ -349,6 +371,21 @@ def roll_triggers(weights: dict) -> dict:
return weights
def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("bosses"))):
if "pre_rolled" in weights:
pre_rolled = weights["pre_rolled"]
if "plando_items" in pre_rolled:
pre_rolled["plando_items"] = [PlandoItem(item["item"],
item["location"],
item["world"],
item["from_pool"],
item["force"]) for item in pre_rolled["plando_items"]]
if "plando_connections" in pre_rolled:
pre_rolled["plando_connections"] = [PlandoConnection(connection["entrance"],
connection["exit"],
connection["direction"]) for connection in pre_rolled["plando_connections"]]
return argparse.Namespace(**pre_rolled)
if "linked_options" in weights:
weights = roll_linked_options(weights)

View File

@ -188,6 +188,7 @@ def get_default_options() -> dict:
"players": 0,
"weights_file_path": "weights.yaml",
"meta_file_path": "meta.yaml",
"pre_roll": False,
"player_name": "",
"create_spoiler": 1,
"zip_roms": 0,

View File

@ -1602,37 +1602,6 @@
}
}
},
"triforcehud": {
"keyString": "rom.triforcehud",
"friendlyName": "Triforce Hud Options",
"description": "Hide the triforce hud in certain circumstances.",
"inputType": "range",
"subOptions": {
"normal": {
"keyString": "rom.triforcehud.normal",
"friendlyName": "Normal",
"description": "Always displays HUD as usual.",
"defaultValue": 0
},
"hide_goal": {
"keyString": "rom.triforcehud.hide_goal",
"friendlyName": "Hide Goal",
"description": "Hide Triforce Hud elements until a single triforce piece is acquired or spoken to Murahadala",
"defaultValue": 50
},
"hide_total": {
"keyString": "rom.triforcehud.hide_required",
"friendlyName": "Hide Required Total",
"description": "Hide total amount needed to win the game (unless spoken to Murahadala)",
"defaultValue": 0
},
"hide_both": {
"keyString": "rom.triforcehud.hide_both",
"friendlyName": "Hide Both",
"description": "Hide both of the above options",
"defaultValue": 0
}
},
"reduceflashing": {
"keyString": "rom.reduceflashing",
"friendlyName": "Full-Screen Flashing Effects",
@ -1673,6 +1642,38 @@
}
}
},
"triforcehud": {
"keyString": "rom.triforcehud",
"friendlyName": "Triforce Hunt HUD Options",
"description": "Hide the triforce hud in certain circumstances.",
"inputType": "range",
"subOptions": {
"normal": {
"keyString": "rom.triforcehud.normal",
"friendlyName": "Always Show",
"description": "Always display HUD",
"defaultValue": 50
},
"hide_goal": {
"keyString": "rom.triforcehud.hide_goal",
"friendlyName": "Hide HUD",
"description": "Hide Triforce HUD elements until a single triforce piece is acquired or you speak to Murahadala",
"defaultValue": 0
},
"hide_total": {
"keyString": "rom.triforcehud.hide_required",
"friendlyName": "Hide Total",
"description": "Hide total triforce pieces needed to win the game until you speak with Murahadala",
"defaultValue": 0
},
"hide_both": {
"keyString": "rom.triforcehud.hide_both",
"friendlyName": "Hide HUD Total",
"description": "Combination of Hide HUD and Hide Total",
"defaultValue": 0
}
}
},
"menuspeed": {
"keyString": "menuspeed",
"friendlyName": "Menu Speed",

View File

@ -20,7 +20,7 @@
# For use with the weighted-settings page on the website. Changing this value will cause all users to be prompted
# to update their settings. The version number should match the current released version number, and the revision
# should be updated manually by whoever edits this file.
ws_version: 4.0.1 rev1
ws_version: 4.1.0 rev0
description: Template Name # Used to describe your yaml. Useful if you have multiple files
name: YourName # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit
@ -363,6 +363,11 @@ rom:
quickswap: # Enable switching items by pressing the L+R shoulder buttons
on: 50
off: 0
triforcehud: # Disable visibility of the triforce hud unless collecting a piece or speaking to Murahadala
normal: 50 # original behavior (always visible)
hide_goal: 0 # hide counter until a piece is collected or speaking to Murahadala
hide_required: 0 # Always visible, but required amount is invisible until determined by Murahadala
hide_both: 0 # Hide both under above circumstances
reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more.
on: 50
off: 0

View File

@ -350,8 +350,9 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
# Add items to player inventory
for (ms_team, ms_player), locations_checked in room.multisave.get("location_checks", {}):
# logging.info(f"{ms_team}, {ms_player}, {locations_checked}")
# Skip teams and players not matching the request
if ms_team != (team - 1) or ms_player != player:
if ms_team != (team - 1):
continue
# If the player does not have the item, do nothing
@ -360,7 +361,10 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
continue
item, recipient = locations[location, ms_player]
attribute_item_solo(inventory, item)
if recipient == player:
attribute_item_solo(inventory, item)
if ms_player != player:
continue
checks_done[player_location_to_area[location]] += 1
checks_done["Total"] += 1
@ -391,7 +395,7 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
sword_acquired = False
sword_names = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword']
if "Progressive Sword" in acquired_items:
sword_url = icons[sword_names[inventory[progressive_items["Progressive Sword"]] - 1]]
sword_url = icons[sword_names[min(inventory[progressive_items["Progressive Sword"]], 4) - 1]]
sword_acquired = True
else:
for sword in reversed(sword_names):
@ -404,7 +408,7 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
gloves_acquired = False
glove_names = ["Power Glove", "Titan Mitts"]
if "Progressive Glove" in acquired_items:
gloves_url = icons[glove_names[inventory[progressive_items["Progressive Glove"]] - 1]]
gloves_url = icons[glove_names[min(inventory[progressive_items["Progressive Glove"]], 2) - 1]]
gloves_acquired = True
else:
for glove in reversed(glove_names):
@ -417,7 +421,7 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
bow_acquired = False
bow_names = ["Bow", "Silver Bow"]
if "Progressive Bow" in acquired_items:
bow_url = icons[bow_names[inventory[progressive_items["Progressive Bow"]] - 1]]
bow_url = icons[bow_names[min(inventory[progressive_items["Progressive Bow"]], 2) - 1]]
bow_acquired = True
else:
for bow in reversed(bow_names):
@ -429,7 +433,7 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
mail_url = icons["Green Mail"]
mail_names = ["Blue Mail", "Red Mail"]
if "Progressive Mail" in acquired_items:
mail_url = icons[mail_names[inventory[progressive_items["Progressive Mail"]] - 1]]
mail_url = icons[mail_names[min(inventory[progressive_items["Progressive Mail"]], 2) - 1]]
else:
for mail in reversed(mail_names):
if mail in acquired_items:
@ -440,7 +444,7 @@ def getPlayerTracker(tracker: UUID, team: int, player: int):
shield_acquired = False
shield_names = ["Blue Shield", "Red Shield", "Mirror Shield"]
if "Progressive Shield" in acquired_items:
shield_url = icons[shield_names[inventory[progressive_items["Progressive Shield"]] - 1]]
shield_url = icons[shield_names[min(inventory[progressive_items["Progressive Shield"]], 3) - 1]]
shield_acquired = True
else:
for shield in reversed(shield_names):

View File

@ -64,6 +64,10 @@ multi_mystery_options:
weights_file_path: "weights.yaml"
# Meta file name, within the stated player_files_path location
meta_file_path: "meta.yaml"
# Option to pre-roll a yaml that will be used to roll future seeds with the exact same settings every single time.
# If using a pre-rolled yaml fails with "Please fix your yaml.", please file a bug report including both the original yaml
# as well as the generated pre-rolled yaml.
pre_roll: false
# Automatically launches {player_name}.yaml's ROM file using the OS's default program once generation completes. (likely your emulator)
# Does nothing if the name is not found
# Example: player_name = "Berserker"

View File

@ -404,10 +404,10 @@ rom:
quickswap: # Enable switching items by pressing the L+R shoulder buttons
on: 50
off: 0
triforcehud: # Disable visibility of the triforce hud unless collecting a piece or speaking to Murahalda
triforcehud: # Disable visibility of the triforce hud unless collecting a piece or speaking to Murahadala
normal: 0 # original behavior (always visible)
hide_goal: 50 # hide counter until a piece is collected or speaking to Murahalda
hide_required: 0 # Always visible, but required amount is invisible until determined by Murahalda
hide_goal: 50 # hide counter until a piece is collected or speaking to Murahadala
hide_required: 0 # Always visible, but required amount is invisible until determined by Murahadala
hide_both: 0 # Hide both under above circumstances
reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more.
on: 50