From 8b992cbf00dd69d187cbc9bad0046b0b1421bded Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 23 May 2024 17:50:40 -0500 Subject: [PATCH 01/22] Webhost: Disallow empty option groups (#3369) * move item_and_loc_options out of the meta class and into the Options module * don't allow empty world specified option groups * reuse option_group generation code instead of rewriting it * delete the default group if it's empty * indent --- Options.py | 44 +++++++++++++++++++++++++++++++------------ WebHostLib/options.py | 14 ++------------ worlds/AutoWorld.py | 8 ++------ 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Options.py b/Options.py index 39fd567656..e11e078a1d 100644 --- a/Options.py +++ b/Options.py @@ -1132,7 +1132,37 @@ class OptionGroup(typing.NamedTuple): """Options to be in the defined group.""" -def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True): +item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints, + StartLocationHints, ExcludeLocations, PriorityLocations, ItemLinks] +""" +Options that are always populated in "Item & Location Options" Option Group. Cannot be moved to another group. +If desired, a custom "Item & Location Options" Option Group can be defined, but only for adding additional options to +it. +""" + + +def get_option_groups(world: typing.Type[World], visibility_level: Visibility = Visibility.template) -> typing.Dict[ + str, typing.Dict[str, typing.Type[Option[typing.Any]]]]: + """Generates and returns a dictionary for the option groups of a specified world.""" + option_groups = {option: option_group.name + for option_group in world.web.option_groups + for option in option_group.options} + # add a default option group for uncategorized options to get thrown into + ordered_groups = ["Game Options"] + [ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups] + grouped_options = {group: {} for group in ordered_groups} + for option_name, option in world.options_dataclass.type_hints.items(): + if visibility_level & option.visibility: + grouped_options[option_groups.get(option, "Game Options")][option_name] = option + + # if the world doesn't have any ungrouped options, this group will be empty so just remove it + if not grouped_options["Game Options"]: + del grouped_options["Game Options"] + + return grouped_options + + +def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True) -> None: import os import yaml @@ -1170,17 +1200,7 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge for game_name, world in AutoWorldRegister.world_types.items(): if not world.hidden or generate_hidden: - - option_groups = {option: option_group.name - for option_group in world.web.option_groups - for option in option_group.options} - ordered_groups = ["Game Options"] - [ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups] - grouped_options = {group: {} for group in ordered_groups} - for option_name, option in world.options_dataclass.type_hints.items(): - if option.visibility >= Visibility.template: - grouped_options[option_groups.get(option, "Game Options")][option_name] = option - + grouped_options = get_option_groups(world) with open(local_path("data", "options.yaml")) as f: file_data = f.read() res = Template(file_data).render( diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 94f173df70..bc63ec9331 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -27,26 +27,16 @@ def get_world_theme(game_name: str) -> str: def render_options_page(template: str, world_name: str, is_complex: bool = False) -> Union[Response, str]: - visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui world = AutoWorldRegister.world_types[world_name] if world.hidden or world.web.options_page is False: return redirect("games") - - option_groups = {option: option_group.name - for option_group in world.web.option_groups - for option in option_group.options} - ordered_groups = ["Game Options", *[group.name for group in world.web.option_groups]] - grouped_options = {group: {} for group in ordered_groups} - for option_name, option in world.options_dataclass.type_hints.items(): - # Exclude settings from options pages if their visibility is disabled - if visibility_flag in option.visibility: - grouped_options[option_groups.get(option, "Game Options")][option_name] = option + visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui return render_template( template, world_name=world_name, world=world, - option_groups=grouped_options, + option_groups=Options.get_option_groups(world, visibility_level=visibility_flag), issubclass=issubclass, Options=Options, theme=get_world_theme(world_name), diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 32a84f5d57..f8bc525ea5 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -10,10 +10,7 @@ from dataclasses import make_dataclass from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union) -from Options import ( - ExcludeLocations, ItemLinks, LocalItems, NonLocalItems, OptionGroup, PerGameCommonOptions, - PriorityLocations, StartHints, StartInventory, StartInventoryPool, StartLocationHints -) +from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions from BaseClasses import CollectionState if TYPE_CHECKING: @@ -119,12 +116,11 @@ class WebWorldRegister(type): # don't allow an option to appear in multiple groups, allow "Item & Location Options" to appear anywhere by the # dev, putting it at the end if they don't define options in it option_groups: List[OptionGroup] = dct.get("option_groups", []) - item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints, - StartLocationHints, ExcludeLocations, PriorityLocations, ItemLinks] seen_options = [] item_group_in_list = False for group in option_groups: assert group.name != "Game Options", "Game Options is a pre-determined group and can not be defined." + assert group.options, "A custom defined Option Group must contain at least one Option." if group.name == "Item & Location Options": group.options.extend(item_and_loc_options) item_group_in_list = True From 2a47f03e725607319e506d52fc0c3a492fe96155 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 23 May 2024 20:36:45 -0400 Subject: [PATCH 02/22] Docs: Update trigger guide and advanced yaml guide (#3385) * I guess these don't exist anymore * Update worlds/generic/docs/advanced_settings_en.md Co-authored-by: Scipio Wright --------- Co-authored-by: Scipio Wright --- worlds/generic/docs/advanced_settings_en.md | 12 ++++++------ worlds/generic/docs/triggers_en.md | 5 +---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index 5b1b583e61..37467eeb46 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -79,7 +79,7 @@ are `description`, `name`, `game`, `requires`, and the name of the games you wan different weights. * `requires` details different requirements from the generator for the YAML to work as you expect it to. Generally this - is good for detailing the version of Archipelago this YAML was prepared for as, if it is rolled on an older version, + is good for detailing the version of Archipelago this YAML was prepared for. If it is rolled on an older version, options may be missing and as such it will not work as expected. If any plando is used in the file then requiring it here to ensure it will be used is good practice. @@ -137,7 +137,7 @@ guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en) locations. * `item_links` allows players to link their items into a group with the same item link name and game. The items declared in `item_pool` get combined and when an item is found for the group, all players in the group receive it. Item links - can also have local and non local items, forcing the items to either be placed within the worlds of the group or in + can also have local and non-local items, forcing the items to either be placed within the worlds of the group or in worlds outside the group. If players have a varying amount of a specific item in the link, the lowest amount from the players will be the amount put into the group. @@ -277,7 +277,7 @@ one file, removing the need to manage separate files if one chooses to do so. As a precautionary measure, before submitting a multi-game yaml like this one in a synchronous/sync multiworld, please confirm that the other players in the multi are OK with what you are submitting, and please be fairly reasonable about -the submission. (ie. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered +the submission. (i.e. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered reasonable, but submitting a ChecksFinder alongside another game OR submitting multiple Slay the Spire runs is likely OK) @@ -295,7 +295,7 @@ requires: version: 0.3.2 Super Mario 64: progression_balancing: 50 - accessibilty: items + accessibility: items EnableCoinStars: false StrictCapRequirements: true StrictCannonRequirements: true @@ -315,7 +315,7 @@ name: Minecraft game: Minecraft Minecraft: progression_balancing: 50 - accessibilty: items + accessibility: items advancement_goal: 40 combat_difficulty: hard include_hard_advancements: false @@ -341,7 +341,7 @@ game: ChecksFinder ChecksFinder: progression_balancing: 50 - accessibilty: items + accessibility: items ``` The above example will generate 3 worlds - one Super Mario 64, one Minecraft, and one ChecksFinder. diff --git a/worlds/generic/docs/triggers_en.md b/worlds/generic/docs/triggers_en.md index c084b53fb1..b751b8a3ec 100644 --- a/worlds/generic/docs/triggers_en.md +++ b/worlds/generic/docs/triggers_en.md @@ -129,7 +129,7 @@ List, set, and dict options can additionally have values added to or removed fro option value by prefixing the option name in the trigger block with `+` (add) or `-` (remove). The exact behavior for each will depend on the option type. -- For sets, `+` will add the value(s) to the set and `-` will remove any value(s) of the set. Sets do not allow +- For sets, `+` will add the value(s) to the set and `-` will remove the value(s) from the set. Sets do not allow duplicates. - For lists, `+` will add new values(s) to the list and `-` will remove the first matching values(s) it comes across. Lists allow duplicate values. @@ -160,6 +160,3 @@ Super Metroid: In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be created. If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph Ball. - -Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key -will replace that value within the dict. From 613e76689e128ad66e37a9b443615a76f6ddd20d Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 24 May 2024 03:25:41 +0200 Subject: [PATCH 03/22] CODEOWNERS: Actually link the correct person for Yu Gi Oh (#3389) --- docs/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index c34046d5dc..068c724057 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -207,7 +207,7 @@ /worlds/yoshisisland/ @PinkSwitch #Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 -/worlds/yugioh06/ @rensen +/worlds/yugioh06/ @Rensen3 # Zillion /worlds/zillion/ @beauxq From 8045c8717c645dbc000402e81fd08b8cd41536bd Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Fri, 24 May 2024 00:18:21 -0500 Subject: [PATCH 04/22] Webhost: Allow Option Groups to specify whether they start collapsed (#3370) * allow option groups to specify whether they should be hidden or not * allow worlds to override whether game options starts collapsed * remove Game Options assert so the visibility of that group can be changed * if "Game Options" or "Item & Location Options" groups are specified, fix casing * don't allow item & location options to have duplicates of the auto added options * use a generator instead of a comprehension * use consistent naming --- Options.py | 2 ++ WebHostLib/options.py | 5 +++++ WebHostLib/templates/playerOptions/playerOptions.html | 2 +- .../templates/weightedOptions/weightedOptions.html | 2 +- worlds/AutoWorld.py | 11 +++++++++-- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Options.py b/Options.py index e11e078a1d..7f480cbaae 100644 --- a/Options.py +++ b/Options.py @@ -1130,6 +1130,8 @@ class OptionGroup(typing.NamedTuple): """Name of the group to categorize these options in for display on the WebHost and in generated YAMLS.""" options: typing.List[typing.Type[Option[typing.Any]]] """Options to be in the defined group.""" + start_collapsed: bool = False + """Whether the group will start collapsed on the WebHost options pages.""" item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints, diff --git a/WebHostLib/options.py b/WebHostLib/options.py index bc63ec9331..4a791135d7 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -32,11 +32,16 @@ def render_options_page(template: str, world_name: str, is_complex: bool = False return redirect("games") visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui + start_collapsed = {"Game Options": False} + for group in world.web.option_groups: + start_collapsed[group.name] = group.start_collapsed + return render_template( template, world_name=world_name, world=world, option_groups=Options.get_option_groups(world, visibility_level=visibility_flag), + start_collapsed=start_collapsed, issubclass=issubclass, Options=Options, theme=get_world_theme(world_name), diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html index 5657610914..2506cf9619 100644 --- a/WebHostLib/templates/playerOptions/playerOptions.html +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -69,7 +69,7 @@
{% for group_name, group_options in option_groups.items() %} -
+
{{ group_name }}
diff --git a/WebHostLib/templates/weightedOptions/weightedOptions.html b/WebHostLib/templates/weightedOptions/weightedOptions.html index c21671a863..b3aefd4835 100644 --- a/WebHostLib/templates/weightedOptions/weightedOptions.html +++ b/WebHostLib/templates/weightedOptions/weightedOptions.html @@ -51,7 +51,7 @@
{% for group_name, group_options in option_groups.items() %} -
+
{{ group_name }} {% for option_name, option in group_options.items() %}
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index f8bc525ea5..5d674c0c22 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -116,12 +116,19 @@ class WebWorldRegister(type): # don't allow an option to appear in multiple groups, allow "Item & Location Options" to appear anywhere by the # dev, putting it at the end if they don't define options in it option_groups: List[OptionGroup] = dct.get("option_groups", []) + prebuilt_options = ["Game Options", "Item & Location Options"] seen_options = [] item_group_in_list = False for group in option_groups: - assert group.name != "Game Options", "Game Options is a pre-determined group and can not be defined." assert group.options, "A custom defined Option Group must contain at least one Option." + # catch incorrectly titled versions of the prebuilt groups so they don't create extra groups + title_name = group.name.title() + if title_name in prebuilt_options: + group.name = title_name + if group.name == "Item & Location Options": + assert not any(option in item_and_loc_options for option in group.options), \ + f"Item and Location Options cannot be specified multiple times" group.options.extend(item_and_loc_options) item_group_in_list = True else: @@ -133,7 +140,7 @@ class WebWorldRegister(type): assert option not in seen_options, f"{option} found in two option groups" seen_options.append(option) if not item_group_in_list: - option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options)) + option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options, True)) return super().__new__(mcs, name, bases, dct) From 18390ecc09ad13d94767091675801e4164cdf3ea Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Fri, 24 May 2024 13:32:23 -0400 Subject: [PATCH 05/22] Witness: Fix option description (#3396) * Fixing description * Another mistake --- worlds/witness/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 00b58ab869..f51d86ba22 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -76,8 +76,8 @@ class DoorGroupings(Choice): """ Controls how door items are grouped. - - None: There will be one key for each door, potentially resulting in upwards of 120 keys being added to the item pool. - - Regional: - All doors in the same general region will open at once with a single key, reducing the amount of door items and complexity. + - Off: There will be one key for each door, potentially resulting in upwards of 120 keys being added to the item pool. + - Regional: All doors in the same general region will open at once with a single key, reducing the amount of door items and complexity. """ display_name = "Door Groupings" option_off = 0 From 61e88526cf4ab8cce380ddf2bb5ce06a7dee6151 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 25 May 2024 13:14:13 +0200 Subject: [PATCH 06/22] Core: Rename "count_exclusive" methods to "count_unique" (#3386) * rename exclusive to unique * lint * group as well --- BaseClasses.py | 8 +- worlds/bomb_rush_cyberfunk/Rules.py | 22 ++-- worlds/yugioh06/rules.py | 164 ++++++++++++++-------------- 3 files changed, 97 insertions(+), 97 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ada18f1e1d..88857f8032 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -728,7 +728,7 @@ class CollectionState(): return True return False - def has_from_list_exclusive(self, items: Iterable[str], player: int, count: int) -> bool: + def has_from_list_unique(self, items: Iterable[str], player: int, count: int) -> bool: """Returns True if the state contains at least `count` items matching any of the item names from a list. Ignores duplicates of the same item.""" found: int = 0 @@ -743,7 +743,7 @@ class CollectionState(): """Returns the cumulative count of items from a list present in state.""" return sum(self.prog_items[player][item_name] for item_name in items) - def count_from_list_exclusive(self, items: Iterable[str], player: int) -> int: + def count_from_list_unique(self, items: Iterable[str], player: int) -> int: """Returns the cumulative count of items from a list present in state. Ignores duplicates of the same item.""" return sum(self.prog_items[player][item_name] > 0 for item_name in items) @@ -758,7 +758,7 @@ class CollectionState(): return True return False - def has_group_exclusive(self, item_name_group: str, player: int, count: int = 1) -> bool: + def has_group_unique(self, item_name_group: str, player: int, count: int = 1) -> bool: """Returns True if the state contains at least `count` items present in a specified item group. Ignores duplicates of the same item. """ @@ -778,7 +778,7 @@ class CollectionState(): for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group] ) - def count_group_exclusive(self, item_name_group: str, player: int) -> int: + def count_group_unique(self, item_name_group: str, player: int) -> int: """Returns the cumulative count of items from an item group present in state. Ignores duplicates of the same item.""" player_prog_items = self.prog_items[player] diff --git a/worlds/bomb_rush_cyberfunk/Rules.py b/worlds/bomb_rush_cyberfunk/Rules.py index 6f31882cb1..f59a428570 100644 --- a/worlds/bomb_rush_cyberfunk/Rules.py +++ b/worlds/bomb_rush_cyberfunk/Rules.py @@ -5,17 +5,17 @@ from .Regions import Stages def graffitiM(state: CollectionState, player: int, limit: bool, spots: int) -> bool: - return state.count_group_exclusive("graffitim", player) * 7 >= spots if limit \ + return state.count_group_unique("graffitim", player) * 7 >= spots if limit \ else state.has_group("graffitim", player) def graffitiL(state: CollectionState, player: int, limit: bool, spots: int) -> bool: - return state.count_group_exclusive("graffitil", player) * 6 >= spots if limit \ + return state.count_group_unique("graffitil", player) * 6 >= spots if limit \ else state.has_group("graffitil", player) def graffitiXL(state: CollectionState, player: int, limit: bool, spots: int) -> bool: - return state.count_group_exclusive("graffitixl", player) * 4 >= spots if limit \ + return state.count_group_unique("graffitixl", player) * 4 >= spots if limit \ else state.has_group("graffitixl", player) @@ -469,7 +469,7 @@ def spots_s_glitchless(state: CollectionState, player: int, limit: bool, access_ break if limit: - sprayable: int = 5 + (state.count_group_exclusive("characters", player) * 5) + sprayable: int = 5 + (state.count_group_unique("characters", player) * 5) if total <= sprayable: return total else: @@ -492,7 +492,7 @@ def spots_s_glitched(state: CollectionState, player: int, limit: bool, access_ca break if limit: - sprayable: int = 5 + (state.count_group_exclusive("characters", player) * 5) + sprayable: int = 5 + (state.count_group_unique("characters", player) * 5) if total <= sprayable: return total else: @@ -537,7 +537,7 @@ def spots_m_glitchless(state: CollectionState, player: int, limit: bool, access_ break if limit: - sprayable: int = state.count_group_exclusive("graffitim", player) * 7 + sprayable: int = state.count_group_unique("graffitim", player) * 7 if total <= sprayable: return total else: @@ -563,7 +563,7 @@ def spots_m_glitched(state: CollectionState, player: int, limit: bool, access_ca break if limit: - sprayable: int = state.count_group_exclusive("graffitim", player) * 7 + sprayable: int = state.count_group_unique("graffitim", player) * 7 if total <= sprayable: return total else: @@ -614,7 +614,7 @@ def spots_l_glitchless(state: CollectionState, player: int, limit: bool, access_ break if limit: - sprayable: int = state.count_group_exclusive("graffitil", player) * 6 + sprayable: int = state.count_group_unique("graffitil", player) * 6 if total <= sprayable: return total else: @@ -641,7 +641,7 @@ def spots_l_glitched(state: CollectionState, player: int, limit: bool, access_ca break if limit: - sprayable: int = state.count_group_exclusive("graffitil", player) * 6 + sprayable: int = state.count_group_unique("graffitil", player) * 6 if total <= sprayable: return total else: @@ -685,7 +685,7 @@ def spots_xl_glitchless(state: CollectionState, player: int, limit: bool, access break if limit: - sprayable: int = state.count_group_exclusive("graffitixl", player) * 4 + sprayable: int = state.count_group_unique("graffitixl", player) * 4 if total <= sprayable: return total else: @@ -712,7 +712,7 @@ def spots_xl_glitched(state: CollectionState, player: int, limit: bool, access_c break if limit: - sprayable: int = state.count_group_exclusive("graffitixl", player) * 4 + sprayable: int = state.count_group_unique("graffitixl", player) * 4 if total <= sprayable: return total else: diff --git a/worlds/yugioh06/rules.py b/worlds/yugioh06/rules.py index 53ea95b27b..a804c7e728 100644 --- a/worlds/yugioh06/rules.py +++ b/worlds/yugioh06/rules.py @@ -154,11 +154,11 @@ def set_rules(world): lambda state: state.has_all(["Yata-Garasu", "Chaos Emperor Dragon - Envoy of the End", "Sangan"], player) and state.has_any(["No Banlist", "Banlist September 2003"], player), "Can Stall with Monsters": - lambda state: state.count_from_list_exclusive( + lambda state: state.count_from_list_unique( ["Spirit Reaper", "Giant Germ", "Marshmallon", "Nimble Momonga"], player) >= 2, "Can Stall with ST": - lambda state: state.count_from_list_exclusive(["Level Limit - Area B", "Gravity Bind", "Messenger of Peace"], - player) >= 2, + lambda state: state.count_from_list_unique(["Level Limit - Area B", "Gravity Bind", "Messenger of Peace"], + player) >= 2, "Has Back-row removal": lambda state: back_row_removal(state, player) @@ -201,8 +201,8 @@ def set_rules(world): lambda state: yugioh06_difficulty(state, player, 3), "LD18 Attacks forbidden": lambda state: state.has_all(["Wave-Motion Cannon", "Stealth Bird"], player) - and state.count_from_list_exclusive(["Dark World Lightning", "Nobleman of Crossout", - "Shield Crash", "Tribute to the Doomed"], player) >= 2 + and state.count_from_list_unique(["Dark World Lightning", "Nobleman of Crossout", + "Shield Crash", "Tribute to the Doomed"], player) >= 2 and yugioh06_difficulty(state, player, 3), "LD19 All except E-Hero's forbidden": lambda state: state.has_any(["Polymerization", "Fusion Gate"], player) and @@ -363,7 +363,7 @@ def set_rules(world): "TD30 Tribute Summon": lambda state: state.has("Treeborn Frog", player) and yugioh06_difficulty(state, player, 2), "TD31 Special Summon C": - lambda state: state.count_from_list_exclusive( + lambda state: state.count_from_list_unique( ["Aqua Spirit", "Rock Spirit", "Spirit of Flames", "Garuda the Wind Spirit", "Gigantes", "Inferno", "Megarock Dragon", "Silpheed"], player) > 4 and yugioh06_difficulty(state, player, 3), @@ -393,11 +393,11 @@ def set_rules(world): and yugioh06_difficulty(state, player, 3), "TD39 Raviel, Lord of Phantasms": lambda state: state.has_all(["Raviel, Lord of Phantasms", "Giant Germ"], player) and - state.count_from_list_exclusive(["Archfiend Soldier", - "Skull Descovery Knight", - "Slate Warrior", - "D. D. Trainer", - "Earthbound Spirit"], player) >= 3 + state.count_from_list_unique(["Archfiend Soldier", + "Skull Descovery Knight", + "Slate Warrior", + "D. D. Trainer", + "Earthbound Spirit"], player) >= 3 and yugioh06_difficulty(state, player, 3), "TD40 Make a Chain": lambda state: state.has("Ultimate Offering", player) @@ -450,20 +450,20 @@ def set_rules(world): def only_light(state, player): - return state.has_from_list_exclusive([ + return state.has_from_list_unique([ "Dunames Dark Witch", "X-Head Cannon", "Homunculus the Alchemic Being", "Hysteric Fairy", "Ninja Grandmaster Sasuke"], player, 2)\ - and state.has_from_list_exclusive([ + and state.has_from_list_unique([ "Chaos Command Magician", "Cybernetic Magician", "Kaiser Glider", "The Agent of Judgment - Saturn", "Zaborg the Thunder Monarch", "Cyber Dragon"], player, 1) \ - and state.has_from_list_exclusive([ + and state.has_from_list_unique([ "D.D. Warrior Lady", "Mystic Swordsman LV2", "Y-Dragon Head", @@ -472,7 +472,7 @@ def only_light(state, player): def only_dark(state, player): - return state.has_from_list_exclusive([ + return state.has_from_list_unique([ "Dark Elf", "Archfiend Soldier", "Mad Dog of Darkness", @@ -501,7 +501,7 @@ def only_dark(state, player): "Jinzo", "Ryu Kokki" ], player) \ - and state.has_from_list_exclusive([ + and state.has_from_list_unique([ "Legendary Fiend", "Don Zaloog", "Newdoria", @@ -512,7 +512,7 @@ def only_dark(state, player): def only_earth(state, player): - return state.has_from_list_exclusive([ + return state.has_from_list_unique([ "Berserk Gorilla", "Gemini Elf", "Insect Knight", @@ -527,7 +527,7 @@ def only_earth(state, player): "Granmarg the Rock Monarch", "Hieracosphinx", "Saber Beetle" - ], player) and state.has_from_list_exclusive([ + ], player) and state.has_from_list_unique([ "Hyper Hammerhead", "Green Gadget", "Red Gadget", @@ -539,7 +539,7 @@ def only_earth(state, player): def only_water(state, player): - return state.has_from_list_exclusive([ + return state.has_from_list_unique([ "Gagagigo", "Familiar-Possessed - Eria", "7 Colored Fish", @@ -550,7 +550,7 @@ def only_water(state, player): "Amphibian Beast", "Terrorking Salmon", "Mobius the Frost Monarch" - ], player) and state.has_from_list_exclusive([ + ], player) and state.has_from_list_unique([ "Revival Jam", "Yomi Ship", "Treeborn Frog" @@ -558,7 +558,7 @@ def only_water(state, player): def only_fire(state, player): - return state.has_from_list_exclusive([ + return state.has_from_list_unique([ "Blazing Inpachi", "Familiar-Possessed - Hiita", "Great Angus", @@ -566,7 +566,7 @@ def only_fire(state, player): ], player, 2) and state.has_any([ "Thestalos the Firestorm Monarch", "Horus the Black Flame Dragon LV6" - ], player) and state.has_from_list_exclusive([ + ], player) and state.has_from_list_unique([ "Solar Flare Dragon", "Tenkabito Shien", "Ultimate Baseball Kid" @@ -574,7 +574,7 @@ def only_fire(state, player): def only_wind(state, player): - return state.has_from_list_exclusive([ + return state.has_from_list_unique([ "Luster Dragon", "Slate Warrior", "Spear Dragon", @@ -588,7 +588,7 @@ def only_wind(state, player): "Luster Dragon #2", "Armed Dragon LV5", "Roc from the Valley of Haze" - ], player) and state.has_from_list_exclusive([ + ], player) and state.has_from_list_unique([ "Armed Dragon LV3", "Twin-Headed Behemoth", "Harpie Lady 1" @@ -599,7 +599,7 @@ def only_fairy(state, player): return state.has_any([ "Dunames Dark Witch", "Hysteric Fairy" - ], player) and (state.count_from_list_exclusive([ + ], player) and (state.count_from_list_unique([ "Dunames Dark Witch", "Hysteric Fairy", "Dancing Fairy", @@ -623,7 +623,7 @@ def only_warrior(state, player): "Gearfried the Iron knight", "Ninja Grandmaster Sasuke", "Warrior Beaters" - ], player) and (state.count_from_list_exclusive([ + ], player) and (state.count_from_list_unique([ "Warrior Lady of the Wasteland", "Exiled Force", "Mystic Swordsman LV2", @@ -644,7 +644,7 @@ def only_warrior(state, player): def only_zombie(state, player): return state.has("Pyramid Turtle", player) \ - and state.has_from_list_exclusive([ + and state.has_from_list_unique([ "Regenerating Mummy", "Ryu Kokki", "Spirit Reaper", @@ -665,7 +665,7 @@ def only_dragon(state, player): "Luster Dragon", "Spear Dragon", "Cave Dragon" - ], player) and (state.count_from_list_exclusive([ + ], player) and (state.count_from_list_unique([ "Luster Dragon", "Spear Dragon", "Cave Dragon" @@ -692,7 +692,7 @@ def only_spellcaster(state, player): "Toon Gemini Elf", "Kycoo the Ghost Destroyer", "Familiar-Possessed - Aussa" - ], player) and (state.count_from_list_exclusive([ + ], player) and (state.count_from_list_unique([ "Dark Elf", "Gemini Elf", "Skilled Dark Magician", @@ -730,7 +730,7 @@ def equip_unions(state, player): def can_gain_lp_every_turn(state, player): - return state.count_from_list_exclusive([ + return state.count_from_list_unique([ "Solemn Wishes", "Cure Mermaid", "Dancing Fairy", @@ -739,7 +739,7 @@ def can_gain_lp_every_turn(state, player): def only_normal(state, player): - return (state.has_from_list_exclusive([ + return (state.has_from_list_unique([ "Archfiend Soldier", "Gemini Elf", "Insect Knight", @@ -784,21 +784,21 @@ def only_level(state, player): def spell_counter(state, player): return (state.has("Pitch-Black Power Stone", player) and - state.has_from_list_exclusive(["Blast Magician", - "Magical Marionette", - "Mythical Beast Cerberus", - "Royal Magical Library", - "Spell-Counter Cards"], player, 2)) + state.has_from_list_unique(["Blast Magician", + "Magical Marionette", + "Mythical Beast Cerberus", + "Royal Magical Library", + "Spell-Counter Cards"], player, 2)) def take_control(state, player): - return state.has_from_list_exclusive(["Aussa the Earth Charmer", - "Jowls of Dark Demise", - "Brain Control", - "Creature Swap", - "Enemy Controller", - "Mind Control", - "Magician of Faith"], player, 5) + return state.has_from_list_unique(["Aussa the Earth Charmer", + "Jowls of Dark Demise", + "Brain Control", + "Creature Swap", + "Enemy Controller", + "Mind Control", + "Magician of Faith"], player, 5) def only_toons(state, player): @@ -818,51 +818,51 @@ def only_spirit(state, player): def pacman_deck(state, player): - return state.has_from_list_exclusive(["Des Lacooda", - "Swarm of Locusts", - "Swarm of Scarabs", - "Wandering Mummy", - "Golem Sentry", - "Great Spirit", - "Royal Keeper", - "Stealth Bird"], player, 4) + return state.has_from_list_unique(["Des Lacooda", + "Swarm of Locusts", + "Swarm of Scarabs", + "Wandering Mummy", + "Golem Sentry", + "Great Spirit", + "Royal Keeper", + "Stealth Bird"], player, 4) def quick_plays(state, player): - return state.has_from_list_exclusive(["Collapse", - "Emergency Provisions", - "Enemy Controller", - "Graceful Dice", - "Mystik Wok", - "Offerings to the Doomed", - "Poison of the Old Man", - "Reload", - "Rush Recklessly", - "The Reliable Guardian"], player, 4) + return state.has_from_list_unique(["Collapse", + "Emergency Provisions", + "Enemy Controller", + "Graceful Dice", + "Mystik Wok", + "Offerings to the Doomed", + "Poison of the Old Man", + "Reload", + "Rush Recklessly", + "The Reliable Guardian"], player, 4) def counter_traps(state, player): - return state.has_from_list_exclusive(["Cursed Seal of the Forbidden Spell", - "Divine Wrath", - "Horn of Heaven", - "Magic Drain", - "Magic Jammer", - "Negate Attack", - "Seven Tools of the Bandit", - "Solemn Judgment", - "Spell Shield Type-8"], player, 5) + return state.has_from_list_unique(["Cursed Seal of the Forbidden Spell", + "Divine Wrath", + "Horn of Heaven", + "Magic Drain", + "Magic Jammer", + "Negate Attack", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Spell Shield Type-8"], player, 5) def back_row_removal(state, player): - return state.has_from_list_exclusive(["Anteatereatingant", - "B.E.S. Tetran", - "Breaker the Magical Warrior", - "Calamity of the Wicked", - "Chiron the Mage", - "Dust Tornado", - "Heavy Storm", - "Mystical Space Typhoon", - "Mobius the Frost Monarch", - "Raigeki Break", - "Stamping Destruction", - "Swarm of Locusts"], player, 2) + return state.has_from_list_unique(["Anteatereatingant", + "B.E.S. Tetran", + "Breaker the Magical Warrior", + "Calamity of the Wicked", + "Chiron the Mage", + "Dust Tornado", + "Heavy Storm", + "Mystical Space Typhoon", + "Mobius the Frost Monarch", + "Raigeki Break", + "Stamping Destruction", + "Swarm of Locusts"], player, 2) From f249c36f8b86887e1ea3bbeb7013f9bd0723e3c1 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 26 May 2024 21:22:40 +0200 Subject: [PATCH 07/22] Setup: pin cx_freeze to 7.0.0 (#3406) 7.1.0 is broken on Linux when using pygobject, which we use as optional dependency for kivy. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3e128eec7e..54d5118a2c 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ from pathlib import Path # This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it try: - requirement = 'cx-Freeze>=7.0.0' + requirement = 'cx-Freeze==7.0.0' import pkg_resources try: pkg_resources.require(requirement) From 70d97a0eb43c424e000c86c9c3d1ddfa1611cee0 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Sun, 26 May 2024 17:27:04 -0700 Subject: [PATCH 08/22] BizHawkClient: Add suggestion when no handler is found (#3375) --- worlds/_bizhawk/context.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/_bizhawk/context.py b/worlds/_bizhawk/context.py index 0a28a47894..9fe6c9e1ff 100644 --- a/worlds/_bizhawk/context.py +++ b/worlds/_bizhawk/context.py @@ -177,7 +177,8 @@ async def _game_watcher(ctx: BizHawkClientContext): if ctx.client_handler is None: if not showed_no_handler_message: - logger.info("No handler was found for this game") + logger.info("No handler was found for this game. Double-check that the apworld is installed " + "correctly and that you loaded the right ROM file.") showed_no_handler_message = True continue else: From df877a9254784930801d13ed6531d50ff11c2fc8 Mon Sep 17 00:00:00 2001 From: Justus Lind Date: Mon, 27 May 2024 10:27:43 +1000 Subject: [PATCH 09/22] Muse Dash: 4.4.0 (#3395) --- worlds/musedash/MuseDashData.txt | 8 +++++++- worlds/musedash/Options.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt index b0f3b80c99..d822a3dc38 100644 --- a/worlds/musedash/MuseDashData.txt +++ b/worlds/musedash/MuseDashData.txt @@ -519,7 +519,7 @@ Hey Vincent.|43-49|MD Plus Project|True|6|8|10| Meteor feat. TEA|43-50|MD Plus Project|True|3|6|9| Narcissism Angel|43-51|MD Plus Project|True|1|3|6| AlterLuna|43-52|MD Plus Project|True|6|8|11|12 -Niki Tousen|43-53|MD Plus Project|True|6|8|10|11 +Niki Tousen|43-53|MD Plus Project|True|6|8|10|12 Rettou Joutou|70-0|Rin Len's Mirrorland|False|4|7|9| Telecaster B-Boy|70-1|Rin Len's Mirrorland|False|5|7|10| Iya Iya Iya|70-2|Rin Len's Mirrorland|False|2|4|7| @@ -551,3 +551,9 @@ Dance Dance Good Night Dance|73-2|Happy Otaku Pack Vol.19|True|2|4|7| Ops Limone|73-3|Happy Otaku Pack Vol.19|True|5|8|11| NOVA|73-4|Happy Otaku Pack Vol.19|True|6|8|10| Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10| +Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10| +World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11 +Territory Battles|74-2|CHUNITHM COURSE MUSE|True|5|7|9| +The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11 +Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11 +Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12 diff --git a/worlds/musedash/Options.py b/worlds/musedash/Options.py index b695395135..4f4f52ad2d 100644 --- a/worlds/musedash/Options.py +++ b/worlds/musedash/Options.py @@ -38,7 +38,7 @@ class AdditionalSongs(Range): - The final song count may be lower due to other settings. """ range_start = 15 - range_end = 528 # Note will probably not reach this high if any other settings are done. + range_end = 534 # Note will probably not reach this high if any other settings are done. default = 40 display_name = "Additional Song Count" From 74aa4eca9dc7513b2a6579481576cceca3e797b1 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Mon, 27 May 2024 18:43:25 +0200 Subject: [PATCH 10/22] MultiServer: make !hint prefer early sphere (#2862) --- Main.py | 12 ++++++++++++ MultiServer.py | 22 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 8b15a57a69..de6b467f93 100644 --- a/Main.py +++ b/Main.py @@ -372,6 +372,17 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {} + # get spheres -> filter address==None -> skip empty + spheres: List[Dict[int, Set[int]]] = [] + for sphere in multiworld.get_spheres(): + current_sphere: Dict[int, Set[int]] = collections.defaultdict(set) + for sphere_location in sphere: + if type(sphere_location.address) is int: + current_sphere[sphere_location.player].add(sphere_location.address) + + if current_sphere: + spheres.append(dict(current_sphere)) + multidata = { "slot_data": slot_data, "slot_info": slot_info, @@ -386,6 +397,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No "tags": ["AP"], "minimum_versions": minimum_versions, "seed_name": multiworld.seed_name, + "spheres": spheres, "datapackage": data_package, } AutoWorld.call_all(multiworld, "modify_multidata", multidata) diff --git a/MultiServer.py b/MultiServer.py index e95e44dd7d..4fb03732d8 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -175,8 +175,11 @@ class Context: all_item_and_group_names: typing.Dict[str, typing.Set[str]] all_location_and_group_names: typing.Dict[str, typing.Set[str]] non_hintable_names: typing.Dict[str, typing.Set[str]] + spheres: typing.List[typing.Dict[int, typing.Set[int]]] + """ each sphere is { player: { location_id, ... } } """ logger: logging.Logger + def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int, hint_cost: int, item_cheat: bool, release_mode: str = "disabled", collect_mode="disabled", remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2, @@ -238,6 +241,7 @@ class Context: self.stored_data = {} self.stored_data_notification_clients = collections.defaultdict(weakref.WeakSet) self.read_data = {} + self.spheres = [] # init empty to satisfy linter, I suppose self.gamespackage = {} @@ -466,6 +470,9 @@ class Context: for game_name, data in self.location_name_groups.items(): self.read_data[f"location_name_groups_{game_name}"] = lambda lgame=game_name: self.location_name_groups[lgame] + # sorted access spheres + self.spheres = decoded_obj.get("spheres", []) + # saving def save(self, now=False) -> bool: @@ -624,6 +631,16 @@ class Context: self.recheck_hints(team, slot) return self.hints[team, slot] + def get_sphere(self, player: int, location_id: int) -> int: + """Get sphere of a location, -1 if spheres are not available.""" + if self.spheres: + for i, sphere in enumerate(self.spheres): + if location_id in sphere.get(player, set()): + return i + raise KeyError(f"No Sphere found for location ID {location_id} belonging to player {player}. " + f"Location or player may not exist.") + return -1 + def get_players_package(self): return [NetworkPlayer(t, p, self.get_aliased_name(t, p), n) for (t, p), n in self.player_names.items()] @@ -1549,6 +1566,9 @@ class ClientMessageProcessor(CommonCommandProcessor): self.ctx.random.shuffle(not_found_hints) # By popular vote, make hints prefer non-local placements not_found_hints.sort(key=lambda hint: int(hint.receiving_player != hint.finding_player)) + # By another popular vote, prefer early sphere + not_found_hints.sort(key=lambda hint: self.ctx.get_sphere(hint.finding_player, hint.location), + reverse=True) hints = found_hints + old_hints while can_pay > 0: @@ -1558,10 +1578,10 @@ class ClientMessageProcessor(CommonCommandProcessor): hints.append(hint) can_pay -= 1 self.ctx.hints_used[self.client.team, self.client.slot] += 1 - points_available = get_client_points(self.ctx, self.client) self.ctx.notify_hints(self.client.team, hints) if not_found_hints: + points_available = get_client_points(self.ctx, self.client) if hints and cost and int((points_available // cost) == 0): self.output( f"There may be more hintables, however, you cannot afford to pay for any more. " From dfc347cd241494ee79af90cfedba9265d1122dcf Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Mon, 27 May 2024 16:52:23 -0500 Subject: [PATCH 11/22] Core: add options to the list of valid names instead of deleting game weights (#3381) --- Generate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Generate.py b/Generate.py index 30b992317d..fab34c893a 100644 --- a/Generate.py +++ b/Generate.py @@ -432,7 +432,6 @@ def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, player_option = option.from_any(game_weights[option_key]) else: player_option = option.from_any(get_choice(option_key, game_weights)) - del game_weights[option_key] else: player_option = option.from_any(option.default) # call the from_any here to support default "random" setattr(ret, option_key, player_option) @@ -446,9 +445,9 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b if "linked_options" in weights: weights = roll_linked_options(weights) - valid_trigger_names = set() + valid_keys = set() if "triggers" in weights: - weights = roll_triggers(weights, weights["triggers"], valid_trigger_names) + weights = roll_triggers(weights, weights["triggers"], valid_keys) requirements = weights.get("requires", {}) if requirements: @@ -490,7 +489,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b raise Exception(f"Remove tag cannot be used outside of trigger contexts. Found {weight}") if "triggers" in game_weights: - weights = roll_triggers(weights, game_weights["triggers"], valid_trigger_names) + weights = roll_triggers(weights, game_weights["triggers"], valid_keys) game_weights = weights[ret.game] ret.name = get_choice('name', weights) @@ -499,8 +498,9 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b for option_key, option in world_type.options_dataclass.type_hints.items(): handle_option(ret, game_weights, option_key, option, plando_options) + valid_keys.add(option_key) for option_key in game_weights: - if option_key in {"triggers", *valid_trigger_names}: + if option_key in {"triggers", *valid_keys}: continue logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.") if PlandoOptions.items in plando_options: From 04e9f5c47ab55305d6d0babc7d53283e1bc62b32 Mon Sep 17 00:00:00 2001 From: Seldom <38388947+Seldom-SE@users.noreply.github.com> Date: Tue, 28 May 2024 11:37:07 -0700 Subject: [PATCH 12/22] Migrate Terraria to new options API (#3414) --- worlds/terraria/Options.py | 16 ++++++++-------- worlds/terraria/__init__.py | 19 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/worlds/terraria/Options.py b/worlds/terraria/Options.py index 1f9ba69afe..4c4b96056c 100644 --- a/worlds/terraria/Options.py +++ b/worlds/terraria/Options.py @@ -1,5 +1,5 @@ -from Options import Choice, Option, Toggle, DeathLink -import typing +from dataclasses import dataclass +from Options import Choice, DeathLink, PerGameCommonOptions class Goal(Choice): @@ -49,9 +49,9 @@ class FillExtraChecksWith(Choice): default = 1 -options: typing.Dict[str, type(Option)] = { # type: ignore - "goal": Goal, - "achievements": Achievements, - "fill_extra_checks_with": FillExtraChecksWith, - "death_link": DeathLink, -} +@dataclass +class TerrariaOptions(PerGameCommonOptions): + goal: Goal + achievements: Achievements + fill_extra_checks_with: FillExtraChecksWith + death_link: DeathLink diff --git a/worlds/terraria/__init__.py b/worlds/terraria/__init__.py index 6ef281157f..ac6b25e516 100644 --- a/worlds/terraria/__init__.py +++ b/worlds/terraria/__init__.py @@ -25,7 +25,7 @@ from .Checks import ( armor_minions, accessory_minions, ) -from .Options import options +from .Options import TerrariaOptions class TerrariaWeb(WebWorld): @@ -49,7 +49,8 @@ class TerrariaWorld(World): game = "Terraria" web = TerrariaWeb() - option_definitions = options + options_dataclass = TerrariaOptions + options: TerrariaOptions # data_version is used to signal that items, locations or their names # changed. Set this to 0 during development so other games' clients do not @@ -70,7 +71,7 @@ class TerrariaWorld(World): goal_locations: Set[str] def generate_early(self) -> None: - goal, goal_locations = goals[self.multiworld.goal[self.player].value] + goal, goal_locations = goals[self.options.goal.value] ter_goals = {} goal_items = set() for location in goal_locations: @@ -79,7 +80,7 @@ class TerrariaWorld(World): ter_goals[item] = location goal_items.add(item) - achievements = self.multiworld.achievements[self.player].value + achievements = self.options.achievements.value location_count = 0 locations = [] for rule, flags, _, _ in rules[:goal]: @@ -89,7 +90,7 @@ class TerrariaWorld(World): or (achievements < 2 and "Grindy" in flags) or (achievements < 3 and "Fishing" in flags) or ( - rule == "Zenith" and self.multiworld.goal[self.player].value != 11 + rule == "Zenith" and self.options.goal.value != 11 ) # Bad hardcoding ): continue @@ -123,7 +124,7 @@ class TerrariaWorld(World): # Event items.append(rule) - extra_checks = self.multiworld.fill_extra_checks_with[self.player].value + extra_checks = self.options.fill_extra_checks_with.value ordered_rewards = [ reward for reward in labels["ordered"] @@ -241,7 +242,7 @@ class TerrariaWorld(World): elif condition == "calamity": return sign == self.calamity elif condition == "grindy": - return sign == (self.multiworld.achievements[self.player].value >= 2) + return sign == (self.options.achievements.value >= 2) elif condition == "pickaxe": if type(arg) is not int: raise Exception("@pickaxe requires an integer argument") @@ -340,6 +341,6 @@ class TerrariaWorld(World): def fill_slot_data(self) -> Dict[str, object]: return { "goal": list(self.goal_locations), - "achievements": self.multiworld.achievements[self.player].value, - "deathlink": bool(self.multiworld.death_link[self.player]), + "achievements": self.options.achievements.value, + "deathlink": bool(self.options.death_link), } From 5b34e06c8ba39cd04977e4022871cc5699303561 Mon Sep 17 00:00:00 2001 From: qwint Date: Tue, 28 May 2024 20:37:44 -0500 Subject: [PATCH 13/22] adds godtuner to prog and requires it for godhome flower quest manually (#3402) --- worlds/hk/GodhomeData.py | 2 +- worlds/hk/__init__.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/worlds/hk/GodhomeData.py b/worlds/hk/GodhomeData.py index 6e9d77f4dc..a2dd69ed73 100644 --- a/worlds/hk/GodhomeData.py +++ b/worlds/hk/GodhomeData.py @@ -9,7 +9,7 @@ def set_godhome_rules(hk_world, hk_set_rule): fn = partial(hk_set_rule, hk_world) required_events = { - "Godhome_Flower_Quest": lambda state: state.count('Defeated_Pantheon_5', player) and state.count('Room_Mansion[left1]', player) and state.count('Fungus3_49[right1]', player), + "Godhome_Flower_Quest": lambda state: state.count('Defeated_Pantheon_5', player) and state.count('Room_Mansion[left1]', player) and state.count('Fungus3_49[right1]', player) and state.has('Godtuner', player), "Defeated_Pantheon_5": lambda state: state.has('GG_Atrium_Roof', player) and state.has('WINGS', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) and ((state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player) and state.has('Defeated_Pantheon_4', player) and state.has('COMBAT[Radiance]', player))), "GG_Atrium_Roof": lambda state: state.has('GG_Atrium', player) and state.has('Hit_Pantheon_5_Unlock_Orb', player) and state.has('LEFTCLAW', player), diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 1359bea5ce..3530030fa6 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -659,6 +659,8 @@ class HKItem(Item): def __init__(self, name, advancement, code, type: str, player: int = None): if name == "Mimic_Grub": classification = ItemClassification.trap + elif name == "Godtuner": + classification = ItemClassification.progression elif type in ("Grub", "DreamWarrior", "Root", "Egg", "Dreamer"): classification = ItemClassification.progression_skip_balancing elif type == "Charm" and name not in progression_charms: From 649ee117dafbd49511899a7bf4c100815688dc1c Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 28 May 2024 20:46:17 -0500 Subject: [PATCH 14/22] Docs: improve contributing sign posting (#2888) * Docs: improve sign posting for contributing * fix styling as per the style guide * address review comments * apply medic's feedback --- README.md | 45 ++++++++++++++++++++++++++--------- docs/contributing.md | 56 ++++++++++++++++++++++++-------------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 4633c99c66..cebd4f7e75 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # [Archipelago](https://archipelago.gg) ![Discord Shield](https://discordapp.com/api/guilds/731205301247803413/widget.png?style=shield) | [Install](https://github.com/ArchipelagoMW/Archipelago/releases) -Archipelago provides a generic framework for developing multiworld capability for game randomizers. In all cases, presently, Archipelago is also the randomizer itself. +Archipelago provides a generic framework for developing multiworld capability for game randomizers. In all cases, +presently, Archipelago is also the randomizer itself. Currently, the following games are supported: + * The Legend of Zelda: A Link to the Past * Factorio * Minecraft @@ -77,36 +79,57 @@ windows binaries. ## History -Archipelago is built upon a strong legacy of brilliant hobbyists. We want to honor that legacy by showing it here. The repositories which Archipelago is built upon, inspired by, or otherwise owes its gratitude to are: +Archipelago is built upon a strong legacy of brilliant hobbyists. We want to honor that legacy by showing it here. +The repositories which Archipelago is built upon, inspired by, or otherwise owes its gratitude to are: * [bonta0's MultiWorld](https://github.com/Bonta0/ALttPEntranceRandomizer/tree/multiworld_31) * [AmazingAmpharos' Entrance Randomizer](https://github.com/AmazingAmpharos/ALttPEntranceRandomizer) * [VT Web Randomizer](https://github.com/sporchia/alttp_vt_randomizer) * [Dessyreqt's alttprandomizer](https://github.com/Dessyreqt/alttprandomizer) -* [Zarby89's](https://github.com/Ijwu/Enemizer/commits?author=Zarby89) and [sosuke3's](https://github.com/Ijwu/Enemizer/commits?author=sosuke3) contributions to Enemizer, which make the vast majority of Enemizer contributions. +* [Zarby89's](https://github.com/Ijwu/Enemizer/commits?author=Zarby89) + and [sosuke3's](https://github.com/Ijwu/Enemizer/commits?author=sosuke3) contributions to Enemizer, which make up the + vast majority of Enemizer contributions. -We recognize that there is a strong community of incredibly smart people that have come before us and helped pave the path. Just because one person's name may be in a repository title does not mean that only one person made that project happen. We can't hope to perfectly cover every single contribution that lead up to Archipelago but we hope to honor them fairly. +We recognize that there is a strong community of incredibly smart people that have come before us and helped pave the +path. Just because one person's name may be in a repository title does not mean that only one person made that project +happen. We can't hope to perfectly cover every single contribution that lead up to Archipelago, but we hope to honor +them fairly. ### Path to the Archipelago -Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to _MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as "Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository (as opposed to a 'forked repo') and change the name (which came later) to better reflect our project. + +Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a +long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to +_MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as +"Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository +(as opposed to a 'forked repo') and change the name (which came later) to better reflect our project. ## Running Archipelago -For most people, all you need to do is head over to the [releases](https://github.com/ArchipelagoMW/Archipelago/releases) page then download and run the appropriate installer, or AppImage for Linux-based systems. -If you are a developer or are running on a platform with no compiled releases available, please see our doc on [running Archipelago from source](docs/running%20from%20source.md). +For most people, all you need to do is head over to +the [releases page](https://github.com/ArchipelagoMW/Archipelago/releases), then download and run the appropriate +installer, or AppImage for Linux-based systems. + +If you are a developer or are running on a platform with no compiled releases available, please see our doc on +[running Archipelago from source](docs/running%20from%20source.md). ## Related Repositories -This project makes use of multiple other projects. We wouldn't be here without these other repositories and the contributions of their developers, past and present. + +This project makes use of multiple other projects. We wouldn't be here without these other repositories and the +contributions of their developers, past and present. * [z3randomizer](https://github.com/ArchipelagoMW/z3randomizer) * [Enemizer](https://github.com/Ijwu/Enemizer) * [Ocarina of Time Randomizer](https://github.com/TestRunnerSRL/OoT-Randomizer) ## Contributing -For contribution guidelines, please see our [Contributing doc.](/docs/contributing.md) + +To contribute to Archipelago, including the WebHost, core program, or by adding a new game, see our +[Contributing guidelines](/docs/contributing.md). ## FAQ -For Frequently asked questions, please see the website's [FAQ Page.](https://archipelago.gg/faq/en/) + +For Frequently asked questions, please see the website's [FAQ Page](https://archipelago.gg/faq/en/). ## Code of Conduct -Please refer to our [code of conduct.](/docs/code_of_conduct.md) + +Please refer to our [code of conduct](/docs/code_of_conduct.md). diff --git a/docs/contributing.md b/docs/contributing.md index e7f3516712..9fd21408eb 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,43 +1,49 @@ # Contributing -Contributions are welcome. We have a few requests for new contributors: + +All contributions are welcome, though we have a few requests of contributors, whether they be for core, webhost, or new +game contributions: * **Follow styling guidelines.** Please take a look at the [code style documentation](/docs/style.md) to ensure ease of communication and uniformity. -* **Ensure that critical changes are covered by tests.** -It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working. -If you wish to contribute by adding a new game, please take a look at the [logic unit test documentation](/docs/tests.md). -If you wish to contribute to the website, please take a look at [these tests](/test/webhost). +* **Ensure that critical changes are covered by tests.** + It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working. + If you wish to contribute by adding a new game, please take a look at + the [logic unit test documentation](/docs/tests.md). + If you wish to contribute to the website, please take a look at [these tests](/test/webhost). * **Do not introduce unit test failures/regressions.** -Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test -your changes. Currently, the oldest supported version is [Python 3.8](https://www.python.org/downloads/release/python-380/). -It is recommended that automated github actions are turned on in your fork to have github run all of the unit tests after pushing. -You can turn them on here: -![Github actions example](./img/github-actions-example.png) + Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test + your changes. Currently, the oldest supported version + is [Python 3.8](https://www.python.org/downloads/release/python-380/). + It is recommended that automated github actions are turned on in your fork to have github run unit tests after + pushing. + You can turn them on here: + ![Github actions example](./img/github-actions-example.png) * **When reviewing PRs, please leave a message about what was done.** -We don't have full test coverage, so manual testing can help. -For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing -or checking if all code paths are covered by automated tests is desired. The original author may not have been able -to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to -state which games or settings were rolled, if any. -Please also tell us if you looked at code, just did functional testing, did both, or did neither. -If testing the PR depends on other PRs, please state what you merged into what for testing. -We cannot determine what "LGTM" means without additional context, so that should not be the norm. + We don't have full test coverage, so manual testing can help. + For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing + or checking if all code paths are covered by automated tests is desired. The original author may not have been able + to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to + state which games or settings were rolled, if any. + Please also tell us if you looked at code, just did functional testing, did both, or did neither. + If testing the PR depends on other PRs, please state what you merged into what for testing. + We cannot determine what "LGTM" means without additional context, so that should not be the norm. -Other than these requests, we tend to judge code on a case-by-case basis. +Other than these requests, we tend to judge code on a case-by-case basis. For contribution to the website, please refer to the [WebHost README](/WebHostLib/README.md). If you want to contribute to the core, you will be subject to stricter review on your pull requests. It is recommended that you get in touch with other core maintainers via the [Discord](https://archipelago.gg/discord). -If you want to add Archipelago support for a new game, please take a look at the [adding games documentation](/docs/adding%20games.md), which details what is required -to implement support for a game, as well as tips for how to get started. -If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a -[world maintainer](/docs/world%20maintainer.md). +If you want to add Archipelago support for a new game, please take a look at +the [adding games documentation](/docs/adding%20games.md) +which details what is required to implement support for a game, and has tips on to get started. +If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a +[world maintainer](/docs/world%20maintainer.md). -For other questions, feel free to explore the [main documentation folder](/docs/) and ask us questions in the #archipelago-dev channel -of the [Discord](https://archipelago.gg/discord). +For other questions, feel free to explore the [main documentation folder](/docs), and ask us questions in the +#ap-world-dev channel of the [Discord](https://archipelago.gg/discord). From 527559395c2d7bb7e4f05835b0ec6548a803caa6 Mon Sep 17 00:00:00 2001 From: neocerber <140952826+neocerber@users.noreply.github.com> Date: Tue, 28 May 2024 18:48:52 -0700 Subject: [PATCH 15/22] Docs, Starcraft 2: Add French documentation for setup and game page (#3031) * Started to create the french doc * First version of sc2 setup in french finish, created the file for the introduction of the game in french * French-fy upgrade in setup, continue translation of game description * Finish writing FR game page, added a link to it on the english game page. Re-read and corrected both the game page and setup page. * Corrected a sentence in the SC2 English setup guide. * Applied 120 carac limits for french part, applied modification for consistency. * Added reference to website yaml checker, applied several wording correction/suggestions * Modified link to AP page to be in relative (fr/en), uniformed SC2 and random writing (fr), applied some suggestons in writing quality(fr), added a mention to the datapackage (fr/en), enhanced prog balancing recommendation (fr) * Correction of some grammar issues * Removed name correction for english part since done in other PR; added mention to hotkey and language restriction * Applied suggestions of peer review * Applied mofications proposed by reviewer about the external website --------- Co-authored-by: neocerber --- worlds/sc2/__init__.py | 13 +- worlds/sc2/docs/en_Starcraft 2.md | 5 +- worlds/sc2/docs/fr_Starcraft 2.md | 95 +++++++++++++ worlds/sc2/docs/setup_en.md | 22 ++- worlds/sc2/docs/setup_fr.md | 214 ++++++++++++++++++++++++++++++ 5 files changed, 339 insertions(+), 10 deletions(-) create mode 100644 worlds/sc2/docs/fr_Starcraft 2.md create mode 100644 worlds/sc2/docs/setup_fr.md diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 59c6fe9001..ec8a447d93 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -22,7 +22,7 @@ from .MissionTables import MissionInfo, SC2Campaign, lookup_name_to_mission, SC2 class Starcraft2WebWorld(WebWorld): - setup = Tutorial( + setup_en = Tutorial( "Multiworld Setup Guide", "A guide to setting up the Starcraft 2 randomizer connected to an Archipelago Multiworld", "English", @@ -31,7 +31,16 @@ class Starcraft2WebWorld(WebWorld): ["TheCondor", "Phaneros"] ) - tutorials = [setup] + setup_fr = Tutorial( + setup_en.tutorial_name, + setup_en.description, + "Français", + "setup_fr.md", + "setup/fr", + ["Neocerber"] + ) + + tutorials = [setup_en, setup_fr] class SC2World(World): diff --git a/worlds/sc2/docs/en_Starcraft 2.md b/worlds/sc2/docs/en_Starcraft 2.md index 784d711319..06464e3cd2 100644 --- a/worlds/sc2/docs/en_Starcraft 2.md +++ b/worlds/sc2/docs/en_Starcraft 2.md @@ -1,5 +1,8 @@ # Starcraft 2 +## Game page in other languages: +* [Français](/games/Starcraft%202/info/fr) + ## What does randomization do to this game? The following unlocks are randomized as items: @@ -39,7 +42,7 @@ The goal is to beat the final mission in the mission order. The yaml configurati ## Which of my items can be in another player's world? By default, any of StarCraft 2's items (specified above) can be in another player's world. See the -[Advanced YAML Guide](https://archipelago.gg/tutorial/Archipelago/advanced_settings/en) +[Advanced YAML Guide](/tutorial/Archipelago/advanced_settings/en) for more information on how to change this. ## Unique Local Commands diff --git a/worlds/sc2/docs/fr_Starcraft 2.md b/worlds/sc2/docs/fr_Starcraft 2.md new file mode 100644 index 0000000000..4fcc8e689b --- /dev/null +++ b/worlds/sc2/docs/fr_Starcraft 2.md @@ -0,0 +1,95 @@ +# *StarCraft 2* + +## Quel est l'effet de la *randomization* sur ce jeu ? + +Les éléments qui suivent sont les *items* qui sont *randomized* et qui doivent être débloqués pour être utilisés dans +le jeu: +1. La capacité de produire des unités, excepté les drones/probes/scv. +2. Des améliorations spécifiques à certaines unités incluant quelques combinaisons qui ne sont pas disponibles dans les +campagnes génériques, comme le fait d'avoir les deux types d'évolution en même temps pour une unité *Zerg* et toutes +les améliorations de la *Spear of Adun* simultanément pour les *Protoss*. +3. L'accès aux améliorations génériques des unités, e.g. les améliorations d'attaque et d'armure. +4. D'autres améliorations diverses telles que les améliorations de laboratoire et les mercenaires pour les *Terran*, +les niveaux et les améliorations de Kerrigan pour les *Zerg*, et les améliorations de la *Spear of Adun* pour les +*Protoss*. +5. Avoir des *minerals*, du *vespene gas*, et du *supply* au début de chaque mission. + +Les *items* sont trouvés en accomplissant du progrès dans les catégories suivantes: +* Terminer des missions +* Réussir des objectifs supplémentaires (e.g., récolter le matériel pour les recherches dans *Wings of Liberty*) +* Atteindre des étapes importantes dans la mission, e.g. réussir des sous-objectifs +* Réussir des défis basés sur les succès du jeu de base, e.g. éliminer tous les *Zerg* dans la mission +*Devil's Playground* + +Ces catégories, outre la première, peuvent être désactivées dans les options du jeu. +Par exemple, vous pouvez désactiver le fait d'obtenir des *items* lorsque des étapes importantes d'une mission sont +accomplies. + +Quand vous recevez un *item*, il devient immédiatement disponible, même pendant une mission, et vous serez avertis via +la boîte de texte situé dans le coin en haut à droite de *StarCraft 2*. +L'acquisition d'un *item* est aussi indiquée dans le client d'Archipelago. + +Les missions peuvent être lancées par le client *StarCraft 2 Archipelago*, via l'interface graphique de l'onglet +*StarCraft 2 Launcher*. +Les segments qui se passent sur l'*Hyperion*, un Léviathan et la *Spear of Adun* ne sont pas inclus. +De plus, les points de progression tels que les crédits ou la Solarite ne sont pas utilisés dans *StarCraft 2 +Archipelago*. + +## Quel est le but de ce jeu quand il est *randomized*? + +Le but est de réussir la mission finale dans la disposition des missions (e.g. *blitz*, *grid*, etc.). +Les choix faits dans le fichier *yaml* définissent la disposition des missions et comment elles sont mélangées. + +## Quelles sont les modifications non aléatoires comparativement à la version de base de *StarCraft 2* + +1. Certaines des missions ont plus de *vespene geysers* pour permettre l'utilisation d'une plus grande variété d'unités. +2. Plusieurs unités et améliorations ont été ajoutées sous la forme d*items*. +Ils proviennent de la version *co-op*, *melee*, des autres campagnes, d'expansions ultérieures, de *Brood War*, ou de +l'imagination des développeurs de *StarCraft 2 Archipelago*. +3. Les structures de production, e.g. *Factory*, *Starport*, *Robotics Facility*, and *Stargate*, n'ont plus +d'exigences technologiques. +4. Les missions avec la race *Zerg* ont été modifiées pour que les joueurs débuttent avec un *Lair* lorsqu'elles +commençaient avec une *Hatchery*. +5. Les désavantages des améliorations ont été enlevés, e.g. *automated refinery* qui coûte plus cher ou les *tech +reactors* qui prennent plus de temps à construire. +6. La collision des unités dans les couloirs de la mission *Enemy Within* a été ajustée pour permettre des unités +plus larges de les traverser sans être coincés dans des endroits étranges. +7. Plusieurs *bugs* du jeu original ont été corrigés. + +## Quels sont les *items* qui peuvent être dans le monde d'un autre joueur? + +Par défaut, tous les *items* de *StarCraft 2 Archipelago* (voir la section précédente) peuvent être dans le monde d'un +autre joueur. +Consulter [*Advanced YAML Guide*](/tutorial/Archipelago/advanced_settings/en) pour savoir comment +changer ça. + +## Commandes du client qui sont uniques à ce jeu + +Les commandes qui suivent sont seulement disponibles uniquement pour le client de *StarCraft 2 Archipelago*. +Vous pouvez les afficher en utilisant la commande `/help` dans le client de *StarCraft 2 Archipelago*. +Toutes ces commandes affectent seulement le client où elles sont utilisées. + +* `/download_data` Télécharge les versions les plus récentes des fichiers pour jouer à *StarCraft 2 Archipelago*. +Les fichiers existants vont être écrasés. +* `/difficulty [difficulty]` Remplace la difficulté choisie pour le monde. + * Les options sont *casual*, *normal*, *hard*, et *brutal*. +* `/game_speed [game_speed]` Remplace la vitesse du jeu pour le monde. + * Les options sont *default*, *slower*, *slow*, *normal*, *fast*, and *faster*. +* `/color [faction] [color]` Remplace la couleur d'une des *factions* qui est jouable. + * Les options de *faction*: raynor, kerrigan, primal, protoss, nova. + * Les options de couleur: *white*, *red*, *blue*, *teal*, *purple*, *yellow*, *orange*, *green*, *lightpink*, +*violet*, *lightgrey*, *darkgreen*, *brown*, *lightgreen*, *darkgrey*, *pink*, *rainbow*, *random*, *default*. +* `/option [option_name] [option_value]` Permet de changer un option normalement définit dans le *yaml*. + * Si la commande est lancée sans option, la liste des options qui sont modifiables va être affichée. + * Les options qui peuvent être changées avec cette commande incluent sauter les cinématiques automatiquement, la +présence de Kerrigan dans les missions, la disponibilité de la *Spear of Adun*, la quantité de ressources +supplémentaires données au début des missions, la capacité de contrôler les alliées IA, etc. +* `/disable_mission_check` Désactive les requit pour lancer les missions. +Cette option a pour but de permettre de jouer en mode coopératif en permettant à un joueur de jouer à la prochaine +mission de la chaîne qu'un autre joueur est en train d'entamer. +* `/play [mission_id]` Lance la mission correspondant à l'identifiant donné. +* `/available` Affiche les missions qui sont présentement accessibles. +* `/unfinished` Affiche les missions qui sont présentement accessibles et dont certains des objectifs permettant +l'accès à un *item* n'ont pas été accomplis. +* `/set_path [path]` Permet de définir manuellement où *StarCraft 2* est installé ce qui est pertinent seulement si la +détection automatique de cette dernière échoue. diff --git a/worlds/sc2/docs/setup_en.md b/worlds/sc2/docs/setup_en.md index 4956109778..991ed57e87 100644 --- a/worlds/sc2/docs/setup_en.md +++ b/worlds/sc2/docs/setup_en.md @@ -23,18 +23,20 @@ Yaml files are configuration files that tell Archipelago how you'd like your gam When you're setting up a multiworld, every world needs its own yaml file. There are three basic ways to get a yaml: -* You can go to the [Player Options](https://archipelago.gg/games/Starcraft%202/player-options) page, set your options in the GUI, and export the yaml. -* You can generate a template, either by downloading it from the [Player Options](https://archipelago.gg/games/Starcraft%202/player-options) page or by generating it from the Launcher (ArchipelagoLauncher.exe). The template includes descriptions of each option, you just have to edit it in your text editor of choice. +* You can go to the [Player Options](/games/Starcraft%202/player-options) page, set your options in the GUI, and export the yaml. +* You can generate a template, either by downloading it from the [Player Options](/games/Starcraft%202/player-options) page or by generating it from the Launcher (ArchipelagoLauncher.exe). The template includes descriptions of each option, you just have to edit it in your text editor of choice. * You can ask someone else to share their yaml to use it for yourself or adjust it as you wish. Remember the name you enter in the options page or in the yaml file, you'll need it to connect later! -Check out [Creating a YAML](https://archipelago.gg/tutorial/Archipelago/setup/en#creating-a-yaml) for more game-agnostic information. +Check out [Creating a YAML](/tutorial/Archipelago/setup/en#creating-a-yaml) for more game-agnostic information. ### Common yaml questions #### How do I know I set my yaml up correctly? -The simplest way to check is to test it out. Save your yaml to the Players/ folder within your Archipelago installation and run ArchipelagoGenerate.exe. You should see a new .zip file within the output/ folder of your Archipelago installation if things worked correctly. It's advisable to run ArchipelagoGenerate through a terminal so that you can see the printout, which will include any errors and the precise output file name if it's successful. If you don't like terminals, you can also check the log file in the logs/ folder. +The simplest way to check is to use the website [validator](/check). + +You can also test it by attempting to generate a multiworld with your yaml. Save your yaml to the Players/ folder within your Archipelago installation and run ArchipelagoGenerate.exe. You should see a new .zip file within the output/ folder of your Archipelago installation if things worked correctly. It's advisable to run ArchipelagoGenerate through a terminal so that you can see the printout, which will include any errors and the precise output file name if it's successful. If you don't like terminals, you can also check the log file in the logs/ folder. #### What does Progression Balancing do? @@ -64,9 +66,15 @@ start_inventory: An empty mapping is just a matching pair of curly braces: `{}`. That's the default value in the template, which should let you know to use this syntax. -#### How do I know the exact names of items? +#### How do I know the exact names of items and locations? -You can look up a complete list if item names in the [Icon Repository](https://matthewmarinets.github.io/ap_sc2_icons/). +The [*datapackage*](/datapackage) page of the Archipelago website provides a complete list of the items and locations for each game that it currently supports, including StarCraft 2. + +You can also look up a complete list of the item names in the [Icon Repository](https://matthewmarinets.github.io/ap_sc2_icons/) page. +This page also contains supplementary information of each item. +However, the items shown in that page might differ from those shown in the datapackage page of Archipelago since the former is generated, most of the time, from beta versions of StarCraft 2 Archipelago undergoing development. + +As for the locations, you can see all the locations associated to a mission in your world by placing your cursor over the mission in the 'StarCraft 2 Launcher' tab in the client. ## How do I join a MultiWorld game? @@ -86,7 +94,7 @@ specific description of what's going wrong and attach your log file to your mess ## Running in macOS -To run StarCraft 2 through Archipelago in macOS, you will need to run the client via source as seen here: [macOS Guide](https://archipelago.gg/tutorial/Archipelago/mac/en). Note: when running the client, you will need to run the command `python3 Starcraft2Client.py`. +To run StarCraft 2 through Archipelago in macOS, you will need to run the client via source as seen here: [macOS Guide](/tutorial/Archipelago/mac/en). Note: to launch the client, you will need to run the command `python3 Starcraft2Client.py`. ## Running in Linux diff --git a/worlds/sc2/docs/setup_fr.md b/worlds/sc2/docs/setup_fr.md new file mode 100644 index 0000000000..bb6c35bce1 --- /dev/null +++ b/worlds/sc2/docs/setup_fr.md @@ -0,0 +1,214 @@ +# Guide d'installation du *StarCraft 2 Randomizer* + +Ce guide contient les instructions pour installer et dépanner le client de *StarCraft 2 Archipelago*, ainsi que des +indications pour obtenir un fichier de configuration de *StarCraft 2 Archipelago* et comment modifier ce dernier. + +## Logiciels requis + +- [*StarCraft 2*](https://starcraft2.com/en-us/) +- [La version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) + +## Comment est-ce que j'installe ce *randomizer*? + +1. Installer *StarCraft 2* et Archipelago en suivant les instructions indiquées dans les liens précédents. Le client de +*StarCraft 2 Archipelago* est téléchargé par le programme d'installation d'Archipelago. + - Les utilisateurs de Linux devraient aussi suivre les instructions qui se retrouvent à la fin de cette page +(["Exécuter sous Linux"](#exécuter-sous-linux)). + - Notez que votre jeu *StarCraft 2* doit être en anglais pour fonctionner avec Archipelago. +2. Exécuter `ArchipelagoStarcraft2Client.exe`. + - Uniquement pour cette étape, les utilisateurs de macOS devraient plutôt suivre les instructions qui se trouvent à +["Exécuter sous macOS"](#exécuter-sous-macos). +3. Dans le client de *StarCraft 2 Archipelago*, écrire la commande `/download_data`. Cette commande va lancer +l'installation des fichiers qui sont nécessaires pour jouer à *StarCraft 2 Archipelago*. + +## Où est-ce que j'obtiens le fichier de configuration (i.e., le *yaml*) pour ce jeu? + +Un fichier dans le format *yaml* est utilisé pour communiquer à Archipelago comment vous voulez que votre jeu soit +*randomized*. +Ce dernier est nécessaire même si vous voulez utiliser les options par défaut. +L'approche usuelle pour générer un *multiworld* consiste à avoir un fichier *yaml* par monde. + +Il y a trois approches pour obtenir un fichier *yaml* pour *StarCraft 2 Randomizer*: +* Vous pouvez aller à la page [*Player options*](/games/Starcraft%202/player-options) qui vous permet de définir vos +choix via une interface graphique et ensuite télécharger le *yaml* correspondant à ces choix. +* Vous pouvez obtenir le modèle de base en le téléchargeant à la page +[*Player options*](/games/Starcraft%202/player-options) ou en cliquant sur *Generate template* après avoir exécuté le +*Launcher* d'Archipelago (i.e., `ArchipelagoLauncher.exe`). Ce modèle de base inclut une description pour chacune des +options et vous n'avez qu'à modifier les options dans un éditeur de texte de votre choix. +* Vous pouvez demander à quelqu'un d'autre de partager un de ces fichiers *yaml* pour l'utiliser ou l'ajuster à vos +préférences. + +Prenez soin de vous rappeler du nom de joueur que vous avez inscrit dans la page à options ou dans le fichier *yaml* +puisque vous en aurez besoin pour vous connecter à votre monde! + +Notez que la page *Player options* ne permet pas de définir certaines des options avancées, e.g., l'exclusion de +certaines unités ou de leurs améliorations. +Utilisez la page [*Weighted Options*](/weighted-options) pour avoir accès à ces dernières. + +Si vous désirez des informations et/ou instructions générales sur l'utilisation d'un fichier *yaml* pour Archipelago, +veuillez consulter [*Creating a YAML*](/tutorial/Archipelago/setup/en#creating-a-yaml). + +### Questions récurrentes à propos du fichier *yaml* +#### Comment est-ce que je sais que mon *yaml* est bien défini? + +La manière la plus simple de valider votre *yaml* est d'utiliser le +[système de validation](/check) du site web. + +Vous pouvez aussi le tester en tentant de générer un *multiworld* avec votre *yaml*. +Pour faire ça, sauvegardez votre *yaml* dans le dossier `Players/` de votre installation d'Archipelago et exécutez +`ArchipelagoGenerate.exe`. +Si votre *yaml* est bien défini, vous devriez voir un nouveau fichier, avec l'extension `.zip`, apparaître dans le +dossier `output/` de votre installation d'Archipelago. +Il est recommandé de lancer `ArchipelagoGenerate.exe` via un terminal afin que vous puissiez voir les messages générés +par le logiciel, ce qui va inclure toutes erreurs qui ont eu lieu et le nom de fichier généré. +Si vous n'appréciez pas le fait d'utiliser un terminal, vous pouvez aussi regarder le fichier *log* qui va être produit +dans le dossier `logs/`. + +#### À quoi sert l'option *Progression Balancing*? + +Pour *Starcraft 2*, cette option ne fait pas grand-chose. +Il s'agit d'une option d'Archipelago permettant d'équilibrer la progression des mondes en interchangeant les *items* +dans les *spheres*. +Si le *Progression Balancing* d'un monde est plus grand que ceux des autres, les *items* de progression de ce monde ont +plus de chance d'être obtenus tôt et vice-versa si sa valeur est plus petite que celle des autres mondes. +Cependant, *Starcraft 2* est beaucoup plus permissif en termes d'*items* qui permettent de progresser, ce réglage à +donc peu d'influence sur la progression dans *StarCraft 2*. +Vu qu'il augmente le temps de génération d'un *MultiWorld*, nous recommandons de le désactiver, c-à-d le définir à +zéro, pour *Starcraft 2*. + + +#### Comment est-ce que je définis une liste d'*items*, e.g. pour l'option *excluded items*? + +Vous pouvez lire sur la syntaxe des conteneurs dans le format *yaml* à la page +[*YAML specification*](https://yaml.org/spec/1.2.2/#21-collections). +Pour les listes, chaque *item* doit être sur sa propre ligne et doit être précédé par un trait d'union. + +```yaml +excluded_items: + - Battlecruiser + - Drop-Pods (Kerrigan Tier 7) +``` + +Une liste vide est représentée par une paire de crochets: `[]`. +Il s'agit de la valeur par défaut dans le modèle de base, ce qui devrait vous aider à apprendre à utiliser cette +syntaxe. + +#### Comment est-ce que je fais pour avoir des *items* dès le départ? + +L'option *starting inventory* est un *map* et non une liste. +Ainsi, elle permet de spécifier le nombre de chaque *item* avec lequel vous allez commencer. +Sa syntaxe consiste à indiquer le nom de l'*item*, suivi par un deux-points, puis par un espace et enfin par le nombre +désiré de cet *item*. + +```yaml +start_inventory: + Micro-Filtering: 1 + Additional Starting Vespene: 5 +``` + +Un *map* vide est représenté par une paire d'accolades: `{}`. +Il s'agit de la valeur par défaut dans le modèle de base, ce qui devrait vous aider à apprendre à utiliser cette +syntaxe. + +#### Comment est-ce que je fais pour connaître le nom des *items* et des *locations* dans *StarCraft 2 Archipelago*? + +La page [*datapackage*](/datapackage) d'Archipelago liste l'ensemble des *items* et des *locations* de tous les jeux +que le site web prend en charge actuellement, dont ceux de *StarCraft 2*. + +Vous trouverez aussi la liste complète des *items* de *StarCraft 2 Archipelago* à la page +[*Icon Repository*](https://matthewmarinets.github.io/ap_sc2_icons/). +Notez que cette page contient diverses informations supplémentaires sur chacun des *items*. +Cependant, l'information présente dans cette dernière peut différer de celle du *datapackage* d'Archipelago +puisqu'elle est générée, habituellement, à partir de la version en développement de *StarCraft 2 Archipelago* qui +n'ont peut-être pas encore été inclus dans le site web d'Archipelago. + +## Comment est-ce que je peux joindre un *MultiWorld*? + +1. Exécuter `ArchipelagoStarcraft2Client.exe`. + - Uniquement pour cette étape, les utilisateurs de macOS devraient plutôt suivre les instructions à la page +["Exécuter sous macOS"](#exécuter-sous-macos). +2. Entrer la commande `/connect [server ip]`. + - Si le *MultiWorld* est hébergé via un siteweb, l'IP du server devrait être indiqué dans le haut de la page de +votre *room*. +3. Inscrivez le nom de joueur spécifié dans votre *yaml* lorsque vous y êtes invité. +4. Si le serveur a un mot de passe, l'inscrire lorsque vous y êtes invité. +5. Une fois connecté, aller sur l'onglet *StarCraft 2 Launcher* dans le client. Dans cet onglet, vous devriez trouver +toutes les missions de votre monde. Les missions qui ne sont pas disponibles présentement auront leur texte dans une +nuance de gris. Vous n'avez qu'à cliquer une des missions qui est disponible pour la commencer! + +## *StarCraft 2* ne démarre pas quand je tente de commencer une mission + +Pour commencer, regarder le fichier *log* pour trouver le problème (ce dernier devrait être dans +`[Archipelago Directory]/logs/SC2Client.txt`). +Si vous ne comprenez pas le problème avec le fichier *log*, visitez notre +[*Discord*](https://discord.com/invite/8Z65BR2) pour demander de l'aide dans le forum *tech-support*. +Dans votre message, veuillez inclure une description détaillée de ce qui ne marche pas et ajouter en pièce jointe le +fichier *log*. + +## Mon profil de raccourcis clavier n'est pas disponibles quand je joue à *StarCraft 2 Archipelago* + +Pour que votre profil de raccourcis clavier fonctionne dans Archipelago, vous devez copier votre fichier de raccourcis +qui se trouve dans `Documents/StarCraft II/Accounts/######/Hotkeys` vers `Documents/StarCraft II/Hotkeys`. +Si le dossier n'existe pas, créez-le. + +Pour que *StarCraft 2 Archipelago* utilise votre profil, suivez les étapes suivantes. +Lancez *Starcraft 2* via l'application *Battle.net*. +Changez votre profil de raccourcis clavier pour le mode standard et acceptez, puis sélectionnez votre profil +personnalisé et acceptez. +Vous n'aurez besoin de faire ça qu'une seule fois. + +## Exécuter sous macOS + +Pour exécuter *StarCraft 2* via Archipelago sous macOS, vous devez exécuter le client à partir de la source +comme indiqué ici: [*macOS Guide*](/tutorial/Archipelago/mac/en). +Notez que pour lancer le client, vous devez exécuter la commande `python3 Starcraft2Client.py`. + +## Exécuter sous Linux + +Pour exécuter *StarCraft 2* via Archipelago sous Linux, vous allez devoir installer le jeu avec *Wine* et ensuite +exécuter le client d'Archipelago pour Linux. + +Confirmez que vous avez installé *StarCraft 2* via *Wine* et que vous avez suivi les +[instructions d'installation](#comment-est-ce-que-j'installe-ce-randomizer?) pour ajouter les *Maps* et les *Data +files* nécessairent pour *StarCraft 2 Archipelago* au bon endroit. +Vous n'avez pas besoin de copier les fichiers `.dll`. +Si vous avez des difficultés pour installer ou exécuter *StarCraft 2* sous Linux, il est recommandé d'utiliser le +logiciel *Lutris*. + +Copier ce qui suit dans un fichier avec l'extension `.sh`, en prenant soin de définir les variables **WINE** et +**SC2PATH** avec les bons chemins et de définir **PATH_TO_ARCHIPELAGO** avec le chemin vers le dossier qui contient le +*AppImage* si ce dernier n'est pas dans le même dossier que ce script. + +```sh +# Permet au client de savoir que SC2 est exécuté via Wine +export SC2PF=WineLinux +export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +# À_CHANGER Remplacer le chemin avec celui qui correspond à la version de Wine utilisé pour exécuter SC2 +export WINE="/usr/bin/wine" + +# À_CHANGER Remplacer le chemin par celui qui indique où StarCraft II est installé +export SC2PATH="/home/user/Games/starcraft-ii/drive_c/Program Files (x86)/StarCraft II/" + +# À_CHANGER Indiquer le dossier qui contient l'AppImage d'Archipelago +PATH_TO_ARCHIPELAGO= + +# Obtiens la dernière version de l'AppImage de Archipelago dans le dossier PATH_TO_ARCHIPELAGO. +# Si PATH_TO_ARCHIPELAGO n'est pas défini, la valeur par défaut est le dossier qui contient ce script. +ARCHIPELAGO="$(ls ${PATH_TO_ARCHIPELAGO:-$(dirname $0)}/Archipelago_*.AppImage | sort -r | head -1)" + +# Lance le client de Archipelago +$ARCHIPELAGO Starcraft2Client +``` + +Pour une installation via Lutris, vous pouvez exécuter `lutris -l` pour obtenir l'identifiant numérique de votre +installation *StarCraft II* et ensuite exécuter la commande suivante, en remplacant **${ID}** pour cet identifiant +numérique. + + lutris lutris:rungameid/${ID} --output-script sc2.sh + +Cette commande va définir toutes les variables d'environnement nécessaires pour exécuter *StarCraft 2* dans un script, +incluant le chemin vers l'exécutable *Wine* que Lutris utilise. +Après ça, vous pouvez enlever la ligne qui permet de démarrer *Battle.Net* et copier le code décrit plus haut dans le +script produit. + From e31a7093de004b062b76f8ebb6b115740c7b86e8 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Wed, 29 May 2024 16:53:18 +0200 Subject: [PATCH 16/22] WebHost: use settings defaults for /api/generate and options -> Single Player Generate (#3411) --- WebHostLib/generate.py | 28 +++++++++++++--------------- WebHostLib/options.py | 3 ++- settings.py | 11 ----------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index a78560cb0b..a12dc0f4ae 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -6,7 +6,7 @@ import random import tempfile import zipfile from collections import Counter -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, Set from flask import flash, redirect, render_template, request, session, url_for from pony.orm import commit, db_session @@ -16,6 +16,7 @@ from Generate import PlandoOptions, handle_name from Main import main as ERmain from Utils import __version__ from WebHostLib import app +from settings import ServerOptions, GeneratorOptions from worlds.alttp.EntranceRandomizer import parse_arguments from .check import get_yaml_data, roll_options from .models import Generation, STATE_ERROR, STATE_QUEUED, Seed, UUID @@ -23,25 +24,22 @@ from .upload import upload_zip_to_db def get_meta(options_source: dict, race: bool = False) -> Dict[str, Union[List[str], Dict[str, Any]]]: - plando_options = { - options_source.get("plando_bosses", ""), - options_source.get("plando_items", ""), - options_source.get("plando_connections", ""), - options_source.get("plando_texts", "") - } - plando_options -= {""} + plando_options: Set[str] = set() + for substr in ("bosses", "items", "connections", "texts"): + if options_source.get(f"plando_{substr}", substr in GeneratorOptions.plando_options): + plando_options.add(substr) server_options = { - "hint_cost": int(options_source.get("hint_cost", 10)), - "release_mode": options_source.get("release_mode", "goal"), - "remaining_mode": options_source.get("remaining_mode", "disabled"), - "collect_mode": options_source.get("collect_mode", "disabled"), - "item_cheat": bool(int(options_source.get("item_cheat", 1))), + "hint_cost": int(options_source.get("hint_cost", ServerOptions.hint_cost)), + "release_mode": options_source.get("release_mode", ServerOptions.release_mode), + "remaining_mode": options_source.get("remaining_mode", ServerOptions.remaining_mode), + "collect_mode": options_source.get("collect_mode", ServerOptions.collect_mode), + "item_cheat": bool(int(options_source.get("item_cheat", not ServerOptions.disable_item_cheat))), "server_password": options_source.get("server_password", None), } generator_options = { - "spoiler": int(options_source.get("spoiler", 0)), - "race": race + "spoiler": int(options_source.get("spoiler", GeneratorOptions.spoiler)), + "race": race, } if race: diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 4a791135d7..1026d76385 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -11,6 +11,7 @@ import Options from Utils import local_path from worlds.AutoWorld import AutoWorldRegister from . import app, cache +from .generate import get_meta def create() -> None: @@ -50,7 +51,7 @@ def render_options_page(template: str, world_name: str, is_complex: bool = False def generate_game(options: Dict[str, Union[dict, str]]) -> Union[Response, str]: from .generate import start_generation - return start_generation(options, {"plando_options": ["items", "connections", "texts", "bosses"]}) + return start_generation(options, get_meta({})) def send_yaml(player_name: str, formatted_options: dict) -> Response: diff --git a/settings.py b/settings.py index 9d1c0904dd..7ab618c344 100644 --- a/settings.py +++ b/settings.py @@ -643,17 +643,6 @@ class GeneratorOptions(Group): PLAYTHROUGH = 2 FULL = 3 - class GlitchTriforceRoom(IntEnum): - """ - Glitch to Triforce room from Ganon - When disabled, you have to have a weapon that can hurt ganon (master sword or swordless/easy item functionality - + hammer) and have completed the goal required for killing ganon to be able to access the triforce room. - 1 -> Enabled. - 0 -> Disabled (except in no-logic) - """ - OFF = 0 - ON = 1 - class PlandoOptions(str): """ List of options that can be plando'd. Can be combined, for example "bosses, items" From 34f903e97a2dc7f14124d64f6a21ad04685f525a Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Wed, 29 May 2024 09:59:40 -0500 Subject: [PATCH 17/22] CODEOWNERS: Remove @jtoyoda as world maintainer for Final Fantasy (#3398) --- docs/CODEOWNERS | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 068c724057..f54132e24a 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -6,10 +6,6 @@ # # All usernames must be GitHub usernames (and are case sensitive). -################### -## Active Worlds ## -################### - # Adventure /worlds/adventure/ @JusticePS @@ -67,9 +63,6 @@ # Factorio /worlds/factorio/ @Berserker66 -# Final Fantasy -/worlds/ff1/ @jtoyoda - # Final Fantasy Mystic Quest /worlds/ffmq/ @Alchav @wildham0 @@ -215,9 +208,22 @@ # Zork Grand Inquisitor /worlds/zork_grand_inquisitor/ @nbrochu -################################## -## Disabled Unmaintained Worlds ## -################################## + +## Active Unmaintained Worlds + +# The following worlds in this repo are currently unmaintained, but currently still work in core. If any update breaks +# compatibility, these worlds may be moved to `worlds_disabled`. If you are interested in stepping up as maintainer for +# any of these worlds, please review `/docs/world maintainer.md` documentation. + +# Final Fantasy (1) +# /worlds/ff1/ + + +## Disabled Unmaintained Worlds + +# The following worlds in this repo are currently unmaintained and disabled as they do not work in core. If you are +# interested in stepping up as maintainer for any of these worlds, please review `/docs/world maintainer.md` +# documentation. # Ori and the Blind Forest -# /worlds_disabled/oribf/ +# /worlds_disabled/oribf/ From 378af4b07c65f36c1f0d7cf437d89a45e81acb74 Mon Sep 17 00:00:00 2001 From: Witchybun <96719127+Witchybun@users.noreply.github.com> Date: Wed, 29 May 2024 13:16:19 -0500 Subject: [PATCH 18/22] Stardew Valley: Fix magic altar logic (#3417) * Fix magic altar logic * Force a tuple (really?) * Fix received and force progression on all spells * Reversing the tuple change (?yllaer) --- worlds/stardew_valley/data/items.csv | 18 +++---- .../stardew_valley/mods/logic/magic_logic.py | 5 +- worlds/stardew_valley/strings/spells.py | 50 +++++++++++-------- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index a3096cf789..9ecb2ba364 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -735,26 +735,26 @@ id,name,classification,groups,mod_name 10007,Tractor Garage,useful,,Tractor Mod 10008,Woods Obelisk,progression,,DeepWoods 10009,Spell: Clear Debris,progression,MAGIC_SPELL,Magic -10010,Spell: Till,useful,MAGIC_SPELL,Magic +10010,Spell: Till,progression,MAGIC_SPELL,Magic 10011,Spell: Water,progression,MAGIC_SPELL,Magic 10012,Spell: Blink,progression,MAGIC_SPELL,Magic -10013,Spell: Evac,useful,MAGIC_SPELL,Magic -10014,Spell: Haste,useful,MAGIC_SPELL,Magic +10013,Spell: Evac,progression,MAGIC_SPELL,Magic +10014,Spell: Haste,progression,MAGIC_SPELL,Magic 10015,Spell: Heal,progression,MAGIC_SPELL,Magic -10016,Spell: Buff,useful,MAGIC_SPELL,Magic +10016,Spell: Buff,progression,MAGIC_SPELL,Magic 10017,Spell: Shockwave,progression,MAGIC_SPELL,Magic 10018,Spell: Fireball,progression,MAGIC_SPELL,Magic 10019,Spell: Frostbolt,progression,MAGIC_SPELL,Magic 10020,Spell: Teleport,progression,MAGIC_SPELL,Magic -10021,Spell: Lantern,useful,MAGIC_SPELL,Magic +10021,Spell: Lantern,progression,MAGIC_SPELL,Magic 10022,Spell: Tendrils,progression,MAGIC_SPELL,Magic -10023,Spell: Photosynthesis,useful,MAGIC_SPELL,Magic +10023,Spell: Photosynthesis,progression,MAGIC_SPELL,Magic 10024,Spell: Descend,progression,MAGIC_SPELL,Magic 10025,Spell: Meteor,progression,MAGIC_SPELL,Magic -10026,Spell: Bloodmana,useful,MAGIC_SPELL,Magic -10027,Spell: Lucksteal,useful,MAGIC_SPELL,Magic +10026,Spell: Bloodmana,progression,MAGIC_SPELL,Magic +10027,Spell: Lucksteal,progression,MAGIC_SPELL,Magic 10028,Spell: Spirit,progression,MAGIC_SPELL,Magic -10029,Spell: Rewind,useful,MAGIC_SPELL,Magic +10029,Spell: Rewind,progression,MAGIC_SPELL,Magic 10030,Pendant of Community,progression,,DeepWoods 10031,Pendant of Elders,progression,,DeepWoods 10032,Pendant of Depths,progression,,DeepWoods diff --git a/worlds/stardew_valley/mods/logic/magic_logic.py b/worlds/stardew_valley/mods/logic/magic_logic.py index 99482b0630..662ff3acae 100644 --- a/worlds/stardew_valley/mods/logic/magic_logic.py +++ b/worlds/stardew_valley/mods/logic/magic_logic.py @@ -8,7 +8,7 @@ from ...mods.mod_data import ModNames from ...stardew_rule import StardewRule, False_ from ...strings.ap_names.skill_level_names import ModSkillLevel from ...strings.region_names import MagicRegion -from ...strings.spells import MagicSpell +from ...strings.spells import MagicSpell, all_spells class MagicLogicMixin(BaseLogicMixin): @@ -27,7 +27,8 @@ class MagicLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, HasLogicM def can_use_altar(self) -> StardewRule: if ModNames.magic not in self.options.mods: return False_() - return self.logic.region.can_reach(MagicRegion.altar) + spell_rule = False_() + return self.logic.region.can_reach(MagicRegion.altar) & self.logic.received_any(*all_spells) def has_any_spell(self) -> StardewRule: if ModNames.magic not in self.options.mods: diff --git a/worlds/stardew_valley/strings/spells.py b/worlds/stardew_valley/strings/spells.py index ef5545c569..4b246c173a 100644 --- a/worlds/stardew_valley/strings/spells.py +++ b/worlds/stardew_valley/strings/spells.py @@ -1,22 +1,30 @@ +all_spells = [] + + +def spell(name: str) -> str: + all_spells.append(name) + return name + + class MagicSpell: - clear_debris = "Spell: Clear Debris" - till = "Spell: Till" - water = "Spell: Water" - blink = "Spell: Blink" - evac = "Spell: Evac" - haste = "Spell: Haste" - heal = "Spell: Heal" - buff = "Spell: Buff" - shockwave = "Spell: Shockwave" - fireball = "Spell: Fireball" - frostbite = "Spell: Frostbolt" - teleport = "Spell: Teleport" - lantern = "Spell: Lantern" - tendrils = "Spell: Tendrils" - photosynthesis = "Spell: Photosynthesis" - descend = "Spell: Descend" - meteor = "Spell: Meteor" - bloodmana = "Spell: Bloodmana" - lucksteal = "Spell: Lucksteal" - spirit = "Spell: Spirit" - rewind = "Spell: Rewind" + clear_debris = spell("Spell: Clear Debris") + till = spell("Spell: Till") + water = spell("Spell: Water") + blink = spell("Spell: Blink") + evac = spell("Spell: Evac") + haste = spell("Spell: Haste") + heal = spell("Spell: Heal") + buff = spell("Spell: Buff") + shockwave = spell("Spell: Shockwave") + fireball = spell("Spell: Fireball") + frostbite = spell("Spell: Frostbolt") + teleport = spell("Spell: Teleport") + lantern = spell("Spell: Lantern") + tendrils = spell("Spell: Tendrils") + photosynthesis = spell("Spell: Photosynthesis") + descend = spell("Spell: Descend") + meteor = spell("Spell: Meteor") + bloodmana = spell("Spell: Bloodmana") + lucksteal = spell("Spell: Lucksteal") + spirit = spell("Spell: Spirit") + rewind = spell("Spell: Rewind") From 6f6bf3c62d4411b41570ffc0728d5c937ed4cafb Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 30 May 2024 18:16:13 +0200 Subject: [PATCH 19/22] CustomServer: properly 'inherit' Archipelago from static_server_data (#3366) This fixes a potential exception during room spin-up. --- WebHostLib/customserver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index 16769b7a76..3a86cb551d 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -106,9 +106,9 @@ class WebHostContext(Context): static_gamespackage = self.gamespackage # this is shared across all rooms static_item_name_groups = self.item_name_groups static_location_name_groups = self.location_name_groups - self.gamespackage = {"Archipelago": static_gamespackage["Archipelago"]} # this may be modified by _load - self.item_name_groups = {} - self.location_name_groups = {} + self.gamespackage = {"Archipelago": static_gamespackage.get("Archipelago", {})} # this may be modified by _load + self.item_name_groups = {"Archipelago": static_item_name_groups.get("Archipelago", {})} + self.location_name_groups = {"Archipelago": static_location_name_groups.get("Archipelago", {})} for game in list(multidata.get("datapackage", {})): game_data = multidata["datapackage"][game] From 2fe8c433510831429ecef766accfec03af01c2f8 Mon Sep 17 00:00:00 2001 From: Salzkorn Date: Thu, 30 May 2024 18:52:01 +0200 Subject: [PATCH 20/22] SC2: Fix Kerrigan Primal Form on Half Completion (#3419) --- worlds/sc2/Client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index 96b3ddc66b..4e55509dda 100644 --- a/worlds/sc2/Client.py +++ b/worlds/sc2/Client.py @@ -966,8 +966,8 @@ def kerrigan_primal(ctx: SC2Context, kerrigan_level: int) -> bool: return kerrigan_level >= 35 elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_half_completion: total_missions = len(ctx.mission_id_to_location_ids) - completed = len([(mission_id * VICTORY_MODULO + get_location_offset(mission_id)) in ctx.checked_locations - for mission_id in ctx.mission_id_to_location_ids]) + completed = sum((mission_id * VICTORY_MODULO + get_location_offset(mission_id)) in ctx.checked_locations + for mission_id in ctx.mission_id_to_location_ids) return completed >= (total_missions / 2) elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_item: codes = [item.item for item in ctx.items_received] From 7058575c9502a4a35bb8e75fae767f2269c576c4 Mon Sep 17 00:00:00 2001 From: BadMagic100 Date: Thu, 30 May 2024 10:57:54 -0700 Subject: [PATCH 21/22] Hollow Knight: Add missing comma (#3403) --- worlds/hk/Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index f7b4420c74..f408528821 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -105,7 +105,7 @@ default_on = { "RandomizeVesselFragments", "RandomizeCharmNotches", "RandomizePaleOre", - "RandomizeRancidEggs" + "RandomizeRancidEggs", "RandomizeRelics", "RandomizeStags", "RandomizeLifebloodCocoons" From b055a394547a1180d1cadd370b4cd072455ae9e8 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Fri, 31 May 2024 15:48:21 -0400 Subject: [PATCH 22/22] PKMN R/B: "J.r" -> "Jr." (#3423) --- worlds/pokemon_rb/locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index b7b7e533a5..251beb59cc 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -636,7 +636,7 @@ location_data = [ LocationData("Rock Tunnel B1F-W", "PokeManiac 3", None, rom_addresses["Trainersanity_EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_2_ITEM"], EventFlag(11), inclusion=trainersanity), LocationData("Route 10-N", "Jr. Trainer F 1", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_3_ITEM"], EventFlag(308), inclusion=trainersanity), LocationData("Route 10-C", "PokeManiac 1", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_0_ITEM"], EventFlag(311), inclusion=trainersanity), - LocationData("Route 10-S", "J.r Trainer F 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_5_ITEM"], EventFlag(306), inclusion=trainersanity), + LocationData("Route 10-S", "Jr. Trainer F 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_5_ITEM"], EventFlag(306), inclusion=trainersanity), LocationData("Route 10-S", "Hiker 1", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_1_ITEM"], EventFlag(310), inclusion=trainersanity), LocationData("Route 10-S", "Hiker 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_4_ITEM"], EventFlag(307), inclusion=trainersanity), LocationData("Route 10-S", "PokeManiac 2", None, rom_addresses["Trainersanity_EVENT_BEAT_ROUTE_10_TRAINER_2_ITEM"], EventFlag(309), inclusion=trainersanity),