diff --git a/BaseClasses.py b/BaseClasses.py
index 0c90f37c..bbf110d2 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -1210,8 +1210,6 @@ class Spoiler():
if self.world.players > 1:
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_name(player)))
outfile.write('Game: %s\n' % self.world.game[player])
- for f_option, option in Options.common_options.items():
- write_option(f_option, option)
for f_option, option in Options.per_game_common_options.items():
write_option(f_option, option)
options = self.world.worlds[player].options
diff --git a/Fill.py b/Fill.py
index f434010b..eb0a7d47 100644
--- a/Fill.py
+++ b/Fill.py
@@ -2,8 +2,10 @@ import logging
import typing
import collections
import itertools
+from collections import Counter, deque
-from BaseClasses import CollectionState, Location, MultiWorld
+
+from BaseClasses import CollectionState, Location, MultiWorld, Item
from worlds.generic import PlandoItem
from worlds.AutoWorld import call_all
@@ -12,30 +14,35 @@ class FillError(RuntimeError):
pass
-def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool, single_player_placement=False,
- lock=False):
- def sweep_from_pool():
- new_state = base_state.copy()
- for item in itempool:
- new_state.collect(item, True)
- new_state.sweep_for_events()
- return new_state
+def sweep_from_pool(base_state: CollectionState, itempool):
+ new_state = base_state.copy()
+ for item in itempool:
+ new_state.collect(item, True)
+ new_state.sweep_for_events()
+ return new_state
+
+def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool: typing.List[Item],
+ single_player_placement=False, lock=False):
unplaced_items = []
placements = []
- reachable_items = {}
+ swapped_items = Counter()
+ reachable_items: dict[str, deque] = {}
for item in itempool:
- reachable_items.setdefault(item.player, []).append(item)
+ reachable_items.setdefault(item.player, deque()).append(item)
while any(reachable_items.values()) and locations:
- items_to_place = [items.pop() for items in reachable_items.values() if items] # grab one item per player
+ # grab one item per player
+ items_to_place = [items.pop()
+ for items in reachable_items.values() if items]
for item in items_to_place:
itempool.remove(item)
- maximum_exploration_state = sweep_from_pool()
+ maximum_exploration_state = sweep_from_pool(base_state, itempool)
has_beaten_game = world.has_beaten_game(maximum_exploration_state)
for item_to_place in items_to_place:
+ spot_to_fill: Location = None
if world.accessibility[item_to_place.player] == 'minimal':
perform_access_check = not world.has_beaten_game(maximum_exploration_state,
item_to_place.player) if single_player_placement else not has_beaten_game
@@ -45,19 +52,48 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
for i, location in enumerate(locations):
if (not single_player_placement or location.player == item_to_place.player) \
and location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
- spot_to_fill = locations.pop(i) # poping by index is faster than removing by content,
+ # poping by index is faster than removing by content,
+ spot_to_fill = locations.pop(i)
# skipping a scan for the element
break
else:
- # we filled all reachable spots. Maybe the game can be beaten anyway?
- unplaced_items.append(item_to_place)
- if world.accessibility[item_to_place.player] != 'minimal' and world.can_beat_game():
- logging.warning(
- f'Not all items placed. Game beatable anyway. (Could not place {item_to_place})')
- continue
- raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
- f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
+ # we filled all reachable spots.
+ # try swaping this item with previously placed items
+ for(i, location) in enumerate(placements):
+ placed_item = location.item
+ # Unplaceable items can sometimes be swapped infinitely. Limit the
+ # number of times we will swap an individual item to prevent this
+ if swapped_items[placed_item.player, placed_item.name] > 0:
+ continue
+ location.item = None
+ placed_item.location = None
+ swap_state = sweep_from_pool(base_state, itempool)
+ if (not single_player_placement or location.player == item_to_place.player) \
+ and location.can_fill(swap_state, item_to_place, perform_access_check):
+ # Add this item to the exisiting placement, and
+ # add the old item to the back of the queue
+ spot_to_fill = placements.pop(i)
+ swapped_items[placed_item.player,
+ placed_item.name] += 1
+ reachable_items[placed_item.player].appendleft(
+ placed_item)
+ itempool.append(placed_item)
+ break
+ else:
+ # Item can't be placed here, restore original item
+ location.item = placed_item
+ placed_item.location = location
+
+ if spot_to_fill == None:
+ # Maybe the game can be beaten anyway?
+ unplaced_items.append(item_to_place)
+ if world.accessibility[item_to_place.player] != 'minimal' and world.can_beat_game():
+ logging.warning(
+ f'Not all items placed. Game beatable anyway. (Could not place {item_to_place})')
+ continue
+ raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
+ f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
world.push_item(spot_to_fill, item_to_place, False)
spot_to_fill.locked = lock
diff --git a/Generate.py b/Generate.py
index a8c88125..fc82ad43 100644
--- a/Generate.py
+++ b/Generate.py
@@ -469,7 +469,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
ret = argparse.Namespace()
for option_key in Options.per_game_common_options:
- if option_key in weights:
+ if option_key in weights and option_key not in Options.common_options:
raise Exception(f"Option {option_key} has to be in a game's section, not on its own.")
ret.game = get_choice("game", weights)
diff --git a/Options.py b/Options.py
index cd4d9148..ef786e47 100644
--- a/Options.py
+++ b/Options.py
@@ -334,7 +334,7 @@ class Accessibility(Choice):
Locations: ensure everything can be reached and acquired.
Items: ensure all logically relevant items can be acquired.
Minimal: ensure what is needed to reach your goal can be acquired."""
-
+ displayname = "Accessibility"
option_locations = 0
option_items = 1
option_minimal = 2
@@ -344,6 +344,7 @@ class Accessibility(Choice):
class ProgressionBalancing(DefaultOnToggle):
"""A system that moves progression earlier, to try and prevent the player from getting stuck and bored early."""
+ displayname = "Progression Balancing"
common_options = {
@@ -395,6 +396,7 @@ class DeathLink(Toggle):
per_game_common_options = {
+ **common_options, # can be overwritten per-game
"local_items": LocalItems,
"non_local_items": NonLocalItems,
"start_inventory": StartInventory,
diff --git a/SNIClient.py b/SNIClient.py
index 6d7540ea..f6509938 100644
--- a/SNIClient.py
+++ b/SNIClient.py
@@ -532,9 +532,9 @@ def launch_sni(ctx: Context):
snes_logger.info(f"Attempting to start {sni_path}")
import sys
if not sys.stdout: # if it spawns a visible console, may as well populate it
- subprocess.Popen(sni_path, cwd=os.path.dirname(sni_path))
+ subprocess.Popen(os.path.abspath(sni_path), cwd=os.path.dirname(sni_path))
else:
- subprocess.Popen(sni_path, cwd=os.path.dirname(sni_path), stdout=subprocess.DEVNULL,
+ subprocess.Popen(os.path.abspath(sni_path), cwd=os.path.dirname(sni_path), stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
else:
snes_logger.info(
diff --git a/Utils.py b/Utils.py
index 02a25e70..df5d3c96 100644
--- a/Utils.py
+++ b/Utils.py
@@ -23,7 +23,7 @@ class Version(typing.NamedTuple):
build: int
-__version__ = "0.2.1"
+__version__ = "0.2.2"
version_tuple = tuplize_version(__version__)
from yaml import load, dump, safe_load
diff --git a/WebHostLib/options.py b/WebHostLib/options.py
index c8a7b9a6..8411e138 100644
--- a/WebHostLib/options.py
+++ b/WebHostLib/options.py
@@ -11,6 +11,8 @@ target_folder = os.path.join("WebHostLib", "static", "generated")
def create():
+ os.makedirs(os.path.join(target_folder, 'configs'), exist_ok=True)
+
def dictify_range(option):
data = {option.range_start: 0, option.range_end: 0, "random": 0, "random-low": 0, "random-high": 0,
option.default: 50}
@@ -25,15 +27,26 @@ def create():
return list(default_value)
return default_value
+ weighted_settings = {
+ "baseOptions": {
+ "description": "Generated by https://archipelago.gg/",
+ "name": "Player",
+ "game": {},
+ },
+ "games": {},
+ }
+
for game_name, world in AutoWorldRegister.world_types.items():
+ if (world.hidden):
+ continue
+
+ all_options = {**world.options, **Options.per_game_common_options}
res = Template(open(os.path.join("WebHostLib", "templates", "options.yaml")).read()).render(
- options={**world.options, **Options.per_game_common_options},
+ options=all_options,
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
dictify_range=dictify_range, default_converter=default_converter,
)
- os.makedirs(os.path.join(target_folder, 'configs'), exist_ok=True)
-
with open(os.path.join(target_folder, 'configs', game_name + ".yaml"), "w") as f:
f.write(res)
@@ -47,7 +60,7 @@ def create():
}
game_options = {}
- for option_name, option in world.options.items():
+ for option_name, option in all_options.items():
if option.options:
game_options[option_name] = this_option = {
"type": "select",
@@ -87,3 +100,11 @@ def create():
with open(os.path.join(target_folder, 'player-settings', game_name + ".json"), "w") as f:
f.write(json.dumps(player_settings, indent=2, separators=(',', ': ')))
+
+ weighted_settings["baseOptions"]["game"][game_name] = 0
+ weighted_settings["games"][game_name] = {}
+ weighted_settings["games"][game_name]["gameOptions"] = game_options
+ weighted_settings["games"][game_name]["gameItems"] = tuple(world.item_name_to_id.keys())
+
+ with open(os.path.join(target_folder, 'weighted-settings.json'), "w") as f:
+ f.write(json.dumps(weighted_settings, indent=2, separators=(',', ': ')))
diff --git a/WebHostLib/static/assets/tutorial/archipelago/plando_en.md b/WebHostLib/static/assets/tutorial/archipelago/plando_en.md
index 3850276f..f0c5a87e 100644
--- a/WebHostLib/static/assets/tutorial/archipelago/plando_en.md
+++ b/WebHostLib/static/assets/tutorial/archipelago/plando_en.md
@@ -1,109 +1,76 @@
# Archipelago Plando Guide
-This guide details the use of the plando modules available with Archipelago. This guide is intended for a more advanced
-user who has more in-depth knowledge of the randomizer they're playing as well as experience editing YAML files. This
-guide should take about 10 minutes to read.
-
## What is Plando?
-
-The purposes of randomizers is to randomize the items in a game to give a new experience. Plando takes this concept and
-changes it up by allowing you to plan out certain aspects of the game by placing certain items in certain locations,
-certain bosses in certain rooms, edit text for certain NPCs/signs, or even force certain region connections. Each of
-these options are going to be detailed separately as `item plando`, `boss plando`, `text plando`,
-and `connection plando`. Every game in archipelago supports item plando but the other plando options are only supported
-by certain games. Currently, Minecraft and LTTP both support connection plando, but only LTTP supports text and boss
-plando.
+The purposes of randomizers is to randomize the items in a game to give a new experience.
+Plando takes this concept and changes it up by allowing you to plan out certain aspects of the game by placing certain
+items in certain locations, certain bosses in certain rooms, edit text for certain NPCs/signs, or even force certain region
+connections. Each of these options are going to be detailed separately as `item plando`, `boss plando`, `text plando`,
+and `connection plando`. Every game in archipelago supports item plando but the other plando options are only supported
+by certain games. Currently, Minecraft and LTTP both support connection plando, but only LTTP supports text and boss plando.
### Enabling Plando
-
-On the website plando will already be enabled. If you will be generating the game locally plando features must be
-enabled manually (opt-in). To opt-in go to the archipelago installation directory (
-default: `C:\ProgramData\Archipelago`), open the host.yaml with a text editor and find the `plando_options` key. The
-available plando modules can be enabled by adding them after this such
-as `plando_options: bosses, items, texts, connections`.
+On the website plando will already be enabled. If you will be generating the game locally plando features must be enabled (opt-in).
+* To opt-in go to the archipelago installation (default: `C:\ProgramData\Archipelago`), open the host.yaml with a text
+editor and find the `plando_options` key. The available plando modules can be enabled by adding them after this such as
+`plando_options: bosses, items, texts, connections`.
## Item Plando
-
-Item plando allows a player to place an item in a specific location or specific locations, place multiple items into a
-list of specific locations both in their own game or in another player's game. **Note that there's a very good chance
-that cross-game plando could very well be broken i.e. placing on of your items in someone else's world playing a
-different game.**
-
-* The options for item plando are `from_pool`, `world`, `percentage`, `force`, and either item and location, or items
- and locations.
- * `from_pool` determines if the item should be taken *from* the item pool or *added* to it. This can be true or
- false and defaults to true if omitted.
- * `world` is the target world to place the item in.
- * It gets ignored if only one world is generated.
- * Can be a number, name, true, false, or null. False is the default.
- * If a number is used it targets that slot or player number in the multiworld.
- * If a name is used it will target the world with that player name.
- * If set to true it will be any player's world besides your own.
- * If set to false it will target your own world.
- * If set to null it will target a random world in the multiworld.
- * `force` determines whether the generator will fail if the item can't be placed in the location can be true, false,
- or silent. Silent is the default.
- * If set to true the item must be placed and the generator will throw an error if it is unable to do so.
- * If set to false the generator will log a warning if the placement can't be done but will still generate.
- * If set to silent and the placement fails it will be ignored entirely.
- * `percentage` is the percentage chance for the relevant block to trigger. This can be any value from 0 to 100 and
- if omitted will default to 100.
- * Single Placement is when you use a plando block to place a single item at a single location.
- * `item` is the item you would like to place and `location` is the location to place it.
- * Multi Placement uses a plando block to place multiple items in multiple locations until either list is exhausted.
- * `items` defines the items to use and a number letting you place multiple of it.
- * `locations` is a list of possible locations those items can be placed in.
- * Using the multi placement method, placements are picked randomly.
+Item plando allows a player to place an item in a specific location or specific locations, place multiple items into
+a list of specific locations both in their own game or in another player's game. **Note that there's a very good chance that
+cross-game plando could very well be broken i.e. placing on of your items in someone else's world playing a different game.**
+* The options for item plando are `from_pool`, `world`, `percentage`, `force`, and either item and location, or items and locations.
+ * `from_pool` determines if the item should be taken *from* the item pool or *added* to it. This can be true or false
+and defaults to true if omitted.
+ * `world` is the target world to place the item in.
+ * It gets ignored if only one world is generated.
+ * Can be a number, name, true, false, or null. False is the default.
+ * If a number is used it targets that slot or player number in the multiworld.
+ * If a name is used it will target the world with that player name.
+ * If set to true it will be any player's world besides your own.
+ * If set to false it will target your own world.
+ * If set to null it will target a random world in the multiworld.
+ * `force` determines whether the generator will fail if the item can't be placed in the location can be true, false,
+or silent. Silent is the default.
+ * If set to true the item must be placed and the generator will throw an error if it is unable to do so.
+ * If set to false the generator will log a warning if the placement can't be done but will still generate.
+ * If set to silent and the placement fails it will be ignored entirely.
+ * `percentage` is the percentage chance for the relevant block to trigger. This can be any value from 0 to 100 and if
+omitted will default to 100.
+ * Single Placement is when you use a plando block to place a single item at a single location.
+ * `item` is the item you would like to place and `location` is the location to place it.
+ * Multi Placement uses a plando block to place multiple items in multiple locations until either list is exhausted.
+ * `items` defines the items to use and a number letting you place multiple of it.
+ * `locations` is a list of possible locations those items can be placed in.
+ * Using the multi placement method, placements are picked randomly.
### Available Items
-
-* A Link to the
- Past: [Link to the Past Item List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Items.py#L52)
-* Factorio Non-Progressive: [Factorio Technologies Wiki List](https://wiki.factorio.com/Technologies)
- * Note that these use the *internal names*. For example, `advanced-electronics`
-* Factorio
- Progressive: [Factorio Progressive Technologies List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/factorio/Technologies.py#L374)
-*
-Minecraft: [Minecraft Items List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Items.py#L14)
-* Ocarina of
- Time: [Ocarina of Time Items List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/Items.py#L61)
-* Risk of Rain
- 2: [Risk of Rain 2 Items List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/ror2/Items.py#L8)
-* Slay the
- Spire: [Slay the Spire Items List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/spire/Items.py#L13)
-*
-Subnautica: [Subnautica Items List JSON File](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/subnautica/items.json)
-*
-Timespinner: [Timespinner Items List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/timespinner/Items.py#L11)
+* [A Link to the Past](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Items.py#L52)
+* [Factorio Non-Progressive](https://wiki.factorio.com/Technologies) Note that these use the *internal names*. For example, `advanced-electronics`
+* [Factorio Progressive](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/factorio/Technologies.py#L374)
+* [Minecraft](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Items.py#L14)
+* [Ocarina of Time](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/Items.py#L61)
+* [Risk of Rain 2](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/ror2/Items.py#L8)
+* [Slay the Spire](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/spire/Items.py#L13)
+* [Subnautica](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/subnautica/items.json)
+* [Timespinner](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/timespinner/Items.py#L11)
### Available Locations
+* [A Link to the Past](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Regions.py#L429)
+* [Factorio](https://wiki.factorio.com/Technologies) Same as items
+* [Minecraft](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Locations.py#L18)
+* [Ocarina of Time](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/LocationList.py#L38)
+* [Risk of Rain 2](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/ror2/Locations.py#L17) This is a special
+case. The locations are "ItemPickup[number]" up to the maximum set in the yaml.
+* [Slay the Spire](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/spire/Locations.py)
+* [Subnautica](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/subnautica/locations.json)
+* [Timespinner](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/timespinner/Locations.py#L13)
-* A Link to the
- Past: [Link to the Past Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Regions.py#L429)
-* Factorio: [Factorio Technologies List Wiki](https://wiki.factorio.com/Technologies)
- * In Factorio the location names are the same as the item names.
-*
-Minecraft: [Minecraft Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Locations.py#L18)
-* Ocarina of
- Time: [Ocarina of Time Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/LocationList.py#L38)
-* Risk of Rain
- 2: [Risk of Rain 2 Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/ror2/Locations.py#L17)
- * This is a special case. The locations are "ItemPickup[number]" up to the maximum set in the yaml.
-* Slay the
- Spire: [Slay the Spire Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/spire/Locations.py)
-*
-Subnautica: [Subnautica Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/subnautica/locations.json)
-*
-Timespinner: [Timespinner Locations List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/timespinner/Locations.py#L13)
-
-A list of all available items and locations can also be found in the server's datapackage. Data package
-JSON: [DataPackage JSON](/api/datapackage).
+A list of all available items and locations can also be found in the [server's datapackage](/api/datapackage).
### Examples
-
```yaml
plando_items:
- # example block 1 - Timespinner
+# example block 1 - Timespinner
- item:
Empire Orb: 1
Radiant Orb: 1
@@ -111,8 +78,8 @@ plando_items:
from_pool: true
world: true
percentage: 50
-
- # example block 2 - Ocarina of Time
+
+# example block 2 - Ocarina of Time
- items:
Kokiri Sword: 1
Biggoron Sword: 1
@@ -131,8 +98,8 @@ plando_items:
- Shadow Temple Hover Boots Chest
- Spirit Temple Silver Gauntlets Chest
world: false
-
- # example block 3 - Slay the Spire
+
+# example block 3 - Slay the Spire
- items:
Boss Relic: 3
locations:
@@ -140,7 +107,7 @@ plando_items:
Boss Relic 2
Boss Relic 3
- # example block 4 - Factorio
+# example block 4 - Factorio
- items:
progressive-electric-energy-distribution: 2
electric-energy-accumulators: 1
@@ -153,49 +120,39 @@ plando_items:
percentage: 80
force: true
```
-
-1. This block has a 50% chance to occur, and if it does will place either the Empire Orb or Radiant Orb on another
- player's Starter Chest 1 and removes the chosen item from the item pool.
+1. This block has a 50% chance to occur, and if it does will place either the Empire Orb or Radiant Orb on another player's
+Starter Chest 1 and removes the chosen item from the item pool.
2. This block will always trigger and will place the player's swords, bow, magic meter, strength upgrades, and hookshots
- in their own dungeon major item chests.
+in their own dungeon major item chests.
3. This block will always trigger and will lock boss relics on the bosses.
4. This block has an 80% chance of occuring and when it does will place all but 1 of the items randomly among the four
- locations chosen here.
+locations chosen here.
## Boss Plando
-
-As this is currently only supported by A Link to the Past instead of explaining here please refer to the Z3 plando
-guide. Z3 plando guide: [LttP Plando Guide](/tutorial/zelda3/plando/en)
+As this is currently only supported by A Link to the Past instead of explaining here please refer to the
+[relevant guide](/tutorial/zelda3/plando/en)
## Text Plando
-
-As this is currently only supported by A Link to the Past instead of explaining here please refer to the Z3 plando
-guide. Z3 plando guide: [LttP Plando Guide](/tutorial/zelda3/plando/en)
+As this is currently only supported by A Link to the Past instead of explaining here please refer to the
+[relevant guide](/tutorial/zelda3/plando/en)
## Connections Plando
-
-This is currently only supported by Minecraft and A Link to the Past. As the way that these games interact with their
-connections is different I will only explain the basics here while more specifics for Link to the Past connection plando
-can be found in its plando guide.
-
-* The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options support
- subweights.
+This is currently only supported by Minecraft and A Link to the Past. As the way that these games interact with
+their connections is different I will only explain the basics here while more specifics for Link to the Past connection
+plando can be found in its plando guide.
+* The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options support subweights.
* `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100.
-* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance
- shuffle.
+* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance shuffle.
* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate.
-Link to the Past
-connections: [Link to the Past Connections List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
+[Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
-Minecraft
-connections: [Minecraft Connections List in the Code](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Regions.py#L62)
+[Minecraft connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Regions.py#L62)
### Examples
-
```yaml
plando_connections:
- # example block 1 - Link to the Past
+# example block 1 - Link to the Past
- entrance: Cave Shop (Lake Hylia)
exit: Cave 45
direction: entrance
@@ -205,8 +162,8 @@ plando_connections:
- entrance: Agahnims Tower
exit: Old Man Cave Exit (West)
direction: exit
-
- # example block 2 - Minecraft
+
+# example block 2 - Minecraft
- entrance: Overworld Structure 1
exit: Nether Fortress
direction: both
diff --git a/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md b/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md
index ce3ab541..59a47563 100644
--- a/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md
+++ b/WebHostLib/static/assets/tutorial/zelda3/multiworld_en.md
@@ -1,136 +1,130 @@
# A Link to the Past Randomizer Setup Guide
## Required Software
-
-- A client, one of:
- - Z3Client: [Z3Client Releases Page](https://github.com/ArchipelagoMW/Z3Client/releases)
- - SNIClient included with Archipelago:
- [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
- - If installing Archipelago, make sure to check the box for SNIClient -> A Link to the Past Patch Setup during
- install, or SNI will not be included
-- Super Nintendo Interface (SNI): [SNI Releases Page](https://github.com/alttpo/sni/releases)
- - (Included in both Z3Client and SNIClient)
+- [SNIClient](https://github.com/ArchipelagoMW/Archipelago/releases) included with the main Archipelago install
+or [SuperNintendoClient](https://github.com/ArchipelagoMW/SuperNintendoClient/releases)
+ - If installing Archipelago, make sure to check the box for `SNI Client - A Link to the Past Patch Setup`
+- [SNI](https://github.com/alttpo/sni/releases) (Included in both clients from the first step)
- Hardware or software capable of loading and playing SNES ROM files
- - An emulator capable of connecting to SNI, one of:
- -
- snes9x_Multitroid: [snes9x Multitroid Download in Google Drive](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz)
- - BizHawk: [BizHawk Official Website](http://tasvideos.org/BizHawk.html)
- - An SD2SNES, FXPak Pro ([FXPak Pro Store page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other
- compatible hardware
+ - An emulator capable of connecting to SNI
+ ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz),
+ [BizHawk](http://tasvideos.org/BizHawk.html))
+ - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware
- Your Japanese v1.0 ROM file, probably named `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
## Installation Procedures
-
1. Download and install your preferred client from the link above, making sure to install the most recent version.
- **The installer file is located in the assets section at the bottom of the version information**.
+**The installer file is located in the assets section at the bottom of the version information**.
- During setup, you will be asked to locate your base ROM file. This is your Japanese Link to the Past ROM file.
-2. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
- files.
- 1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
+2. If you are using an emulator, you should assign your Lua capable emulator as your default program
+for launching ROM files.
+ 1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
2. Right-click on a ROM file and select **Open with...**
3. Check the box next to **Always use this app to open .sfc files**
4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC**
- 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you
- extracted in step one.
+ 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside
+ the folder you extracted in step one.
## Create a Config (.yaml) File
### What is a config file and why do I need one?
-
-Your config file contains a set of configuration options which provide the generator with information about how it
-should generate your game. Each player of a multiworld will provide their own config file. This setup allows each player
-to enjoy an experience customized for their taste, and different players in the same multiworld can all have different
-options.
+Your config file contains a set of configuration options which provide the generator with information about how
+it should generate your game. Each player of a multiworld will provide their own config file. This setup allows
+each player to enjoy an experience customized for their taste, and different players in the same multiworld
+can all have different options.
### Where do I get a config file?
-
-The Player Settings page on the website allows you to configure your personal settings and export a config file from
-them. ([Player Settings Page for A Link to the Past](/games/A%20Link%20to%20the%20Past/player-settings))
+The [Player Settings](/games/A%20Link%20to%20the%20Past/player-settings) page on the website allows you to configure
+your personal settings and export a config file from them.
### Verifying your config file
-
-If you would like to validate your config file to make sure it works, you may do so on the YAML Validation
-page. ([YAML Validation Page](/mysterycheck))
+If you would like to validate your config file to make sure it works, you may do so on the
+[YAML Validator](/mysterycheck) page.
## Generating a Single-Player Game
-
-1. Navigate to the Player Settings page, configure your options, and click the "Generate Game"
- button. ([Player Settings for A Link to the Past](/games/A%20Link%20to%20the%20Past/player-settings))
+1. Navigate to the [Player Settings](/games/A%20Link%20to%20the%20Past/player-settings) page, configure your options,
+ and click the "Generate Game" button.
2. You will be presented with a "Seed Info" page.
3. Click the "Create New Room" link.
4. You will be presented with a server page, from which you can download your patch file.
-5. Double-click on your patch file, and the Z3Client will launch automatically, create your ROM from the patch file, and
- open your emulator for you.
+5. Double-click on your patch file, and the Z3Client will launch automatically, create your ROM from
+ the patch file, and open your emulator for you.
6. Since this is a single-player game, you will no longer need the client, so feel free to close it.
## Joining a MultiWorld Game
### Obtain your patch file and create your ROM
+When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that
+is done, the host will provide you with either a link to download your patch file, or with a zip file containing
+everyone's patch files. Your patch file should have a `.apbp` extension.
-When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done,
-the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch
-files. Your patch file should have a `.apbp` extension.
-
-Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the
-client, and will also create your ROM in the same place as your patch file.
+Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically
+launch the client, and will also create your ROM in the same place as your patch file.
### Connect to the client
#### With an emulator
-
-When the client launched automatically, SNI should have also automatically launched in the background. If this is its
-first time launching, you may be prompted to allow it to communicate through the Windows Firewall.
+When the client launched automatically, SNI should have also automatically launched in the background.
+If this is its first time launching, you may be prompted to allow it to communicate through the Windows
+Firewall.
##### snes9x Multitroid
-
1. Load your ROM file if it hasn't already been loaded.
2. Click on the File menu and hover on **Lua Scripting**
3. Click on **New Lua Script Window...**
4. In the new window, click **Browse...**
5. Select the connector lua file included with your client
- - Z3Client users should download `sniConnector.lua` from the client download page
- - SNIClient users should look in their Archipelago folder for `/sni/lua`
+ - SuperNintendoClient users should download `sniConnector.lua` from the client download page
+ - SNIClient users should look in their Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if
+the emulator is 64-bit or 32-bit.
##### BizHawk
-
-1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these
- menu options:
+1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following
+ these menu options:
`Config --> Cores --> SNES --> BSNES`
Once you have changed the loaded core, you must restart BizHawk.
2. Load your ROM file if it hasn't already been loaded.
3. Click on the Tools menu and click on **Lua Console**
4. Click Script -> Open Script...
5. Select the `Connector.lua` file you downloaded above
- - Z3Client users should download `sniConnector.lua` from the client download page
- - SNIClient users should look in their Archipelago folder for `/sni/lua`
-6. Run the script by double-clicking it in the listing
+ - SuperNintendoClient users should download `sniConnector.lua` from the client download page
+ - SNIClient users should look in their Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if
+the emulator is 64-bit or 32-bit.
#### With hardware
-
-This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do
-this now. SD2SNES and FXPak Pro users may download the appropriate
-firmware [from the sd2snes releases page](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find
-helpful information [on the usb2snes supported platforms page](http://usb2snes.com/#supported-platforms).
+This guide assumes you have downloaded the correct firmware for your device. If you have not
+done so already, please do this now. SD2SNES and FXPak Pro users may download the appropriate firmware
+[here](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find helpful information
+[on this page](http://usb2snes.com/#supported-platforms).
1. Close your emulator, which may have auto-launched.
2. Power on your device and load the ROM.
### Connect to the Archipelago Server
+The patch file which launched your client should have automatically connected you to the AP Server.
+There are a few reasons this may not happen however, including if the game is hosted on the website but
+was generated elsewhere. If the client window shows "Server Status: Not Connected", simply ask the host
+for the address of the server, and copy/paste it into the "Server" input field then press enter.
-The patch file which launched your client should have automatically connected you to the AP Server. There are a few
-reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the
-client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it
-into the "Server" input field then press enter.
-
-The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected".
+The client will attempt to reconnect to the new server address, and should momentarily show "Server
+Status: Connected".
### Play the game
-
-When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on
-successfully joining a multiworld game!
+When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations
+on successfully joining a multiworld game! You can execute various commands in your client. For more information
+regarding these commands you can use `/help` for local client commands and `!help` for server commands.
## Hosting a MultiWorld game
+The recommended way to host a game is to use our [hosting service](/generate). The process is relatively simple:
-The recommended way to host a game is to use our hosting service on the [seed generation page](/generate). Or check out
-the Archipelago website guide for more information: [Archipelago Website Guide](/tutorial/archipelago/using_website/en)
\ No newline at end of file
+1. Collect config files from your players.
+2. Create a zip file containing your players' config files.
+3. Upload that zip file to the website linked above.
+4. Wait a moment while the seed is generated.
+5. When the seed is generated, you will be redirected to a "Seed Info" page.
+6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players,
+ so they may download their patch files from there.
+7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all
+ players in the game. Any observers may also be given the link to this page.
+8. Once all players have joined, you may begin playing.
diff --git a/WebHostLib/static/styles/hostRoom.css b/WebHostLib/static/styles/hostRoom.css
index bef8d147..cd1cf35b 100644
--- a/WebHostLib/static/styles/hostRoom.css
+++ b/WebHostLib/static/styles/hostRoom.css
@@ -18,3 +18,34 @@
border-radius: 3px;
width: 500px;
}
+
+#host-room table {
+ border-spacing: 0px;
+}
+
+#host-room table tbody{
+ background-color: #dce2bd;
+}
+
+#host-room table tbody tr:hover{
+ background-color: #e2eabb;
+}
+
+#host-room table tbody td{
+ padding: 4px 6px;
+ color: black;
+}
+
+#host-room table tbody a{
+ color: #234ae4;
+}
+
+#host-room table thead td{
+ background-color: #b0a77d;
+ color: black;
+ top: 0;
+}
+
+#host-room table tbody td{
+ border: 1px solid #bba967;
+}
diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html
index 37ca89ee..549d3ace 100644
--- a/WebHostLib/templates/macros.html
+++ b/WebHostLib/templates/macros.html
@@ -8,22 +8,43 @@
{%- endmacro %}
{% macro list_patches_room(room) %}
{% if room.seed.slots %}
-
+
+
+
+
Id
+
Name
+
Game
+
Download Link
+
Tracker Page
+
+
+
{% for patch in room.seed.slots|list|sort(attribute="player_id") %}
- {% if patch.game == "Minecraft" %}
-