Merge branch 'main' into logic_bug_fixes

This commit is contained in:
Louis M
2024-07-30 21:17:50 -04:00
committed by GitHub
22 changed files with 191 additions and 153 deletions
+3 -5
View File
@@ -680,13 +680,13 @@ class CollectionState():
def can_reach_region(self, spot: str, player: int) -> bool: def can_reach_region(self, spot: str, player: int) -> bool:
return self.multiworld.get_region(spot, player).can_reach(self) return self.multiworld.get_region(spot, player).can_reach(self)
def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None: def sweep_for_events(self, locations: Optional[Iterable[Location]] = None) -> None:
if locations is None: if locations is None:
locations = self.multiworld.get_filled_locations() locations = self.multiworld.get_filled_locations()
reachable_events = True reachable_events = True
# since the loop has a good chance to run more than once, only filter the events once # since the loop has a good chance to run more than once, only filter the events once
locations = {location for location in locations if location.advancement and location not in self.events and locations = {location for location in locations if location.advancement and location not in self.events}
not key_only or getattr(location.item, "locked_dungeon_item", False)}
while reachable_events: while reachable_events:
reachable_events = {location for location in locations if location.can_reach(self)} reachable_events = {location for location in locations if location.can_reach(self)}
locations -= reachable_events locations -= reachable_events
@@ -1291,8 +1291,6 @@ class Spoiler:
state = CollectionState(multiworld) state = CollectionState(multiworld)
collection_spheres = [] collection_spheres = []
while required_locations: while required_locations:
state.sweep_for_events(key_only=True)
sphere = set(filter(state.can_reach, required_locations)) sphere = set(filter(state.can_reach, required_locations))
for location in sphere: for location in sphere:
-1
View File
@@ -646,7 +646,6 @@ def balance_multiworld_progression(multiworld: MultiWorld) -> None:
def get_sphere_locations(sphere_state: CollectionState, def get_sphere_locations(sphere_state: CollectionState,
locations: typing.Set[Location]) -> typing.Set[Location]: locations: typing.Set[Location]) -> typing.Set[Location]:
sphere_state.sweep_for_events(key_only=True, locations=locations)
return {loc for loc in locations if sphere_state.can_reach(loc)} return {loc for loc in locations if sphere_state.can_reach(loc)}
def item_percentage(player: int, num: int) -> float: def item_percentage(player: int, num: int) -> float:
+7
View File
@@ -231,6 +231,13 @@ def generate_yaml(game: str):
del options[key] del options[key]
# Detect keys which end with -range, indicating a NamedRange with a possible custom value
elif key_parts[-1].endswith("-range"):
if options[key_parts[-1][:-6]] == "custom":
options[key_parts[-1][:-6]] = val
del options[key]
# Detect random-* keys and set their options accordingly # Detect random-* keys and set their options accordingly
for key, val in options.copy().items(): for key, val in options.copy().items():
if key.startswith("random-"): if key.startswith("random-"):
@@ -54,7 +54,7 @@
{% macro NamedRange(option_name, option) %} {% macro NamedRange(option_name, option) %}
{{ OptionTitle(option_name, option) }} {{ OptionTitle(option_name, option) }}
<div class="named-range-container"> <div class="named-range-container">
<select id="{{ option_name }}-select" data-option-name="{{ option_name }}" {{ "disabled" if option.default == "random" }}> <select id="{{ option_name }}-select" name="{{ option_name }}" data-option-name="{{ option_name }}" {{ "disabled" if option.default == "random" }}>
{% for key, val in option.special_range_names.items() %} {% for key, val in option.special_range_names.items() %}
{% if option.default == val %} {% if option.default == val %}
<option value="{{ val }}" selected>{{ key|replace("_", " ")|title }} ({{ val }})</option> <option value="{{ val }}" selected>{{ key|replace("_", " ")|title }} ({{ val }})</option>
@@ -64,17 +64,17 @@
{% endfor %} {% endfor %}
<option value="custom" hidden>Custom</option> <option value="custom" hidden>Custom</option>
</select> </select>
<div class="named-range-wrapper"> <div class="named-range-wrapper js-required">
<input <input
type="range" type="range"
id="{{ option_name }}" id="{{ option_name }}"
name="{{ option_name }}" name="{{ option_name }}-range"
min="{{ option.range_start }}" min="{{ option.range_start }}"
max="{{ option.range_end }}" max="{{ option.range_end }}"
value="{{ option.default | default(option.range_start) if option.default != "random" else option.range_start }}" value="{{ option.default | default(option.range_start) if option.default != "random" else option.range_start }}"
{{ "disabled" if option.default == "random" }} {{ "disabled" if option.default == "random" }}
/> />
<span id="{{ option_name }}-value" class="range-value js-required"> <span id="{{ option_name }}-value" class="range-value">
{{ option.default | default(option.range_start) if option.default != "random" else option.range_start }} {{ option.default | default(option.range_start) if option.default != "random" else option.range_start }}
</span> </span>
{{ RandomizeButton(option_name, option) }} {{ RandomizeButton(option_name, option) }}
@@ -11,7 +11,7 @@
<noscript> <noscript>
<style> <style>
.js-required{ .js-required{
display: none; display: none !important;
} }
</style> </style>
</noscript> </noscript>
+1 -1
View File
@@ -79,7 +79,7 @@ class TrackerData:
# Normal lookup tables as well. # Normal lookup tables as well.
self.item_name_to_id[game] = game_package["item_name_to_id"] self.item_name_to_id[game] = game_package["item_name_to_id"]
self.location_name_to_id[game] = game_package["item_name_to_id"] self.location_name_to_id[game] = game_package["location_name_to_id"]
def get_seed_name(self) -> str: def get_seed_name(self) -> str:
"""Retrieves the seed name.""" """Retrieves the seed name."""
+1 -1
View File
@@ -39,7 +39,7 @@ def create_itempool(world: "HatInTimeWorld") -> List[Item]:
continue continue
else: else:
if name == "Scooter Badge": if name == "Scooter Badge":
if world.options.CTRLogic is CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE: if world.options.CTRLogic == CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE:
item_type = ItemClassification.progression item_type = ItemClassification.progression
elif name == "No Bonk Badge" and world.is_dw(): elif name == "No Bonk Badge" and world.is_dw():
item_type = ItemClassification.progression item_type = ItemClassification.progression
+10 -2
View File
@@ -659,6 +659,10 @@ def is_valid_act_combo(world: "HatInTimeWorld", entrance_act: Region,
if exit_act.name not in chapter_finales: if exit_act.name not in chapter_finales:
return False return False
exit_chapter: str = act_chapters.get(exit_act.name)
# make sure that certain time rift combinations never happen
always_block: bool = exit_chapter != "Mafia Town" and exit_chapter != "Subcon Forest"
if not ignore_certain_rules or always_block:
if entrance_act.name in rift_access_regions and exit_act.name in rift_access_regions[entrance_act.name]: if entrance_act.name in rift_access_regions and exit_act.name in rift_access_regions[entrance_act.name]:
return False return False
@@ -684,9 +688,12 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
if act.name not in guaranteed_first_acts: if act.name not in guaranteed_first_acts:
return False return False
if world.options.ActRandomizer == ActRandomizer.option_light and "Time Rift" in act.name:
return False
# If there's only a single level in the starting chapter, only allow Mafia Town or Subcon Forest levels # If there's only a single level in the starting chapter, only allow Mafia Town or Subcon Forest levels
start_chapter = world.options.StartingChapter start_chapter = world.options.StartingChapter
if start_chapter is ChapterIndex.ALPINE or start_chapter is ChapterIndex.SUBCON: if start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON:
if "Time Rift" in act.name: if "Time Rift" in act.name:
return False return False
@@ -723,7 +730,8 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings: elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings:
return False return False
if world.options.ShuffleSubconPaintings and act_chapters.get(act.name, "") == "Subcon Forest": if world.options.ShuffleSubconPaintings and "Time Rift" not in act.name \
and act_chapters.get(act.name, "") == "Subcon Forest":
# Only allow Subcon levels if painting skips are allowed # Only allow Subcon levels if painting skips are allowed
if diff < Difficulty.MODERATE or world.options.NoPaintingSkips: if diff < Difficulty.MODERATE or world.options.NoPaintingSkips:
return False return False
+6 -10
View File
@@ -1,7 +1,6 @@
from worlds.AutoWorld import CollectionState from worlds.AutoWorld import CollectionState
from worlds.generic.Rules import add_rule, set_rule from worlds.generic.Rules import add_rule, set_rule
from .Locations import location_table, zipline_unlocks, is_location_valid, contract_locations, \ from .Locations import location_table, zipline_unlocks, is_location_valid, shop_locations, event_locs
shop_locations, event_locs
from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty, HitType from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty, HitType
from BaseClasses import Location, Entrance, Region from BaseClasses import Location, Entrance, Region
from typing import TYPE_CHECKING, List, Callable, Union, Dict from typing import TYPE_CHECKING, List, Callable, Union, Dict
@@ -148,14 +147,14 @@ def set_rules(world: "HatInTimeWorld"):
if world.is_dlc1(): if world.is_dlc1():
chapter_list.append(ChapterIndex.CRUISE) chapter_list.append(ChapterIndex.CRUISE)
if world.is_dlc2() and final_chapter is not ChapterIndex.METRO: if world.is_dlc2() and final_chapter != ChapterIndex.METRO:
chapter_list.append(ChapterIndex.METRO) chapter_list.append(ChapterIndex.METRO)
chapter_list.remove(starting_chapter) chapter_list.remove(starting_chapter)
world.random.shuffle(chapter_list) world.random.shuffle(chapter_list)
# Make sure Alpine is unlocked before any DLC chapters are, as the Alpine door needs to be open to access them # Make sure Alpine is unlocked before any DLC chapters are, as the Alpine door needs to be open to access them
if starting_chapter is not ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()): if starting_chapter != ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()):
index1 = 69 index1 = 69
index2 = 69 index2 = 69
pos: int pos: int
@@ -165,7 +164,7 @@ def set_rules(world: "HatInTimeWorld"):
if world.is_dlc1(): if world.is_dlc1():
index1 = chapter_list.index(ChapterIndex.CRUISE) index1 = chapter_list.index(ChapterIndex.CRUISE)
if world.is_dlc2() and final_chapter is not ChapterIndex.METRO: if world.is_dlc2() and final_chapter != ChapterIndex.METRO:
index2 = chapter_list.index(ChapterIndex.METRO) index2 = chapter_list.index(ChapterIndex.METRO)
lowest_index = min(index1, index2) lowest_index = min(index1, index2)
@@ -242,9 +241,6 @@ def set_rules(world: "HatInTimeWorld"):
if not is_location_valid(world, key): if not is_location_valid(world, key):
continue continue
if key in contract_locations.keys():
continue
loc = world.multiworld.get_location(key, world.player) loc = world.multiworld.get_location(key, world.player)
for hat in data.required_hats: for hat in data.required_hats:
@@ -256,7 +252,7 @@ def set_rules(world: "HatInTimeWorld"):
if data.paintings > 0 and world.options.ShuffleSubconPaintings: if data.paintings > 0 and world.options.ShuffleSubconPaintings:
add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings)) add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings))
if data.hit_type is not HitType.none and world.options.UmbrellaLogic: if data.hit_type != HitType.none and world.options.UmbrellaLogic:
if data.hit_type == HitType.umbrella: if data.hit_type == HitType.umbrella:
add_rule(loc, lambda state: state.has("Umbrella", world.player)) add_rule(loc, lambda state: state.has("Umbrella", world.player))
@@ -518,7 +514,7 @@ def set_hard_rules(world: "HatInTimeWorld"):
lambda state: can_use_hat(state, world, HatType.ICE)) lambda state: can_use_hat(state, world, HatType.ICE))
# Hard: clear Rush Hour with Brewing Hat only # Hard: clear Rush Hour with Brewing Hat only
if world.options.NoTicketSkips is not NoTicketSkips.option_true: if world.options.NoTicketSkips != NoTicketSkips.option_true:
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
lambda state: can_use_hat(state, world, HatType.BREWING)) lambda state: can_use_hat(state, world, HatType.BREWING))
else: else:
+28 -16
View File
@@ -1,15 +1,16 @@
from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld
from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \ from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \
calculate_yarn_costs calculate_yarn_costs, alps_hooks
from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region
from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \ from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \
get_total_locations get_total_locations
from .Rules import set_rules from .Rules import set_rules, has_paintings
from .Options import AHITOptions, slot_data_options, adjust_options, RandomizeHatOrder, EndGoal, create_option_groups from .Options import AHITOptions, slot_data_options, adjust_options, RandomizeHatOrder, EndGoal, create_option_groups
from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item, Difficulty
from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes
from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses
from worlds.AutoWorld import World, WebWorld, CollectionState from worlds.AutoWorld import World, WebWorld, CollectionState
from worlds.generic.Rules import add_rule
from typing import List, Dict, TextIO from typing import List, Dict, TextIO
from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type
from Utils import local_path from Utils import local_path
@@ -86,19 +87,27 @@ class HatInTimeWorld(World):
if self.is_dw_only(): if self.is_dw_only():
return return
# If our starting chapter is 4 and act rando isn't on, force hookshot into inventory # Take care of some extremely restrictive starts in other chapters with act shuffle off
# If starting chapter is 3 and painting shuffle is enabled, and act rando isn't, give one free painting unlock if not self.options.ActRandomizer:
start_chapter: ChapterIndex = ChapterIndex(self.options.StartingChapter) start_chapter = self.options.StartingChapter
if start_chapter == ChapterIndex.ALPINE:
self.multiworld.push_precollected(self.create_item("Hookshot Badge"))
if self.options.UmbrellaLogic:
self.multiworld.push_precollected(self.create_item("Umbrella"))
if start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON: if self.options.ShuffleAlpineZiplines:
if not self.options.ActRandomizer: ziplines = list(alps_hooks.keys())
if start_chapter == ChapterIndex.ALPINE: ziplines.remove("Zipline Unlock - The Twilight Bell Path") # not enough checks from this one
self.multiworld.push_precollected(self.create_item("Hookshot Badge")) self.multiworld.push_precollected(self.create_item(self.random.choice(ziplines)))
if self.options.UmbrellaLogic: elif start_chapter == ChapterIndex.SUBCON:
self.multiworld.push_precollected(self.create_item("Umbrella")) if self.options.ShuffleSubconPaintings:
if start_chapter == ChapterIndex.SUBCON and self.options.ShuffleSubconPaintings:
self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock")) self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock"))
elif start_chapter == ChapterIndex.BIRDS:
if self.options.UmbrellaLogic:
if self.options.LogicDifficulty < Difficulty.EXPERT:
self.multiworld.push_precollected(self.create_item("Umbrella"))
elif self.options.LogicDifficulty < Difficulty.MODERATE:
self.multiworld.push_precollected(self.create_item("Umbrella"))
def create_regions(self): def create_regions(self):
# noinspection PyClassVar # noinspection PyClassVar
@@ -119,7 +128,10 @@ class HatInTimeWorld(World):
# place vanilla contract locations if contract shuffle is off # place vanilla contract locations if contract shuffle is off
if not self.options.ShuffleActContracts: if not self.options.ShuffleActContracts:
for name in contract_locations.keys(): for name in contract_locations.keys():
self.multiworld.get_location(name, self.player).place_locked_item(create_item(self, name)) loc = self.get_location(name)
loc.place_locked_item(create_item(self, name))
if self.options.ShuffleSubconPaintings and loc.name != "Snatcher's Contract - The Subcon Well":
add_rule(loc, lambda state: has_paintings(state, self, 1))
def create_items(self): def create_items(self):
if self.has_yarn(): if self.has_yarn():
@@ -317,7 +329,7 @@ class HatInTimeWorld(World):
def remove(self, state: "CollectionState", item: "Item") -> bool: def remove(self, state: "CollectionState", item: "Item") -> bool:
old_count: int = state.count(item.name, self.player) old_count: int = state.count(item.name, self.player)
change = super().collect(state, item) change = super().remove(state, item)
if change and old_count == 1: if change and old_count == 1:
if "Stamp" in item.name: if "Stamp" in item.name:
if "2 Stamp" in item.name: if "2 Stamp" in item.name:
-4
View File
@@ -76,10 +76,6 @@ class ALttPItem(Item):
if self.type in {"SmallKey", "BigKey", "Map", "Compass"}: if self.type in {"SmallKey", "BigKey", "Map", "Compass"}:
return self.type return self.type
@property
def locked_dungeon_item(self):
return self.location.locked and self.dungeon_item
class LTTPRegionType(IntEnum): class LTTPRegionType(IntEnum):
LightWorld = 1 LightWorld = 1
@@ -660,11 +660,18 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
end end
local tech local tech
local force = game.forces["player"] local force = game.forces["player"]
if call.parameter == nil then
game.print("ap-get-technology is only to be used by the Archipelago Factorio Client")
return
end
chunks = split(call.parameter, "\t") chunks = split(call.parameter, "\t")
local item_name = chunks[1] local item_name = chunks[1]
local index = chunks[2] local index = chunks[2]
local source = chunks[3] or "Archipelago" local source = chunks[3] or "Archipelago"
if index == -1 then -- for coop sync and restoring from an older savegame if index == nil then
game.print("ap-get-technology is only to be used by the Archipelago Factorio Client")
return
elseif index == -1 then -- for coop sync and restoring from an older savegame
tech = force.technologies[item_name] tech = force.technologies[item_name]
if tech.researched ~= true then if tech.researched ~= true then
game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."}) game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."})
+1 -1
View File
@@ -71,7 +71,7 @@ class FFMQClient(SNIClient):
received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1]) received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1])
data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START) data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START)
check_2 = await snes_read(ctx, 0xF53749, 1) check_2 = await snes_read(ctx, 0xF53749, 1)
if check_1 != b'01' or check_2 != b'01': if check_1 != b'\x01' or check_2 != b'\x01':
return return
def get_range(data_range): def get_range(data_range):
+5 -1
View File
@@ -1,10 +1,12 @@
import typing import typing
import re import re
from dataclasses import dataclass, make_dataclass
from .ExtractedData import logic_options, starts, pool_options from .ExtractedData import logic_options, starts, pool_options
from .Rules import cost_terms from .Rules import cost_terms
from schema import And, Schema, Optional from schema import And, Schema, Optional
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink, PerGameCommonOptions
from .Charms import vanilla_costs, names as charm_names from .Charms import vanilla_costs, names as charm_names
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
@@ -538,3 +540,5 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
}, },
**cost_sanity_weights **cost_sanity_weights
} }
HKOptions = make_dataclass("HKOptions", [(name, option) for name, option in hollow_knight_options.items()], bases=(PerGameCommonOptions,))
+39
View File
@@ -49,3 +49,42 @@ def set_rules(hk_world: World):
if term == "GEO": # No geo logic! if term == "GEO": # No geo logic!
continue continue
add_rule(location, lambda state, term=term, amount=amount: state.count(term, player) >= amount) add_rule(location, lambda state, term=term, amount=amount: state.count(term, player) >= amount)
def _hk_nail_combat(state, player) -> bool:
return state.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player)
def _hk_can_beat_thk(state, player) -> bool:
return (
state.has('Opened_Black_Egg_Temple', player)
and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1
and _hk_nail_combat(state, player)
and (
state.has_any({'LEFTDASH', 'RIGHTDASH'}, player)
or state._hk_option(player, 'ProficientCombat')
)
and state.has('FOCUS', player)
)
def _hk_siblings_ending(state, player) -> bool:
return _hk_can_beat_thk(state, player) and state.has('WHITEFRAGMENT', player, 3)
def _hk_can_beat_radiance(state, player) -> bool:
return (
state.has('Opened_Black_Egg_Temple', player)
and _hk_nail_combat(state, player)
and state.has('WHITEFRAGMENT', player, 3)
and state.has('DREAMNAIL', player)
and (
(state.has('LEFTCLAW', player) and state.has('RIGHTCLAW', player))
or state.has('WINGS', player)
)
and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1
and (
(state.has('LEFTDASH', player, 2) and state.has('RIGHTDASH', player, 2)) # Both Shade Cloaks
or (state._hk_option(player, 'ProficientCombat') and state.has('QUAKE', player)) # or Dive
)
)
+64 -98
View File
@@ -10,9 +10,9 @@ logger = logging.getLogger("Hollow Knight")
from .Items import item_table, lookup_type_to_names, item_name_groups from .Items import item_table, lookup_type_to_names, item_name_groups
from .Regions import create_regions from .Regions import create_regions
from .Rules import set_rules, cost_terms from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance
from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \ from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \
shop_to_option shop_to_option, HKOptions
from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \ from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \
event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs
from .Charms import names as charm_names from .Charms import names as charm_names
@@ -142,7 +142,8 @@ class HKWorld(World):
As the enigmatic Knight, youll traverse the depths, unravel its mysteries and conquer its evils. As the enigmatic Knight, youll traverse the depths, unravel its mysteries and conquer its evils.
""" # from https://www.hollowknight.com """ # from https://www.hollowknight.com
game: str = "Hollow Knight" game: str = "Hollow Knight"
option_definitions = hollow_knight_options options_dataclass = HKOptions
options: HKOptions
web = HKWeb() web = HKWeb()
@@ -155,8 +156,8 @@ class HKWorld(World):
charm_costs: typing.List[int] charm_costs: typing.List[int]
cached_filler_items = {} cached_filler_items = {}
def __init__(self, world, player): def __init__(self, multiworld, player):
super(HKWorld, self).__init__(world, player) super(HKWorld, self).__init__(multiworld, player)
self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = { self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = {
location: list() for location in multi_locations location: list() for location in multi_locations
} }
@@ -165,29 +166,29 @@ class HKWorld(World):
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs) self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
def generate_early(self): def generate_early(self):
world = self.multiworld options = self.options
charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random) charm_costs = options.RandomCharmCosts.get_costs(self.random)
self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs) self.charm_costs = options.PlandoCharmCosts.get_costs(charm_costs)
# world.exclude_locations[self.player].value.update(white_palace_locations) # options.exclude_locations.value.update(white_palace_locations)
for term, data in cost_terms.items(): for term, data in cost_terms.items():
mini = getattr(world, f"Minimum{data.option}Price")[self.player] mini = getattr(options, f"Minimum{data.option}Price")
maxi = getattr(world, f"Maximum{data.option}Price")[self.player] maxi = getattr(options, f"Maximum{data.option}Price")
# if minimum > maximum, set minimum to maximum # if minimum > maximum, set minimum to maximum
mini.value = min(mini.value, maxi.value) mini.value = min(mini.value, maxi.value)
self.ranges[term] = mini.value, maxi.value self.ranges[term] = mini.value, maxi.value
world.push_precollected(HKItem(starts[world.StartLocation[self.player].current_key], self.multiworld.push_precollected(HKItem(starts[options.StartLocation.current_key],
True, None, "Event", self.player)) True, None, "Event", self.player))
def white_palace_exclusions(self): def white_palace_exclusions(self):
exclusions = set() exclusions = set()
wp = self.multiworld.WhitePalace[self.player] wp = self.options.WhitePalace
if wp <= WhitePalace.option_nopathofpain: if wp <= WhitePalace.option_nopathofpain:
exclusions.update(path_of_pain_locations) exclusions.update(path_of_pain_locations)
if wp <= WhitePalace.option_kingfragment: if wp <= WhitePalace.option_kingfragment:
exclusions.update(white_palace_checks) exclusions.update(white_palace_checks)
if wp == WhitePalace.option_exclude: if wp == WhitePalace.option_exclude:
exclusions.add("King_Fragment") exclusions.add("King_Fragment")
if self.multiworld.RandomizeCharms[self.player]: if self.options.RandomizeCharms:
# If charms are randomized, this will be junk-filled -- so transitions and events are not progression # If charms are randomized, this will be junk-filled -- so transitions and events are not progression
exclusions.update(white_palace_transitions) exclusions.update(white_palace_transitions)
exclusions.update(white_palace_events) exclusions.update(white_palace_events)
@@ -200,7 +201,7 @@ class HKWorld(World):
# check for any goal that godhome events are relevant to # check for any goal that godhome events are relevant to
all_event_names = event_names.copy() all_event_names = event_names.copy()
if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]: if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]:
from .GodhomeData import godhome_event_names from .GodhomeData import godhome_event_names
all_event_names.update(set(godhome_event_names)) all_event_names.update(set(godhome_event_names))
@@ -230,12 +231,12 @@ class HKWorld(World):
pool: typing.List[HKItem] = [] pool: typing.List[HKItem] = []
wp_exclusions = self.white_palace_exclusions() wp_exclusions = self.white_palace_exclusions()
junk_replace: typing.Set[str] = set() junk_replace: typing.Set[str] = set()
if self.multiworld.RemoveSpellUpgrades[self.player]: if self.options.RemoveSpellUpgrades:
junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark")) junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark"))
randomized_starting_items = set() randomized_starting_items = set()
for attr, items in randomizable_starting_items.items(): for attr, items in randomizable_starting_items.items():
if getattr(self.multiworld, attr)[self.player]: if getattr(self.options, attr):
randomized_starting_items.update(items) randomized_starting_items.update(items)
# noinspection PyShadowingNames # noinspection PyShadowingNames
@@ -257,7 +258,7 @@ class HKWorld(World):
if item_name in junk_replace: if item_name in junk_replace:
item_name = self.get_filler_item_name() item_name = self.get_filler_item_name()
item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.multiworld.AddUnshuffledLocations[self.player] else self.create_event(item_name) item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.options.AddUnshuffledLocations else self.create_event(item_name)
if location_name == "Start": if location_name == "Start":
if item_name in randomized_starting_items: if item_name in randomized_starting_items:
@@ -281,55 +282,55 @@ class HKWorld(World):
location.progress_type = LocationProgressType.EXCLUDED location.progress_type = LocationProgressType.EXCLUDED
for option_key, option in hollow_knight_randomize_options.items(): for option_key, option in hollow_knight_randomize_options.items():
randomized = getattr(self.multiworld, option_key)[self.player] randomized = getattr(self.options, option_key)
if all([not randomized, option_key in logicless_options, not self.multiworld.AddUnshuffledLocations[self.player]]): if all([not randomized, option_key in logicless_options, not self.options.AddUnshuffledLocations]):
continue continue
for item_name, location_name in zip(option.items, option.locations): for item_name, location_name in zip(option.items, option.locations):
if item_name in junk_replace: if item_name in junk_replace:
item_name = self.get_filler_item_name() item_name = self.get_filler_item_name()
if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \ if (item_name == "Crystal_Heart" and self.options.SplitCrystalHeart) or \
(item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]): (item_name == "Mothwing_Cloak" and self.options.SplitMothwingCloak):
_add("Left_" + item_name, location_name, randomized) _add("Left_" + item_name, location_name, randomized)
_add("Right_" + item_name, "Split_" + location_name, randomized) _add("Right_" + item_name, "Split_" + location_name, randomized)
continue continue
if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]: if item_name == "Mantis_Claw" and self.options.SplitMantisClaw:
_add("Left_" + item_name, "Left_" + location_name, randomized) _add("Left_" + item_name, "Left_" + location_name, randomized)
_add("Right_" + item_name, "Right_" + location_name, randomized) _add("Right_" + item_name, "Right_" + location_name, randomized)
continue continue
if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]: if item_name == "Shade_Cloak" and self.options.SplitMothwingCloak:
if self.multiworld.random.randint(0, 1): if self.random.randint(0, 1):
item_name = "Left_Mothwing_Cloak" item_name = "Left_Mothwing_Cloak"
else: else:
item_name = "Right_Mothwing_Cloak" item_name = "Right_Mothwing_Cloak"
if item_name == "Grimmchild2" and self.multiworld.RandomizeGrimmkinFlames[self.player] and self.multiworld.RandomizeCharms[self.player]: if item_name == "Grimmchild2" and self.options.RandomizeGrimmkinFlames and self.options.RandomizeCharms:
_add("Grimmchild1", location_name, randomized) _add("Grimmchild1", location_name, randomized)
continue continue
_add(item_name, location_name, randomized) _add(item_name, location_name, randomized)
if self.multiworld.RandomizeElevatorPass[self.player]: if self.options.RandomizeElevatorPass:
randomized = True randomized = True
_add("Elevator_Pass", "Elevator_Pass", randomized) _add("Elevator_Pass", "Elevator_Pass", randomized)
for shop, locations in self.created_multi_locations.items(): for shop, locations in self.created_multi_locations.items():
for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value): for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value):
loc = self.create_location(shop) loc = self.create_location(shop)
unfilled_locations += 1 unfilled_locations += 1
# Balance the pool # Balance the pool
item_count = len(pool) item_count = len(pool)
additional_shop_items = max(item_count - unfilled_locations, self.multiworld.ExtraShopSlots[self.player].value) additional_shop_items = max(item_count - unfilled_locations, self.options.ExtraShopSlots.value)
# Add additional shop items, as needed. # Add additional shop items, as needed.
if additional_shop_items > 0: if additional_shop_items > 0:
shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16) shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16)
if not self.multiworld.EggShopSlots[self.player].value: # No eggshop, so don't place items there if not self.options.EggShopSlots: # No eggshop, so don't place items there
shops.remove('Egg_Shop') shops.remove('Egg_Shop')
if shops: if shops:
for _ in range(additional_shop_items): for _ in range(additional_shop_items):
shop = self.multiworld.random.choice(shops) shop = self.random.choice(shops)
loc = self.create_location(shop) loc = self.create_location(shop)
unfilled_locations += 1 unfilled_locations += 1
if len(self.created_multi_locations[shop]) >= 16: if len(self.created_multi_locations[shop]) >= 16:
@@ -355,7 +356,7 @@ class HKWorld(World):
loc.costs = costs loc.costs = costs
def apply_costsanity(self): def apply_costsanity(self):
setting = self.multiworld.CostSanity[self.player].value setting = self.options.CostSanity.value
if not setting: if not setting:
return # noop return # noop
@@ -369,10 +370,10 @@ class HKWorld(World):
return {k: v for k, v in weights.items() if v} return {k: v for k, v in weights.items() if v}
random = self.multiworld.random random = self.random
hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value hybrid_chance = getattr(self.options, f"CostSanityHybridChance").value
weights = { weights = {
data.term: getattr(self.multiworld, f"CostSanity{data.option}Weight")[self.player].value data.term: getattr(self.options, f"CostSanity{data.option}Weight").value
for data in cost_terms.values() for data in cost_terms.values()
} }
weights_geoless = dict(weights) weights_geoless = dict(weights)
@@ -427,22 +428,22 @@ class HKWorld(World):
location.sort_costs() location.sort_costs()
def set_rules(self): def set_rules(self):
world = self.multiworld multiworld = self.multiworld
player = self.player player = self.player
goal = world.Goal[player] goal = self.options.Goal
if goal == Goal.option_hollowknight: if goal == Goal.option_hollowknight:
world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player)
elif goal == Goal.option_siblings: elif goal == Goal.option_siblings:
world.completion_condition[player] = lambda state: state._hk_siblings_ending(player) multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player)
elif goal == Goal.option_radiance: elif goal == Goal.option_radiance:
world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player) multiworld.completion_condition[player] = lambda state: _hk_can_beat_radiance(state, player)
elif goal == Goal.option_godhome: elif goal == Goal.option_godhome:
world.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player)
elif goal == Goal.option_godhome_flower: elif goal == Goal.option_godhome_flower:
world.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
else: else:
# Any goal # Any goal
world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player) multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player)
set_rules(self) set_rules(self)
@@ -450,8 +451,8 @@ class HKWorld(World):
slot_data = {} slot_data = {}
options = slot_data["options"] = {} options = slot_data["options"] = {}
for option_name in self.option_definitions: for option_name in hollow_knight_options:
option = getattr(self.multiworld, option_name)[self.player] option = getattr(self.options, option_name)
try: try:
optionvalue = int(option.value) optionvalue = int(option.value)
except TypeError: except TypeError:
@@ -460,10 +461,10 @@ class HKWorld(World):
options[option_name] = optionvalue options[option_name] = optionvalue
# 32 bit int # 32 bit int
slot_data["seed"] = self.multiworld.per_slot_randoms[self.player].randint(-2147483647, 2147483646) slot_data["seed"] = self.random.randint(-2147483647, 2147483646)
# Backwards compatibility for shop cost data (HKAP < 0.1.0) # Backwards compatibility for shop cost data (HKAP < 0.1.0)
if not self.multiworld.CostSanity[self.player]: if not self.options.CostSanity:
for shop, terms in shop_cost_types.items(): for shop, terms in shop_cost_types.items():
unit = cost_terms[next(iter(terms))].option unit = cost_terms[next(iter(terms))].option
if unit == "Geo": if unit == "Geo":
@@ -498,7 +499,7 @@ class HKWorld(World):
basename = name basename = name
if name in shop_cost_types: if name in shop_cost_types:
costs = { costs = {
term: self.multiworld.random.randint(*self.ranges[term]) term: self.random.randint(*self.ranges[term])
for term in shop_cost_types[name] for term in shop_cost_types[name]
} }
elif name in vanilla_location_costs: elif name in vanilla_location_costs:
@@ -512,7 +513,7 @@ class HKWorld(World):
region = self.multiworld.get_region("Menu", self.player) region = self.multiworld.get_region("Menu", self.player)
if vanilla and not self.multiworld.AddUnshuffledLocations[self.player]: if vanilla and not self.options.AddUnshuffledLocations:
loc = HKLocation(self.player, name, loc = HKLocation(self.player, name,
None, region, costs=costs, vanilla=vanilla, None, region, costs=costs, vanilla=vanilla,
basename=basename) basename=basename)
@@ -560,26 +561,26 @@ class HKWorld(World):
return change return change
@classmethod @classmethod
def stage_write_spoiler(cls, world: MultiWorld, spoiler_handle): def stage_write_spoiler(cls, multiworld: MultiWorld, spoiler_handle):
hk_players = world.get_game_players(cls.game) hk_players = multiworld.get_game_players(cls.game)
spoiler_handle.write('\n\nCharm Notches:') spoiler_handle.write('\n\nCharm Notches:')
for player in hk_players: for player in hk_players:
name = world.get_player_name(player) name = multiworld.get_player_name(player)
spoiler_handle.write(f'\n{name}\n') spoiler_handle.write(f'\n{name}\n')
hk_world: HKWorld = world.worlds[player] hk_world: HKWorld = multiworld.worlds[player]
for charm_number, cost in enumerate(hk_world.charm_costs): for charm_number, cost in enumerate(hk_world.charm_costs):
spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}") spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}")
spoiler_handle.write('\n\nShop Prices:') spoiler_handle.write('\n\nShop Prices:')
for player in hk_players: for player in hk_players:
name = world.get_player_name(player) name = multiworld.get_player_name(player)
spoiler_handle.write(f'\n{name}\n') spoiler_handle.write(f'\n{name}\n')
hk_world: HKWorld = world.worlds[player] hk_world: HKWorld = multiworld.worlds[player]
if world.CostSanity[player].value: if hk_world.options.CostSanity:
for loc in sorted( for loc in sorted(
( (
loc for loc in itertools.chain(*(region.locations for region in world.get_regions(player))) loc for loc in itertools.chain(*(region.locations for region in multiworld.get_regions(player)))
if loc.costs if loc.costs
), key=operator.attrgetter('name') ), key=operator.attrgetter('name')
): ):
@@ -603,15 +604,15 @@ class HKWorld(World):
'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests', 'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests',
'RandomizeRancidEggs' 'RandomizeRancidEggs'
): ):
if getattr(self.multiworld, group): if getattr(self.options, group):
fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in
exclusions) exclusions)
self.cached_filler_items[self.player] = fillers self.cached_filler_items[self.player] = fillers
return self.multiworld.random.choice(self.cached_filler_items[self.player]) return self.random.choice(self.cached_filler_items[self.player])
def create_region(world: MultiWorld, player: int, name: str, location_names=None) -> Region: def create_region(multiworld: MultiWorld, player: int, name: str, location_names=None) -> Region:
ret = Region(name, player, world) ret = Region(name, player, multiworld)
if location_names: if location_names:
for location in location_names: for location in location_names:
loc_id = HKWorld.location_name_to_id.get(location, None) loc_id = HKWorld.location_name_to_id.get(location, None)
@@ -684,42 +685,7 @@ class HKLogicMixin(LogicMixin):
return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches) return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches)
def _hk_option(self, player: int, option_name: str) -> int: def _hk_option(self, player: int, option_name: str) -> int:
return getattr(self.multiworld, option_name)[player].value return getattr(self.multiworld.worlds[player].options, option_name).value
def _hk_start(self, player, start_location: str) -> bool: def _hk_start(self, player, start_location: str) -> bool:
return self.multiworld.StartLocation[player] == start_location return self.multiworld.worlds[player].options.StartLocation == start_location
def _hk_nail_combat(self, player: int) -> bool:
return self.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player)
def _hk_can_beat_thk(self, player: int) -> bool:
return (
self.has('Opened_Black_Egg_Temple', player)
and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1
and self._hk_nail_combat(player)
and (
self.has_any({'LEFTDASH', 'RIGHTDASH'}, player)
or self._hk_option(player, 'ProficientCombat')
)
and self.has('FOCUS', player)
)
def _hk_siblings_ending(self, player: int) -> bool:
return self._hk_can_beat_thk(player) and self.has('WHITEFRAGMENT', player, 3)
def _hk_can_beat_radiance(self, player: int) -> bool:
return (
self.has('Opened_Black_Egg_Temple', player)
and self._hk_nail_combat(player)
and self.has('WHITEFRAGMENT', player, 3)
and self.has('DREAMNAIL', player)
and (
(self.has('LEFTCLAW', player) and self.has('RIGHTCLAW', player))
or self.has('WINGS', player)
)
and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1
and (
(self.has('LEFTDASH', player, 2) and self.has('RIGHTDASH', player, 2)) # Both Shade Cloaks
or (self._hk_option(player, 'ProficientCombat') and self.has('QUAKE', player)) # or Dive
)
)
+7
View File
@@ -1556,6 +1556,8 @@
room: Owl Hallway room: Owl Hallway
door: Shortcut to Hedge Maze door: Shortcut to Hedge Maze
Roof: True Roof: True
The Incomparable:
door: Observant Entrance
panels: panels:
DOWN: DOWN:
id: Maze Room/Panel_down_up id: Maze Room/Panel_down_up
@@ -1967,6 +1969,9 @@
door: Eight Door door: Eight Door
Orange Tower Sixth Floor: Orange Tower Sixth Floor:
painting: True painting: True
Hedge Maze:
room: Hedge Maze
door: Observant Entrance
panels: panels:
Achievement: Achievement:
id: Countdown Panels/Panel_incomparable_incomparable id: Countdown Panels/Panel_incomparable_incomparable
@@ -7649,6 +7654,8 @@
LEAP: LEAP:
id: Double Room/Panel_leap_leap id: Double Room/Panel_leap_leap
tag: midwhite tag: midwhite
required_door:
door: Door to Cross
doors: doors:
Door to Cross: Door to Cross:
id: Double Room Area Doors/Door_room_4a id: Double Room Area Doors/Door_room_4a
Binary file not shown.
-1
View File
@@ -1,2 +1 @@
nest-asyncio >= 1.5.5 nest-asyncio >= 1.5.5
six >= 1.16.0
+2 -2
View File
@@ -215,7 +215,6 @@ class SMZ3World(World):
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World) niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World) junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
allJunkItems = niceItems + junkItems
self.junkItemsNames = [item.Type.name for item in junkItems] self.junkItemsNames = [item.Type.name for item in junkItems]
if (self.smz3World.Config.Keysanity): if (self.smz3World.Config.Keysanity):
@@ -228,7 +227,8 @@ class SMZ3World(World):
self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item)) self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item))
itemPool = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \ itemPool = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \
[SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in allJunkItems] [SMZ3Item(item.Type.name, ItemClassification.useful, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in niceItems] + \
[SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in junkItems]
self.smz3DungeonItems = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon] self.smz3DungeonItems = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon]
self.multiworld.itempool += itemPool self.multiworld.itempool += itemPool
+1 -1
View File
@@ -2212,7 +2212,7 @@ id,region,name,tags,mod_name
3808,Shipping,Shipsanity: Mystery Box,"SHIPSANITY", 3808,Shipping,Shipsanity: Mystery Box,"SHIPSANITY",
3809,Shipping,Shipsanity: Golden Tag,"SHIPSANITY", 3809,Shipping,Shipsanity: Golden Tag,"SHIPSANITY",
3810,Shipping,Shipsanity: Deluxe Bait,"SHIPSANITY", 3810,Shipping,Shipsanity: Deluxe Bait,"SHIPSANITY",
3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT", 3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3812,Shipping,Shipsanity: Mossy Seed,"SHIPSANITY", 3812,Shipping,Shipsanity: Mossy Seed,"SHIPSANITY",
3813,Shipping,Shipsanity: Sonar Bobber,"SHIPSANITY", 3813,Shipping,Shipsanity: Sonar Bobber,"SHIPSANITY",
3814,Shipping,Shipsanity: Tent Kit,"SHIPSANITY", 3814,Shipping,Shipsanity: Tent Kit,"SHIPSANITY",
1 id region name tags mod_name
2212 3808 Shipping Shipsanity: Mystery Box SHIPSANITY
2213 3809 Shipping Shipsanity: Golden Tag SHIPSANITY
2214 3810 Shipping Shipsanity: Deluxe Bait SHIPSANITY
2215 3811 Shipping Shipsanity: Moss SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT SHIPSANITY,SHIPSANITY_FULL_SHIPMENT
2216 3812 Shipping Shipsanity: Mossy Seed SHIPSANITY
2217 3813 Shipping Shipsanity: Sonar Bobber SHIPSANITY
2218 3814 Shipping Shipsanity: Tent Kit SHIPSANITY
+3 -3
View File
@@ -121,7 +121,7 @@ class TunicWorld(World):
cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value, cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value,
laurels_at_10_fairies=tunic.options.laurels_location == 3, laurels_at_10_fairies=tunic.options.laurels_location == 3,
fixed_shop=bool(tunic.options.fixed_shop), fixed_shop=bool(tunic.options.fixed_shop),
plando=multiworld.plando_connections[tunic.player]) plando=tunic.options.plando_connections)
continue continue
# lower value is more restrictive # lower value is more restrictive
@@ -134,9 +134,9 @@ class TunicWorld(World):
if tunic.options.fixed_shop: if tunic.options.fixed_shop:
cls.seed_groups[group]["fixed_shop"] = True cls.seed_groups[group]["fixed_shop"] = True
if multiworld.plando_connections[tunic.player]: if tunic.options.plando_connections:
# loop through the connections in the player's yaml # loop through the connections in the player's yaml
for cxn in multiworld.plando_connections[tunic.player]: for cxn in tunic.options.plando_connections:
new_cxn = True new_cxn = True
for group_cxn in cls.seed_groups[group]["plando"]: for group_cxn in cls.seed_groups[group]["plando"]:
# if neither entrance nor exit match anything in the group, add to group # if neither entrance nor exit match anything in the group, add to group