mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-18 21:38:13 -07:00
Compare commits
29 Commits
revert-401
...
core_fille
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
775f56036c | ||
|
|
39342ad5d5 | ||
|
|
ce09144261 | ||
|
|
a29ba4a6c4 | ||
|
|
fe06fe075e | ||
|
|
de58cb03da | ||
|
|
3204680662 | ||
|
|
07e896508c | ||
|
|
2d3faea713 | ||
|
|
7c89a83d19 | ||
|
|
16f8b41cb9 | ||
|
|
7d506990f5 | ||
|
|
aadcb4c903 | ||
|
|
daf94fcdb2 | ||
|
|
1cef659b78 | ||
|
|
25381ef2c2 | ||
|
|
5927926314 | ||
|
|
2a11d9fec3 | ||
|
|
82c44aaa22 | ||
|
|
a7b483e4b7 | ||
|
|
917335ec54 | ||
|
|
6e59ee2926 | ||
|
|
3c9270d802 | ||
|
|
c4bbcf9890 | ||
|
|
8dbecf3d57 | ||
|
|
0de1369ec5 | ||
|
|
fa95ae4b24 | ||
|
|
2065246186 | ||
|
|
ca1b3df45b |
@@ -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
13
Fill.py
@@ -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
|
||||
|
||||
@@ -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":
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
|
||||
3
Main.py
3
Main.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
-
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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":
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user