Compare commits

...

29 Commits

Author SHA1 Message Date
Fabian Dill
775f56036c fix type 2025-01-09 17:02:36 +01:00
Fabian Dill
39342ad5d5 review concerns 2025-01-09 17:01:19 +01:00
Fabian Dill
ce09144261 Core: add creation reason to filler 2025-01-08 00:30:03 +01:00
Aaron Wagener
a29ba4a6c4 The Messenger: reduce strictness of output path check (#4442) 2025-01-07 23:11:26 +01:00
Fabian Dill
fe06fe075e Factorio: add fluid mining technology to logic requirements (#4385) 2025-01-07 23:06:48 +01:00
qwint
de58cb03da Core: Pickle hints by value (#4441) 2025-01-07 22:24:19 +01:00
TheLX5
3204680662 SNIClient: Let clients based on SNIClient monitor packages via on_package method (#3093) 2025-01-07 00:10:23 +01:00
shananas
07e896508c KH2: Doc Updates (#4434) 2025-01-06 14:02:04 -05:00
Scipio Wright
2d3faea713 Core: Include unfilled locations in error when there are not enough locations for progression items (#4285)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2025-01-06 09:52:33 -05:00
eudaimonistic
7c89a83d19 Docs: Clarify !alias commands in commands_en.md (#4426)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
2025-01-06 09:42:18 -05:00
qwint
16f8b41cb9 Core: add docstrings for launcher components (#4148)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2025-01-06 09:35:37 -05:00
qwint
7d506990f5 HK: add location counts to option descriptions (#4083) 2025-01-06 09:35:12 -05:00
qwint
aadcb4c903 HK: use rich_text_options_doc to make webhost formatting look better (#4079) 2025-01-06 09:21:44 -05:00
coveleski
daf94fcdb2 Pokemon RB: Fixing misnamed locations (#4404) 2025-01-04 08:27:41 -05:00
Kory Dondzila
1cef659b78 Shivers: Fix spelling error in naming (#4425) 2025-01-04 07:42:34 -05:00
Scipio Wright
25381ef2c2 Core: Make the error for a missing option display the player name (#4430) 2025-01-04 07:29:30 -05:00
Mysteryem
5927926314 Blasphemous: Fix starting_location: random affecting all Blasphemous worlds (#4428)
Option resolution for the `StartingLocation` option (the only
`ChoiceIsRandom` subclass) was writing to the `randomized` attribute on
the class instead of on the instance, meaning that
`self.options.starting_location.randomized` would be `True` for all
Blasphemous players in the multiworld if any one of the players set
their `StartingLocation` option to `"random"`.

This patch fixes the issue by writing to the `randomized` attribute on
the new instance instead of on the class.
2025-01-03 07:03:30 -05:00
CaitSith2
2a11d9fec3 try again to award the starting items post cutscene if needed. (#4408) 2025-01-02 19:45:32 -08:00
Nicholas Saylor
82c44aaa22 FFMQ: Fix encoding issue with Game Page (#4299) 2025-01-02 22:03:07 -05:00
Kory Dondzila
a7b483e4b7 Shivers: Adds ixupi captures priority option (#4403) 2025-01-02 10:12:00 -05:00
Fabian Dill
917335ec54 Core: it's 2025 (#4417) 2025-01-01 02:02:18 +01:00
Mysteryem
6e59ee2926 Zork Grand Inquisitor: Precollect Start with Hotspot Items in deterministic order (#4412) 2024-12-31 09:16:29 -05:00
Mysteryem
3c9270d802 FFMQ: Create itempool in deterministic order (#4413) 2024-12-31 09:02:02 -05:00
Mysteryem
c4bbcf9890 TUNIC: Add relics and abilities to the item pool in deterministic order (#4411) 2024-12-30 23:57:09 -05:00
NewSoupVi
8dbecf3d57 The Witness: Make location order in the spoiler log deterministic (#3895)
* Fix location order

* Update worlds/witness/data/static_logic.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-12-30 00:50:39 +01:00
Fabian Dill
0de1369ec5 Factorio: hide hidden vanilla techs in factoriopedia too (#4332) 2024-12-29 11:56:41 -08:00
Fabian Dill
fa95ae4b24 Factorio: require version that fixes a randomizer exploit (#4391) 2024-12-29 11:55:40 -08:00
CaitSith2
2065246186 Factorio: Make it possible to use rocket part in blueprint parameterization. (#4396)
This allows for example, making a blueprint of your rocket silo with requester chests specifying a request for the 2-8 rocket part ingredients needed to build the rocket.
2024-12-29 20:13:34 +01:00
Kory Dondzila
ca1b3df45b Shivers: Follow on PR to cleanup options #4401 2024-12-27 23:38:01 +01:00
34 changed files with 260 additions and 138 deletions

View File

@@ -373,7 +373,8 @@ class MultiWorld():
items_to_add.append(AutoWorld.call_single(self, "create_item", item_player,
group["replacement_items"][player]))
else:
items_to_add.append(AutoWorld.call_single(self, "create_filler", item_player))
items_to_add.append(AutoWorld.call_single(self, "create_filler", item_player,
AutoWorld.FillerReason.item_link))
self.random.shuffle(items_to_add)
self.itempool.extend(items_to_add[:itemcount - len(self.itempool)])

13
Fill.py
View File

@@ -7,7 +7,7 @@ from collections import Counter, deque
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld
from Options import Accessibility
from worlds.AutoWorld import call_all
from worlds.AutoWorld import call_all, FillerReason
from worlds.generic.Rules import add_item_rule
@@ -316,7 +316,7 @@ def remaining_fill(multiworld: MultiWorld,
for item in unplaced_items:
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
multiworld.push_precollected(item)
last_batch.append(multiworld.worlds[item.player].create_filler())
last_batch.append(multiworld.worlds[item.player].create_filler(FillerReason.panic_fill))
remaining_fill(multiworld, locations, unplaced_items, name + " Start Inventory Retry")
else:
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
@@ -521,7 +521,7 @@ def distribute_items_restrictive(multiworld: MultiWorld,
for item in progitempool:
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
multiworld.push_precollected(item)
filleritempool.append(multiworld.worlds[item.player].create_filler())
filleritempool.append(multiworld.worlds[item.player].create_filler(FillerReason.panic_fill))
logging.warning(f"{len(progitempool)} items moved to start inventory,"
f" due to failure in Progression fill step.")
progitempool[:] = []
@@ -531,7 +531,8 @@ def distribute_items_restrictive(multiworld: MultiWorld,
if progitempool:
raise FillError(
f"Not enough locations for progression items. "
f"There are {len(progitempool)} more progression items than there are available locations.",
f"There are {len(progitempool)} more progression items than there are available locations.\n"
f"Unfilled locations:\n{multiworld.get_unfilled_locations()}.",
multiworld=multiworld,
)
accessibility_corrections(multiworld, multiworld.state, defaultlocations)
@@ -544,7 +545,7 @@ def distribute_items_restrictive(multiworld: MultiWorld,
inaccessible_location_rules(multiworld, multiworld.state, defaultlocations)
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded",
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
move_unplaceable_to_start_inventory=panic_method == "start_inventory")
if excludedlocations:
raise FillError(
@@ -556,7 +557,7 @@ def distribute_items_restrictive(multiworld: MultiWorld,
restitempool = filleritempool + usefulitempool
remaining_fill(multiworld, defaultlocations, restitempool,
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
move_unplaceable_to_start_inventory=panic_method == "start_inventory")
unplaced = restitempool
unfilled = defaultlocations

View File

@@ -500,7 +500,8 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
for option_key in game_weights:
if option_key in {"triggers", *valid_keys}:
continue
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.")
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers "
f"for player {ret.name}.")
if PlandoOptions.items in plando_options:
ret.plando_items = copy.deepcopy(game_weights.get("plando_items", []))
if ret.game == "A Link to the Past":

View File

@@ -1,7 +1,7 @@
MIT License
Copyright (c) 2017 LLCoolDave
Copyright (c) 2022 Berserker66
Copyright (c) 2025 Berserker66
Copyright (c) 2022 CaitSith2
Copyright (c) 2021 LegendaryLinux

View File

@@ -181,7 +181,8 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
logger.warning(f"{player_name} tried to remove items from their pool that don't exist: {unfound_items}")
needed_items = target_per_player[player] - sum(unfound_items.values())
new_itempool += [multiworld.worlds[player].create_filler() for _ in range(needed_items)]
new_itempool += [multiworld.worlds[player].create_filler(AutoWorld.FillerReason.start_inventory_from_pool)
for _ in range(needed_items)]
assert len(multiworld.itempool) == len(new_itempool), "Item Pool amounts should not change."
multiworld.itempool[:] = new_itempool

View File

@@ -10,7 +10,7 @@ import websockets
from Utils import ByValue, Version
class HintStatus(enum.IntEnum):
class HintStatus(ByValue, enum.IntEnum):
HINT_FOUND = 0
HINT_UNSPECIFIED = 1
HINT_NO_PRIORITY = 10

View File

@@ -243,6 +243,9 @@ class SNIContext(CommonContext):
# Once the games handled by SNIClient gets made to be remote items,
# this will no longer be needed.
async_start(self.send_msgs([{"cmd": "LocationScouts", "locations": list(new_locations)}]))
if self.client_handler is not None:
self.client_handler.on_package(self, cmd, args)
def run_gui(self) -> None:
from kvui import GameManager

View File

@@ -1,6 +1,6 @@
{% block footer %}
<footer id="island-footer">
<div id="copyright-notice">Copyright 2024 Archipelago</div>
<div id="copyright-notice">Copyright 2025 Archipelago</div>
<div id="links">
<a href="/sitemap">Site Map</a>
-

View File

@@ -86,3 +86,7 @@ class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister):
async def deathlink_kill_player(self, ctx: SNIContext) -> None:
""" override this with implementation to kill player """
pass
def on_package(self, ctx: SNIContext, cmd: str, args: Dict[str, Any]) -> None:
""" override this with code to handle packages from the server """
pass

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import enum
import hashlib
import logging
import pathlib
@@ -18,6 +19,15 @@ if TYPE_CHECKING:
from . import GamesPackage
from settings import Group
class FillerReason(enum.StrEnum):
undefined = enum.auto()
item_link = enum.auto()
panic_fill = enum.auto()
start_inventory_from_pool = enum.auto()
world = enum.auto()
perf_logger = logging.getLogger("performance")
@@ -527,7 +537,7 @@ class World(metaclass=AutoWorldRegister):
return False
# following methods should not need to be overridden.
def create_filler(self) -> "Item":
def create_filler(self, reason: FillerReason = FillerReason.undefined) -> "Item":
return self.create_item(self.get_filler_item_name())
# convenience methods

View File

@@ -18,16 +18,42 @@ class Type(Enum):
class Component:
"""
A Component represents a process launchable by Archipelago Launcher, either by a User action in the GUI,
by resolving an archipelago://user:pass@host:port link from the WebHost, by resolving a patch file's metadata,
or by using a component name arg while running the Launcher in CLI i.e. `ArchipelagoLauncher.exe "Text Client"`
Expected to be appended to LauncherComponents.component list to be used.
"""
display_name: str
"""Used as the GUI button label and the component name in the CLI args"""
type: Type
"""
Enum "Type" classification of component intent, for filtering in the Launcher GUI
If not set in the constructor, it will be inferred by display_name
"""
script_name: Optional[str]
"""Recommended to use func instead; Name of file to run when the component is called"""
frozen_name: Optional[str]
"""Recommended to use func instead; Name of the frozen executable file for this component"""
icon: str # just the name, no suffix
"""Lookup ID for the icon path in LauncherComponents.icon_paths"""
cli: bool
"""Bool to control if the component gets launched in an appropriate Terminal for the OS"""
func: Optional[Callable]
"""
Function that gets called when the component gets launched
Any arg besides the component name arg is passed into the func as well, so handling *args is suggested
"""
file_identifier: Optional[Callable[[str], bool]]
"""
Function that is run against patch file arg to identify which component is appropriate to launch
If the function is an Instance of SuffixIdentifier the suffixes will also be valid for the Open Patch component
"""
game_name: Optional[str]
"""Game name to identify component when handling launch links from WebHost"""
supports_uri: Optional[bool]
"""Bool to identify if a component supports being launched by launch links from WebHost"""
def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None,

View File

@@ -4,14 +4,17 @@ import random
class ChoiceIsRandom(Choice):
randomized: bool = False
randomized: bool
def __init__(self, value: int, randomized: bool = False):
super().__init__(value)
self.randomized = randomized
@classmethod
def from_text(cls, text: str) -> Choice:
text = text.lower()
if text == "random":
cls.randomized = True
return cls(random.choice(list(cls.name_lookup)))
return cls(random.choice(list(cls.name_lookup)), True)
for option_name, value in cls.options.items():
if option_name == text:
return cls(value)

View File

@@ -37,8 +37,8 @@ base_info = {
"description": "Integration client for the Archipelago Randomizer",
"factorio_version": "2.0",
"dependencies": [
"base >= 2.0.15",
"? quality >= 2.0.15",
"base >= 2.0.28",
"? quality >= 2.0.28",
"! space-age",
"? science-not-invited",
"? factory-levels"

View File

@@ -63,17 +63,19 @@ class FactorioElement:
class Technology(FactorioElement): # maybe make subclass of Location?
has_modifier: bool
factorio_id: int
progressive: Tuple[str]
unlocks: Union[Set[str], bool] # bool case is for progressive technologies
modifiers: list[str]
def __init__(self, technology_name: str, factorio_id: int, progressive: Tuple[str] = (),
has_modifier: bool = False, unlocks: Union[Set[str], bool] = None):
modifiers: list[str] = None, unlocks: Union[Set[str], bool] = None):
self.name = technology_name
self.factorio_id = factorio_id
self.progressive = progressive
self.has_modifier = has_modifier
if modifiers is None:
modifiers = []
self.modifiers = modifiers
if unlocks:
self.unlocks = unlocks
else:
@@ -82,6 +84,10 @@ class Technology(FactorioElement): # maybe make subclass of Location?
def __hash__(self):
return self.factorio_id
@property
def has_modifier(self) -> bool:
return bool(self.modifiers)
def get_custom(self, world, allowed_packs: Set[str], player: int) -> CustomTechnology:
return CustomTechnology(self, world, allowed_packs, player)
@@ -191,13 +197,14 @@ class Machine(FactorioElement):
recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source
mining_with_fluid_sources: set[str] = set()
# recipes and technologies can share names in Factorio
for technology_name, data in sorted(techs_future.result().items()):
technology = Technology(
technology_name,
factorio_tech_id,
has_modifier=data["has_modifier"],
modifiers=data.get("modifiers", []),
unlocks=set(data["unlocks"]) - start_unlocked_recipes,
)
factorio_tech_id += 1
@@ -205,7 +212,8 @@ for technology_name, data in sorted(techs_future.result().items()):
technology_table[technology_name] = technology
for recipe_name in technology.unlocks:
recipe_sources.setdefault(recipe_name, set()).add(technology_name)
if "mining-with-fluid" in technology.modifiers:
mining_with_fluid_sources.add(technology_name)
del techs_future
recipes = {}
@@ -221,6 +229,8 @@ for resource_name, resource_data in resources_future.result().items():
"energy": resource_data["mining_time"],
"category": resource_data["category"]
}
if "required_fluid" in resource_data:
recipe_sources.setdefault(f"mining-{resource_name}", set()).update(mining_with_fluid_sources)
del resources_future
for recipe_name, recipe_data in raw_recipes.items():
@@ -431,7 +441,9 @@ for root in sorted_rows:
factorio_tech_id += 1
progressive_technology = Technology(root, factorio_tech_id,
tuple(progressive),
has_modifier=any(technology_table[tech].has_modifier for tech in progressive),
modifiers=sorted(set.union(
*(set(technology_table[tech].modifiers) for tech in progressive)
)),
unlocks=any(technology_table[tech].unlocks for tech in progressive),)
progressive_tech_table[root] = progressive_technology.factorio_id
progressive_technology_table[root] = progressive_technology

View File

@@ -445,6 +445,10 @@ end
script.on_event(defines.events.on_player_main_inventory_changed, update_player_event)
-- Update players when the cutscene is cancelled or finished. (needed for skins_factored)
script.on_event(defines.events.on_cutscene_cancelled, update_player_event)
script.on_event(defines.events.on_cutscene_finished, update_player_event)
function add_samples(force, name, count)
local function add_to_table(t)
if count <= 0 then

View File

@@ -1,6 +1,7 @@
{% from "macros.lua" import dict_to_recipe, variable_to_lua %}
-- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template
require('lib')
data.raw["item"]["rocket-part"].hidden = false
data.raw["rocket-silo"]["rocket-silo"].fluid_boxes = {
{
production_type = "input",
@@ -162,6 +163,7 @@ data.raw["ammo"]["artillery-shell"].stack_size = 10
{# each randomized tech gets set to be invisible, with new nodes added that trigger those #}
{%- for original_tech_name in base_tech_table -%}
technologies["{{ original_tech_name }}"].hidden = true
technologies["{{ original_tech_name }}"].hidden_in_factoriopedia = true
{% endfor %}
{%- for location, item in locations %}
{#- the tech researched by the local player #}

File diff suppressed because one or more lines are too long

View File

@@ -260,7 +260,8 @@ def create_items(self) -> None:
items.append(i)
for item_group in ("Key Items", "Spells", "Armors", "Helms", "Shields", "Accessories", "Weapons"):
for item in self.item_name_groups[item_group]:
# Sort for deterministic order
for item in sorted(self.item_name_groups[item_group]):
add_item(item)
if self.options.brown_boxes == "include":

View File

@@ -1,7 +1,7 @@
# Final Fantasy Mystic Quest
## Game page in other languages:
* [Français](/games/Final%20Fantasy%20Mystic%20Quest/info/fr)
* [Français](/games/Final%20Fantasy%20Mystic%20Quest/info/fr)
## Where is the options page?

View File

@@ -27,6 +27,7 @@ including the exclamation point.
- `!countdown <number of seconds>` Starts a countdown using the given seconds value. Useful for synchronizing starts.
Defaults to 10 seconds if no argument is provided.
- `!alias <alias>` Sets your alias, which allows you to use commands with the alias rather than your provided name.
`!alias` on its own will reset the alias to the player's original name.
- `!admin <command>` Executes a command as if you typed it into the server console. Remote administration must be
enabled.
@@ -65,6 +66,7 @@ including the exclamation point.
argument is provided.
- `/option <option name> <option value>` Set a server option. For a list of options, use the `/options` command.
- `/alias <player name> <alias name>` Assign a player an alias, allowing you to reference the player by the alias in commands.
`!alias <player name>` on its own will reset the alias to the player's original name.
### Collect/Release

View File

@@ -132,7 +132,13 @@ splitter_pattern = re.compile(r'(?<!^)(?=[A-Z])')
for option_name, option_data in pool_options.items():
extra_data = {"__module__": __name__, "items": option_data[0], "locations": option_data[1]}
if option_name in option_docstrings:
extra_data["__doc__"] = option_docstrings[option_name]
if option_name == "RandomizeFocus":
# pool options for focus are just lying
count = 1
else:
count = len([loc for loc in option_data[1] if loc != "Start"])
extra_data["__doc__"] = option_docstrings[option_name] + \
f"\n This option adds approximately {count} location{'s' if count != 1 else ''}."
if option_name in default_on:
option = type(option_name, (DefaultOnToggle,), extra_data)
else:
@@ -213,6 +219,7 @@ class MaximumEssencePrice(MinimumEssencePrice):
class MinimumEggPrice(Range):
"""The minimum rancid egg price in the range of prices that an item should cost from Jiji.
Only takes effect if the EggSlotShops option is greater than 0."""
rich_text_doc = False
display_name = "Minimum Egg Price"
range_start = 1
range_end = 20
@@ -222,6 +229,7 @@ class MinimumEggPrice(Range):
class MaximumEggPrice(MinimumEggPrice):
"""The maximum rancid egg price in the range of prices that an item should cost from Jiji.
Only takes effect if the EggSlotShops option is greater than 0."""
rich_text_doc = False
display_name = "Maximum Egg Price"
default = 10
@@ -265,6 +273,7 @@ class RandomCharmCosts(NamedRange):
Set to -1 or vanilla for vanilla costs.
Set to -2 or shuffle to shuffle around the vanilla costs to different charms."""
rich_text_doc = False
display_name = "Randomize Charm Notch Costs"
range_start = 0
range_end = 240
@@ -437,6 +446,7 @@ class Goal(Choice):
class GrubHuntGoal(NamedRange):
"""The amount of grubs required to finish Grub Hunt.
On 'All' any grubs from item links replacements etc. will be counted"""
rich_text_doc = False
display_name = "Grub Hunt Goal"
range_start = 1
range_end = 46
@@ -446,7 +456,7 @@ class GrubHuntGoal(NamedRange):
class WhitePalace(Choice):
"""
Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be
Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be
required if charms are vanilla.
"""
display_name = "White Palace"
@@ -483,6 +493,7 @@ class DeathLinkShade(Choice):
** Self-death shade behavior is not changed; if a self-death normally creates a shade in vanilla, it will override
your existing shade, if any.
"""
rich_text_doc = False
option_vanilla = 0
option_shadeless = 1
option_shade = 2
@@ -497,6 +508,7 @@ class DeathLinkBreaksFragileCharms(Toggle):
** Self-death fragile charm behavior is not changed; if a self-death normally breaks fragile charms in vanilla, it
will continue to do so.
"""
rich_text_doc = False
display_name = "Deathlink Breaks Fragile Charms"
@@ -515,6 +527,7 @@ class CostSanity(Choice):
These costs can be in Geo (except Grubfather, Seer and Eggshop), Grubs, Charms, Essence and/or Rancid Eggs
"""
rich_text_doc = False
option_off = 0
alias_no = 0
option_on = 1

View File

@@ -134,7 +134,9 @@ shop_cost_types: typing.Dict[str, typing.Tuple[str, ...]] = {
class HKWeb(WebWorld):
setup_en = Tutorial(
rich_text_options_doc = True
setup_en = Tutorial(
"Mod Setup and Use Guide",
"A guide to playing Hollow Knight with Archipelago.",
"English",
@@ -143,7 +145,7 @@ class HKWeb(WebWorld):
["Ijwu"]
)
setup_pt_br = Tutorial(
setup_pt_br = Tutorial(
setup_en.tutorial_name,
setup_en.description,
"Português Brasileiro",

View File

@@ -7,18 +7,21 @@
<h2 style="text-transform:none";>Required Software:</h2>
`Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) or [Steam](https://store.steampowered.com/app/2552430/KINGDOM_HEARTS_HD_1525_ReMIX/)
Kingdom Hearts II Final Mix from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) or [Steam](https://store.steampowered.com/app/2552430/KINGDOM_HEARTS_HD_1525_ReMIX/)
- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/)
1. `Version 3.3.0 or greater OpenKH Mod Manager with Panacea`
2. `Lua Backend from the OpenKH Mod Manager`
3. `Install the mod KH2FM-Mods-Num/GoA-ROM-Edition using OpenKH Mod Manager`
1. Version 3.4.0 or greater OpenKH Mod Manager with Panacea
2. Lua Backend from the OpenKH Mod Manager
3. Install the mod `KH2FM-Mods-Num/GoA-ROM-Edition` using OpenKH Mod Manager
- Needed for Archipelago
1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases)
2. `Install the Archipelago Companion mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`
3. `Install the Archipelago Quality Of Life mod from JaredWeakStrike/AP_QOL using OpenKH Mod Manager`
4. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`
5. `AP Randomizer Seed`
1. [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases)
2. Install the Archipelago Companion mod from `JaredWeakStrike/APCompanion` using OpenKH Mod Manager
3. Install the mod from `KH2FM-Mods-equations19/auto-save` using OpenKH Mod Manager
4. Install the mod from `KH2FM-Mods-equations19/KH2-Lua-Library` using OpenKH Mod Manager
5. AP Randomizer Seed
- Optional Quality of Life Mods for Archipelago
1. Optionally Install the Archipelago Quality Of Life mod from `JaredWeakStrike/AP_QOL` using OpenKH Mod Manager
2. Optionally Install the Quality Of Life mod from `shananas/BearSkip` using OpenKH Mod Manager
<h3 style="text-transform:none";>Required: Archipelago Companion Mod</h3>
@@ -26,15 +29,21 @@ Load this mod just like the <b>GoA ROM</b> you did during the KH2 Rando setup. `
Have this mod second-highest priority below the .zip seed.<br>
This mod is based upon Num's Garden of Assemblege Mod and requires it to work. Without Num this could not be possible.
<h3 style="text-transform:none";>Required: Auto Save Mod</h3>
<h3 style="text-transform:none";>Required: Auto Save Mod and KH2 Lua Library</h3>
Load this mod just like the GoA ROM you did during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` Location doesn't matter, required in case of crashes. See [Best Practices](en#best-practices) on how to load the auto save
Load these mods just like you loaded the GoA ROM mod during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` and `KH2FM-Mods-equations19/KH2-Lua-Library` Location doesn't matter, required in case of crashes. See [Best Practices](en#best-practices) on how to load the auto save
<h3 style="text-transform:none";>Optional QoL Mods: AP QoL and Bear Skip</h3>
`JaredWeakStrike/AP_QOL` Makes the urns minigames much faster, makes Cavern of Remembrance orbs drop significantly more drive orbs for refilling drive/leveling master form, skips the animation when using the bulky vendor RC, skips carpet escape auto scroller in Agrabah 2, and prevents the wardrobe in the Beasts Castle wardrobe push minigame from waking up while being pushed.
`shananas/BearSkip` Skips all minigames in 100 Acre Woods except the Spooky Cave minigame since there are chests in Spooky Cave you can only get during the minigame. For Spooky Cave, Pooh is moved to the other side of the invisible wall that prevents you from using his RC to finish the minigame.
<h3 style="text-transform:none";>Installing A Seed</h3>
When you generate a game you will see a download link for a KH2 .zip seed on the room page. Download the seed then open OpenKH Mod Manager and click the green plus and `Select and install Mod Archive`.<br>
When you generate a game you will see a download link for a KH2 .zip seed on the room page. Download the seed then open OpenKH Mod Manager and click the green plus and "Select and install Mod Archive".<br>
Make sure the seed is on the top of the list (Highest Priority)<br>
After Installing the seed click `Mod Loader -> Build/Build and Run`. Every slot is a unique mod to install and will be needed be repatched for different slots/rooms.
After Installing the seed click "Mod Loader -> Build/Build and Run". Every slot is a unique mod to install and will be needed be repatched for different slots/rooms.
<h2 style="text-transform:none";>Optional Software:</h2>
@@ -48,18 +57,21 @@ After Installing the seed click `Mod Loader -> Build/Build and Run`. Every slot
<h2 style="text-transform:none";>Using the KH2 Client</h2>
Once you have started the game through OpenKH Mod Manager and are on the title screen run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases).<br>
Start the game through OpenKH Mod Manager. If starting a new run, enter the Garden of Assemblage from a new save. If returning to a run, load the save and enter the Garden of Assemblage. Then run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases).<br>
When you successfully connect to the server the client will automatically hook into the game to send/receive checks. <br>
If the client ever loses connection to the game, it will also disconnect from the server and you will need to reconnect.<br>
`Make sure the game is open whenever you try to connect the client to the server otherwise it will immediately disconnect you.`<br>
Make sure the game is open whenever you try to connect the client to the server otherwise it will immediately disconnect you.<br>
Most checks will be sent to you anywhere outside a load or cutscene.<br>
`If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.`
If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.
<h2 style="text-transform:none";>KH2 Client should look like this: </h2>
![image](https://i.imgur.com/qP6CmV8.png)
Enter `The room's port number` into the top box <b> where the x's are</b> and press "Connect". Follow the prompts there and you should be connected
Enter The room's port number into the top box <b> where the x's are</b> and press "Connect". Follow the prompts there and you should be connected
<h2 style="text-transform:none";>Common Pitfalls</h2>
@@ -102,7 +114,7 @@ This pack will handle logic, received items, checked locations and autotabbing f
- Why is my Client giving me a "Cannot Open Process: " error?
- Due to how the client reads kingdom hearts 2 memory some people's computer flags it as a virus. Run the client as admin.
- Why is my HP/MP continuously increasing without stopping?
- You do not have `JaredWeakStrike/APCompanion` set up correctly. Make sure it is above the `GoA ROM Mod` in the mod manager.
- You do not have `JaredWeakStrike/APCompanion` set up correctly. Make sure it is above the GoA ROM Edition Mod in the mod manager.
- Why is my HP/MP continuously increasing without stopping when I have the APCompanion Mod?
- You have a leftover GOA lua script in your `Documents\KINGDOM HEARTS HD 1.5+2.5 ReMIX\scripts\KH2`.
- Why am I missing worlds/portals in the GoA?
@@ -110,9 +122,9 @@ This pack will handle logic, received items, checked locations and autotabbing f
- Why did I not load into the correct visit?
- You need to trigger a cutscene or visit The World That Never Was for it to register that you have received the item.
- What versions of Kingdom Hearts 2 are supported?
- Currently the `only` supported versions are `Epic Games Version 1.0.0.9_WW` and `Steam Build Version 14716933`.
- Currently the only supported versions are Epic Games Version 1.0.0.10_WW and Steam Build Version 15194255.
- Why am I getting wallpapered while going into a world for the first time?
- Your `Lua Backend` was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide.
- Your Lua Backend was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide.
- Why am I not getting magic?
- If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.
- Why did I crash after picking my dream weapon?
@@ -124,7 +136,7 @@ This pack will handle logic, received items, checked locations and autotabbing f
- You will need to get the `JaredWeakStrike/APCompanion` (you can find how to get this if you scroll up)
- Why am I not sending or receiving items?
- Make sure you are connected to the KH2 client and the correct room (for more information scroll up)
- Why should I install the auto save mod at `KH2FM-Mods-equations19/auto-save`?
- Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress.
- Why should I install the auto save mod at `KH2FM-Mods-equations19/auto-save` and `KH2FM-Mods-equations19/KH2-Lua-Library`?
- Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress. Both mods are needed for auto save to work.
- How do I load an auto save?
- To load an auto-save, hold down the Select or your equivalent on your prefered controller while choosing a file. Make sure to hold the button down the whole time.

View File

@@ -381,7 +381,7 @@ class MessengerWorld(World):
return
# the messenger client calls into AP with specific args, so check the out path matches what the client sends
out_path = output_path(multiworld.get_out_file_name_base(1) + ".aptm")
if "The Messenger\\Archipelago\\output" not in out_path:
if "Messenger\\Archipelago\\output" not in out_path:
return
import orjson
data = {

View File

@@ -749,8 +749,8 @@ location_data = [
LocationData("Cinnabar Gym", "Super Nerd 2", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_2_ITEM"], EventFlag(372), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Burglar 2", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_3_ITEM"], EventFlag(371), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 3", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_4_ITEM"], EventFlag(370), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 4", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM"], EventFlag(369), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 5", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM"], EventFlag(368), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Burglar 3", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM"], EventFlag(369), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 4", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM"], EventFlag(368), inclusion=trainersanity),
LocationData("Celadon Prize Corner", "Item Prize 1", "TM23 Dragon Rage", rom_addresses["Prize_Item_A"], EventFlag(0x69a), inclusion=prizesanity),
LocationData("Celadon Prize Corner", "Item Prize 2", "TM15 Hyper Beam", rom_addresses["Prize_Item_B"], EventFlag(0x69B), inclusion=prizesanity),

View File

@@ -13,7 +13,7 @@ class ItemType(enum.Enum):
POT = "pot"
POT_COMPLETE = "pot-complete"
POT_DUPLICATE = "pot-duplicate"
POT_COMPELTE_DUPLICATE = "pot-complete-duplicate"
POT_COMPLETE_DUPLICATE = "pot-complete-duplicate"
KEY = "key"
KEY_OPTIONAL = "key-optional"
ABILITY = "ability"
@@ -117,16 +117,16 @@ item_table = {
"Lightning Pot Top DUPE": ItemData(None, ItemType.POT_DUPLICATE),
"Sand Pot Top DUPE": ItemData(None, ItemType.POT_DUPLICATE),
"Metal Pot Top DUPE": ItemData(None, ItemType.POT_DUPLICATE),
"Water Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Wax Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Ash Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Oil Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Cloth Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Wood Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Crystal Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Lightning Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Sand Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Metal Pot Complete DUPE": ItemData(None, ItemType.POT_COMPELTE_DUPLICATE),
"Water Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Wax Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Ash Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Oil Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Cloth Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Wood Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Crystal Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Lightning Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Sand Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
"Metal Pot Complete DUPE": ItemData(None, ItemType.POT_COMPLETE_DUPLICATE),
# Filler
"Empty": ItemData(None, ItemType.FILLER, ItemClassification.filler),

View File

@@ -1,7 +1,8 @@
from dataclasses import dataclass
from Options import (
Choice, DefaultOnToggle, ItemDict, ItemSet, LocationSet, OptionGroup, PerGameCommonOptions, Range, Toggle,
Choice, DefaultOnToggle, ExcludeLocations, LocalItems, NonLocalItems, OptionGroup, PerGameCommonOptions,
PriorityLocations, Range, StartHints, StartInventory, StartLocationHints, Toggle,
)
from . import ItemType, item_table
from .Constants import location_info
@@ -109,6 +110,13 @@ class FullPots(Choice):
option_mixed = 2
class IxupiCapturesPriority(DefaultOnToggle):
"""
Ixupi captures are set to priority locations. This forces a progression item into these locations if possible.
"""
display_name = "Ixupi Captures are Priority"
class PuzzleCollectBehavior(Choice):
"""
Defines what happens to puzzles on collect.
@@ -129,53 +137,38 @@ valid_item_keys = [name for name, data in item_table.items() if data.type != Ite
valid_location_keys = [name for name in location_info["all_locations"] if name != "Mystery Solved"]
class LocalItems(ItemSet):
"""Forces these items to be in their native world."""
display_name = "Local Items"
rich_text_doc = True
class ShiversLocalItems(LocalItems):
__doc__ = LocalItems.__doc__
valid_keys = valid_item_keys
class NonLocalItems(ItemSet):
"""Forces these items to be outside their native world."""
display_name = "Non-local Items"
rich_text_doc = True
class ShiversNonLocalItems(NonLocalItems):
__doc__ = NonLocalItems.__doc__
valid_keys = valid_item_keys
class StartInventory(ItemDict):
"""Start with these items."""
verify_item_name = True
display_name = "Start Inventory"
rich_text_doc = True
class ShiversStartInventory(StartInventory):
__doc__ = StartInventory.__doc__
valid_keys = valid_item_keys
class StartHints(ItemSet):
"""Start with these item's locations prefilled into the ``!hint`` command."""
display_name = "Start Hints"
rich_text_doc = True
class ShiversStartHints(StartHints):
__doc__ = StartHints.__doc__
valid_keys = valid_item_keys
class StartLocationHints(LocationSet):
"""Start with these locations and their item prefilled into the ``!hint`` command."""
display_name = "Start Location Hints"
rich_text_doc = True
class ShiversStartLocationHints(StartLocationHints):
__doc__ = StartLocationHints.__doc__
valid_keys = valid_location_keys
class ExcludeLocations(LocationSet):
"""Prevent these locations from having an important item."""
display_name = "Excluded Locations"
rich_text_doc = True
class ShiversExcludeLocations(ExcludeLocations):
__doc__ = ExcludeLocations.__doc__
valid_keys = valid_location_keys
class PriorityLocations(LocationSet):
"""Prevent these locations from having an unimportant item."""
display_name = "Priority Locations"
rich_text_doc = True
class ShiversPriorityLocations(PriorityLocations):
__doc__ = PriorityLocations.__doc__
valid_keys = valid_location_keys
@@ -191,24 +184,27 @@ class ShiversOptions(PerGameCommonOptions):
early_lightning: EarlyLightning
location_pot_pieces: LocationPotPieces
full_pots: FullPots
ixupi_captures_priority: IxupiCapturesPriority
puzzle_collect_behavior: PuzzleCollectBehavior
local_items: LocalItems
non_local_items: NonLocalItems
start_inventory: StartInventory
start_hints: StartHints
start_location_hints: StartLocationHints
exclude_locations: ExcludeLocations
priority_locations: PriorityLocations
local_items: ShiversLocalItems
non_local_items: ShiversNonLocalItems
start_inventory: ShiversStartInventory
start_hints: ShiversStartHints
start_location_hints: ShiversStartLocationHints
exclude_locations: ShiversExcludeLocations
priority_locations: ShiversPriorityLocations
shivers_option_groups = [
OptionGroup("Item & Location Options", [
LocalItems,
NonLocalItems,
StartInventory,
StartHints,
StartLocationHints,
ExcludeLocations,
PriorityLocations
], True),
OptionGroup(
"Item & Location Options", [
ShiversLocalItems,
ShiversNonLocalItems,
ShiversStartInventory,
ShiversStartHints,
ShiversStartLocationHints,
ShiversExcludeLocations,
ShiversPriorityLocations
], True,
),
]

View File

@@ -16,7 +16,7 @@ class ShiversWeb(WebWorld):
"English",
"setup_en.md",
"setup/en",
["GodlFire", "Mathx2"]
["GodlFire", "Cynbel_Terreus"]
)]
option_groups = shivers_option_groups
@@ -41,6 +41,20 @@ class ShiversWorld(World):
def generate_early(self):
self.pot_completed_list = []
# Pot piece shuffle location:
if self.options.location_pot_pieces == "own_world":
self.options.local_items.value |= {name for name, data in item_table.items() if
data.type in [ItemType.POT, ItemType.POT_COMPLETE]}
elif self.options.location_pot_pieces == "different_world":
self.options.non_local_items.value |= {name for name, data in item_table.items() if
data.type in [ItemType.POT, ItemType.POT_COMPLETE]}
# Ixupi captures priority locations:
if self.options.ixupi_captures_priority:
self.options.priority_locations.value |= (
{name for name in self.location_names if name.startswith('Ixupi Captured')}
)
def create_item(self, name: str) -> Item:
data = item_table[name]
return ShiversItem(name, data.classification, data.code, self.player)
@@ -194,14 +208,6 @@ class ShiversWorld(World):
["Heal", "Easier Lyre"], weights=[95, 5], k=filler_needed
))
# Pot piece shuffle location:
if self.options.location_pot_pieces == "own_world":
self.options.local_items.value |= {name for name, data in item_table.items() if
data.type in [ItemType.POT, ItemType.POT_COMPLETE]}
elif self.options.location_pot_pieces == "different_world":
self.options.non_local_items.value |= {name for name, data in item_table.items() if
data.type in [ItemType.POT, ItemType.POT_COMPLETE]}
self.multiworld.itempool += item_pool
def pre_fill(self) -> None:
@@ -220,13 +226,13 @@ class ShiversWorld(World):
data.type == ItemType.POT_DUPLICATE]
elif self.options.full_pots == "complete":
storage_items += [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_COMPELTE_DUPLICATE]
data.type == ItemType.POT_COMPLETE_DUPLICATE]
storage_items += [self.create_item("Empty") for _ in range(10)]
else:
pieces = [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_DUPLICATE]
complete = [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_COMPELTE_DUPLICATE]
data.type == ItemType.POT_COMPLETE_DUPLICATE]
for i in range(10):
# Pieces
if self.pot_completed_list[i] == 0:

View File

@@ -7,6 +7,11 @@
- [ScummVM](https://www.scummvm.org/downloads/) version 2.7.0 or later
- [Shivers Randomizer](https://github.com/GodlFire/Shivers-Randomizer-CSharp/releases/latest) Latest release version
## Optional Software
- [PopTracker](https://github.com/black-sliver/PopTracker/releases/)
- [Jax's Shivers PopTracker pack](https://github.com/blazik-barth/Shivers-Tracker/releases/)
## Setup ScummVM for Shivers
### GOG version of Shivers
@@ -57,4 +62,4 @@ validator page: [YAML Validation page](/mysterycheck)
- Every puzzle
- Every puzzle hint/solution
- Every document that is considered a Flashback
- Optionally information plaques.
- Optionally information plaques

View File

@@ -5,7 +5,7 @@ import itertools
from typing import List, Dict, Any, cast
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
from worlds.AutoWorld import World, WebWorld, FillerReason
from . import items
from . import locations
from . import creatures
@@ -142,7 +142,7 @@ class SubnauticaWorld(World):
# resource bundle filler
for _ in range(extras):
item = self.create_filler()
item = self.create_filler(FillerReason.world)
item = cast(SubnauticaItem, item)
pool.append(item)

View File

@@ -284,12 +284,14 @@ class TunicWorld(World):
remove_filler(items_to_create[gold_hexagon])
for hero_relic in item_name_groups["Hero Relics"]:
# Sort for deterministic order
for hero_relic in sorted(item_name_groups["Hero Relics"]):
tunic_items.append(self.create_item(hero_relic, ItemClassification.useful))
items_to_create[hero_relic] = 0
if not self.options.ability_shuffling:
for page in item_name_groups["Abilities"]:
# Sort for deterministic order
for page in sorted(item_name_groups["Abilities"]):
if items_to_create[page] > 0:
tunic_items.append(self.create_item(page, ItemClassification.useful))
items_to_create[page] = 0

View File

@@ -106,6 +106,7 @@ class StaticWitnessLogicObj:
"entityType": location_id,
"locationType": None,
"area": current_area,
"order": len(self.ENTITIES_BY_HEX),
}
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
@@ -186,6 +187,7 @@ class StaticWitnessLogicObj:
"entityType": entity_type,
"locationType": location_type,
"area": current_area,
"order": len(self.ENTITIES_BY_HEX),
}
self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name

View File

@@ -114,7 +114,7 @@ class WitnessPlayerRegions:
if k not in player_logic.UNREACHABLE_REGIONS
}
event_locations_per_region = defaultdict(list)
event_locations_per_region = defaultdict(dict)
for event_location, event_item_and_entity in player_logic.EVENT_ITEM_PAIRS.items():
region = static_witness_logic.ENTITIES_BY_HEX[event_item_and_entity[1]]["region"]
@@ -122,20 +122,33 @@ class WitnessPlayerRegions:
region_name = "Entry"
else:
region_name = region["name"]
event_locations_per_region[region_name].append(event_location)
order = self.reference_logic.ENTITIES_BY_HEX[event_item_and_entity[1]]["order"]
event_locations_per_region[region_name][event_location] = order
for region_name, region in regions_to_create.items():
locations_for_this_region = [
self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] for panel in region["entities"]
if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"]
in self.player_locations.CHECK_LOCATION_TABLE
location_entities_for_this_region = [
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region["entities"]
]
locations_for_this_region = {
entity["checkName"]: entity["order"] for entity in location_entities_for_this_region
if entity["checkName"] in self.player_locations.CHECK_LOCATION_TABLE
}
locations_for_this_region += event_locations_per_region[region_name]
events = event_locations_per_region[region_name]
locations_for_this_region.update(events)
# First, sort by keys.
locations_for_this_region = dict(sorted(locations_for_this_region.items()))
# Then, sort by game order (values)
locations_for_this_region = dict(sorted(
locations_for_this_region.items(),
key=lambda location_name_and_order: location_name_and_order[1]
))
all_locations = all_locations | set(locations_for_this_region)
new_region = create_region(world, region_name, self.player_locations, locations_for_this_region)
new_region = create_region(world, region_name, self.player_locations, list(locations_for_this_region))
regions_by_name[region_name] = new_region

View File

@@ -176,7 +176,7 @@ class ZorkGrandInquisitorWorld(World):
if start_with_hotspot_items:
item: ZorkGrandInquisitorItems
for item in items_with_tag(ZorkGrandInquisitorTags.HOTSPOT):
for item in sorted(items_with_tag(ZorkGrandInquisitorTags.HOTSPOT), key=lambda item: item.name):
self.multiworld.push_precollected(self.create_item(item.value))
def create_item(self, name: str) -> ZorkGrandInquisitorItem: