{{ (players.values()|sum(attribute='Total') / total_locations[team] * 100) | int }}
+
+
+
+ {% endif %}
{% endfor %}
diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py
index d3fd0fb0364e..80ccc720a3d9 100644
--- a/WebHostLib/tracker.py
+++ b/WebHostLib/tracker.py
@@ -1366,6 +1366,10 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
for playernumber in range(1, len(team) + 1) if playernumber not in groups}
for teamnumber, team in enumerate(names)}
+ total_locations = {teamnumber: sum(len(locations[playernumber])
+ for playernumber in range(1, len(team) + 1) if playernumber not in groups)
+ for teamnumber, team in enumerate(names)}
+
hints = {team: set() for team in range(len(names))}
if room.multisave:
multisave = restricted_loads(room.multisave)
@@ -1390,11 +1394,14 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
activity_timers[team, player] = now - datetime.datetime.utcfromtimestamp(timestamp)
player_names = {}
+ completed_worlds = 0
states: typing.Dict[typing.Tuple[int, int], int] = {}
for team, names in enumerate(names):
for player, name in enumerate(names, 1):
player_names[team, player] = name
states[team, player] = multisave.get("client_game_state", {}).get((team, player), 0)
+ if states[team, player] == 30: # Goal Completed
+ completed_worlds += 1
long_player_names = player_names.copy()
for (team, player), alias in multisave.get("name_aliases", {}).items():
player_names[team, player] = alias
@@ -1410,7 +1417,8 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
activity_timers=activity_timers, video=video, hints=hints,
long_player_names=long_player_names,
multisave=multisave, precollected_items=precollected_items, groups=groups,
- locations=locations, games=games, states=states,
+ locations=locations, total_locations=total_locations, games=games, states=states,
+ completed_worlds=completed_worlds,
custom_locations=custom_locations, custom_items=custom_items,
)
From c4a3204af75574e3346577f6f9f40ea8af809c5d Mon Sep 17 00:00:00 2001
From: agilbert1412
Date: Thu, 31 Aug 2023 00:45:52 -0400
Subject: [PATCH 02/78] Stardew Valley: Add missing special order logic rules
(#2136)
* - Added missing special order requirements, mostly for the regions where to place the collected items, or the NPC to talk to when done
* - Added missing requirement on being able to go see the wizard cutscene in order to interact with bundles
---
worlds/stardew_valley/logic.py | 58 ++++++++++++++++++++--------------
1 file changed, 34 insertions(+), 24 deletions(-)
diff --git a/worlds/stardew_valley/logic.py b/worlds/stardew_valley/logic.py
index b12e02fff989..46580d549ac6 100644
--- a/worlds/stardew_valley/logic.py
+++ b/worlds/stardew_valley/logic.py
@@ -492,35 +492,39 @@ def __post_init__(self):
})
self.special_order_rules.update({
- SpecialOrder.island_ingredients: self.has_island_transport() & self.can_farm_perfectly() &
- self.has(Vegetable.taro_root) & self.has(Fruit.pineapple) & self.has(Forageable.ginger),
- SpecialOrder.cave_patrol: self.can_mine_perfectly() & self.can_mine_to_floor(120),
- SpecialOrder.aquatic_overpopulation: self.can_fish_perfectly(),
- SpecialOrder.biome_balance: self.can_fish_perfectly(),
- SpecialOrder.rock_rejuivenation: self.has(Mineral.ruby) & self.has(Mineral.topaz) & self.has(Mineral.emerald) &
- self.has(Mineral.jade) & self.has(Mineral.amethyst) & self.has_relationship(NPC.emily, 4) &
- self.has(ArtisanGood.cloth) & self.can_reach_region(Region.haley_house),
- SpecialOrder.gifts_for_george: self.has_season(Season.spring) & self.has(Forageable.leek),
- SpecialOrder.fragments_of_the_past: self.can_reach_region(Region.dig_site),
- SpecialOrder.gus_famous_omelet: self.has(AnimalProduct.any_egg),
- SpecialOrder.crop_order: self.can_farm_perfectly(),
- SpecialOrder.community_cleanup: self.can_crab_pot(),
- SpecialOrder.the_strong_stuff: self.can_keg(Vegetable.potato),
- SpecialOrder.pierres_prime_produce: self.can_farm_perfectly(),
- SpecialOrder.robins_project: self.can_chop_perfectly() & self.has(Material.hardwood),
- SpecialOrder.robins_resource_rush: self.can_chop_perfectly() & self.has(Fertilizer.tree) & self.can_mine_perfectly(),
- SpecialOrder.juicy_bugs_wanted_yum: self.has(Loot.bug_meat),
- SpecialOrder.tropical_fish: self.has_island_transport() & self.has(Fish.stingray) & self.has(Fish.blue_discus) & self.has(Fish.lionfish),
- SpecialOrder.a_curious_substance: self.can_mine_perfectly() & self.can_mine_to_floor(80),
- SpecialOrder.prismatic_jelly: self.can_mine_perfectly() & self.can_mine_to_floor(40),
+ SpecialOrder.island_ingredients: self.can_meet(NPC.caroline) & self.has_island_transport() & self.can_farm_perfectly() &
+ self.can_ship(Vegetable.taro_root) & self.can_ship(Fruit.pineapple) & self.can_ship(Forageable.ginger),
+ SpecialOrder.cave_patrol: self.can_meet(NPC.clint) & self.can_mine_perfectly() & self.can_mine_to_floor(120),
+ SpecialOrder.aquatic_overpopulation: self.can_meet(NPC.demetrius) & self.can_fish_perfectly(),
+ SpecialOrder.biome_balance: self.can_meet(NPC.demetrius) & self.can_fish_perfectly(),
+ SpecialOrder.rock_rejuivenation: self.has_relationship(NPC.emily, 4) & self.has(Mineral.ruby) & self.has(Mineral.topaz) &
+ self.has(Mineral.emerald) & self.has(Mineral.jade) & self.has(Mineral.amethyst) &
+ self.has(ArtisanGood.cloth) & self.can_reach_region(Region.haley_house),
+ SpecialOrder.gifts_for_george: self.can_reach_region(Region.alex_house) & self.has_season(Season.spring) & self.has(Forageable.leek),
+ SpecialOrder.fragments_of_the_past: self.can_reach_region(Region.museum) & self.can_reach_region(Region.dig_site) & self.has_tool(Tool.pickaxe),
+ SpecialOrder.gus_famous_omelet: self.can_reach_region(Region.saloon) & self.has(AnimalProduct.any_egg),
+ SpecialOrder.crop_order: self.can_farm_perfectly() & self.can_ship(),
+ SpecialOrder.community_cleanup: self.can_reach_region(Region.railroad) & self.can_crab_pot(),
+ SpecialOrder.the_strong_stuff: self.can_reach_region(Region.trailer) & self.can_keg(Vegetable.potato),
+ SpecialOrder.pierres_prime_produce: self.can_reach_region(Region.pierre_store) & self.can_farm_perfectly(),
+ SpecialOrder.robins_project: self.can_meet(NPC.robin) & self.can_reach_region(Region.carpenter) & self.can_chop_perfectly() &
+ self.has(Material.hardwood),
+ SpecialOrder.robins_resource_rush: self.can_meet(NPC.robin) & self.can_reach_region(Region.carpenter) & self.can_chop_perfectly() &
+ self.has(Fertilizer.tree) & self.can_mine_perfectly(),
+ SpecialOrder.juicy_bugs_wanted_yum: self.can_reach_region(Region.beach) & self.has(Loot.bug_meat),
+ SpecialOrder.tropical_fish: self.can_meet(NPC.willy) & self.received("Island Resort") & self.has_island_transport() &
+ self.has(Fish.stingray) & self.has(Fish.blue_discus) & self.has(Fish.lionfish),
+ SpecialOrder.a_curious_substance: self.can_reach_region(Region.wizard_tower) & self.can_mine_perfectly() & self.can_mine_to_floor(80),
+ SpecialOrder.prismatic_jelly: self.can_reach_region(Region.wizard_tower) & self.can_mine_perfectly() & self.can_mine_to_floor(40),
SpecialOrder.qis_crop: self.can_farm_perfectly() & self.can_reach_region(Region.greenhouse) &
self.can_reach_region(Region.island_west) & self.has_total_skill_level(50) &
- self.has(Machine.seed_maker),
+ self.has(Machine.seed_maker) & self.has_building(Building.shipping_bin),
SpecialOrder.lets_play_a_game: self.has_junimo_kart_max_level(),
SpecialOrder.four_precious_stones: self.has_lived_months(MAX_MONTHS) & self.has("Prismatic Shard") &
self.can_mine_perfectly_in_the_skull_cavern(),
SpecialOrder.qis_hungry_challenge: self.can_mine_perfectly_in_the_skull_cavern() & self.has_max_buffs(),
- SpecialOrder.qis_cuisine: self.can_cook() & (self.can_spend_money_at(Region.saloon, 205000) | self.can_spend_money_at(Region.pierre_store, 170000)),
+ SpecialOrder.qis_cuisine: self.can_cook() & (self.can_spend_money_at(Region.saloon, 205000) | self.can_spend_money_at(Region.pierre_store, 170000)) &
+ self.can_ship(),
SpecialOrder.qis_kindness: self.can_give_loved_gifts_to_everyone(),
SpecialOrder.extended_family: self.can_fish_perfectly() & self.has(Fish.angler) & self.has(Fish.glacierfish) &
self.has(Fish.crimsonfish) & self.has(Fish.mutant_carp) & self.has(Fish.legend),
@@ -1157,7 +1161,7 @@ def can_complete_bundle(self, bundle_requirements: List[BundleItem], number_requ
item_rules.append(bundle_item.item.name)
if bundle_item.quality > highest_quality_yet:
highest_quality_yet = bundle_item.quality
- return self.has(item_rules, number_required) & self.can_grow_gold_quality(highest_quality_yet)
+ return self.can_reach_region(Region.wizard_tower) & self.has(item_rules, number_required) & self.can_grow_gold_quality(highest_quality_yet)
def can_grow_gold_quality(self, quality: int) -> StardewRule:
if quality <= 0:
@@ -1605,3 +1609,9 @@ def has_all_rarecrows(self) -> StardewRule:
rules.append(self.received(f"Rarecrow #{rarecrow_number}"))
return And(rules)
+ def can_ship(self, item: str = "") -> StardewRule:
+ shipping_bin_rule = self.has_building(Building.shipping_bin)
+ if item == "":
+ return shipping_bin_rule
+ return shipping_bin_rule & self.has(item)
+
From 77a349c1c6da8dcd3dc6112b495c8d552d6de9df Mon Sep 17 00:00:00 2001
From: Fabian Dill
Date: Thu, 31 Aug 2023 21:54:59 +0200
Subject: [PATCH 03/78] Core/LttP: remove can_reach_private
---
BaseClasses.py | 8 --------
worlds/alttp/Rules.py | 2 --
2 files changed, 10 deletions(-)
diff --git a/BaseClasses.py b/BaseClasses.py
index 02d050c66761..26cdfb528569 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -853,14 +853,6 @@ def can_reach(self, state: CollectionState) -> bool:
state.update_reachable_regions(self.player)
return self in state.reachable_regions[self.player]
- def can_reach_private(self, state: CollectionState) -> bool:
- for entrance in self.entrances:
- if entrance.can_reach(state):
- if not self in state.path:
- state.path[self] = (self.name, state.path.get(entrance, None))
- return True
- return False
-
@property
def hint_text(self) -> str:
return self._hint_text if self._hint_text else self.name
diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py
index 09c63aca01d2..ce4a941ead1b 100644
--- a/worlds/alttp/Rules.py
+++ b/worlds/alttp/Rules.py
@@ -32,7 +32,6 @@ def set_rules(world):
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
if world.players == 1:
- world.get_region('Menu', player).can_reach_private = lambda state: True
no_logic_rules(world, player)
for exit in world.get_region('Menu', player).exits:
exit.hide_path = True
@@ -196,7 +195,6 @@ def global_rules(world, player):
add_item_rule(world.get_location(prize_location, player),
lambda item: item.name in crystals_and_pendants and item.player == player)
# determines which S&Q locations are available - hide from paths since it isn't an in-game location
- world.get_region('Menu', player).can_reach_private = lambda state: True
for exit in world.get_region('Menu', player).exits:
exit.hide_path = True
From 536845186754a9ae56e9d4a4fae97d661e272691 Mon Sep 17 00:00:00 2001
From: Mathx2
Date: Thu, 7 Sep 2023 13:23:42 -0700
Subject: [PATCH 04/78] Timespinner: Options.py Typo (#2154)
Line 63, changed the commend from (Reccomended) to (Recommended)
---
worlds/timespinner/Options.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py
index 5f4d230688bd..8b111849442c 100644
--- a/worlds/timespinner/Options.py
+++ b/worlds/timespinner/Options.py
@@ -60,7 +60,7 @@ class BossRando(Toggle):
class BossScaling(DefaultOnToggle):
- "When Boss Rando is enabled, scales the bosses' HP, XP, and ATK to the stats of the location they replace (Reccomended)"
+ "When Boss Rando is enabled, scales the bosses' HP, XP, and ATK to the stats of the location they replace (Recommended)"
display_name = "Scale Random Boss Stats"
From 2b9e8fa273ceee19c59139e8744bb3a905c33e7b Mon Sep 17 00:00:00 2001
From: Fabian Dill
Date: Sat, 9 Sep 2023 05:02:05 +0200
Subject: [PATCH 05/78] WebHost: flask caching doesn't do lazy init anymore
(#2155)
---
WebHost.py | 3 ++-
WebHostLib/__init__.py | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/WebHost.py b/WebHost.py
index 45d017cf1f67..36645ad27de0 100644
--- a/WebHost.py
+++ b/WebHost.py
@@ -14,7 +14,7 @@
Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
-from WebHostLib import register, app as raw_app
+from WebHostLib import register, cache, app as raw_app
from waitress import serve
from WebHostLib.models import db
@@ -40,6 +40,7 @@ def get_app():
app.config["HOST_ADDRESS"] = Utils.get_public_ipv4()
logging.info(f"HOST_ADDRESS was set to {app.config['HOST_ADDRESS']}")
+ cache.init_app(app)
db.bind(**app.config["PONY"])
db.generate_mapping(create_tables=True)
return app
diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py
index a59e3aa553f3..441f3272fd3c 100644
--- a/WebHostLib/__init__.py
+++ b/WebHostLib/__init__.py
@@ -49,11 +49,11 @@
'create_db': True
}
app.config["MAX_ROLL"] = 20
-app.config["CACHE_TYPE"] = "flask_caching.backends.SimpleCache"
+app.config["CACHE_TYPE"] = "SimpleCache"
app.config["JSON_AS_ASCII"] = False
app.config["HOST_ADDRESS"] = ""
-cache = Cache(app)
+cache = Cache()
Compress(app)
From f6dafa2b560ff1f33070903ff0655d7340d9dfd2 Mon Sep 17 00:00:00 2001
From: Fabian Dill
Date: Sat, 9 Sep 2023 05:02:53 +0200
Subject: [PATCH 06/78] Core: collect errors from generate_output at same step
as multidata
---
Main.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Main.py b/Main.py
index fe56dc7d9e09..860be6347c66 100644
--- a/Main.py
+++ b/Main.py
@@ -392,7 +392,7 @@ def precollect_hint(location):
f.write(bytes([3])) # version of format
f.write(multidata)
- multidata_task = pool.submit(write_multidata)
+ output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result():
if not world.can_beat_game():
raise Exception("Game appears as unbeatable. Aborting.")
@@ -400,7 +400,6 @@ def precollect_hint(location):
logger.warning("Location Accessibility requirements not fulfilled.")
# retrieve exceptions via .result() if they occurred.
- multidata_task.result()
for i, future in enumerate(concurrent.futures.as_completed(output_file_futures), start=1):
if i % 10 == 0 or i == len(output_file_futures):
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
From 29f8053d6ed256f87963ce17815dec9ea2484d85 Mon Sep 17 00:00:00 2001
From: Fabian Dill
Date: Sun, 10 Sep 2023 00:33:36 +0200
Subject: [PATCH 07/78] Factorio: fix website multitracker (#2126)
Co-authored-by: Remy Jette
---
.../templates/multiFactorioTracker.html | 16 +++---
WebHostLib/tracker.py | 50 +++++++++++++------
2 files changed, 43 insertions(+), 23 deletions(-)
diff --git a/WebHostLib/templates/multiFactorioTracker.html b/WebHostLib/templates/multiFactorioTracker.html
index e8fa7b152cf2..faca756ee975 100644
--- a/WebHostLib/templates/multiFactorioTracker.html
+++ b/WebHostLib/templates/multiFactorioTracker.html
@@ -27,14 +27,14 @@
{% endblock %}
{% block custom_table_row scoped %}
{% if games[player] == "Factorio" %}
-{% set player_inventory = inventory[team][player] %}
-{% set prog_science = player_inventory[custom_items["progressive-science-pack"]] %}
-
{% if player_inventory[custom_items["logistic-science-pack"]] or prog_science %}✔{% endif %}
-
{% if player_inventory[custom_items["military-science-pack"]] or prog_science > 1%}✔{% endif %}
-
{% if player_inventory[custom_items["chemical-science-pack"]] or prog_science > 2%}✔{% endif %}
-
{% if player_inventory[custom_items["production-science-pack"]] or prog_science > 3%}✔{% endif %}
-
{% if player_inventory[custom_items["utility-science-pack"]] or prog_science > 4%}✔{% endif %}
-
{% if player_inventory[custom_items["space-science-pack"]] or prog_science > 5%}✔{% endif %}
+{% set player_inventory = named_inventory[team][player] %}
+{% set prog_science = player_inventory["progressive-science-pack"] %}
+
{% if player_inventory["logistic-science-pack"] or prog_science %}✔{% endif %}
+
{% if player_inventory["military-science-pack"] or prog_science > 1%}✔{% endif %}
+
{% if player_inventory["chemical-science-pack"] or prog_science > 2%}✔{% endif %}
+
{% if player_inventory["production-science-pack"] or prog_science > 3%}✔{% endif %}
+
{% if player_inventory["utility-science-pack"] or prog_science > 4%}✔{% endif %}
+
{% if player_inventory["space-science-pack"] or prog_science > 5%}✔{% endif %}
{% else %}
❌
❌
diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py
index 80ccc720a3d9..5b89495ecc64 100644
--- a/WebHostLib/tracker.py
+++ b/WebHostLib/tracker.py
@@ -11,7 +11,7 @@
from MultiServer import Context, get_saving_second
from NetUtils import SlotType, NetworkSlot
from Utils import restricted_loads
-from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name, network_data_package
+from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name, network_data_package, games
from worlds.alttp import Items
from . import app, cache
from .models import GameDataPackage, Room
@@ -1423,9 +1423,12 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
)
-def _get_inventory_data(data: typing.Dict[str, typing.Any]) -> typing.Dict[int, typing.Dict[int, int]]:
- inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in team_data}
- for teamnumber, team_data in data["checks_done"].items()}
+def _get_inventory_data(data: typing.Dict[str, typing.Any]) \
+ -> typing.Dict[int, typing.Dict[int, typing.Dict[int, int]]]:
+ inventory: typing.Dict[int, typing.Dict[int, typing.Dict[int, int]]] = {
+ teamnumber: {playernumber: collections.Counter() for playernumber in team_data}
+ for teamnumber, team_data in data["checks_done"].items()
+ }
groups = data["groups"]
@@ -1444,6 +1447,17 @@ def _get_inventory_data(data: typing.Dict[str, typing.Any]) -> typing.Dict[int,
return inventory
+def _get_named_inventory(inventory: typing.Dict[int, int], custom_items: typing.Dict[int, str] = None) \
+ -> typing.Dict[str, int]:
+ """slow"""
+ if custom_items:
+ mapping = collections.ChainMap(custom_items, lookup_any_item_id_to_name)
+ else:
+ mapping = lookup_any_item_id_to_name
+
+ return collections.Counter({mapping.get(item_id, None): count for item_id, count in inventory.items()})
+
+
@app.route('/tracker/')
@cache.memoize(timeout=60) # multisave is currently created at most every minute
def get_multiworld_tracker(tracker: UUID):
@@ -1455,18 +1469,22 @@ def get_multiworld_tracker(tracker: UUID):
return render_template("multiTracker.html", **data)
+if "Factorio" in games:
+ @app.route('/tracker//Factorio')
+ @cache.memoize(timeout=60) # multisave is currently created at most every minute
+ def get_Factorio_multiworld_tracker(tracker: UUID):
+ data = _get_multiworld_tracker_data(tracker)
+ if not data:
+ abort(404)
-@app.route('/tracker//Factorio')
-@cache.memoize(timeout=60) # multisave is currently created at most every minute
-def get_Factorio_multiworld_tracker(tracker: UUID):
- data = _get_multiworld_tracker_data(tracker)
- if not data:
- abort(404)
+ data["inventory"] = _get_inventory_data(data)
+ data["named_inventory"] = {team_id : {
+ player_id: _get_named_inventory(inventory, data["custom_items"])
+ for player_id, inventory in team_inventory.items()
+ } for team_id, team_inventory in data["inventory"].items()}
+ data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Factorio")
- data["inventory"] = _get_inventory_data(data)
- data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Factorio")
-
- return render_template("multiFactorioTracker.html", **data)
+ return render_template("multiFactorioTracker.html", **data)
@app.route('/tracker//A Link to the Past')
@@ -1596,5 +1614,7 @@ def attribute_item(team: int, recipient: int, item: int):
multi_trackers: typing.Dict[str, typing.Callable] = {
"A Link to the Past": get_LttP_multiworld_tracker,
- "Factorio": get_Factorio_multiworld_tracker,
}
+
+if "Factorio" in games:
+ multi_trackers["Factorio"] = get_Factorio_multiworld_tracker
From a1418ccb66c5e8814e61ddb41469dd5ea307d4ea Mon Sep 17 00:00:00 2001
From: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
Date: Sat, 9 Sep 2023 21:30:03 -0400
Subject: [PATCH 08/78] Docs: Small typo and proofreading edits (#2078)
* Slight rewording of DS3 game page
Lists made more concise, space added between "generated weapons" and open parenthesis
* Proofread Final Fantasy pages
Fixed minor typos and reworded sentences for conciseness.
* Edited Kingdom Hearts 2 Game Page
Refined style, capitalization, and sentence structure for clarity
* Fixed nested list in Minecraft game page
Each nest needed an additional 2 spaces
* Edited Risk of Rain 2 Game Page
Made various edits to redundancy within the page as well as omitted/unclear information
* Edited Stardew Valley game page
Small capitalization consistency edits and slight rewording for conciseness
* Update worlds/ff1/docs/multiworld_en.md
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
* Update worlds/kh2/docs/en_Kingdom Hearts 2.md
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
* Update worlds/kh2/docs/en_Kingdom Hearts 2.md
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
* Add information for EXP multiplier
Include Drive Forms and Summons
* Correction for Newt Altars RoR2
Co-Authored-By: kindasneaki <19377912+kindasneaki@users.noreply.github.com>
---------
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: kindasneaki <19377912+kindasneaki@users.noreply.github.com>
---
worlds/dark_souls_3/docs/en_Dark Souls III.md | 15 +-
worlds/ff1/docs/en_Final Fantasy.md | 18 +--
worlds/ff1/docs/multiworld_en.md | 8 +-
worlds/kh2/docs/en_Kingdom Hearts 2.md | 29 ++--
worlds/minecraft/docs/en_Minecraft.md | 128 +++++++++---------
worlds/ror2/docs/en_Risk of Rain 2.md | 55 ++++----
.../stardew_valley/docs/en_Stardew Valley.md | 14 +-
7 files changed, 138 insertions(+), 129 deletions(-)
diff --git a/worlds/dark_souls_3/docs/en_Dark Souls III.md b/worlds/dark_souls_3/docs/en_Dark Souls III.md
index 3ad8236ccfae..1b55593bad98 100644
--- a/worlds/dark_souls_3/docs/en_Dark Souls III.md
+++ b/worlds/dark_souls_3/docs/en_Dark Souls III.md
@@ -7,19 +7,18 @@ config file.
## What does randomization do to this game?
-In Dark Souls III, all unique items you can earn from a static corpse, a chest or the death of a Boss/NPC are
+In Dark Souls III, all unique items you can earn from a static corpse, chest, or the death of a Boss/NPC are
randomized.
-An option is available from the settings page to also randomize the upgrade materials, the Estus shards and the
-consumables.
-Another option is available to randomize the level of the generated weapons(from +0 to +10/+5)
+An option is available from the settings page to also randomize upgrade materials, Estus shards, and consumables.
+Another option is available to randomize the level of the generated weapons (from +0 to +10/+5).
-To beat the game you need to collect the 4 "Cinders of a Lord" randomized in the multiworld
-and kill the final boss "Soul of Cinder"
+To beat the game, you need to collect the 4 "Cinders of a Lord" randomized in the multiworld
+and kill the final boss "Soul of Cinder."
## What Dark Souls III items can appear in other players' worlds?
-Every unique item from Dark Souls III can appear in other player's worlds, such as a piece of armor, an upgraded weapon,
-or a key item.
+Every unique item from Dark Souls III can appear in other player's worlds, such as a piece of armor, upgraded weapons,
+or key items.
## What does another world's item look like in Dark Souls III?
diff --git a/worlds/ff1/docs/en_Final Fantasy.md b/worlds/ff1/docs/en_Final Fantasy.md
index a62b5ec12605..29d4d29f8094 100644
--- a/worlds/ff1/docs/en_Final Fantasy.md
+++ b/worlds/ff1/docs/en_Final Fantasy.md
@@ -8,19 +8,19 @@ website: [FF1R Website](https://finalfantasyrandomizer.com/)
## What does randomization do to this game?
-A better questions is what isn't randomized at this point. Enemies stats and spell, character spells, shop inventory and
-boss stats and spells are all commonly randomized. Unlike most other randomizers it is also most standard to shuffle
-progression items and non-progression items into separate pools and then redistribute them to their respective
-locations. So, for example, Princess Sarah may have the CANOE instead of the LUTE; however, she will never have a Heal
-Pot or some armor. There are plenty of other things that can be randomized on the main randomizer
-site: [FF1R Website](https://finalfantasyrandomizer.com/)
+Enemy stats and spell, boss stats and spells, character spells, and shop inventories are all commonly randomized. Unlike
+most other randomizers, it is standard to shuffle progression items and non-progression items into separate pools
+and then redistribute them to their respective locations. For example, Princess Sarah may have the CANOE instead
+of the LUTE; however, she will never have a Heal Pot or armor.
+
+Plenty of other things to be randomized can be found on the main randomizer site:
+[FF1R Website](https://finalfantasyrandomizer.com/)
## What Final Fantasy items can appear in other players' worlds?
-All items can appear in other players worlds. This includes consumables, shards, weapons, armor and, of course, key
-items.
+All items can appear in other players worlds, including consumables, shards, weapons, armor, and key items.
## What does another world's item look like in Final Fantasy
-All local and remote items appear the same. It will say that you received an item and then BOTH the client log and the
+All local and remote items appear the same. Final Fantasy will say that you received an item, then BOTH the client log and the
emulator will display what was found external to the in-game text box.
diff --git a/worlds/ff1/docs/multiworld_en.md b/worlds/ff1/docs/multiworld_en.md
index 51fcd9b7bfc4..d3dc457f01be 100644
--- a/worlds/ff1/docs/multiworld_en.md
+++ b/worlds/ff1/docs/multiworld_en.md
@@ -32,14 +32,14 @@ Generate a game by going to the site and performing the following steps:
prefer, or it is your first time we suggest starting with the 'Shard Hunt' preset (which requires you to collect a
number of shards to go to the end dungeon) or the 'Beginner' preset if you prefer to kill the original fiends.
2. Go to the `Goal` tab and ensure `Archipelago` is enabled. Set your player name to any name that represents you.
-3. Upload you `Final Fantasy(USA).nes` (and click `Remember ROM` for the future!)
+3. Upload your `Final Fantasy(USA).nes` (and click `Remember ROM` for the future!)
4. Press the `NEW` button beside `Seed` a few times
5. Click `GENERATE ROM`
-It should download two files. One is the `*.nes` file which your emulator will run and the other is the yaml file
+It should download two files. One is the `*.nes` file which your emulator will run, and the other is the yaml file
required by Archipelago.gg
-At this point you are ready to join the multiworld. If you are uncertain on how to generate, host or join a multiworld
+At this point, you are ready to join the multiworld. If you are uncertain on how to generate, host, or join a multiworld,
please refer to the [game agnostic setup guide](/tutorial/Archipelago/setup/en).
## Running the Client Program and Connecting to the Server
@@ -67,7 +67,7 @@ Once the Archipelago server has been hosted:
## Play the game
-When the client shows both NES and server are connected you are good to go. You can check the connection status of the
+When the client shows both NES and server are connected, you are good to go. You can check the connection status of the
NES at any time by running `/nes`
### Other Client Commands
diff --git a/worlds/kh2/docs/en_Kingdom Hearts 2.md b/worlds/kh2/docs/en_Kingdom Hearts 2.md
index d132b29ca4dd..8258a099cc95 100644
--- a/worlds/kh2/docs/en_Kingdom Hearts 2.md
+++ b/worlds/kh2/docs/en_Kingdom Hearts 2.md
@@ -2,7 +2,7 @@
Changes from the vanilla game
-This randomizer takes Kingdom Hearts 2 and randomizes the locations of the items for a more dynamic play experience. The items that randomize currently are all items within Chests, Popups, Get Bonuses, Form Levels, and Sora's Levels. This allows abilities that Sora would normally have to also be placed on Keyblades with random stats. With several options on ways to finish the game.
+This randomizer creates a more dynamic play experience by randomizing the locations of most items in Kingdom Hearts 2. Currently all items within Chests, Popups, Get Bonuses, Form Levels, and Sora's Levels are randomized. This allows abilities that Sora would normally have to be placed on Keyblades with random stats. Additionally, there are several options for ways to finish the game, allowing for different goals beyond beating the final boss.
Where is the settings page
@@ -12,12 +12,18 @@ The [player settings page for this game](../player-settings) contains all the op
What is randomized in this game?
-The Chests, Popups, Get Bonuses, Form Levels, and Sora's Levels.
+- Chests
+- Popups
+- Get Bonuses
+- Form Levels
+- Sora's Levels
+- Keyblade Stats
+- Keyblade Abilities
What Kingdom Hearts 2 items can appear in other players' worlds?
-Every item in the game with the exception being party members' abilities.
+Every item in the game except for party members' abilities.
What is The Garden of Assemblage "GoA"?
@@ -37,10 +43,10 @@ It is added to your inventory. If you obtain magic, you will need to pause your
What Happens if I die before Room Saving?
-When you die in Kingdom Hearts 2, you are reverted to the last non-boss room you entered and your status is reverted to what it was at that time. However, in archipelago, any item that you have sent/received will not be taken away from the player, any chest you have opened will remain open, and you will keep your level but lose the expereince. Unlike vanilla Kingdom Hearts 2.
+When you die in vanilla Kingdom Hearts 2, you are reverted to the last non-boss room you entered and your status is reverted to what it was at that time. However, in archipelago, any item that you have sent/received will not be taken away from the player, any chest you have opened will remain open, and you will keep your level, but lose the experience.
-For example, if you are fighting Roxas and you receive Reflect Element and you die fighting Roxas, you will keep that reflect. You will still need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.
+For example, if you are fighting Roxas, receive Reflect Element, then die mid-fight, you will keep that Reflect Element. You will still need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.
Customization options:
@@ -49,13 +55,12 @@ For example, if you are fighting Roxas and you receive Reflect Element and you d
1. Obtain Three Proofs.
2. Obtain a desired amount of Lucky Emblems.
3. Obtain a desired amount of Bounties that are on late locations.
-- Customize how many World Locking Items You Need to Progress in that World.
-- Customize the Amount of World Locking Items You Start With.
-- Customize how many locations you want on Sora's Levels.
-- Customize the EXP Multiplier of everything that affects Sora.
-- Customize the Available Abilities on Keyblades.
-- Customize the level of Progressive Movement (Growth Abilities) you start with.
-- Customize the amount of Progressive Movement (Growth Abilities) you start with.
+- Customize how many World-Locking Items you need to progress in that world.
+- Customize the amount of World-Locking Items you start with.
+- Customize how many of Sora's Levels are locations.
+- Customize the EXP multiplier for Sora, his Drive Forms, and his Summons.
+- Customize the available abilities on keyblades.
+- Customize the amount and level of progressive movement (Growth Abilities) you start with.
- Customize start inventory, i.e., begin every run with certain items or spells of your choice.
Quality of life:
diff --git a/worlds/minecraft/docs/en_Minecraft.md b/worlds/minecraft/docs/en_Minecraft.md
index 2d4f063b79a9..1ef347983bc4 100644
--- a/worlds/minecraft/docs/en_Minecraft.md
+++ b/worlds/minecraft/docs/en_Minecraft.md
@@ -29,82 +29,82 @@ sequence either by skipping it or watching hit play out.
## Which recipes are locked?
* Archery
- * Bow
- * Arrow
- * Crossbow
+ * Bow
+ * Arrow
+ * Crossbow
* Brewing
- * Blaze Powder
- * Brewing Stand
+ * Blaze Powder
+ * Brewing Stand
* Enchanting
- * Enchanting Table
- * Bookshelf
+ * Enchanting Table
+ * Bookshelf
* Bucket
* Flint & Steel
* All Beds
* Bottles
* Shield
* Fishing Rod
- * Fishing Rod
- * Carrot on a Stick
- * Warped Fungus on a Stick
+ * Fishing Rod
+ * Carrot on a Stick
+ * Warped Fungus on a Stick
* Campfire
- * Campfire
- * Soul Campfire
+ * Campfire
+ * Soul Campfire
* Spyglass
* Lead
* Progressive Weapons
- * Tier I
- * Stone Sword
- * Stone Axe
- * Tier II
- * Iron Sword
- * Iron Axe
- * Tier III
- * Diamond Sword
- * Diamond Axe
+ * Tier I
+ * Stone Sword
+ * Stone Axe
+ * Tier II
+ * Iron Sword
+ * Iron Axe
+ * Tier III
+ * Diamond Sword
+ * Diamond Axe
* Progessive Tools
- * Tier I
- * Stone Shovel
- * Stone Hoe
- * Tier II
- * Iron Shovel
- * Iron Hoe
- * Tier III
- * Diamond Shovel
- * Diamond Hoe
- * Netherite Ingot
+ * Tier I
+ * Stone Shovel
+ * Stone Hoe
+ * Tier II
+ * Iron Shovel
+ * Iron Hoe
+ * Tier III
+ * Diamond Shovel
+ * Diamond Hoe
+ * Netherite Ingot
* Progressive Armor
- * Tier I
- * Iron Helmet
- * Iron Chestplate
- * Iron Leggings
- * Iron Boots
- * Tier II
- * Diamond Helmet
- * Diamond Chestplate
- * Diamond Leggings
- * Diamond Boots
+ * Tier I
+ * Iron Helmet
+ * Iron Chestplate
+ * Iron Leggings
+ * Iron Boots
+ * Tier II
+ * Diamond Helmet
+ * Diamond Chestplate
+ * Diamond Leggings
+ * Diamond Boots
* Progressive Resource Crafting
- * Tier I
- * Iron Ingot from Nuggets
- * Iron Nugget
- * Gold Ingot from Nuggets
- * Gold Nugget
- * Furnace
- * Blast Furnace
- * Tier II
- * Redstone
- * Redstone Block
- * Glowstone
- * Iron Ingot from Iron Block
- * Iron Block
- * Gold Ingot from Gold Block
- * Gold Block
- * Diamond
- * Diamond Block
- * Netherite Block
- * Netherite Ingot from Netherite Block
- * Anvil
- * Emerald
- * Emerald Block
- * Copper Block
+ * Tier I
+ * Iron Ingot from Nuggets
+ * Iron Nugget
+ * Gold Ingot from Nuggets
+ * Gold Nugget
+ * Furnace
+ * Blast Furnace
+ * Tier II
+ * Redstone
+ * Redstone Block
+ * Glowstone
+ * Iron Ingot from Iron Block
+ * Iron Block
+ * Gold Ingot from Gold Block
+ * Gold Block
+ * Diamond
+ * Diamond Block
+ * Netherite Block
+ * Netherite Ingot from Netherite Block
+ * Anvil
+ * Emerald
+ * Emerald Block
+ * Copper Block
diff --git a/worlds/ror2/docs/en_Risk of Rain 2.md b/worlds/ror2/docs/en_Risk of Rain 2.md
index ca22d1a44d70..d30edf888944 100644
--- a/worlds/ror2/docs/en_Risk of Rain 2.md
+++ b/worlds/ror2/docs/en_Risk of Rain 2.md
@@ -8,7 +8,7 @@ config file.
## What does randomization do to this game?
Risk of Rain is already a random game, by virtue of being a roguelite. The Archipelago mod implements pure multiworld
-functionality in which certain chests (made clear via a location check progress bar) will send an item out to the
+functionality in which certain chests will send an item out to the
multiworld. The items that _would have been_ in those chests will be returned to the Risk of Rain player via grants by
other players in other worlds.
@@ -16,28 +16,30 @@ There are two modes in risk of rain. Classic Mode and Explore Mode
Classic Mode:
- - Classic mode implements pure multiworld
-functionality in which certain chests (made clear via a location check progress bar) will send an item out to the
-multiworld. The items that _would have been_ in those chests will be returned to the Risk of Rain player via grants by
-other players in other worlds.
+ - Certain chests (made clear via a location check progress bar) will send an item out to the
+ multiworld. The location of these chests do not matter, since all environments share a unified location pool.
Explore Mode:
- - Just like in Classic mode chests will send out an item to the multiworld. The difference is that each environment
- will have a set amount that can be sent out and shrines along with other things that will need to be checked.
- Also, each environment is an item and, you'll need it to be able to access it.
+ - Chests will continue to work as they did in Classic Mode, the difference being that each environment
+ will have a set amount of items that can be sent out. In addition, shrines, radio scanners, newt altars,
+ and scavenger bags will need to be checked, depending on your settings.
+ This mode also makes each environment an item. In order to access a particular stage, you'll need it to be
+ sent in the multiworld.
## What is the goal of Risk of Rain 2 in Archipelago?
-Just like in the original game, any way to "beat the game" counts as a win. Alternatively, if you are new to the game and
+Just like in the original game, any way to "beat the game" counts as a win. This means beating one of the bosses
+on Commencement, The Planetarium, or A Moment, Whole. Alternatively, if you are new to the game and
aren't very confident in being able to "beat the game", you can set **Final Stage Death is Win** to true
-(You can turn this on in your player settings.) This will make it so if you die on either Commencement or The Planetarium,
-it will count as your goal, and **Obliterating yourself** will count as well.
+(You can turn this on in your player settings.) This will make it so dying on either Commencement or The Planetarium,
+or **obliterating yourself in A Moment, Fractured** will count as your goal.
**You do not need to complete all the location checks** to win; any item you don't collect may be released if the
server options allow.
If you die before you accomplish your goal, you can start a new run. You will start the run with any items that you
-received from other players. Any items that you picked up the "normal" way will be lost.
+received from other players. However, these items will be randomized within their rarity at the start of each run.
+Any items that you picked up the "normal" way will be lost.
Note, you can play Simulacrum mode as part of an Archipelago, but you can't achieve any of the victory conditions in
Simulacrum. So you could, for example, collect most of your items through a Simulacrum run(only works in classic mode),
@@ -72,10 +74,10 @@ The Risk of Rain items are:
Each item grants you a random in-game item from the category it belongs to.
-When an item is granted by another world to the Risk of Rain player (one of the items listed above) then a random
+When an item is granted by another world to the Risk of Rain player then a random
in-game item of that tier will appear in the Risk of Rain player's inventory. If the item grant is an `Equipment` and
-the player already has an equipment item equipped then the _item that was equipped_ will be dropped on the ground and _
-the new equipment_ will take it's place. (If you want the old one back, pick it up.)
+the player already has an equipment item equipped then the _item that was equipped_ will be dropped on the ground and
+_the new equipment_ will take it's place.
Explore Mode items are:
@@ -93,10 +95,10 @@ Dlc_Sotv items
* `Sulfur Pools`
* `Void Locus`
-When a explore item is granted it will unlock that environment and will now be accessible to progress to victory! The
-game will still pick randomly which environment is next but it will first check to see if they are available. If you have
-them unlocked it will weight the game to have a ***higher chance*** to go to one you have checks versus one you have
-already completed. You will still not be able to goto a stage 3 environment from a stage 1 environment.
+When an explore item is granted, it will unlock that environment and will now be accessible! The
+game will still pick randomly which environment is next, but it will first check to see if they are available. If you have
+multiple of the next environments unlocked, it will weight the game to have a ***higher chance*** to go to one you
+have checks in versus one you have already completed. You will still be unable to go to a stage 3 environment from a stage 1 environment.
@@ -108,9 +110,9 @@ to 250** items. The number of items will be randomized between all players, so y
item pickup step based on how many items the other players in the multiworld have. (Around 100 seems to be a good
ballpark if you want to have a similar number of items to most other games.)
-In explore mode the amount of checks base on how many **chests, shrines, scavengers, radio scanners and, newt altars**
-are in the pool. With just the base game the numbers are **52 to 516** and with the dlc its **60 to 660** with
-everything on default being **216**
+In explore mode, the amount of checks are based on how many **chests, shrines, scavengers, radio scanners, and newt altars**
+are in the pool. With just the base game, checks can range from **52 to 516**, with the DLC expanding it to **60 to 660**.
+Leaving everything on default, the total number of checks comes out to **216** locations.
After you have completed the specified number of checks, you won't send anything else to the multiworld. You can
receive up to the specified number of randomized items from the multiworld as the players find them. In either case,
@@ -120,12 +122,15 @@ you can continue to collect items as normal in Risk of Rain 2 if you've already
When the Risk of Rain player fills up their location check bar then the next spawned item will become an item grant for
another player's world (or possibly get sent back to yourself). The item in Risk of Rain will disappear in a poof of
-smoke and the grant will automatically go out to the multiworld.
+smoke and the grant will automatically go out to the multiworld. Additionally, you will see a message in the chat saying
+what item you sent out. If the message does not appear, this likely means that another game has collected their items from you.
## What is the item pickup step?
-The item pickup step is a YAML setting which allows you to set how many items you need to spawn before the _next_ item
-that is spawned disappears (in a poof of smoke) and goes out to the multiworld.
+The item pickup step is a setting in the YAML which allows you to set how many items you need to spawn before the _next_ item
+that is spawned disappears (in a poof of smoke) and goes out to the multiworld. For instance, an item step of **1** means that
+every other chest will send an item to the multiworld. An item step of **2** means that every third chest sends out an item
+just as an item step of **0** would send an item on **each chest.**
## Is Archipelago compatible with other Risk of Rain 2 mods?
diff --git a/worlds/stardew_valley/docs/en_Stardew Valley.md b/worlds/stardew_valley/docs/en_Stardew Valley.md
index 042163343eb9..a880a40b971a 100644
--- a/worlds/stardew_valley/docs/en_Stardew Valley.md
+++ b/worlds/stardew_valley/docs/en_Stardew Valley.md
@@ -7,7 +7,7 @@ config file.
## What does randomization do to this game?
-A vast number of optional objectives in stardew valley can be shuffled around the multiworld. Most of these are optional, and the player can customize their experience in their YAML file.
+A vast number of objectives in Stardew Valley can be shuffled around the multiworld. Most of these are optional, and the player can customize their experience in their YAML file.
For these objectives, if they have a vanilla reward, this reward will instead be an item in the multiworld. For the remaining number of such objectives, there are a number of "Resource Pack" items, which are simply an item or a stack of items that may be useful to the player.
@@ -28,24 +28,24 @@ The player can choose from a number of goals, using their YAML settings.
Location checks in Stardew Valley always include:
- [Community Center Bundles](https://stardewvalleywiki.com/Bundles)
-- [Mineshaft chest rewards](https://stardewvalleywiki.com/The_Mines#Remixed_Rewards)
+- [Mineshaft Chest Rewards](https://stardewvalleywiki.com/The_Mines#Remixed_Rewards)
- [Story Quests](https://stardewvalleywiki.com/Quests#List_of_Story_Quests)
-- [Traveling Merchant items](https://stardewvalleywiki.com/Traveling_Cart)
+- [Traveling Merchant Items](https://stardewvalleywiki.com/Traveling_Cart)
- Isolated objectives such as the [beach bridge](https://stardewvalleywiki.com/The_Beach#Tide_Pools), [Old Master Cannoli](https://stardewvalleywiki.com/Secret_Woods#Old_Master_Cannoli), [Grim Reaper Statue](https://stardewvalleywiki.com/Golden_Scythe), etc
There also are a number of location checks that are optional, and individual players choose to include them or not in their shuffling:
- Tools and Fishing Rod Upgrades
- Carpenter Buildings
- Backpack Upgrades
-- Mine elevator levels
+- Mine Elevator Levels
- Skill Levels
- Arcade Machines
-- Help Wanted quests
+- Help Wanted Quests
- Participating in Festivals
- Special Orders from the town board, or from Mr Qi
-- Cropsanity: Growing and harvesting individual crop types
+- Cropsanity: Growing and Harvesting individual crop types
- Fishsanity: Catching individual fish
-- Museumsanity: Donating individual items to the museum, or reaching the museum milestones for donations
+- Museumsanity: Donating individual items, or reaching milestones for museum donations
- Friendsanity: Reaching specific friendship levels with NPCs
## Which items can be in another player's world?
From faf4887616042eafa7e890a0e430b01a08e309c8 Mon Sep 17 00:00:00 2001
From: Brooty Johnson <83629348+Br00ty@users.noreply.github.com>
Date: Sat, 9 Sep 2023 21:33:57 -0400
Subject: [PATCH 09/78] DS3: add more options to slot_data for autotracking
(#2148)
---
worlds/dark_souls_3/__init__.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py
index faf6c2812177..5d845e3ccce1 100644
--- a/worlds/dark_souls_3/__init__.py
+++ b/worlds/dark_souls_3/__init__.py
@@ -502,6 +502,15 @@ def fill_slot_data(self) -> Dict[str, object]:
slot_data = {
"options": {
+ "enable_weapon_locations": self.multiworld.enable_weapon_locations[self.player].value,
+ "enable_shield_locations": self.multiworld.enable_shield_locations[self.player].value,
+ "enable_armor_locations": self.multiworld.enable_armor_locations[self.player].value,
+ "enable_ring_locations": self.multiworld.enable_ring_locations[self.player].value,
+ "enable_spell_locations": self.multiworld.enable_spell_locations[self.player].value,
+ "enable_key_locations": self.multiworld.enable_key_locations[self.player].value,
+ "enable_boss_locations": self.multiworld.enable_boss_locations[self.player].value,
+ "enable_npc_locations": self.multiworld.enable_npc_locations[self.player].value,
+ "enable_misc_locations": self.multiworld.enable_misc_locations[self.player].value,
"auto_equip": self.multiworld.auto_equip[self.player].value,
"lock_equip": self.multiworld.lock_equip[self.player].value,
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value,
From bf685dc85045286b9984bec24401b70246286b09 Mon Sep 17 00:00:00 2001
From: Bicoloursnake <60069210+Bicoloursnake@users.noreply.github.com>
Date: Sat, 9 Sep 2023 21:51:12 -0400
Subject: [PATCH 10/78] Docs, SM64, SC2: Minor Documentation Updates (#2008)
* Update SC2 setup guide
Removed a sentence that made sense when I included sudo in the command in the previous sentence, but does not make sense otherwise.
* Update en_Super Mario 64.md
It turns out castle has a lowercase l in it.
---
worlds/sc2wol/docs/setup_en.md | 2 +-
worlds/sm64ex/docs/en_Super Mario 64.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/worlds/sc2wol/docs/setup_en.md b/worlds/sc2wol/docs/setup_en.md
index 13c7cb91e3aa..419f98a7330d 100644
--- a/worlds/sc2wol/docs/setup_en.md
+++ b/worlds/sc2wol/docs/setup_en.md
@@ -49,7 +49,7 @@ specific description of what's going wrong and attach your log file to your mess
## Running in macOS
-To run StarCraft 2 through Archipelago in macOS, you will need to run the client via source as seen here: [macOS Guide](https://archipelago.gg/tutorial/Archipelago/mac/en). Note: when running the client, you will need to run the command `python3 Starcraft2Client.py`. This is done to make sure that `/download_data` works correctly.
+To run StarCraft 2 through Archipelago in macOS, you will need to run the client via source as seen here: [macOS Guide](https://archipelago.gg/tutorial/Archipelago/mac/en). Note: when running the client, you will need to run the command `python3 Starcraft2Client.py`.
## Running in Linux
diff --git a/worlds/sm64ex/docs/en_Super Mario 64.md b/worlds/sm64ex/docs/en_Super Mario 64.md
index 4586369e5ec7..def6e2a37536 100644
--- a/worlds/sm64ex/docs/en_Super Mario 64.md
+++ b/worlds/sm64ex/docs/en_Super Mario 64.md
@@ -14,7 +14,7 @@ as different Items from within SM64.
As in most Mario Games, save the Princess!
## Which items can be in another player's world?
-Any of the 120 Stars, and the two Caste Keys. Additionally, Cap Switches are also considered "Items" and the "!"-Boxes will only be active
+Any of the 120 Stars, and the two Castle Keys. Additionally, Cap Switches are also considered "Items" and the "!"-Boxes will only be active
when someone collects the corresponding Cap Switch Item.
## What does another world's item look like in SM64EX?
@@ -25,4 +25,4 @@ and who will receive it.
When you receive an Item, a Message will pop up to inform you where you received the Item from,
and which one it is.
-NOTE: The Secret Star count in the Menu is broken.
\ No newline at end of file
+NOTE: The Secret Star count in the Menu is broken.
From 2bdb1b2029c76258b9101ecede4f585f22542888 Mon Sep 17 00:00:00 2001
From: Bryce Wilson
Date: Sat, 9 Sep 2023 19:41:52 -0700
Subject: [PATCH 11/78] DS3: Update game page (#2163)
* DS3: Update game page
* DS3: Split long sentence in game page docs
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
* DS3: Minor word change
---------
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
---
worlds/dark_souls_3/docs/en_Dark Souls III.md | 21 +++++++++++--------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/worlds/dark_souls_3/docs/en_Dark Souls III.md b/worlds/dark_souls_3/docs/en_Dark Souls III.md
index 1b55593bad98..e844925df1ea 100644
--- a/worlds/dark_souls_3/docs/en_Dark Souls III.md
+++ b/worlds/dark_souls_3/docs/en_Dark Souls III.md
@@ -7,19 +7,22 @@ config file.
## What does randomization do to this game?
-In Dark Souls III, all unique items you can earn from a static corpse, chest, or the death of a Boss/NPC are
-randomized.
-An option is available from the settings page to also randomize upgrade materials, Estus shards, and consumables.
-Another option is available to randomize the level of the generated weapons (from +0 to +10/+5).
+Items that can be picked up from static corpses, taken from chests, or earned from defeating enemies or NPCs can be
+randomized. Common pickups like titanite shards or firebombs can be randomized as "progressive" items. That is, the
+location "Titanite Shard #5" is the fifth titanite shard you pick up, no matter where it was from. This is also what
+happens when you randomize Estus Shards and Undead Bone Shards.
-To beat the game, you need to collect the 4 "Cinders of a Lord" randomized in the multiworld
-and kill the final boss "Soul of Cinder."
+It's also possible to randomize the upgrade level of weapons and shields as well as their infusions (if they can have
+one). Additionally, there are settings that can make the randomized experience more convenient or more interesting, such as
+removing weapon requirements or auto-equipping whatever equipment you most recently received.
+
+The goal is to find the four "Cinders of a Lord" items randomized into the multiworld and defeat the Soul of Cinder.
## What Dark Souls III items can appear in other players' worlds?
-Every unique item from Dark Souls III can appear in other player's worlds, such as a piece of armor, upgraded weapons,
-or key items.
+Practically anything can be found in other worlds including pieces of armor, upgraded weapons, key items, consumables,
+spells, upgrade materials, etc...
## What does another world's item look like in Dark Souls III?
-In Dark Souls III, items which need to be sent to other worlds appear as a Prism Stone.
+In Dark Souls III, items which are sent to other worlds appear as Prism Stones.
From 72b44be41c5b35205fcb2d0c40085a9a91c7463f Mon Sep 17 00:00:00 2001
From: Fabian Dill
Date: Sun, 10 Sep 2023 07:19:40 +0200
Subject: [PATCH 12/78] SNIClient: fix /snes command if tree (#791)
---
SNIClient.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/SNIClient.py b/SNIClient.py
index 66d0b2ca9c22..0909c61382b6 100644
--- a/SNIClient.py
+++ b/SNIClient.py
@@ -68,12 +68,11 @@ def connect_to_snes(self, snes_options: str = "") -> bool:
options = snes_options.split()
num_options = len(options)
- if num_options > 0:
- snes_device_number = int(options[0])
-
if num_options > 1:
snes_address = options[0]
snes_device_number = int(options[1])
+ elif num_options > 0:
+ snes_device_number = int(options[0])
self.ctx.snes_reconnect_address = None
if self.ctx.snes_connect_task:
From e01eb4e00c6e99840f196a6b62535e7fbd95b1fa Mon Sep 17 00:00:00 2001
From: Doug Hoskisson
Date: Sun, 10 Sep 2023 14:03:22 -0700
Subject: [PATCH 13/78] Zillion: webhost config fix (#2145)
---
worlds/zillion/__init__.py | 4 ++--
worlds/zillion/config.py | 17 +++++++++++++++++
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py
index 2ea7ffdea5cd..7c927c10eb92 100644
--- a/worlds/zillion/__init__.py
+++ b/worlds/zillion/__init__.py
@@ -10,6 +10,7 @@
from BaseClasses import ItemClassification, LocationProgressType, \
MultiWorld, Item, CollectionState, Entrance, Tutorial
+from .config import detect_test
from .logic import cs_to_zz_locs
from .region import ZillionLocation, ZillionRegion
from .options import ZillionStartChar, zillion_options, validate
@@ -145,8 +146,7 @@ def generate_early(self) -> None:
self._item_counts = item_counts
- import __main__
- rom_dir_name = "" if "test" in __main__.__file__ else os.path.dirname(get_base_rom_path())
+ rom_dir_name = "" if detect_test() else os.path.dirname(get_base_rom_path())
with redirect_stdout(self.lsi): # type: ignore
self.zz_system.make_patcher(rom_dir_name)
self.zz_system.make_randomizer(zz_op)
diff --git a/worlds/zillion/config.py b/worlds/zillion/config.py
index ca02f9a99f41..db61d0c45347 100644
--- a/worlds/zillion/config.py
+++ b/worlds/zillion/config.py
@@ -2,3 +2,20 @@
base_id = 8675309
zillion_map = os.path.join(os.path.dirname(__file__), "empty-zillion-map-row-col-labels-281.png")
+
+
+def detect_test() -> bool:
+ """
+ Parts of generation that are in unit tests need the rom.
+ This is to detect whether we are running unit tests
+ so we can work around the need for the rom.
+ """
+ import __main__
+ try:
+ if "test" in __main__.__file__:
+ return True
+ except AttributeError:
+ # In some environments, __main__ doesn't have __file__
+ # We'll assume that's not unit tests.
+ pass
+ return False
From fbd64651e48cd1bd41436eec0fc8cf1726f308b2 Mon Sep 17 00:00:00 2001
From: Remy Jette
Date: Sun, 10 Sep 2023 14:24:09 -0700
Subject: [PATCH 14/78] Pokemon RB: Fix typo on the game info page (#2142)
Thanks Shiny for pointing it out https://discord.com/channels/731205301247803413/1043592720603693167/1147300361883893790
---
worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md
index 535054182707..daefd6b2f7eb 100644
--- a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md
+++ b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md
@@ -20,7 +20,7 @@ Many baseline changes are made to the game, including:
* PC item storage increased to 64 slots (up from 50).
* You can hold B to run (or bike extra fast!).
* You can hold select while talking to a trainer to re-battle them.
-* You can select "Pallet Warp" below the "Continue" option to warp to Pallet Towna s you load your save.
+* You can select "Pallet Warp" below the "Continue" option to warp to Pallet Town as you load your save.
* Mew can be encountered at the S.S. Anne dock truck. This can be randomized depending on your settings.
* The S.S. Anne will never depart.
* Seafoam Islands entrances are swapped. This means you need Strength to travel through from Cinnabar Island to Fuchsia
From 8649b157873b26ea9bac37b8317529f7feb73a4c Mon Sep 17 00:00:00 2001
From: Trevor L <80716066+TRPG0@users.noreply.github.com>
Date: Sun, 10 Sep 2023 15:24:33 -0600
Subject: [PATCH 15/78] Blasphemous: Add missing logic (#2165)
* Blasphemous: Set rules for events later
* Blasphemous: More misc logic fixes
* Update worlds/blasphemous/Rules.py
Co-authored-by: Fabian Dill
* Update worlds/blasphemous/Rules.py
Co-authored-by: Fabian Dill
* Blasphemous: Some cleanup
* Blasphemous: Add missing logic
---------
Co-authored-by: Fabian Dill
---
worlds/blasphemous/Rules.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/worlds/blasphemous/Rules.py b/worlds/blasphemous/Rules.py
index 2ef36c575e63..277a1b15dccb 100644
--- a/worlds/blasphemous/Rules.py
+++ b/worlds/blasphemous/Rules.py
@@ -2451,6 +2451,8 @@ def rules(blasphemousworld):
# Items
set_rule(world.get_location("PotSS: 4th meeting with Redento", player),
lambda state: redento(state, blasphemousworld, player, 4))
+ set_rule(world.get_location("PotSS: Amanecida of the Chiselled Steel", player),
+ lambda state: can_beat_boss(state, "Patio", logic, player))
# No doors
From 6c844750aede98811c834e0e407d99ff7348dbf4 Mon Sep 17 00:00:00 2001
From: blastron
Date: Sun, 10 Sep 2023 14:29:42 -0700
Subject: [PATCH 16/78] Witness: fix items being modified by other slots
(#2161)
---
worlds/witness/items.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/worlds/witness/items.py b/worlds/witness/items.py
index 7e083534c9c6..82c79047f3fb 100644
--- a/worlds/witness/items.py
+++ b/worlds/witness/items.py
@@ -152,7 +152,7 @@ def get_mandatory_items(self) -> Dict[str, int]:
"""
Returns the list of items that must be in the pool for the game to successfully generate.
"""
- return self._mandatory_items
+ return self._mandatory_items.copy()
def get_filler_items(self, quantity: int) -> Dict[str, int]:
"""
From 5eef7a34d3de1b1a82607d7460abb3bd0119e828 Mon Sep 17 00:00:00 2001
From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
Date: Sun, 10 Sep 2023 23:34:20 +0200
Subject: [PATCH 17/78] The Witness: Fix Expert Tutorial Gate Close (#2164)
---
worlds/witness/WitnessLogicExpert.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/WitnessLogicExpert.txt
index a55c22441e28..b373c7417c24 100644
--- a/worlds/witness/WitnessLogicExpert.txt
+++ b/worlds/witness/WitnessLogicExpert.txt
@@ -14,7 +14,7 @@ Tutorial (Tutorial) - Outside Tutorial - True:
158005 - 0x0A3B5 (Back Left) - True - Dots & Full Dots
158006 - 0x0A3B2 (Back Right) - True - Dots & Full Dots
158007 - 0x03629 (Gate Open) - 0x002C2 - Symmetry & Dots
-158008 - 0x03505 (Gate Close) - 0x2FAF6 & 0x03629 - False
+158008 - 0x03505 (Gate Close) - 0x2FAF6 & 0x03629 - True
158009 - 0x0C335 (Pillar) - True - Triangles
158010 - 0x0C373 (Patio Floor) - 0x0C335 - Dots
159512 - 0x33530 (Cloud EP) - True - True
From 0e21a3e121f020bbd2b44d71de199dfd8d07cfde Mon Sep 17 00:00:00 2001
From: Alchav <59858495+Alchav@users.noreply.github.com>
Date: Sun, 10 Sep 2023 17:38:56 -0400
Subject: [PATCH 18/78] =?UTF-8?q?Pok=C3=A9mon=20R/B:=20Fix=20broken=20opti?=
=?UTF-8?q?ons=20(#2162)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
worlds/pokemon_rb/__init__.py | 2 +-
worlds/pokemon_rb/logic.py | 2 +-
worlds/pokemon_rb/rom.py | 25 +++++++++++++------------
3 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py
index 64f62adddb63..3d6e463251ec 100644
--- a/worlds/pokemon_rb/__init__.py
+++ b/worlds/pokemon_rb/__init__.py
@@ -138,7 +138,7 @@ def encode_name(name, t):
if self.multiworld.key_items_only[self.player]:
self.multiworld.trainersanity[self.player] = self.multiworld.trainersanity[self.player].from_text("off")
- self.multiworld.dexsanity[self.player] = self.multiworld.dexsanity[self.player].from_text("false")
+ self.multiworld.dexsanity[self.player].value = 0
self.multiworld.randomize_hidden_items[self.player] = \
self.multiworld.randomize_hidden_items[self.player].from_text("off")
diff --git a/worlds/pokemon_rb/logic.py b/worlds/pokemon_rb/logic.py
index 87398c7267fe..cbe28e0ddb47 100644
--- a/worlds/pokemon_rb/logic.py
+++ b/worlds/pokemon_rb/logic.py
@@ -53,7 +53,7 @@ def has_key_items(state, count, player):
"Hideout Key", "Card Key 2F", "Card Key 3F", "Card Key 4F", "Card Key 5F",
"Card Key 6F", "Card Key 7F", "Card Key 8F", "Card Key 9F", "Card Key 10F",
"Card Key 11F", "Exp. All", "Fire Stone", "Thunder Stone", "Water Stone",
- "Leaf Stone"] if state.has(item, player)])
+ "Leaf Stone", "Moon Stone"] if state.has(item, player)])
+ min(state.count("Progressive Card Key", player), 10))
return key_items >= count
diff --git a/worlds/pokemon_rb/rom.py b/worlds/pokemon_rb/rom.py
index 0757d3343510..4b191d91765b 100644
--- a/worlds/pokemon_rb/rom.py
+++ b/worlds/pokemon_rb/rom.py
@@ -238,18 +238,19 @@ def generate_output(self, output_directory: str):
data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i]
data[address + 1] = map_ids[connected_map_name]
- for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM",
- "Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM",
- "Fuchsia Gym - Koga TM", "Saffron Gym - Sabrina TM",
- "Cinnabar Gym - Blaine TM", "Viridian Gym - Giovanni TM")):
- item_name = self.multiworld.get_location(gym_leader, self.player).item.name
- if item_name.startswith("TM"):
- try:
- tm = int(item_name[2:4])
- move = poke_data.moves[self.local_tms[tm - 1]]["id"]
- data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move
- except KeyError:
- pass
+ if not self.multiworld.key_items_only[self.player]:
+ for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM",
+ "Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM",
+ "Fuchsia Gym - Koga TM", "Saffron Gym - Sabrina TM",
+ "Cinnabar Gym - Blaine TM", "Viridian Gym - Giovanni TM")):
+ item_name = self.multiworld.get_location(gym_leader, self.player).item.name
+ if item_name.startswith("TM"):
+ try:
+ tm = int(item_name[2:4])
+ move = poke_data.moves[self.local_tms[tm - 1]]["id"]
+ data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move
+ except KeyError:
+ pass
def set_trade_mon(address, loc):
mon = self.multiworld.get_location(loc, self.player).item.name
From 3e95ccd06c8955762c20b58555961244fbc09ffe Mon Sep 17 00:00:00 2001
From: Trevor L <80716066+TRPG0@users.noreply.github.com>
Date: Sun, 10 Sep 2023 16:04:57 -0600
Subject: [PATCH 19/78] Blasphemous: Fixed Amanecidas not requiring Petrified
Bell (#2166)
---
worlds/blasphemous/Rules.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/worlds/blasphemous/Rules.py b/worlds/blasphemous/Rules.py
index 277a1b15dccb..248ff645bc12 100644
--- a/worlds/blasphemous/Rules.py
+++ b/worlds/blasphemous/Rules.py
@@ -367,25 +367,25 @@ def has_boss_strength(name: str) -> bool:
elif boss == "Graveyard":
return (
has_boss_strength("amanecida")
- and state.has_all({"D01BZ07S01[Santos]", "D02Z03S23[E]", "D02Z02S14[W]", "Wall Climb Ability"}, player)
+ and state.has_all({"D01Z06S01[Santos]", "D02Z03S23[E]", "D02Z02S14[W]", "Wall Climb Ability"}, player)
)
elif boss == "Jondo":
return (
has_boss_strength("amanecida")
- and state.has("D01BZ07S01[Santos]", player)
+ and state.has("D01Z06S01[Santos]", player)
and state.has_any({"D20Z01S05[W]", "D20Z01S05[E]"}, player)
and state.has_any({"D03Z01S03[W]", "D03Z01S03[SW]"}, player)
)
elif boss == "Patio":
return (
has_boss_strength("amanecida")
- and state.has_all({"D01BZ07S01[Santos]", "D06Z01S18[E]"}, player)
+ and state.has_all({"D01Z06S01[Santos]", "D06Z01S18[E]"}, player)
and state.has_any({"D04Z01S04[W]", "D04Z01S04[E]", "D04Z01S04[Cherubs]"}, player)
)
elif boss == "Wall":
return (
has_boss_strength("amanecida")
- and state.has_all({"D01BZ07S01[Santos]", "D09BZ01S01[Cell24]"}, player)
+ and state.has_all({"D01Z06S01[Santos]", "D09BZ01S01[Cell24]"}, player)
and state.has_any({"D09Z01S01[W]", "D09Z01S01[E]"}, player)
)
elif boss == "Hall":
From 3d9837678c9a4989fa88da1f97109995e07e0812 Mon Sep 17 00:00:00 2001
From: Rob B
Date: Sun, 10 Sep 2023 17:13:39 -0500
Subject: [PATCH 20/78] Factorio: better Technology Tree Information
description (#2121)
* Fix typo in Factorio options tooltip
* Fix typo, add details
* Apply code review suggestion
It doesn't let me apply more than one change to the same line in a batch.
Co-authored-by: Scipio Wright
* Apply code review suggestion from @nicholassaylor
It doesn't let me apply more than one change to the same line in a batch.
---------
Co-authored-by: Scipio Wright
---
worlds/factorio/Options.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py
index 0331c2d013ea..2b579658fc7d 100644
--- a/worlds/factorio/Options.py
+++ b/worlds/factorio/Options.py
@@ -146,8 +146,8 @@ class TechTreeLayout(Choice):
class TechTreeInformation(Choice):
"""How much information should be displayed in the tech tree.
- None: No indication what a research unlocks
- Advancement: Indicators which researches unlock items that are considered logical advancements
+ None: No indication of what a research unlocks.
+ Advancement: Indicates if a research unlocks an item that is considered logical advancement, but not who it is for.
Full: Labels with exact names and recipients of unlocked items; all researches are prefilled into the !hint command.
"""
display_name = "Technology Tree Information"
From 57c13ff2732691c0a44b34e69742cfbdc2ad38f8 Mon Sep 17 00:00:00 2001
From: Remy Jette
Date: Mon, 11 Sep 2023 13:57:14 -0700
Subject: [PATCH 21/78] WebHost: Support multi-select during check/generate
file upload (#2138)
* Support multi-select during check/generate file upload
This will allow the user to select multiple YAML files via Shift-Click
or Control-Click in their browser when generating a game via the site
instead of having to zip them locally first.
* Update generate.html: File -> File(s)
* Change check.html button text to "Upload File(s)" to match generate.html
---
WebHostLib/check.py | 53 ++++++++++++++++--------------
WebHostLib/generate.py | 4 +--
WebHostLib/templates/check.html | 4 +--
WebHostLib/templates/generate.html | 4 +--
4 files changed, 34 insertions(+), 31 deletions(-)
diff --git a/WebHostLib/check.py b/WebHostLib/check.py
index 0c1e090dbe47..c5dfd9f55693 100644
--- a/WebHostLib/check.py
+++ b/WebHostLib/check.py
@@ -24,8 +24,8 @@ def check():
if 'file' not in request.files:
flash('No file part')
else:
- file = request.files['file']
- options = get_yaml_data(file)
+ files = request.files.getlist('file')
+ options = get_yaml_data(files)
if isinstance(options, str):
flash(options)
else:
@@ -39,30 +39,33 @@ def mysterycheck():
return redirect(url_for("check"), 301)
-def get_yaml_data(file) -> Union[Dict[str, str], str, Markup]:
+def get_yaml_data(files) -> Union[Dict[str, str], str, Markup]:
options = {}
- # if user does not select file, browser also
- # submit an empty part without filename
- if file.filename == '':
- return 'No selected file'
- elif file and allowed_file(file.filename):
- if file.filename.endswith(".zip"):
-
- with zipfile.ZipFile(file, 'r') as zfile:
- infolist = zfile.infolist()
-
- if any(file.filename.endswith(".archipelago") for file in infolist):
- return Markup("Error: Your .zip file contains an .archipelago file. "
- 'Did you mean to host a game?')
-
- for file in infolist:
- if file.filename.endswith(banned_zip_contents):
- return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \
- "Your file was deleted."
- elif file.filename.endswith((".yaml", ".json", ".yml", ".txt")):
- options[file.filename] = zfile.open(file, "r").read()
- else:
- options = {file.filename: file.read()}
+ for file in files:
+ # if user does not select file, browser also
+ # submit an empty part without filename
+ if file.filename == '':
+ return 'No selected file'
+ elif file.filename in options:
+ return f'Conflicting files named {file.filename} submitted'
+ elif file and allowed_file(file.filename):
+ if file.filename.endswith(".zip"):
+
+ with zipfile.ZipFile(file, 'r') as zfile:
+ infolist = zfile.infolist()
+
+ if any(file.filename.endswith(".archipelago") for file in infolist):
+ return Markup("Error: Your .zip file contains an .archipelago file. "
+ 'Did you mean to host a game?')
+
+ for file in infolist:
+ if file.filename.endswith(banned_zip_contents):
+ return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \
+ "Your file was deleted."
+ elif file.filename.endswith((".yaml", ".json", ".yml", ".txt")):
+ options[file.filename] = zfile.open(file, "r").read()
+ else:
+ options[file.filename] = file.read()
if not options:
return "Did not find a .yaml file to process."
return options
diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py
index 91d7594a1f23..ddcc5ffb6c7b 100644
--- a/WebHostLib/generate.py
+++ b/WebHostLib/generate.py
@@ -64,8 +64,8 @@ def generate(race=False):
if 'file' not in request.files:
flash('No file part')
else:
- file = request.files['file']
- options = get_yaml_data(file)
+ files = request.files.getlist('file')
+ options = get_yaml_data(files)
if isinstance(options, str):
flash(options)
else:
diff --git a/WebHostLib/templates/check.html b/WebHostLib/templates/check.html
index 04b51340b513..8a3da7db472a 100644
--- a/WebHostLib/templates/check.html
+++ b/WebHostLib/templates/check.html
@@ -17,9 +17,9 @@