From 37b569eca60875b53f554422b30e6b1ad12c6943 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Mon, 27 Jun 2022 03:10:41 -0700 Subject: [PATCH 01/16] Changes: (#639) * Changes: * When client loses connection to the server through no fault of your own, it no longer forgets your username. * It is now possible to do /connect archipelago://username:password@server:port or to paste archipelago://username:password@server:port into the connect bar and hit connect, and have both the username/password filled in that way. * Switch checksfinder client to getting username from url if suppplied by url. * Correct the print statement --- ChecksFinderClient.py | 5 +---- CommonClient.py | 30 ++++++++++++++++++++++++------ Starcraft2Client.py | 5 +---- kvui.py | 11 +++++++++-- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/ChecksFinderClient.py b/ChecksFinderClient.py index edf40995c9..3bb96ceb6c 100644 --- a/ChecksFinderClient.py +++ b/ChecksFinderClient.py @@ -36,10 +36,7 @@ class ChecksFinderContext(CommonContext): async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: await super(ChecksFinderContext, self).server_auth(password_requested) - if not self.auth: # TODO: Replace this if block with await self.getusername() once that PR is merged in. - logger.info('Enter slot name:') - self.auth = await self.console_input() - + await self.get_username() await self.send_connect() async def connection_closed(self): diff --git a/CommonClient.py b/CommonClient.py index 17f013d6e0..76623ff3f2 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -43,12 +43,14 @@ class ClientCommandProcessor(CommandProcessor): def _cmd_connect(self, address: str = "") -> bool: """Connect to a MultiWorld Server""" self.ctx.server_address = None + self.ctx.username = None asyncio.create_task(self.ctx.connect(address if address else None), name="connecting") return True def _cmd_disconnect(self) -> bool: """Disconnect from a MultiWorld Server""" self.ctx.server_address = None + self.ctx.username = None asyncio.create_task(self.ctx.disconnect(), name="disconnecting") return True @@ -161,6 +163,7 @@ class CommonContext: def __init__(self, server_address, password): # server state self.server_address = server_address + self.username = None self.password = password self.hint_cost = None self.slot_info = {} @@ -253,6 +256,13 @@ class CommonContext: self.password = await self.console_input() return self.password + async def get_username(self): + if not self.auth: + self.auth = self.username + if not self.auth: + logger.info('Enter slot name:') + self.auth = await self.console_input() + async def send_connect(self, **kwargs): payload = { 'cmd': 'Connect', @@ -309,6 +319,7 @@ class CommonContext: async def shutdown(self): self.server_address = "" + self.username = None if self.server and not self.server.socket.closed: await self.server.socket.close() if self.server_task: @@ -469,12 +480,20 @@ async def server_loop(ctx: CommonContext, address=None): logger.info('Please connect to an Archipelago server.') return - address = f"ws://{address}" if "://" not in address else address - port = urllib.parse.urlparse(address).port or 38281 + address = f"ws://{address}" if "://" not in address \ + else address.replace("archipelago://", "ws://") + + server_url = urllib.parse.urlparse(address) + if server_url.username: + ctx.username = server_url.username + if server_url.password: + ctx.password = server_url.password + port = server_url.port or 38281 logger.info(f'Connecting to Archipelago server at {address}') try: socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None) + ctx.ui.update_address_bar(server_url.netloc) ctx.server = Endpoint(socket) logger.info('Connected') ctx.server_address = address @@ -585,6 +604,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict): raise Exception('Connection refused by the multiworld host, no reason provided') elif cmd == 'Connected': + ctx.username = ctx.auth ctx.team = args["team"] ctx.slot = args["slot"] # int keys get lost in JSON transfer @@ -708,10 +728,7 @@ if __name__ == '__main__': async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: await super(TextContext, self).server_auth(password_requested) - if not self.auth: - logger.info('Enter slot name:') - self.auth = await self.console_input() - + await self.get_username() await self.send_connect() def on_package(self, cmd: str, args: dict): @@ -722,6 +739,7 @@ if __name__ == '__main__': async def main(args): ctx = TextContext(args.connect, args.password) ctx.auth = args.name + ctx.server_address = args.connect ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop") if gui_enabled: diff --git a/Starcraft2Client.py b/Starcraft2Client.py index 54cc484a75..f9b6b43fe3 100644 --- a/Starcraft2Client.py +++ b/Starcraft2Client.py @@ -95,10 +95,7 @@ class SC2Context(CommonContext): async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: await super(SC2Context, self).server_auth(password_requested) - if not self.auth: - logger.info('Enter slot name:') - self.auth = await self.console_input() - + await self.get_username() await self.send_connect() def on_package(self, cmd: str, args: dict): diff --git a/kvui.py b/kvui.py index 6a8079e913..3c1161f99b 100644 --- a/kvui.py +++ b/kvui.py @@ -334,8 +334,8 @@ class GameManager(App): # top part server_label = ServerLabel() self.connect_layout.add_widget(server_label) - self.server_connect_bar = ConnectBarTextInput(text="archipelago.gg", size_hint_y=None, height=30, multiline=False, - write_tab=False) + self.server_connect_bar = ConnectBarTextInput(text=self.ctx.server_address or "archipelago.gg", size_hint_y=None, + height=30, multiline=False, write_tab=False) self.server_connect_bar.bind(on_text_validate=self.connect_button_action) self.connect_layout.add_widget(self.server_connect_bar) self.server_connect_button = Button(text="Connect", size=(100, 30), size_hint_y=None, size_hint_x=None) @@ -412,6 +412,7 @@ class GameManager(App): def connect_button_action(self, button): if self.ctx.server: self.ctx.server_address = None + self.ctx.username = None asyncio.create_task(self.ctx.disconnect()) else: asyncio.create_task(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", ""))) @@ -445,6 +446,12 @@ class GameManager(App): self.log_panels["Archipelago"].on_message_markup(text) self.log_panels["All"].on_message_markup(text) + def update_address_bar(self, text: str): + if hasattr(self, "server_connect_bar"): + self.server_connect_bar.text = text + else: + logging.getLogger("Client").info("Could not update address bar as the GUI is not yet initialized.") + def enable_energy_link(self): if not hasattr(self, "energy_link_label"): self.energy_link_label = Label(text="Energy Link: Standby", From cd9f8f3119fef1a4ef4578141465f8499c9b0911 Mon Sep 17 00:00:00 2001 From: The T Date: Mon, 27 Jun 2022 08:43:48 -0400 Subject: [PATCH 02/16] SM64: DDD 100 Coins in Entrance Rando should expect sub removal (#711) I brought this up in #super-mario-64, and the minor consensus is that 100 Coins is "possible", the same way Red Coins is possible. According to a FAQ online, DDD has 106 coins. That means you are still required to get at least 5 of the red coins in order to get the 100 coin star. If we already have a rule stating the Red Coins require the sub to be removed (by reaching Bowser in the Fire Sea), it should apply to the 100 coins as well. The consensus on it being "possible" was that it requires a very specific triple jump. There is no "Strict" category for this since it isn't caps/cannons-based, but it is extremely unreasonable to casual play. If you want to sequence break it, go for it, but I don't think it should be expected. --- worlds/sm64ex/Rules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index 0ba025d77a..6229793fe4 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -45,6 +45,7 @@ def set_rules(world, player: int, area_connections): add_rule(world.get_location("BBH: Eye to Eye in the Secret Room", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("DDD: Collect the Caps...", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("DDD: Pole-Jumping for Red Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player)) + add_rule(world.get_location("DDD: 100 Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player)) add_rule(world.get_location("SL: Into the Igloo", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("RR: Somewhere Over the Rainbow", player), lambda state: state.has("Cannon Unlock RR", player)) From 2a0198b618292996e1b00661d989f854e935b15b Mon Sep 17 00:00:00 2001 From: alwaysintreble Date: Mon, 27 Jun 2022 19:59:42 -0500 Subject: [PATCH 03/16] multiserver: allow `!release` as an alias for `!forfeit` (#693) * multiserver: allow `!release` as an alias for `!forfeit` * create `/release` command. Add some periods to messages that print in console and point users to release * Add a missing space on line 1135 Co-authored-by: Chris Wilson --- MultiServer.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 762fe8f4e3..06f9a9f9cd 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -766,7 +766,7 @@ def update_checked_locations(ctx: Context, team: int, slot: int): def forfeit_player(ctx: Context, team: int, slot: int): """register any locations that are in the multidata""" all_locations = set(ctx.locations[slot]) - ctx.notify_all("%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1)) + ctx.notify_all("%s (Team #%d) has released all remaining items from their world." % (ctx.player_names[(team, slot)], team + 1)) register_location_checks(ctx, team, slot, all_locations) update_checked_locations(ctx, team, slot) @@ -779,7 +779,7 @@ def collect_player(ctx: Context, team: int, slot: int, is_group: bool = False): if values[1] == slot: all_locations[source_slot].add(location_id) - ctx.notify_all("%s (Team #%d) has collected" % (ctx.player_names[(team, slot)], team + 1)) + ctx.notify_all("%s (Team #%d) has collected their items from other worlds." % (ctx.player_names[(team, slot)], team + 1)) for source_player, location_ids in all_locations.items(): register_location_checks(ctx, team, source_player, location_ids, count_activity=False) update_checked_locations(ctx, team, source_player) @@ -1106,7 +1106,7 @@ class ClientMessageProcessor(CommonCommandProcessor): return self.ctx.commandprocessor(command) def _cmd_players(self) -> bool: - """Get information about connected and missing players""" + """Get information about connected and missing players.""" if len(self.ctx.player_names) < 10: self.ctx.notify_all(get_players_string(self.ctx)) else: @@ -1118,8 +1118,12 @@ class ClientMessageProcessor(CommonCommandProcessor): self.output(get_status_string(self.ctx, self.client.team)) return True + def _cmd_release(self) -> bool: + """Sends remaining items in your world to their recipients.""" + return self._cmd_forfeit() + def _cmd_forfeit(self) -> bool: - """Surrender and send your remaining items out to their recipients""" + """Surrender and send your remaining items out to their recipients. Use release in the future.""" if self.ctx.allow_forfeits.get((self.client.team, self.client.slot), False): forfeit_player(self.ctx, self.client.team, self.client.slot) return True @@ -1128,7 +1132,7 @@ class ClientMessageProcessor(CommonCommandProcessor): return True elif "disabled" in self.ctx.forfeit_mode: self.output( - "Sorry, client forfeiting has been disabled on this server. You can ask the server admin for a /forfeit") + "Sorry, client item releasing has been disabled on this server. You can ask the server admin for a /release") return False else: # is auto or goal if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL: @@ -1136,8 +1140,8 @@ class ClientMessageProcessor(CommonCommandProcessor): return True else: self.output( - "Sorry, client forfeiting requires you to have beaten the game on this server." - " You can ask the server admin for a /forfeit") + "Sorry, client item releasing requires you to have beaten the game on this server." + " You can ask the server admin for a /release") return False def _cmd_collect(self) -> bool: @@ -1698,43 +1702,48 @@ class ServerCommandProcessor(CommonCommandProcessor): self.output(f"Could not find player {player_name} to collect") return False + @mark_raw + def _cmd_release(self, player_name: str) -> bool: + """Send out the remaining items from a player to their intended recipients.""" + return self._cmd_forfeit(player_name) + @mark_raw def _cmd_forfeit(self, player_name: str) -> bool: - """Send out the remaining items from a player to their intended recipients""" + """Send out the remaining items from a player to their intended recipients.""" seeked_player = player_name.lower() for (team, slot), name in self.ctx.player_names.items(): if name.lower() == seeked_player: forfeit_player(self.ctx, team, slot) return True - self.output(f"Could not find player {player_name} to forfeit") + self.output(f"Could not find player {player_name} to release") return False @mark_raw def _cmd_allow_forfeit(self, player_name: str) -> bool: - """Allow the specified player to use the !forfeit command""" + """Allow the specified player to use the !release command.""" seeked_player = player_name.lower() for (team, slot), name in self.ctx.player_names.items(): if name.lower() == seeked_player: self.ctx.allow_forfeits[(team, slot)] = True - self.output(f"Player {player_name} is now allowed to use the !forfeit command at any time.") + self.output(f"Player {player_name} is now allowed to use the !release command at any time.") return True - self.output(f"Could not find player {player_name} to allow the !forfeit command for.") + self.output(f"Could not find player {player_name} to allow the !release command for.") return False @mark_raw def _cmd_forbid_forfeit(self, player_name: str) -> bool: - """"Disallow the specified player from using the !forfeit command""" + """"Disallow the specified player from using the !release command.""" seeked_player = player_name.lower() for (team, slot), name in self.ctx.player_names.items(): if name.lower() == seeked_player: self.ctx.allow_forfeits[(team, slot)] = False self.output( - f"Player {player_name} has to follow the server restrictions on use of the !forfeit command.") + f"Player {player_name} has to follow the server restrictions on use of the !release command.") return True - self.output(f"Could not find player {player_name} to forbid the !forfeit command for.") + self.output(f"Could not find player {player_name} to forbid the !release command for.") return False def _cmd_send_multiple(self, amount: typing.Union[int, str], player_name: str, *item_name: str) -> bool: From 98b714f84ab902f560342e77cfc002fa9bb1e3fa Mon Sep 17 00:00:00 2001 From: Daniel Grace Date: Mon, 27 Jun 2022 18:05:29 -0700 Subject: [PATCH 04/16] HK: Add options for Deathlink. (#672) --- worlds/hk/Options.py | 46 +++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index b535bfb0da..38e2e955d3 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -288,6 +288,25 @@ class WhitePalace(Choice): default = 0 +class DeathLink(Choice): + """ + When you die, everyone dies. Of course the reverse is true too. + When enabled, choose how incoming deathlinks are handled: + vanilla: DeathLink kills you and is just like any other death. RIP your previous shade and geo. + shadeless: DeathLink kills you, but no shade spawns and no geo is lost. Your previous shade, if any, is untouched. + shade: DeathLink functions like a normal death if you do not already have a shade, shadeless otherwise. + """ + option_off = 0 + alias_false = 0 + alias_no = 0 + alias_true = 1 + alias_on = 1 + alias_yes = 1 + option_shadeless = 1 + option_vanilla = 2 + option_shade = 3 + + class StartingGeo(Range): """The amount of starting geo you have.""" display_name = "Starting Geo" @@ -299,19 +318,16 @@ class StartingGeo(Range): hollow_knight_options: typing.Dict[str, type(Option)] = { **hollow_knight_randomize_options, **hollow_knight_logic_options, - StartLocation.__name__: StartLocation, - MinimumGrubPrice.__name__: MinimumGrubPrice, - MaximumGrubPrice.__name__: MaximumGrubPrice, - MinimumEssencePrice.__name__: MinimumEssencePrice, - MaximumEssencePrice.__name__: MaximumEssencePrice, - MinimumCharmPrice.__name__: MinimumCharmPrice, - MaximumCharmPrice.__name__: MaximumCharmPrice, - RandomCharmCosts.__name__: RandomCharmCosts, - PlandoCharmCosts.__name__: PlandoCharmCosts, - MinimumEggPrice.__name__: MinimumEggPrice, - MaximumEggPrice.__name__: MaximumEggPrice, - EggShopSlots.__name__: EggShopSlots, - Goal.__name__: Goal, - WhitePalace.__name__: WhitePalace, - StartingGeo.__name__: StartingGeo, + **{ + option.__name__: option + for option in ( + StartLocation, Goal, WhitePalace, StartingGeo, DeathLink, + MinimumGrubPrice, MaximumGrubPrice, + MinimumEssencePrice, MaximumEssencePrice, + MinimumCharmPrice, MaximumCharmPrice, + RandomCharmCosts, PlandoCharmCosts, + MinimumEggPrice, MaximumEggPrice, EggShopSlots, + # Add your new options where it makes sense? + ) + } } From 5f2193f2e4e319c4fe6c2a8280a8d4b2fbf9d681 Mon Sep 17 00:00:00 2001 From: alwaysintreble Date: Mon, 27 Jun 2022 20:05:55 -0500 Subject: [PATCH 05/16] ror2: update setup guide (#671) * ror2: remove yaml template from guide and link to player settings page. Add documentation on chat client * ror2: copy paste the good config description like everyone else. --- worlds/ror2/docs/setup_en.md | 83 +++++++----------------------------- 1 file changed, 15 insertions(+), 68 deletions(-) diff --git a/worlds/ror2/docs/setup_en.md b/worlds/ror2/docs/setup_en.md index 73f47081e9..dc1c3769c7 100644 --- a/worlds/ror2/docs/setup_en.md +++ b/worlds/ror2/docs/setup_en.md @@ -23,6 +23,16 @@ all necessary dependencies as well. Click on the "Start modded" button in the top left in r2modman to start the game with the Archipelago mod installed. +## Configuring your YAML File +### What is a YAML and why do I need one? +You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) here on the Archipelago website to learn +about why Archipelago uses YAML files and what they're for. + +### Where do I get a YAML? +You can use the [game settings page for Hollow Knight](/games/Hollow%20Knight/player-settings) here on the Archipelago +website to generate a YAML using a graphical interface. + + ## Joining an Archipelago Session There will be a menu button on the right side of the screen in the character select menu. Click it in order to bring up @@ -37,71 +47,8 @@ The Risk of Rain 2 players send checks by causing items to spawn in-game. That m generally. An item check is only sent out after a certain number of items are picked up. This count is configurable in the player's YAML. -## YAML Settings - -An example YAML would look like this: - -```yaml -description: Ijwu-ror2 -name: Ijwu - -game: - Risk of Rain 2: 1 - -Risk of Rain 2: - total_locations: 15 - total_revivals: 4 - start_with_revive: true - item_pickup_step: 1 - enable_lunar: true - item_weights: - default: 50 - new: 0 - uncommon: 0 - legendary: 0 - lunartic: 0 - chaos: 0 - no_scraps: 0 - even: 0 - scraps_only: 0 - item_pool_presets: true - # custom item weights - green_scrap: 16 - red_scrap: 4 - yellow_scrap: 1 - white_scrap: 32 - common_item: 64 - uncommon_item: 32 - legendary_item: 8 - boss_item: 4 - lunar_item: 16 - equipment: 32 -``` - -| Name | Description | Allowed values | -| ---- | ----------- | -------------- | -| total_locations | The total number of location checks that will be attributed to the Risk of Rain player. This option is ALSO the total number of items in the item pool for the Risk of Rain player. | 10 - 100 | -| total_revivals | The total number of items in the Risk of Rain player's item pool (items other players pick up for them) replaced with `Dio's Best Friend`. | 0 - 5 | -| start_with_revive | Starts the player off with a `Dio's Best Friend`. Functionally equivalent to putting a `Dio's Best Friend` in your `starting_inventory`. | true/false | -| item_pickup_step | The number of item pickups which you are allowed to claim before they become an Archipelago location check. | 0 - 5 | -| enable_lunar | Allows for lunar items to be shuffled into the item pool on behalf of the Risk of Rain player. | true/false | -| item_weights | Each option here is a preset item weight that can be used to customize your generate item pool with certain settings. | default, new, uncommon, legendary, lunartic, chaos, no_scraps, even, scraps_only | -| item_pool_presets | A simple toggle to determine whether the item_weight presets are used or the custom item pool as defined below | true/false | -| custom item weights | Each defined item here is a single item in the pool that will have a weight against the other items when the item pool gets generated. These values can be modified to adjust how frequently certain items appear | 0-100| - -Using the example YAML above: the Risk of Rain 2 player will have 15 total items which they can pick up for other -players. (total_locations = 15) - -They will have 15 items waiting for them in the item pool which will be distributed out to the multiworld. ( -total_locations = 15) - -They will complete a location check every second item. (item_pickup_step = 1) - -They will have 4 of the items which other players can grant them replaced with `Dio's Best Friend`. (total_revivals = 4) - -The player will also start with a `Dio's Best Friend`. (start_with_revive = true) - -The player will have lunar items shuffled into the item pool on their behalf. (enable_lunar = true) - -The player will have the default preset generated item pool with the custom item weights being ignored. (item_weights: -default and item_pool_presets: true) +## Commands +While playing the multiworld you can type `say` then your message to type in the multiworld chat. All other multiworld +remote commands list in the [commands guide](/tutorial/Archipelago/commands/en) work as well in the RoR2 chat. You can +also optionally connect to the multiworld using the text client, which can be found in the +[main Archipelago installation](https://github.com/ArchipelagoMW/Archipelago/releases). \ No newline at end of file From 61f751a1dbe75641294b9051d4f427aef891af0e Mon Sep 17 00:00:00 2001 From: alwaysintreble Date: Mon, 27 Jun 2022 22:34:47 -0500 Subject: [PATCH 06/16] docs: add common terms documentation to website (#680) * docs: add common terms documentation to website * minor cleanup * some rewording and reformatting. * tighten up world definition clarity Co-authored-by: Rome Reginelli * Clarify seed definition a bit better Co-authored-by: Rome Reginelli * add text for "out of logic" and that slot names must be unique * rename common terms to glossary Co-authored-by: Rome Reginelli --- WebHostLib/__init__.py | 5 ++ WebHostLib/static/assets/faq/faq_en.md | 6 ++ WebHostLib/static/assets/faq/glossary_en.md | 94 +++++++++++++++++++++ WebHostLib/static/assets/glossary.js | 53 ++++++++++++ WebHostLib/templates/glossary.html | 17 ++++ WebHostLib/templates/siteMap.html | 1 + 6 files changed, 176 insertions(+) create mode 100644 WebHostLib/static/assets/faq/glossary_en.md create mode 100644 WebHostLib/static/assets/glossary.js create mode 100644 WebHostLib/templates/glossary.html diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index e71dd01691..c0179b2238 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -141,6 +141,11 @@ def faq(lang): return render_template("faq.html", lang=lang) +@app.route('/glossary//') +def terms(lang): + return render_template("glossary.html", lang=lang) + + @app.route('/seed/') def view_seed(seed: UUID): seed = Seed.get(id=seed) diff --git a/WebHostLib/static/assets/faq/faq_en.md b/WebHostLib/static/assets/faq/faq_en.md index e90fe10227..cd144d7eff 100644 --- a/WebHostLib/static/assets/faq/faq_en.md +++ b/WebHostLib/static/assets/faq/faq_en.md @@ -49,6 +49,12 @@ If you are ready to start randomizing games, or want to start playing your favor our discord server at the [Archipelago Discord](https://discord.gg/archipelago). There are always people ready to answer any questions you might have. +## What are some common terms I should know? + +As randomizers and multiworld randomizers have been around for a while now there are quite a lot of common terms +and jargon that is used in conjunction by the communities surrounding them. For a lot of the terms that are more common +to Archipelago and its specific systems please see the [Glossary](/glossary/en). + ## I want to add a game to the Archipelago randomizer. How do I do that? The best way to get started is to take a look at our code on GitHub diff --git a/WebHostLib/static/assets/faq/glossary_en.md b/WebHostLib/static/assets/faq/glossary_en.md new file mode 100644 index 0000000000..b2843953bc --- /dev/null +++ b/WebHostLib/static/assets/faq/glossary_en.md @@ -0,0 +1,94 @@ +# Multiworld Glossary + +There are a lot of common terms used when playing in different game randomizer communities and in multiworld as well. +This document serves as a lookup for common terms that may be used by users in the community or in various other +documentation. + +## Item +Items are what get shuffled around in your world or other worlds that you then receive. This could be a sword, a stat +upgrade, a spell, or any other potential receivable for your game. + +## Location +Locations are where items are placed in your game. Whenever you interact with a location, you or another player will +then receive an item. A location could be a chest, an enemy drop, a shop purchase, or any other interactable that can +contain items in your game. + +## Check +A check is a common term for when you "check", or pick up, a location. In terms of Archipelago this is usually used for +when a player goes to a location and sends its item, or "checks" the location. Players will often reference their now +randomized locations as checks. + +## Slot +A slot is the player name and number assigned during generation. The number of slots is equal to the number of players, +or "worlds", created. Each name must be unique as these are used to identify the slot user. + +## World +World in terms of Archipelago can mean multiple things and is used interchangeably in many situations. +* During gameplay, a world is a single instance of a game, occupying one player "slot". However, +Archipelago allows multiple players to connect to the same slot; then those players can share a world +and complete it cooperatively. For games with native cooperative play, you can also play together and +share a world that way, usually with only one player connected to the multiworld. +* On the programming side, a world typically represents the package that integrates Archipelago with a +particular game. For example this could be the entire `worlds/factorio` directory. + +## RNG +Acronym for "Random Number Generator." Archipelago uses its own custom Random object with a unique seed per generation, +or, if running from source, a seed can be supplied and this seed will control all randomization during generation as all +game worlds will have access to it. + +## Seed +A "seed" is a number used to initialize a pseudorandom number generator. Whenever you generate a new game on Archipelago +this is a new "seed" as it has unique item placement, and you can create multiple "rooms" on the Archipelago site from a +single seed. Using the same seed results in the random placement being the same. + +## Room +Whenever you generate a seed on the Archipelago website you will be put on a seed page that contains all the seed info +with a link to the spoiler if one exists and will show how many unique rooms exist per seed. Each room has its own +unique identifier that is separate from the seed. The room page is where you can find information to connect to the +multiworld and download any patches if necessary. If you have a particularly fun or interesting seed, and you want to +share it with somebody you can link them to this seed page, where they can generate a new room to play it! For seeds +generated with race mode enabled, the seed page will only show rooms created by the unique user so the seed page is +perfectly safe to share for racing purposes. + +## Logic +Base behavior of all seeds generated by Archipelago is they are expected to be completable based on the requirements of +the settings. This is done by using "logic" in order to determine valid locations to place items while still being able +to reach said location without this item. For the purposes of the randomizer a location is considered "in logic" if you +can reach it with your current toolset of items or skills based on settings. Some players are able to obtain locations +"out of logic" by performing various glitches or tricks that the settings may not account for and tend to mention this +when sending out an item they obtained this way. + +## Progression +Certain items will allow access to more locations and are considered progression items as they "progress" the seed. + +## Trash +A term used for "filler" items that have no bearing on the generation and are either marginally useful for the player +or useless. These items can be very useful depending on the player but are never very important and as such are usually +termed trash. + +## Burger King / BK Mode +A term used in multiworlds when a player is unable to continue to progress and is awaiting an item. The term came to be +after a player, allegedly, was unable to progress during a multiworld and went to Burger King while waiting to receive +items from other players. + +* "Logical BK" is when the player is unable to progress according to the settings of their game but may still be able to do +things that would be "out of logic" by the generation. + +* "Hard / full BK" is when the player is completely unable to progress even with tricks they may know and are unable to +continue to play, aside from doing something like killing enemies for experience or money. + +## Sphere +Archipelago calculates the game playthrough by using a "sphere" system where it has a state for each player and checks +to see what the players are able to reach with their current items. Any location that is reachable with the current +state of items is a "sphere." For the purposes of Archipelago it starts playthrough calculation by distributing sphere 0 +items which are items that are either forced in the player's inventory by the game or placed in the `start_inventory` in +their settings. Sphere 1 is then all accessible locations the players can reach with all the items they received from +sphere 0, or their starting inventory. The playthrough continues in this fashion calculating a number of spheres until +all players have completed their goal. + +## Scouts / Scouting +In some games there are locations that have visible items even if the item itself is unobtainable at the current time. +Some games utilize a scouting feature where when the player "sees" the item it will give a free hint for the item in the +client letting the players know what the exact item is, since if the item was for that game it would know but the item +being foreign is a lot harder to represent visually. + diff --git a/WebHostLib/static/assets/glossary.js b/WebHostLib/static/assets/glossary.js new file mode 100644 index 0000000000..44012d699f --- /dev/null +++ b/WebHostLib/static/assets/glossary.js @@ -0,0 +1,53 @@ +window.addEventListener('load', () => { + const tutorialWrapper = document.getElementById('glossary-wrapper'); + new Promise((resolve, reject) => { + const ajax = new XMLHttpRequest(); + ajax.onreadystatechange = () => { + if (ajax.readyState !== 4) { return; } + if (ajax.status === 404) { + reject("Sorry, the glossary page is not available in that language yet."); + return; + } + if (ajax.status !== 200) { + reject("Something went wrong while loading the glossary."); + return; + } + resolve(ajax.responseText); + }; + ajax.open('GET', `${window.location.origin}/static/assets/faq/` + + `glossary_${tutorialWrapper.getAttribute('data-lang')}.md`, true); + ajax.send(); + }).then((results) => { + // Populate page with HTML generated from markdown + showdown.setOption('tables', true); + showdown.setOption('strikethrough', true); + showdown.setOption('literalMidWordUnderscores', true); + tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results); + adjustHeaderWidth(); + + // Reset the id of all header divs to something nicer + const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')); + const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/); + for (let i=0; i < headers.length; i++){ + const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase() + headers[i].setAttribute('id', headerId); + headers[i].addEventListener('click', () => + window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`); + } + + // Manually scroll the user to the appropriate header if anchor navigation is used + if (scrollTargetIndex > -1) { + try{ + const scrollTarget = window.location.href.substring(scrollTargetIndex + 1); + document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" }); + } catch(error) { + console.error(error); + } + } + }).catch((error) => { + console.error(error); + tutorialWrapper.innerHTML = + `

This page is out of logic!

+

Click here to return to safety.

`; + }); +}); diff --git a/WebHostLib/templates/glossary.html b/WebHostLib/templates/glossary.html new file mode 100644 index 0000000000..921f678157 --- /dev/null +++ b/WebHostLib/templates/glossary.html @@ -0,0 +1,17 @@ +{% extends 'pageWrapper.html' %} + +{% block head %} + {% include 'header/grassHeader.html' %} + Glossary + + + +{% endblock %} + +{% block body %} +
+ +
+{% endblock %} diff --git a/WebHostLib/templates/siteMap.html b/WebHostLib/templates/siteMap.html index 9626aef3f5..671b8bb24b 100644 --- a/WebHostLib/templates/siteMap.html +++ b/WebHostLib/templates/siteMap.html @@ -26,6 +26,7 @@
  • User Content
  • Weighted Settings Page
  • Game Statistics
  • +
  • Common Multiworld Terms
  • Game Info Pages

    From 39ac3c38bfd22c148e4a899a3198c1e518faaae0 Mon Sep 17 00:00:00 2001 From: espeon65536 <81029175+espeon65536@users.noreply.github.com> Date: Tue, 28 Jun 2022 02:03:34 -0400 Subject: [PATCH 07/16] sm64: only apply DDD 100 coin star rule if the location exists (#716) --- worlds/sm64ex/Rules.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index 6229793fe4..fcd5619323 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -45,7 +45,8 @@ def set_rules(world, player: int, area_connections): add_rule(world.get_location("BBH: Eye to Eye in the Secret Room", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("DDD: Collect the Caps...", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("DDD: Pole-Jumping for Red Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player)) - add_rule(world.get_location("DDD: 100 Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player)) + if world.EnableCoinStars[player]: + add_rule(world.get_location("DDD: 100 Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player)) add_rule(world.get_location("SL: Into the Igloo", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Vanish Cap", player)) add_rule(world.get_location("RR: Somewhere Over the Rainbow", player), lambda state: state.has("Cannon Unlock RR", player)) From ba2a5c47442840e976a2ecee64fd4910bc6e4d0e Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:23:18 +0200 Subject: [PATCH 08/16] MC: add non-windows install to docs (#713) * MC: add non-windows install to docs * MC: better link naming for non-windows doc Co-authored-by: Hussein Farran * MC: doc change manual forge link to index By removing the direct link to the version we avoid having to update it all the time and users will have to check the other version numbers for manual installation anyway. Co-authored-by: Hussein Farran --- worlds/minecraft/docs/minecraft_en.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/worlds/minecraft/docs/minecraft_en.md b/worlds/minecraft/docs/minecraft_en.md index 872927fd0d..b33710f73c 100644 --- a/worlds/minecraft/docs/minecraft_en.md +++ b/worlds/minecraft/docs/minecraft_en.md @@ -47,16 +47,29 @@ When the console tells you that you have joined the room, you're all set. Congra multiworld game! At this point any additional minecraft players may connect to your forge server. To start the game once everyone is ready use the command `/start`. -## Manual Installation +## Non-Windows Installation + +The Minecraft Client will install forge and the mod for other operating systems but Java has to be provided by the +user. Head to [minecraft_versions.json on the MC AP GitHub](https://raw.githubusercontent.com/KonoTyran/Minecraft_AP_Randomizer/master/versions/minecraft_versions.json) +to see which java version is required. New installations will default to the topmost "release" version. +- Install the matching Amazon Corretto JDK + - see [Manual Installation Software Links](#manual-installation-software-links) + - or package manager provided by your OS / distribution +- Open your `host.yaml` and add the path to your Java below the `minecraft_options` key + - ` java: "path/to/java-xx-amazon-corretto/bin/java"` +- Run the Minecraft Client and select your .apmc file + +## Full Manual Installation It is highly recommended to ues the Archipelago installer to handle the installation of the forge server for you. -support will not be given for those wishing to manually install forge. For those of you who know how, and wish to do so, +Support will not be given for those wishing to manually install forge. For those of you who know how, and wish to do so, the following links are the versions of the software we use. -### Manual install Software links +### Manual Installation Software Links -- [Minecraft Forge Download Page](https://files.minecraftforge.net/net/minecraftforge/forge/index_1.18.2.html) +- [Minecraft Forge Download Page](https://files.minecraftforge.net/net/minecraftforge/forge/) - [Minecraft Archipelago Randomizer Mod Releases Page](https://github.com/KonoTyran/Minecraft_AP_Randomizer/releases) - **DO NOT INSTALL THIS ON YOUR CLIENT** -- [Amazon Corretto Java 17 Download Page](https://docs.aws.amazon.com/corretto/latest/corretto-17-ug/downloads-list.html) +- [Amazon Corretto](https://docs.aws.amazon.com/corretto/) + - pick the matching version and select "Downloads" on the left From 7dcde12e2e5aa5005abf5aea2accfa76f273d03e Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Wed, 29 Jun 2022 00:26:18 -0400 Subject: [PATCH 09/16] Revert SC2 item classifications --- worlds/sc2wol/Items.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/worlds/sc2wol/Items.py b/worlds/sc2wol/Items.py index 704a0f0838..4ecff7e15f 100644 --- a/worlds/sc2wol/Items.py +++ b/worlds/sc2wol/Items.py @@ -6,7 +6,7 @@ class ItemData(typing.NamedTuple): code: typing.Optional[int] type: typing.Optional[str] number: typing.Optional[int] - classification: ItemClassification = ItemClassification.filler + classification: ItemClassification = ItemClassification.useful quantity: int = 1 @@ -59,46 +59,46 @@ item_table = { "Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9), "Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10), "Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11), - "Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.useful), + "Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.filler), "Juggernaut Plating (Firebat)": ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13), "Concussive Shells (Marauder)": ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14), "Kinetic Foam (Marauder)": ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15), "U-238 Rounds (Reaper)": ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16), - "G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.useful), + "G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.filler), - "Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.useful), + "Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.filler), "Thermite Filaments (Hellion)": ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1), "Cerberus Mine (Vulture)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2), "Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3), "Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4), "Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5), - "Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.useful), - "Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, classification=ItemClassification.useful), + "Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.filler), + "Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, classification=ItemClassification.filler), "Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8), "Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9), - "Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.useful), + "Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.filler), "Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11), - "Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.useful), + "Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.filler), "Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13), "Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14), "Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15), - "Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, classification=ItemClassification.useful), + "Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, classification=ItemClassification.filler), "Shockwave Missile Battery (Banshee)": ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17), - "Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, classification=ItemClassification.useful), - "Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.useful), + "Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, classification=ItemClassification.filler), + "Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.filler), "Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20), "Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21), "Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22), "Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23), - "330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.useful), - "Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.useful), + "330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.filler), + "Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.filler), "Bunker": ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, classification=ItemClassification.progression), "Missile Turret": ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, classification=ItemClassification.progression), "Sensor Tower": ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2), "War Pigs": ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0), - "Devil Dogs": ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, classification=ItemClassification.useful), + "Devil Dogs": ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, classification=ItemClassification.filler), "Hammer Securities": ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2), "Spartan Company": ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3), "Siege Breakers": ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4), @@ -120,12 +120,12 @@ item_table = { "Fortified Bunker": ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11), "Planetary Fortress": ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12), "Perdition Turret": ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 13), - "Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14, classification=ItemClassification.useful), + "Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14, classification=ItemClassification.filler), "Hercules": ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 15, classification=ItemClassification.progression), - "Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16, classification=ItemClassification.useful), - "Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17, classification=ItemClassification.useful), + "Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16, classification=ItemClassification.filler), + "Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17, classification=ItemClassification.filler), "Hive Mind Emulator": ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 18), - "Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19, classification=ItemClassification.useful), + "Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19, classification=ItemClassification.filler), "Zealot": ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 0, classification=ItemClassification.progression), "Stalker": ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 1, classification=ItemClassification.progression), @@ -137,9 +137,9 @@ item_table = { "Void Ray": ItemData(707 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 7, classification=ItemClassification.progression), "Carrier": ItemData(708 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 8, classification=ItemClassification.progression), - "+15 Starting Minerals": ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, quantity=0), - "+15 Starting Vespene": ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, quantity=0), - "+2 Starting Supply": ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, quantity=0), + "+15 Starting Minerals": ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, quantity=0, classification=ItemClassification.filler), + "+15 Starting Vespene": ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, quantity=0, classification=ItemClassification.filler), + "+2 Starting Supply": ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, quantity=0, classification=ItemClassification.filler), } basic_unit: typing.Tuple[str, ...] = ( From d7a9b98ce8ea952fec067ee38d887296aced4526 Mon Sep 17 00:00:00 2001 From: alwaysintreble Date: Tue, 28 Jun 2022 20:24:44 -0500 Subject: [PATCH 10/16] fix glossary link on sitemap --- WebHostLib/templates/siteMap.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/templates/siteMap.html b/WebHostLib/templates/siteMap.html index 671b8bb24b..76f1003fd7 100644 --- a/WebHostLib/templates/siteMap.html +++ b/WebHostLib/templates/siteMap.html @@ -26,7 +26,7 @@
  • User Content
  • Weighted Settings Page
  • Game Statistics
  • -
  • Common Multiworld Terms
  • +
  • Glossary
  • Game Info Pages

    From cea7278faf5bb7caf5bf6bb2f575b60de5fc52dd Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 30 Jun 2022 19:00:37 +0200 Subject: [PATCH 11/16] LttP: now that Enemizer allows for AP rom name, rename it. (#730) * LttP: now that Enemizer allows for AP rom name, rename it. * LttP: fix missing Enemizer message parenthesis --- worlds/alttp/Rom.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index a88d879be2..531148923c 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -19,7 +19,7 @@ import threading import xxtea import concurrent.futures import bsdiff4 -from typing import Optional +from typing import Optional, List from BaseClasses import CollectionState, Region, Location from worlds.alttp.Shops import ShopType, ShopPriceType @@ -186,7 +186,7 @@ def check_enemizer(enemizercli): # some time may have passed since the lock was acquired, as such a quick re-check doesn't hurt if getattr(check_enemizer, "done", None): return - + wanted_version = (7, 0, 1) # version info is saved on the lib, for some reason library_info = os.path.join(os.path.dirname(enemizercli), "EnemizerCLI.Core.deps.json") with open(library_info) as f: @@ -197,10 +197,11 @@ def check_enemizer(enemizercli): version = lib.split("/")[-1] version = tuple(int(element) for element in version.split(".")) enemizer_logger.debug(f"Found Enemizer version {version}") - if version < (6, 4, 0): + if version < wanted_version: raise Exception( - f"Enemizer found at {enemizercli} is outdated ({info}), please update your Enemizer. " - f"Such as https://github.com/Ijwu/Enemizer/releases") + f"Enemizer found at {enemizercli} is outdated ({version}) < ({wanted_version}), " + f"please update your Enemizer. " + f"Such as from https://github.com/Ijwu/Enemizer/releases") break else: raise Exception(f"Could not find Enemizer library version information in {library_info}") @@ -1645,8 +1646,7 @@ def patch_rom(world, rom, player, enemized): # set rom name # 21 bytes from Main import __version__ - # TODO: Adjust Enemizer to accept AP and AD - rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name = bytearray(f'AP{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) @@ -1677,7 +1677,7 @@ def patch_race_rom(rom, world, player): rom.encrypt(world, player) -def get_price_data(price: int, price_type: int) -> bytes: +def get_price_data(price: int, price_type: int) -> List[int]: if price_type != ShopPriceType.Rupees: # Set special price flag 0x8000 # Then set the type of price we're setting 0x7F00 (this starts from Hearts, not Rupees, subtract 1) From bce7c258c3017707637a55ab1463c666f086a957 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 30 Jun 2022 20:51:11 +0200 Subject: [PATCH 12/16] CI: update Enemizer to 7.0.1 --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28d1999486..cb9fda69b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: run: | Invoke-WebRequest -Uri https://github.com/alttpo/sni/releases/download/v0.0.81/sni-v0.0.81-windows-amd64.zip -OutFile sni.zip Expand-Archive -Path sni.zip -DestinationPath SNI -Force - Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/7.0/win-x64.zip -OutFile enemizer.zip + Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/7.0.1/win-x64.zip -OutFile enemizer.zip Expand-Archive -Path enemizer.zip -DestinationPath EnemizerCLI -Force - name: Build run: | @@ -67,7 +67,7 @@ jobs: tar xf sni-*.tar.xz rm sni-*.tar.xz mv sni-* SNI - wget -nv https://github.com/Ijwu/Enemizer/releases/download/7.0/ubuntu.16.04-x64.7z + wget -nv https://github.com/Ijwu/Enemizer/releases/download/7.0.1/ubuntu.16.04-x64.7z 7za x -oEnemizerCLI/ ubuntu.16.04-x64.7z - name: Build run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f897bcba62..6e5095cff2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,7 @@ jobs: tar xf sni-*.tar.xz rm sni-*.tar.xz mv sni-* SNI - wget -nv https://github.com/Ijwu/Enemizer/releases/download/7.0/ubuntu.16.04-x64.7z + wget -nv https://github.com/Ijwu/Enemizer/releases/download/7.0.1/ubuntu.16.04-x64.7z 7za x -oEnemizerCLI/ ubuntu.16.04-x64.7z - name: Build run: | From 8a8bc6aa34c331a94163c15c1eee75d02f33c820 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Thu, 30 Jun 2022 15:40:31 -0700 Subject: [PATCH 13/16] Factorio: Fix impossible seeds for rocket-part recipes as well. (#733) --- worlds/factorio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 0d3db21bb9..53c9897c17 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -329,7 +329,7 @@ class Factorio(World): def set_custom_recipes(self): original_rocket_part = recipes["rocket-part"] science_pack_pools = get_science_pack_pools() - valid_pool = sorted(science_pack_pools[self.world.max_science_pack[self.player].get_max_pack()]) + valid_pool = sorted(science_pack_pools[self.world.max_science_pack[self.player].get_max_pack()] & stacking_items) self.world.random.shuffle(valid_pool) while any([valid_pool[x] in fluids for x in range(3)]): self.world.random.shuffle(valid_pool) From b206f2846a55403b1a8331b4d3f7c8855edbfb29 Mon Sep 17 00:00:00 2001 From: strotlog <49286967+strotlog@users.noreply.github.com> Date: Tue, 28 Jun 2022 00:43:05 +0000 Subject: [PATCH 14/16] SNES games: use JPN as abbreviation for Japan/Japanese --- LttPAdjuster.py | 4 ++-- Patch.py | 8 ++++---- worlds/alttp/Rom.py | 8 ++++---- worlds/sm/Rom.py | 8 ++++---- worlds/smz3/Rom.py | 12 ++++++------ 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/LttPAdjuster.py b/LttPAdjuster.py index a34d9f89d6..3368620e6c 100644 --- a/LttPAdjuster.py +++ b/LttPAdjuster.py @@ -47,7 +47,7 @@ def main(): parser.add_argument('rom', nargs="?", default='AP_LttP.sfc', help='Path to an ALttP rom to adjust.') parser.add_argument('--baserom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', - help='Path to an ALttP JAP(1.0) rom to use as a base.') + help='Path to an ALttP Japan(1.0) rom to use as a base.') parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') parser.add_argument('--menuspeed', default='normal', const='normal', nargs='?', @@ -1263,4 +1263,4 @@ class ToolTips(object): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/Patch.py b/Patch.py index bc2b98ecdb..a2f29fdabc 100644 --- a/Patch.py +++ b/Patch.py @@ -178,14 +178,14 @@ preferred_endings = { def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes: if game == GAME_ALTTP: - from worlds.alttp.Rom import JAP10HASH as HASH + from worlds.alttp.Rom import LTTPJPN10HASH as HASH elif game == GAME_SM: - from worlds.sm.Rom import JAP10HASH as HASH + from worlds.sm.Rom import SMJUHASH as HASH elif game == GAME_SOE: from worlds.soe.Patch import USHASH as HASH elif game == GAME_SMZ3: - from worlds.alttp.Rom import JAP10HASH as ALTTPHASH - from worlds.sm.Rom import JAP10HASH as SMHASH + from worlds.alttp.Rom import LTTPJPN10HASH as ALTTPHASH + from worlds.sm.Rom import SMJUHASH as SMHASH HASH = ALTTPHASH + SMHASH else: raise RuntimeError(f"Selected game {game} for base rom not found.") diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 531148923c..72cd1ceac5 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -3,7 +3,7 @@ from __future__ import annotations import Utils from Patch import read_rom -JAP10HASH = '03a63945398191337e896e5771f77173' +LTTPJPN10HASH = '03a63945398191337e896e5771f77173' RANDOMIZERBASEHASH = '9952c2a3ec1b421e408df0d20c8f0c7f' ROM_PLAYER_LIMIT = 255 @@ -2890,7 +2890,7 @@ hash_alphabet = [ class LttPDeltaPatch(Patch.APDeltaPatch): - hash = JAP10HASH + hash = LTTPJPN10HASH game = "A Link to the Past" patch_file_ending = ".aplttp" @@ -2907,8 +2907,8 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) - if JAP10HASH != basemd5.hexdigest(): - raise Exception('Supplied Base Rom does not match known MD5 for JAP(1.0) release. ' + if LTTPJPN10HASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for Japan(1.0) release. ' 'Get the correct game and version, then dump it') get_base_rom_bytes.base_rom_bytes = base_rom_bytes return base_rom_bytes diff --git a/worlds/sm/Rom.py b/worlds/sm/Rom.py index 84ed131f52..a01fcbe3a8 100644 --- a/worlds/sm/Rom.py +++ b/worlds/sm/Rom.py @@ -4,12 +4,12 @@ import os import Utils from Patch import read_rom, APDeltaPatch -JAP10HASH = '21f3e98df4780ee1c667b84e57d88675' +SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' ROM_PLAYER_LIMIT = 65535 class SMDeltaPatch(APDeltaPatch): - hash = JAP10HASH + hash = SMJUHASH game = "Super Metroid" patch_file_ending = ".apsm" @@ -26,8 +26,8 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) - if JAP10HASH != basemd5.hexdigest(): - raise Exception('Supplied Base Rom does not match known MD5 for JAP(1.0) release. ' + if SMJUHASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for Japan+US release. ' 'Get the correct game and version, then dump it') get_base_rom_bytes.base_rom_bytes = base_rom_bytes return base_rom_bytes diff --git a/worlds/smz3/Rom.py b/worlds/smz3/Rom.py index a97fe1672f..a355636fed 100644 --- a/worlds/smz3/Rom.py +++ b/worlds/smz3/Rom.py @@ -4,8 +4,8 @@ import os import Utils from Patch import read_rom, APDeltaPatch -SMJAP10HASH = '21f3e98df4780ee1c667b84e57d88675' -LTTPJAP10HASH = '03a63945398191337e896e5771f77173' +SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' +LTTPJPN10HASH = '03a63945398191337e896e5771f77173' ROM_PLAYER_LIMIT = 256 @@ -27,16 +27,16 @@ def get_base_rom_bytes() -> bytes: basemd5 = hashlib.md5() basemd5.update(sm_base_rom_bytes) - if SMJAP10HASH != basemd5.hexdigest(): - raise Exception('Supplied Base Rom does not match known MD5 for SM JAP(1.0) release. ' + if SMJUHASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for SM Japan+US release. ' 'Get the correct game and version, then dump it') lttp_file_name = get_lttp_base_rom_path() lttp_base_rom_bytes = bytes(read_rom(open(lttp_file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(lttp_base_rom_bytes) - if LTTPJAP10HASH != basemd5.hexdigest(): - raise Exception('Supplied Base Rom does not match known MD5 for LttP JAP(1.0) release. ' + if LTTPJPN10HASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for LttP Japan(1.0) release. ' 'Get the correct game and version, then dump it') get_base_rom_bytes.base_rom_bytes = bytes(combine_smz3_rom(sm_base_rom_bytes, lttp_base_rom_bytes)) From bcd7096e1d211fefa6c4390d70d110df1681eafe Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Fri, 1 Jul 2022 22:01:41 +0200 Subject: [PATCH 15/16] [The Witness] Update data_version as it was forgotten for 0.3.3 # Conflicts: # worlds/witness/docs/setup_en.md --- worlds/timespinner/docs/setup_de.md | 6 +++--- worlds/timespinner/docs/setup_en.md | 6 +++--- worlds/witness/__init__.py | 2 ++ worlds/witness/docs/setup_en.md | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/worlds/timespinner/docs/setup_de.md b/worlds/timespinner/docs/setup_de.md index 0fe3fe1735..463568ecbd 100644 --- a/worlds/timespinner/docs/setup_de.md +++ b/worlds/timespinner/docs/setup_de.md @@ -5,7 +5,7 @@ - [Timespinner (Steam)](https://store.steampowered.com/app/368620/Timespinner/) , [Timespinner (Humble)](https://www.humblebundle.com/store/timespinner) oder [Timespinner (GOG)](https://www.gog.com/game/timespinner) (andere Versionen werden nicht unterstützt) -- [Timespinner Randomizer](https://github.com/JarnoWesthof/TsRandomizer) +- [Timespinner Randomizer](https://github.com/Jarno458/TsRandomizer) ## Wie funktioniert's? @@ -15,7 +15,7 @@ die Randomisierung der Gegenstände zu erlauben ## Installationsanweisungen 1. Die aktuellsten Dateien des Randomizers findest du ganz oben auf dieser - Webseite: [Timespinner Randomizer Releases](https://github.com/JarnoWesthof/TsRandomizer/releases). Lade dir unter ' + Webseite: [Timespinner Randomizer Releases](https://github.com/Jarno458/TsRandomizer/releases). Lade dir unter ' Assets' die .zip Datei für dein Betriebssystem herunter 2. Entpacke die .zip Datei im Ordner, in dem das Spiel Timespinner installiert ist @@ -27,7 +27,7 @@ die Randomisierung der Gegenstände zu erlauben ... im Ordner in dem die Inhalte aus der .zip Datei entpackt wurden -Weitere Informationen zum Randomizer findest du hier: [ReadMe](https://github.com/JarnoWesthof/TsRandomizer) +Weitere Informationen zum Randomizer findest du hier: [ReadMe](https://github.com/Jarno458/TsRandomizer) ## An einer Multiworld teilnehmen diff --git a/worlds/timespinner/docs/setup_en.md b/worlds/timespinner/docs/setup_en.md index ae8c97a1b3..c47c639cd2 100644 --- a/worlds/timespinner/docs/setup_en.md +++ b/worlds/timespinner/docs/setup_en.md @@ -5,7 +5,7 @@ - [Timespinner (Steam)](https://store.steampowered.com/app/368620/Timespinner/) , [Timespinner (Humble)](https://www.humblebundle.com/store/timespinner) or [Timespinner (GOG)](https://www.gog.com/game/timespinner) (other versions are not supported) -- [Timespinner Randomizer](https://github.com/JarnoWesthof/TsRandomizer) +- [Timespinner Randomizer](https://github.com/Jarno458/TsRandomizer) ## General Concept @@ -14,11 +14,11 @@ randomization of the items ## Installation Procedures -Download latest release on [Timespinner Randomizer Releases](https://github.com/JarnoWesthof/TsRandomizer/releases) you +Download latest release on [Timespinner Randomizer Releases](https://github.com/Jarno458/TsRandomizer/releases) you can find the .zip files on the releases page. Download the zip for your current platform. Then extract the zip to the folder where your Timespinner game is installed. Then just run TsRandomizer.exe (on Windows) or TsRandomizer.bin.x86_64 (on Linux) or TsRandomizer.bin.osx (on Mac) instead of Timespinner.exe to start the game in -randomized mode. For more info see the [ReadMe](https://github.com/JarnoWesthof/TsRandomizer) +randomized mode. For more info see the [ReadMe](https://github.com/Jarno458/TsRandomizer) ## Joining a MultiWorld Game diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 8c6dc40cb6..0857ef6b42 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -35,6 +35,8 @@ class WitnessWorld(World): """ game = "The Witness" topology_present = False + data_version = 2 + static_logic = StaticWitnessLogic() static_locat = StaticWitnessLocations() static_items = StaticWitnessItems() diff --git a/worlds/witness/docs/setup_en.md b/worlds/witness/docs/setup_en.md index df114e1256..d577953c38 100644 --- a/worlds/witness/docs/setup_en.md +++ b/worlds/witness/docs/setup_en.md @@ -3,7 +3,7 @@ ## Required Software - [The Witness for 64-bit Windows (e.g. Steam version)](https://store.steampowered.com/app/210970/The_Witness/) -- [The Witness Archipelago Randomizer](https://github.com/JarnoWesthof/The-Witness-Randomizer-for-Archipelago/releases) +- [The Witness Archipelago Randomizer](https://github.com/Jarno458/The-Witness-Randomizer-for-Archipelago/releases) ## Optional Software @@ -18,7 +18,7 @@ It is recommended to do every single one of these steps when you connect to a wo 1. Launch The Witness 2. Start a fresh save (unless you have absolutely no other choice) 3. Do not move -4. Launch [The Witness Archipelago Randomizer](https://github.com/JarnoWesthof/The-Witness-Randomizer-for-Archipelago) +4. Launch [The Witness Archipelago Randomizer](https://github.com/Jarno458/The-Witness-Randomizer-for-Archipelago) 5. Enter the Archipelago address, slot name and password 6. Press "Randomize" 7. Wait for the randomization to fully finish before moving in-game From b9fb4de8787f144b14eaec27a0da2aee74465b4e Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 2 Jul 2022 13:27:50 +0200 Subject: [PATCH 16/16] BaseClasses: make ItemClassification properties faster --- BaseClasses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 78919f22c0..a186404727 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1191,19 +1191,19 @@ class Item: @property def advancement(self) -> bool: - return bool(self.classification & ItemClassification.progression) + return ItemClassification.progression in self.classification @property def skip_in_prog_balancing(self) -> bool: - return self.classification == ItemClassification.progression_skip_balancing + return ItemClassification.progression_skip_balancing in self.classification @property def useful(self) -> bool: - return bool(self.classification & ItemClassification.useful) + return ItemClassification.useful in self.classification @property def trap(self) -> bool: - return bool(self.classification & ItemClassification.trap) + return ItemClassification.trap in self.classification @property def flags(self) -> int: